Browse Source

[991] Broadcast options are enabled on sockets conditionally.

Marcin Siodelski 12 years ago
parent
commit
43ad3049d2

+ 2 - 2
src/bin/dhcp4/dhcp4_srv.cc

@@ -57,7 +57,7 @@ static const char* SERVER_ID_FILE = "b10-dhcp4-serverid";
 // These are hardcoded parameters. Currently this is a skeleton server that only
 // grants those options and a single, fixed, hardcoded lease.
 
-Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
+Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast) {
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
     try {
         // First call to instance() will create IfaceMgr (it's a singleton)
@@ -67,7 +67,7 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
         if (port) {
             // open sockets only if port is non-zero. Port 0 is used
             // for non-socket related testing.
-            IfaceMgr::instance().openSockets4(port);
+            IfaceMgr::instance().openSockets4(port, use_bcast);
         }
 
         string srvid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_ID_FILE);

+ 3 - 1
src/bin/dhcp4/dhcp4_srv.h

@@ -66,8 +66,10 @@ class Dhcpv4Srv : public boost::noncopyable {
     /// @param port specifies port number to listen on
     /// @param dbconfig Lease manager configuration string.  The default
     ///        of the "memfile" manager is used for testing.
+    /// @param use_bcast configure sockets to support broadcast messages.
     Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT,
-              const char* dbconfig = "type=memfile");
+              const char* dbconfig = "type=memfile",
+              const bool use_bcast = true);
 
     /// @brief Destructor. Used during DHCPv4 service shutdown.
     ~Dhcpv4Srv();

+ 10 - 1
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -45,7 +45,16 @@ namespace {
 class NakedDhcpv4Srv: public Dhcpv4Srv {
     // "Naked" DHCPv4 server, exposes internal fields
 public:
-    NakedDhcpv4Srv(uint16_t port = 0):Dhcpv4Srv(port) { }
+
+    /// @brief Constructor.
+    ///
+    /// It disables configuration of broadcast options on
+    /// sockets that are opened by the Dhcpv4Srv constructor.
+    /// Setting broadcast options requires root privileges
+    /// which is not the case when running unit tests.
+    NakedDhcpv4Srv(uint16_t port = 0)
+        : Dhcpv4Srv(port, "type=memfile", false) {
+    }
 
     using Dhcpv4Srv::processDiscover;
     using Dhcpv4Srv::processRequest;

+ 45 - 4
src/lib/dhcp/iface_mgr.cc

@@ -195,10 +195,23 @@ void IfaceMgr::stubDetectIfaces() {
     addInterface(iface);
 }
 
-bool IfaceMgr::openSockets4(const uint16_t port) {
+bool IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast) {
     int sock;
     int count = 0;
 
+// This option is used to bind sockets to particular interfaces.
+// This is currently the only way to discover on which interface
+// the broadcast packet has been received. If this option is
+// not supported then only one interface should be confugured
+// to listen for broadcast traffic.
+#ifdef SO_BINDTODEVICE
+    const bool bind_to_device = true;
+#else
+    const bool bind_to_device = false;
+#endif
+
+    int bcast_num = 0;
+
     for (IfaceCollection::iterator iface = ifaces_.begin();
          iface != ifaces_.end();
          ++iface) {
@@ -219,9 +232,37 @@ bool IfaceMgr::openSockets4(const uint16_t port) {
                 continue;
             }
 
-            // Open socket and enable broadcast traffic
-            // (two last arguments enable broadcast).
-            sock = openSocket(iface->getName(), *addr, port, true, true);
+            // If selected interface is broadcast capable set appropriate
+            // options on the socket so as it can receive and send broadcast
+            // messages.
+            if (iface->flag_broadcast_ && use_bcast) {
+                // If our OS supports binding socket to a device we can listen
+                // for broadcast messages on multiple interfaces. Otherwise we
+                // bind to INADDR_ANY address but we can do it only once. Thus,
+                // if one socket has been bound we can't do it any further.
+                if (!bind_to_device && bcast_num > 0) {
+                    isc_throw(SocketConfigError, "SO_BINDTODEVICE socket option is"
+                              << " not supported on this OS; therefore, DHCP"
+                              << " server can only listen broadcast traffic on"
+                              << " a single interface");
+
+                } else {
+                    // We haven't open any broadcast sockets yet, so we can
+                    // open at least one more.
+                    sock = openSocket(iface->getName(), *addr, port, true, true);
+                    // Binding socket to an interface is not supported so we can't
+                    // open any more broadcast sockets. Increase the number of
+                    // opened broadcast sockets.
+                    if (!bind_to_device) {
+                        ++bcast_num;
+                    }
+                }
+
+            } else {
+                // Not broadcast capable, do not set broadcast flags.
+                sock = openSocket(iface->getName(), *addr, port, false, false);
+
+            }
             if (sock < 0) {
                 isc_throw(SocketConfigError, "failed to open IPv4 socket"
                           << " supporting broadcast traffic");

+ 3 - 1
src/lib/dhcp/iface_mgr.h

@@ -534,10 +534,12 @@ public:
     /// Will throw exception if socket creation fails.
     ///
     /// @param port specifies port number (usually DHCP4_SERVER_PORT)
+    /// @param use_bcast configure sockets to support broadcast messages.
     ///
     /// @throw SocketOpenFailure if tried and failed to open socket.
     /// @return true if any sockets were open
-    bool openSockets4(const uint16_t port = DHCP4_SERVER_PORT);
+    bool openSockets4(const uint16_t port = DHCP4_SERVER_PORT,
+                      const bool use_bcast = true);
 
     /// @brief Closes all open sockets.
     /// Is used in destructor, but also from Dhcpv4_srv and Dhcpv6_srv classes.

+ 2 - 0
src/lib/dhcp/pkt_filter_inet.cc

@@ -49,6 +49,7 @@ PktFilterInet::openSocket(const Iface& iface,
         isc_throw(SocketConfigError, "Failed to create UDP6 socket.");
     }
 
+#ifdef SO_BINDTODEVICE
     if (receive_bcast) {
         // Bind to device so as we receive traffic on a specific interface.
         if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, iface.getName().c_str(),
@@ -58,6 +59,7 @@ PktFilterInet::openSocket(const Iface& iface,
                       << "on socket " << sock);
         }
     }
+#endif
 
     if (send_bcast) {
         // Enable sending to broadcast address.