Kea  1.9.9-git
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>
11 #include <dhcp/pkt_filter_inet6.h>
12 #include <exceptions/isc_assert.h>
14 
15 #include <fcntl.h>
16 #include <netinet/in.h>
17 
18 using namespace isc::asiolink;
19 
20 namespace isc {
21 namespace dhcp {
22 
23 const size_t
24 PktFilterInet6::CONTROL_BUF_LEN = CMSG_SPACE(sizeof(struct in6_pktinfo));
25 
26 SocketInfo
27 PktFilterInet6::openSocket(const Iface& iface,
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 
144 Pkt6Ptr
145 PktFilterInet6::receive(const SocketInfo& socket_info) {
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 
256 int
257 PktFilterInet6::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 =
315  util::io::internal::convertPktInfo6(CMSG_DATA(cmsg));
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 }
#define isc_throw_assert(expr)
Replacement for assert() that throws if the expression is false.
Definition: isc_assert.h:18
IfaceMgr exception thrown thrown when socket opening or configuration failed.
Definition: iface_mgr.h:63
boost::shared_ptr< Iface > IfacePtr
Type definition for the pointer to an Iface object.
Definition: iface_mgr.h:463
Represents a DHCPv6 packet.
Definition: pkt6.h:44
Represents a single network interface.
Definition: iface_mgr.h:118
int sockfd_
IPv4 or IPv6.
Definition: socket_info.h:26
#define ALL_DHCP_RELAY_AGENTS_AND_SERVERS
Definition: dhcp6.h:293
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
boost::shared_ptr< Pkt6 > Pkt6Ptr
A pointer to Pkt6 packet.
Definition: pkt6.h:28
isc::asiolink::IOAddress addr_
Definition: socket_info.h:21
IfaceMgr exception thrown thrown when error occurred during reading data from socket.
Definition: iface_mgr.h:71
struct in6_pktinfo * convertPktInfo6(char *pktinfo)
std::string getName() const
Returns interface name.
Definition: iface_mgr.h:221
Defines the logger used by the top-level component of kea-dhcp-ddns.
IfaceMgr exception thrown thrown when error occurred during sending data through socket.
Definition: iface_mgr.h:79
Holds information about socket.
Definition: socket_info.h:19