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