Kea 3.1.9
pkt_filter_inet6.cc
Go to the documentation of this file.
1// Copyright (C) 2013-2026 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
9#include <dhcp/iface_mgr.h>
10#include <dhcp/pkt6.h>
14
15#include <fcntl.h>
16#include <netinet/in.h>
17
18using namespace isc::asiolink;
19
20namespace isc {
21namespace dhcp {
22
23const size_t PktFilterInet6::CONTROL_BUF_LEN = 512;
24
25bool
27#ifdef SO_TIMESTAMP
28 return (true);
29#else
30 return (false);
31#endif
32}
33
36 const isc::asiolink::IOAddress& addr,
37 const uint16_t port,
38 const bool join_multicast) {
39 struct sockaddr_in6 addr6;
40 memset(&addr6, 0, sizeof(addr6));
41 addr6.sin6_family = AF_INET6;
42 addr6.sin6_port = htons(port);
43 // sin6_scope_id must be set to interface index for link-local addresses.
44 // For unspecified addresses we set the scope id to the interface index
45 // to handle the case when the IfaceMgr is opening a socket which will
46 // join the multicast group. Such socket is bound to in6addr_any.
47 if (addr.isV6Multicast() ||
48 (addr.isV6LinkLocal() && (addr != IOAddress("::1"))) ||
49 (addr == IOAddress("::"))) {
50 addr6.sin6_scope_id = if_nametoindex(iface.getName().c_str());
51 }
52
53 // Copy the address if it has been specified.
54 if (addr != IOAddress("::")) {
55 memcpy(&addr6.sin6_addr, &addr.toBytes()[0], sizeof(addr6.sin6_addr));
56 }
57#ifdef HAVE_SA_LEN
58 addr6.sin6_len = sizeof(addr6);
59#endif
60
61 // @todo use sockcreator once it becomes available
62
63 // make a socket
64 int sock = socket(AF_INET6, SOCK_DGRAM, 0);
65 if (sock < 0) {
66 isc_throw(SocketConfigError, "Failed to create UDP6 socket.");
67 }
68
69 // Set the close-on-exec flag.
70 if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) {
71 close(sock);
72 isc_throw(SocketConfigError, "Failed to set close-on-exec flag"
73 << " on IPv6 socket.");
74 }
75
76 // Set SO_REUSEADDR option.
77 int flag = 1;
78 if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
79 (char *)&flag, sizeof(flag)) < 0) {
80 close(sock);
81 isc_throw(SocketConfigError, "Can't set SO_REUSEADDR option on IPv6"
82 " socket.");
83 }
84
85#ifdef SO_REUSEPORT
86 // Set SO_REUSEPORT has to be set to open multiple sockets and bind to
87 // in6addr_any (binding to port). Binding to port is required on some
88 // operating systems, e.g. NetBSD and OpenBSD so as the socket can
89 // join the socket to multicast group.
90 // RedHat 6.4 defines SO_REUSEPORT but the kernel does not support it
91 // and returns ENOPROTOOPT so ignore this error. Other versions may be
92 // affected, too.
93 if ((setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
94 (char *)&flag, sizeof(flag)) < 0) &&
95 (errno != ENOPROTOOPT)) {
96 close(sock);
97 isc_throw(SocketConfigError, "Can't set SO_REUSEPORT option on IPv6"
98 " socket.");
99 }
100#endif
101
102#ifdef SO_TIMESTAMP
103 int enable = 1;
104 if (setsockopt(sock, SOL_SOCKET, SO_TIMESTAMP, &enable, sizeof(enable))) {
105 const char* errmsg = strerror(errno);
106 isc_throw(SocketConfigError, "Could not enable SO_TIMESTAMP for " << addr.toText()
107 << ", error: " << errmsg);
108 }
109#endif
110
111#ifdef IPV6_V6ONLY
112 // Set IPV6_V6ONLY to get only IPv6 packets.
113 if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
114 (char *)&flag, sizeof(flag)) < 0) {
115 close(sock);
116 isc_throw(SocketConfigError, "Can't set IPV6_V6ONLY option on "
117 "IPv6 socket.");
118 }
119#endif
120
121 if (bind(sock, (struct sockaddr *)&addr6, sizeof(addr6)) < 0) {
122 // Get the error message immediately after the bind because the
123 // invocation to close() below would override the errno.
124 char* errmsg = strerror(errno);
125 close(sock);
126 isc_throw(SocketConfigError, "Failed to bind socket " << sock << " to "
127 << addr.toText() << "/port=" << port
128 << ": " << errmsg);
129 }
130
131#ifdef IPV6_RECVPKTINFO
132 // RFC3542 - a new way
133 if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO,
134 &flag, sizeof(flag)) != 0) {
135 close(sock);
136 isc_throw(SocketConfigError, "setsockopt: IPV6_RECVPKTINFO failed.");
137 }
138#else
139 // RFC2292 - an old way
140 if (setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO,
141 &flag, sizeof(flag)) != 0) {
142 close(sock);
143 isc_throw(SocketConfigError, "setsockopt: IPV6_PKTINFO: failed.");
144 }
145#endif
146
147 // Join All_DHCP_Relay_Agents_and_Servers multicast group if
148 // requested.
149 if (join_multicast &&
150 !joinMulticast(sock, iface.getName(),
151 std::string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS))) {
152 close(sock);
153 isc_throw(SocketConfigError, "Failed to join "
155 << " multicast group.");
156 }
157
158 // Join All_DHCP_Servers multicast group if requested.
159 if (join_multicast &&
160 !joinMulticast(sock, iface.getName(), std::string(ALL_DHCP_SERVERS))) {
161 close(sock);
162 isc_throw(SocketConfigError, "Failed to join "
163 << ALL_DHCP_SERVERS << " multicast group.");
164 }
165
166 return (SocketInfo(addr, port, sock));
167}
168
171 // Now we have a socket, let's get some data from it!
172 uint8_t buf[IfaceMgr::RCVBUFSIZE];
173 uint8_t control_buf[CONTROL_BUF_LEN];
174 memset(&control_buf[0], 0, CONTROL_BUF_LEN);
175 struct sockaddr_in6 from;
176 memset(&from, 0, sizeof(from));
177
178#ifdef SO_TIMESTAMP
179 struct timeval so_rcv_timestamp;
180 memset(&so_rcv_timestamp, 0, sizeof(so_rcv_timestamp));
181#endif
182
183 // Initialize our message header structure.
184 struct msghdr m;
185 memset(&m, 0, sizeof(m));
186
187 // Point so we can get the from address.
188 m.msg_name = &from;
189 m.msg_namelen = sizeof(from);
190
191 // Set the data buffer we're receiving. (Using this wacky
192 // "scatter-gather" stuff... but we that doesn't really make
193 // sense for us, so we use a single vector entry.)
194 struct iovec v;
195 memset(&v, 0, sizeof(v));
196 v.iov_base = static_cast<void*>(buf);
197 v.iov_len = IfaceMgr::RCVBUFSIZE;
198 m.msg_iov = &v;
199 m.msg_iovlen = 1;
200
201 // Getting the interface is a bit more involved.
202 //
203 // We set up some space for a "control message". We have
204 // previously asked the kernel to give us packet
205 // information (when we initialized the interface), so we
206 // should get the destination address from that.
207 m.msg_control = &control_buf[0];
208 m.msg_controllen = CONTROL_BUF_LEN;
209
210 int result = recvmsg(socket_info.sockfd_, &m, 0);
211
212 struct in6_addr to_addr;
213 memset(&to_addr, 0, sizeof(to_addr));
214
215 unsigned int ifindex = UNSET_IFINDEX;
216 if (result >= 0) {
217 struct in6_pktinfo* pktinfo = NULL;
218
219
220 // If we did read successfully, then we need to loop
221 // through the control messages we received and
222 // find the one with our destination address.
223 //
224 // We also keep a flag to see if we found it. If we
225 // didn't, then we consider this to be an error.
226 bool found_pktinfo = false;
227 struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
228 while (cmsg != NULL) {
229 if ((cmsg->cmsg_level == IPPROTO_IPV6) &&
230 (cmsg->cmsg_type == IPV6_PKTINFO)) {
231 pktinfo = util::io::internal::convertPktInfo6(CMSG_DATA(cmsg));
232 to_addr = pktinfo->ipi6_addr;
233 ifindex = pktinfo->ipi6_ifindex;
234 found_pktinfo = true;
235#ifndef SO_TIMESTAMP
236 break;
237 }
238#else
239 } else if ((cmsg->cmsg_level == SOL_SOCKET) &&
240 (cmsg->cmsg_type == SCM_TIMESTAMP)) {
241 memcpy(&so_rcv_timestamp, CMSG_DATA(cmsg), sizeof(so_rcv_timestamp));
242 }
243#endif
244 cmsg = CMSG_NXTHDR(&m, cmsg);
245 }
246 if (!found_pktinfo) {
247 isc_throw(SocketReadError, "unable to find pktinfo");
248 }
249 } else {
250 isc_throw(SocketReadError, "failed to receive data");
251 }
252
253 // Filter out packets sent to global unicast address (not link local and
254 // not multicast) if the socket is set to listen multicast traffic and
255 // is bound to in6addr_any. The traffic sent to global unicast address is
256 // received via dedicated socket.
257 IOAddress local_addr = IOAddress::fromBytes(AF_INET6,
258 reinterpret_cast<const uint8_t*>(&to_addr));
259 if ((socket_info.addr_ == IOAddress("::")) &&
260 !(local_addr.isV6Multicast() || local_addr.isV6LinkLocal())) {
261 return (Pkt6Ptr());
262 }
263
264 // Let's create a packet.
265 Pkt6Ptr pkt;
266 try {
267 pkt = Pkt6Ptr(new Pkt6(buf, result));
268 } catch (const std::exception& ex) {
269 isc_throw(SocketReadError, "failed to create new packet");
270 }
271
272 pkt->updateTimestamp();
273
274#ifdef SO_TIMESTAMP
275 pkt->addPktEvent(PktEvent::SOCKET_RECEIVED, so_rcv_timestamp);
276#endif
277
278 pkt->addPktEvent(PktEvent::BUFFER_READ);
279
280 pkt->setLocalAddr(IOAddress::fromBytes(AF_INET6,
281 reinterpret_cast<const uint8_t*>(&to_addr)));
282 pkt->setRemoteAddr(IOAddress::fromBytes(AF_INET6,
283 reinterpret_cast<const uint8_t*>(&from.sin6_addr)));
284 pkt->setRemotePort(ntohs(from.sin6_port));
285 pkt->setIndex(ifindex);
286
287 IfacePtr received = IfaceMgr::instance().getIface(pkt->getIndex());
288 if (received) {
289 pkt->setIface(received->getName());
290 } else {
291 isc_throw(SocketReadError, "received packet over unknown interface"
292 << "(ifindex=" << pkt->getIndex() << ")");
293 }
294
295 return (pkt);
296
297}
298
299int
300PktFilterInet6::send(const Iface&, uint16_t sockfd, const Pkt6Ptr& pkt) {
301 uint8_t control_buf[CONTROL_BUF_LEN];
302 memset(&control_buf[0], 0, CONTROL_BUF_LEN);
303
304 // Set the target address we're sending to.
305 sockaddr_in6 to;
306 memset(&to, 0, sizeof(to));
307 to.sin6_family = AF_INET6;
308 to.sin6_port = htons(pkt->getRemotePort());
309 memcpy(&to.sin6_addr,
310 &pkt->getRemoteAddr().toBytes()[0],
311 16);
312 to.sin6_scope_id = pkt->getIndex();
313
314 // Initialize our message header structure.
315 struct msghdr m;
316 memset(&m, 0, sizeof(m));
317 m.msg_name = &to;
318 m.msg_namelen = sizeof(to);
319
320 // Set the data buffer we're sending. (Using this wacky
321 // "scatter-gather" stuff... we only have a single chunk
322 // of data to send, so we declare a single vector entry.)
323
324 // As v structure is a C-style is used for both sending and
325 // receiving data, it is shared between sending and receiving
326 // (sendmsg and recvmsg). It is also defined in system headers,
327 // so we have no control over its definition. To set iov_base
328 // (defined as void*) we must use const cast from void *.
329 // Otherwise C++ compiler would complain that we are trying
330 // to assign const void* to void*.
331 struct iovec v;
332 memset(&v, 0, sizeof(v));
333 v.iov_base = const_cast<void *>(pkt->getBuffer().getDataAsVoidPtr());
334 v.iov_len = pkt->getBuffer().getLength();
335 m.msg_iov = &v;
336 m.msg_iovlen = 1;
337
338 // Setting the interface is a bit more involved.
339 //
340 // We have to create a "control message", and set that to
341 // define the IPv6 packet information. We could set the
342 // source address if we wanted, but we can safely let the
343 // kernel decide what that should be.
344 m.msg_control = &control_buf[0];
345 m.msg_controllen = CONTROL_BUF_LEN;
346 struct cmsghdr *cmsg = CMSG_FIRSTHDR(&m);
347
348 // FIXME: Code below assumes that cmsg is not NULL, but
349 // CMSG_FIRSTHDR() is coded to return NULL as a possibility. The
350 // following assertion should never fail, but if it did and you came
351 // here, fix the code. :)
352 isc_throw_assert(cmsg != NULL);
353
354 cmsg->cmsg_level = IPPROTO_IPV6;
355 cmsg->cmsg_type = IPV6_PKTINFO;
356 cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
357 struct in6_pktinfo *pktinfo =
359 memset(pktinfo, 0, sizeof(struct in6_pktinfo));
360 pktinfo->ipi6_ifindex = pkt->getIndex();
361 // According to RFC3542, section 20.2, the msg_controllen field
362 // may be set using CMSG_SPACE (which includes padding) or
363 // using CMSG_LEN. Both forms appear to work fine on Linux, FreeBSD,
364 // NetBSD, but OpenBSD appears to have a bug, discussed here:
365 // https://marc.info/?l=openbsd-bugs&m=123485913417684&w=2
366 // which causes sendmsg to return EINVAL if the CMSG_LEN is
367 // used to set the msg_controllen value.
368 m.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
369
370 pkt->updateTimestamp();
371
372 pkt->addPktEvent(PktEvent::RESPONSE_SENT);
373
374 int result = sendmsg(sockfd, &m, 0);
375 if (result < 0) {
376 isc_throw(SocketWriteError, "pkt6 send failed: sendmsg() returned"
377 " with an error: " << strerror(errno));
378 }
379
380 return (0);
381}
382
383}
384}
IfacePtr getIface(const unsigned int ifindex)
Returns interface specified interface index.
Definition iface_mgr.cc:900
static IfaceMgr & instance()
IfaceMgr is a singleton class.
Definition iface_mgr.cc:49
static const uint32_t RCVBUFSIZE
Packet reception buffer size.
Definition iface_mgr.h:796
Represents a single network interface.
Definition iface_mgr.h:136
std::string getName() const
Returns interface name.
Definition iface_mgr.h:264
static const std::string BUFFER_READ
Event that marks when a packet is read from the socket buffer by application.
Definition pkt.h:97
static const std::string SOCKET_RECEIVED
Event that marks when a packet is placed in the socket buffer by the kernel.
Definition pkt.h:93
static const std::string RESPONSE_SENT
Event that marks when a packet is been written to the socket by application.
Definition pkt.h:101
static bool joinMulticast(int sock, const std::string &ifname, const std::string &mcast)
Joins IPv6 multicast group on a socket.
virtual int send(const Iface &iface, uint16_t sockfd, const Pkt6Ptr &pkt)
Sends DHCPv6 message through a specified interface and socket.
virtual SocketInfo openSocket(Iface &iface, const isc::asiolink::IOAddress &addr, const uint16_t port, const bool join_multicast)
Opens a socket.
virtual bool isSocketReceivedTimeSupported() const
Check if the socket received time is supported.
virtual Pkt6Ptr receive(const SocketInfo &socket_info)
Receives DHCPv6 message on the interface.
IfaceMgr exception thrown thrown when socket opening or configuration failed.
Definition iface_mgr.h:66
IfaceMgr exception thrown thrown when error occurred during reading data from socket.
Definition iface_mgr.h:74
IfaceMgr exception thrown thrown when error occurred during sending data through socket.
Definition iface_mgr.h:82
#define ALL_DHCP_SERVERS
Definition dhcp6.h:298
#define ALL_DHCP_RELAY_AGENTS_AND_SERVERS
Definition dhcp6.h:297
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define isc_throw_assert(expr)
Replacement for assert() that throws if the expression is false.
Definition isc_assert.h:18
boost::shared_ptr< Iface > IfacePtr
Type definition for the pointer to an Iface object.
Definition iface_mgr.h:544
constexpr unsigned int UNSET_IFINDEX
A value used to signal that the interface index was not set.
Definition pkt.h:30
boost::shared_ptr< Pkt6 > Pkt6Ptr
A pointer to Pkt6 packet.
Definition pkt6.h:31
struct in6_pktinfo * convertPktInfo6(char *pktinfo)
Defines the logger used by the top-level component of kea-lfc.
Holds information about socket.
Definition socket_info.h:18
int sockfd_
Socket descriptor (a.k.a. primary socket).
Definition socket_info.h:30