1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
// Copyright (C) 2013-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <dhcp/iface_mgr.h>
#include <dhcp/pkt6.h>
#include <dhcp/pkt_filter_inet6.h>
#include <exceptions/isc_assert.h>
#include <util/io/pktinfo_utilities.h>

#include <fcntl.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <netinet/in.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

using namespace isc::asiolink;

namespace isc {
namespace dhcp {

const size_t PktFilterInet6::CONTROL_BUF_LEN = 512;

bool
PktFilterInet6::isSocketReceivedTimeSupported() const {
#ifdef SO_TIMESTAMP
    return (true);
#else
    return (false);
#endif
}

SocketInfo
PktFilterInet6::openSocket(const Iface& iface,
                           const isc::asiolink::IOAddress& addr,
                           const uint16_t port,
                           const bool join_multicast) {
    struct sockaddr_in6 addr6;
    memset(&addr6, 0, sizeof(addr6));
    addr6.sin6_family = AF_INET6;
    addr6.sin6_port = htons(port);
    // sin6_scope_id must be set to interface index for link-local addresses.
    // For unspecified addresses we set the scope id to the interface index
    // to handle the case when the IfaceMgr is opening a socket which will
    // join the multicast group. Such socket is bound to in6addr_any.
    if (addr.isV6Multicast() ||
        (addr.isV6LinkLocal() && (addr != IOAddress("::1"))) ||
        (addr == IOAddress("::"))) {
        addr6.sin6_scope_id = if_nametoindex(iface.getName().c_str());
    }

    // Copy the address if it has been specified.
    if (addr != IOAddress("::")) {
        memcpy(&addr6.sin6_addr, &addr.toBytes()[0], sizeof(addr6.sin6_addr));
    }
#ifdef HAVE_SA_LEN
    addr6.sin6_len = sizeof(addr6);
#endif

    // @todo use sockcreator once it becomes available

    // make a socket
    int sock = socket(AF_INET6, SOCK_DGRAM, 0);
    if (sock < 0) {
        isc_throw(SocketConfigError, "Failed to create UDP6 socket.");
    }

    // Set the close-on-exec flag.
    if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) {
        close(sock);
        isc_throw(SocketConfigError, "Failed to set close-on-exec flag"
                  << " on IPv6 socket.");
    }

    // Set SO_REUSEADDR option.
    int flag = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
                   (char *)&flag, sizeof(flag)) < 0) {
        close(sock);
        isc_throw(SocketConfigError, "Can't set SO_REUSEADDR option on IPv6"
                  " socket.");
    }

#ifdef SO_REUSEPORT
    // Set SO_REUSEPORT has to be set to open multiple sockets and bind to
    // in6addr_any (binding to port). Binding to port is required on some
    // operating systems, e.g. NetBSD and OpenBSD so as the socket can
    // join the socket to multicast group.
    // RedHat 6.4 defines SO_REUSEPORT but the kernel does not support it
    // and returns ENOPROTOOPT so ignore this error. Other versions may be
    // affected, too.
    if ((setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
                    (char *)&flag, sizeof(flag)) < 0) &&
        (errno != ENOPROTOOPT)) {
        close(sock);
        isc_throw(SocketConfigError, "Can't set SO_REUSEPORT option on IPv6"
                  " socket.");
    }
#endif

#ifdef SO_TIMESTAMP
    int enable = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_TIMESTAMP, &enable, sizeof(enable))) {
        const char* errmsg = strerror(errno);
        isc_throw(SocketConfigError, "Could not enable SO_TIMESTAMP for " << addr.toText()
                  << ", error: " << errmsg);
    }
#endif

#ifdef IPV6_V6ONLY
    // Set IPV6_V6ONLY to get only IPv6 packets.
    if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
                   (char *)&flag, sizeof(flag)) < 0) {
          close(sock);
          isc_throw(SocketConfigError, "Can't set IPV6_V6ONLY option on "
                    "IPv6 socket.");
    }
#endif

    if (bind(sock, (struct sockaddr *)&addr6, sizeof(addr6)) < 0) {
        // Get the error message immediately after the bind because the
        // invocation to close() below would override the errno.
        char* errmsg = strerror(errno);<--- Variable 'errmsg' can be declared as pointer to const
        close(sock);
        isc_throw(SocketConfigError, "Failed to bind socket " << sock << " to "
                  << addr.toText() << "/port=" << port
                  << ": " << errmsg);
    }

#ifdef IPV6_RECVPKTINFO
    // RFC3542 - a new way
    if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO,
                   &flag, sizeof(flag)) != 0) {
        close(sock);
        isc_throw(SocketConfigError, "setsockopt: IPV6_RECVPKTINFO failed.");
    }
