Parcourir la source

[3437] Bind socket to multicast address on Linux, not on BSD.

On BSD open the socket, bind to in6addr_any and join multicast group.
Marcin Siodelski il y a 11 ans
Parent
commit
409830a242

+ 14 - 12
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);
+                    }
+                }
             }
         }
     }
@@ -777,17 +790,6 @@ IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) {
 }
 
 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,
                           const bool send_bcast) {

+ 4 - 1
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.
     ///

+ 14 - 0
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

+ 43 - 1
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
 

+ 10 - 0
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
 

+ 8 - 9
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

+ 4 - 6
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.

+ 16 - 4
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.