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