From: Marcin Siodelski Date: Mon, 23 Jun 2014 15:53:31 +0000 (+0200) Subject: [3437] Bind socket to multicast address on Linux, not on BSD. X-Git-Tag: trac3473_base~2^2~4 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=409830a24289cf7b521d153f812db1f6bbb60f43;p=thirdparty%2Fkea.git [3437] Bind socket to multicast address on Linux, not on BSD. On BSD open the socket, bind to in6addr_any and join multicast group. --- diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc index cabc269d24..d2aa286f7d 100644 --- a/src/lib/dhcp/iface_mgr.cc +++ b/src/lib/dhcp/iface_mgr.cc @@ -344,9 +344,22 @@ IfaceMgr::hasOpenSocket(const IOAddress& addr) const { const Iface::SocketCollection& sockets = iface->getSockets(); for (Iface::SocketCollection::const_iterator sock = sockets.begin(); sock != sockets.end(); ++sock) { - // Check if the socket address matches the specified address. + // Check if the socket address matches the specified address or + // if address is unspecified (in6addr_any). if (sock->addr_ == addr) { return (true); + } else if (sock->addr_ == IOAddress("::")) { + // Handle the case that the address is unspecified (any). + // In this case, we should check if the specified address + // belongs to any of the interfaces. + for (Iface::AddressCollection::const_iterator addr_it = + iface->getAddresses().begin(); + addr_it != iface->getAddresses().end(); + ++addr_it) { + if (addr == *addr_it) { + return (true); + } + } } } } @@ -776,17 +789,6 @@ IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) { return IOAddress(local_address); } -int -IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port, - const bool join_multicast) { - // Assuming that packet filter is not NULL, because its modifier checks it. - SocketInfo info = packet_filter6_->openSocket(iface, addr, port, - join_multicast); - iface.addSocket(info); - - return (info.sockfd_); -} - int IfaceMgr::openSocket4(Iface& iface, const IOAddress& addr, const uint16_t port, const bool receive_bcast, diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h index b3ebd69d1c..b2b86256ce 100644 --- a/src/lib/dhcp/iface_mgr.h +++ b/src/lib/dhcp/iface_mgr.h @@ -898,7 +898,10 @@ public: /// @brief Checks if there is a socket open and bound to an address. /// /// This function checks if one of the sockets opened by the IfaceMgr is - /// bound to the IP address specified as the method parameter. + /// bound to the IP address specified as the method parameter. If the + /// socket is bound to the port (and address is unspecified), the + /// method will check if the address passed in the argument is configured + /// on an interface. /// /// @param addr Address of the socket being searched. /// diff --git a/src/lib/dhcp/iface_mgr_bsd.cc b/src/lib/dhcp/iface_mgr_bsd.cc index d7ec23b47e..cc2eea7ddf 100644 --- a/src/lib/dhcp/iface_mgr_bsd.cc +++ b/src/lib/dhcp/iface_mgr_bsd.cc @@ -171,6 +171,20 @@ IfaceMgr::openMulticastSocket(Iface& iface, return (true); } +int +IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port, + const bool join_multicast) { + // On BSD, we bind the socket to in6addr_any and join multicast group + // to receive multicast traffic. So, if the multicast is requested, + // replace the address specified by the caller with the "unspecified" + // address. + IOAddress actual_address = join_multicast ? IOAddress("::") : addr; + SocketInfo info = packet_filter6_->openSocket(iface, actual_address, port, + join_multicast); + iface.addSocket(info); + return (info.sockfd_); +} + } // end of isc::dhcp namespace } // end of dhcp namespace diff --git a/src/lib/dhcp/iface_mgr_linux.cc b/src/lib/dhcp/iface_mgr_linux.cc index 15f64fac1d..b4b81c80f9 100644 --- a/src/lib/dhcp/iface_mgr_linux.cc +++ b/src/lib/dhcp/iface_mgr_linux.cc @@ -543,8 +543,10 @@ IfaceMgr::openMulticastSocket(Iface& iface, // link-local address. It may be required for us to close this // socket if an attempt to open and bind a socket to multicast // address fails. + int sock; try { - openSocket(iface.getName(), addr, port, iface.flag_multicast_); + sock = openSocket(iface.getName(), addr, port, + iface.flag_multicast_); } catch (const Exception& ex) { IFACEMGR_ERROR(SocketConfigError, error_handler, @@ -554,9 +556,49 @@ IfaceMgr::openMulticastSocket(Iface& iface, return (false); } + + // In order to receive multicast traffic another socket is opened + // and bound to the multicast address. + + /// @todo The DHCPv6 requires multicast so we may want to think + /// whether we want to open the socket on a multicast-incapable + /// interface or not. For now, we prefer to be liberal and allow + /// it for some odd use cases which may utilize non-multicast + /// interfaces. Perhaps a warning should be emitted if the + /// interface is not a multicast one. + if (iface.flag_multicast_) { + try { + openSocket(iface.getName(), + IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS), + port); + } catch (const Exception& ex) { + // An attempt to open and bind a socket to multicast addres + // has failed. We have to close the socket we previously + // bound to link-local address - this is everything or + // nothing strategy. + iface.delSocket(sock); + IFACEMGR_ERROR(SocketConfigError, error_handler, + "Failed to open multicast socket on" + " interface " << iface.getName() + << ", reason: " << ex.what()); + return (false); + } + } + // Both sockets have opened successfully. return (true); } +int +IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port, + const bool join_multicast) { + // Assuming that packet filter is not NULL, because its modifier checks it. + SocketInfo info = packet_filter6_->openSocket(iface, addr, port, + join_multicast); + iface.addSocket(info); + + return (info.sockfd_); +} + } // end of isc::dhcp namespace } // end of isc namespace diff --git a/src/lib/dhcp/iface_mgr_sun.cc b/src/lib/dhcp/iface_mgr_sun.cc index a78de8f4a5..ff30cfeb66 100644 --- a/src/lib/dhcp/iface_mgr_sun.cc +++ b/src/lib/dhcp/iface_mgr_sun.cc @@ -176,6 +176,16 @@ IfaceMgr::openMulticastSocket(Iface& iface, return (true); } +int +IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port, + const bool join_multicast) { + IOAddress actual_address = join_multicast ? IOAddress("::") : addr; + SocketInfo info = packet_filter6_->openSocket(iface, actual_address, port, + join_multicast); + iface.addSocket(info); + return (info.sockfd_); +} + } // end of isc::dhcp namespace } // end of dhcp namespace diff --git a/src/lib/dhcp/pkt_filter_inet6.cc b/src/lib/dhcp/pkt_filter_inet6.cc index 693365764e..11f5d6b7e5 100644 --- a/src/lib/dhcp/pkt_filter_inet6.cc +++ b/src/lib/dhcp/pkt_filter_inet6.cc @@ -38,18 +38,17 @@ PktFilterInet6::openSocket(const Iface& iface, 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 - // only. For other addresses it is left unspecified (0). - if ((addr.toText() != "::1") && addr.isV6LinkLocal()) { + // 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 != IOAddress("::1")) && + ((addr.isV6LinkLocal() || (addr == IOAddress("::"))))) { addr6.sin6_scope_id = if_nametoindex(iface.getName().c_str()); } - // In order to listen to the multicast traffic we need to bind socket - // to in6addr_any. If we bind to the link-local address the multicast - // packets will be filtered out. - if (join_multicast && iface.flag_multicast_) { - memcpy(&addr6.sin6_addr, &in6addr_any, sizeof(addr6.sin6_addr)); - } else { + // 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 diff --git a/src/lib/dhcp/pkt_filter_inet6.h b/src/lib/dhcp/pkt_filter_inet6.h index df524b36c2..b4b810d7b8 100644 --- a/src/lib/dhcp/pkt_filter_inet6.h +++ b/src/lib/dhcp/pkt_filter_inet6.h @@ -36,12 +36,10 @@ public: /// @brief Opens a socket. /// - /// This function open an IPv6 socket on an interface and binds it to a - /// specified UDP port and IP address. If the @c join_multicast parameter - /// is set to @c true, the function will attempt to join the socket to - /// the ff02::1:2 multicast group so as the multicast traffic can be - /// received. In this case the socket is bound to the in6addr_any - /// instead. + /// This function opens an IPv6 socket on an interface and binds it to a + /// specified UDP port and IP address. The @c addr value may be set to + /// "::" in which case the address is unspecified and the socket is + /// bound to in6addr_any. /// /// @param iface Interface descriptor. /// @param addr Address on the interface to be used to send packets. diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc index c8cd195af5..4e6ca6da31 100644 --- a/src/lib/dhcp/tests/iface_mgr_unittest.cc +++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc @@ -99,14 +99,16 @@ public: virtual SocketInfo openSocket(const Iface& iface, const isc::asiolink::IOAddress& addr, const uint16_t port, - const bool, + const bool join_multicast, const bool) { // Check if there is any other socket bound to the specified address // and port on this interface. const Iface::SocketCollection& sockets = iface.getSockets(); for (Iface::SocketCollection::const_iterator socket = sockets.begin(); socket != sockets.end(); ++socket) { - if ((socket->addr_ == addr) && (socket->port_ == port)) { + if (((socket->addr_ == addr) || + ((socket->addr_ == IOAddress("::")) && join_multicast)) && + socket->port_ == port) { isc_throw(SocketConfigError, "test socket bind error"); } } @@ -243,6 +245,16 @@ public: sock != sockets.end(); ++sock) { if (sock->addr_ == IOAddress(addr)) { return (true); + } else if ((sock->addr_ == IOAddress("::")) && + (IOAddress(addr).isV6LinkLocal())) { + for (Iface::AddressCollection::const_iterator addr_it = + iface->getAddresses().begin(); + addr_it != iface->getAddresses().end(); + ++addr_it) { + if (*addr_it == IOAddress(addr)) { + return (true); + } + } } } return (false); @@ -1906,10 +1918,10 @@ TEST_F(IfaceMgrTest, openSocket6ErrorHandler) { ASSERT_TRUE(filter); ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter)); - // Open socket on eth0. + // Open multicast socket on eth0. ASSERT_NO_THROW(ifacemgr.openSocket("eth0", IOAddress("fe80::3a60:77ff:fed5:cdef"), - DHCP6_SERVER_PORT)); + DHCP6_SERVER_PORT, true)); // Install an error handler before trying to open sockets. This handler // should be called when the IfaceMgr fails to open socket on eth0.