diff options
Diffstat (limited to 'src/lib/dhcp/pkt_filter_inet6.cc')
-rw-r--r-- | src/lib/dhcp/pkt_filter_inet6.cc | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/src/lib/dhcp/pkt_filter_inet6.cc b/src/lib/dhcp/pkt_filter_inet6.cc new file mode 100644 index 0000000..20d682c --- /dev/null +++ b/src/lib/dhcp/pkt_filter_inet6.cc @@ -0,0 +1,340 @@ +// Copyright (C) 2013-2020 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 <netinet/in.h> + +using namespace isc::asiolink; + +namespace isc { +namespace dhcp { + +const size_t +PktFilterInet6::CONTROL_BUF_LEN = CMSG_SPACE(sizeof(struct in6_pktinfo)); + +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 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); + 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)); + + // 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)); + + int ifindex = -1; + if (result >= 0) { + struct in6_pktinfo* pktinfo = NULL; + + + // 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; + break; + } + 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(); + + 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().getData()); + 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: + // http://www.archivum.info/mailing.openbsd.bugs/2009-02/00017/ + // kernel-6080-msg_controllen-of-IPV6_PKTINFO.html + // 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(); + + int result = sendmsg(sockfd, &m, 0); + if (result < 0) { + isc_throw(SocketWriteError, "pkt6 send failed: sendmsg() returned" + " with an error: " << strerror(errno)); + } + + return (0); +} + +} +} |