#else
    // RFC2292 - an old way
    if (setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO,
                   &flag, sizeof(flag)) != 0) {
        close(sock);
        isc_throw(SocketConfigError, "setsockopt: IPV6_PKTINFO: failed.");
    }
#endif

    // Join All_DHCP_Relay_Agents_and_Servers multicast group if
    // requested.
    if (join_multicast &&
        !joinMulticast(sock, iface.getName(),
                       std::string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS))) {
        close(sock);
        isc_throw(SocketConfigError, "Failed to join "
                  << ALL_DHCP_RELAY_AGENTS_AND_SERVERS
                  << " multicast group.");
    }

    return (SocketInfo(addr, port, sock));
}

Pkt6Ptr
PktFilterInet6::receive(const SocketInfo& socket_info) {
    // Now we have a socket, let's get some data from it!
    uint8_t buf[IfaceMgr::RCVBUFSIZE];
    uint8_t control_buf[CONTROL_BUF_LEN];
    memset(&control_buf[0], 0, CONTROL_BUF_LEN);
    struct sockaddr_in6 from;
    memset(&from, 0, sizeof(from));

#ifdef SO_TIMESTAMP
    struct timeval so_rcv_timestamp;
    memset(&so_rcv_timestamp, 0, sizeof(so_rcv_timestamp));
#endif

    // Initialize our message header structure.
    struct msghdr m;
    memset(&m, 0, sizeof(m));

    // Point so we can get the from address.
    m.msg_name = &from;
    m.msg_namelen = sizeof(from);

    // Set the data buffer we're receiving. (Using this wacky
    // "scatter-gather" stuff... but we that doesn't really make
    // sense for us, so we use a single vector entry.)
    struct iovec v;
    memset(&v, 0, sizeof(v));
    v.iov_base = static_cast<void*>(buf);
    v.iov_len = IfaceMgr::RCVBUFSIZE;
    m.msg_iov = &v;
    m.msg_iovlen = 1;

    // Getting the interface is a bit more involved.
    //
    // We set up some space for a "control message". We have
    // previously asked the kernel to give us packet
    // information (when we initialized the interface), so we
    // should get the destination address from that.
    m.msg_control = &control_buf[0];
    m.msg_controllen = CONTROL_BUF_LEN;

    int result = recvmsg(socket_info.sockfd_, &m, 0);

    struct in6_addr to_addr;
    memset(&to_addr, 0, sizeof(to_addr));

    unsigned int ifindex = UNSET_IFINDEX;
    if (result >= 0) {
        struct in6_pktinfo* pktinfo = NULL;<--- Variable 'pktinfo' can be declared as pointer to const


        // If we did read successfully, then we need to loop
        // through the control messages we received and
        // find the one with our destination address.
        //
        // We also keep a flag to see if we found it. If we
        // didn't, then we consider this to be an error.
        bool found_pktinfo = false;
        struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
        while (cmsg != NULL) {
            if ((cmsg->cmsg_level == IPPROTO_IPV6) &&
                (cmsg->cmsg_type == IPV6_PKTINFO)) {
                pktinfo = util::io::internal::convertPktInfo6(CMSG_DATA(cmsg));
                to_addr = pktinfo->ipi6_addr;
                ifindex = pktinfo->ipi6_ifindex;
                found_pktinfo = true;
#ifndef SO_TIMESTAMP
                break;
            }
#else
            } else if ((cmsg->cmsg_level == SOL_SOCKET) &&
                       (cmsg->cmsg_type  == SCM_TIMESTAMP)) {
                memcpy(&so_rcv_timestamp, CMSG_DATA(cmsg), sizeof(so_rcv_timestamp));
            }
#endif
            cmsg = CMSG_NXTHDR(&m, cmsg);
        }
        if (!found_pktinfo) {
            isc_throw(SocketReadError, "unable to find pktinfo");
        }
    } else {
        isc_throw(SocketReadError, "failed to receive data");
    }

    // Filter out packets sent to global unicast address (not link local and
    // not multicast) if the socket is set to listen multicast traffic and
    // is bound to in6addr_any. The traffic sent to global unicast address is
    // received via dedicated socket.
    IOAddress local_addr = IOAddress::fromBytes(AF_INET6,
                      reinterpret_cast<const uint8_t*>(&to_addr));
    if ((socket_info.addr_ == IOAddress("::")) &&
        !(local_addr.isV6Multicast() || local_addr.isV6LinkLocal())) {
        return (Pkt6Ptr());
    }

    // Let's create a packet.
    Pkt6Ptr pkt;
    try {
        pkt = Pkt6Ptr(new Pkt6(buf, result));
    } catch (const std::exception& ex) {
        isc_throw(SocketReadError, "failed to create new packet");
    }

    pkt->updateTimestamp();

#ifdef SO_TIMESTAMP
    pkt->addPktEvent(PktEvent::SOCKET_RECEIVED, so_rcv_timestamp);
#endif

    pkt->addPktEvent(PktEvent::BUFFER_READ);

    pkt->setLocalAddr(IOAddress::fromBytes(AF_INET6,
                      reinterpret_cast<const uint8_t*>(&to_addr)));
    pkt->setRemoteAddr(IOAddress::fromBytes(AF_INET6,
                       reinterpret_cast<const uint8_t*>(&from.sin6_addr)));
    pkt->setRemotePort(ntohs(from.sin6_port));
    pkt->setIndex(ifindex);

    IfacePtr received = IfaceMgr::instance().getIface(pkt->getIndex());
    if (received) {
        pkt->setIface(received->getName());
    } else {
        isc_throw(SocketReadError, "received packet over unknown interface"
                  << "(ifindex=" << pkt->getIndex() << ")");
    }

    return (pkt);

}

int
PktFilterInet6::send(const Iface&, uint16_t sockfd, const Pkt6Ptr& pkt) {
    uint8_t control_buf[CONTROL_BUF_LEN];
    memset(&control_buf[0], 0, CONTROL_BUF_LEN);

    // Set the target address we're sending to.
    sockaddr_in6 to;
    memset(&to, 0, sizeof(to));
    to.sin6_family = AF_INET6;
    to.sin6_port = htons(pkt->getRemotePort());
    memcpy(&to.sin6_addr,
           &pkt->getRemoteAddr().toBytes()[0],
           16);
    to.sin6_scope_id = pkt->getIndex();

    // Initialize our message header structure.
    struct msghdr m;
    memset(&m, 0, sizeof(m));
    m.msg_name = &to;
    m.msg_namelen = sizeof(to);

    // Set the data buffer we're sending. (Using this wacky
    // "scatter-gather" stuff... we only have a single chunk
    // of data to send, so we declare a single vector entry.)

    // As v structure is a C-style is used for both sending and
    // receiving data, it is shared between sending and receiving
    // (sendmsg and recvmsg). It is also defined in system headers,
    // so we have no control over its definition. To set iov_base
    // (defined as void*) we must use const cast from void *.
    // Otherwise C++ compiler would complain that we are trying
    // to assign const void* to void*.
    struct iovec v;
    memset(&v, 0, sizeof(v));
    v.iov_base = const_cast<void *>(pkt->getBuffer().getDataAsVoidPtr());
    v.iov_len = pkt->getBuffer().getLength();
    m.msg_iov = &v;
    m.msg_iovlen = 1;

    // Setting the interface is a bit more involved.
    //
    // We have to create a "control message", and set that to
    // define the IPv6 packet information. We could set the
    // source address if we wanted, but we can safely let the
    // kernel decide what that should be.
    m.msg_control = &control_buf[0];
    m.msg_controllen = CONTROL_BUF_LEN;
    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&m);

    // FIXME: Code below assumes that cmsg is not NULL, but
    // CMSG_FIRSTHDR() is coded to return NULL as a possibility.  The
    // following assertion should never fail, but if it did and you came
    // here, fix the code. :)
    isc_throw_assert(cmsg != NULL);

    cmsg->cmsg_level = IPPROTO_IPV6;
    cmsg->cmsg_type = IPV6_PKTINFO;
    cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
    struct in6_pktinfo *pktinfo =
        util::io::internal::convertPktInfo6(CMSG_DATA(cmsg));
    memset(pktinfo, 0, sizeof(struct in6_pktinfo));
    pktinfo->ipi6_ifindex = pkt->getIndex();
    // According to RFC3542, section 20.2, the msg_controllen field
    // may be set using CMSG_SPACE (which includes padding) or
    // using CMSG_LEN. Both forms appear to work fine on Linux, FreeBSD,
    // NetBSD, but OpenBSD appears to have a bug, discussed here:
    // https://marc.info/?l=openbsd-bugs&m=123485913417684&w=2
    // which causes sendmsg to return EINVAL if the CMSG_LEN is
    // used to set the msg_controllen value.
    m.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));

    pkt->updateTimestamp();

    pkt->addPktEvent(PktEvent::RESPONSE_SENT);

    int result = sendmsg(sockfd, &m, 0);
    if (result < 0) {
        isc_throw(SocketWriteError, "pkt6 send failed: sendmsg() returned"
                  " with an error: " << strerror(errno));
    }

    return (0);
}

}
}