Browse Source

[master] Merge branch 'trac3437'

Marcin Siodelski 11 years ago
parent
commit
f4c2fe2fc3

+ 7 - 0
doc/examples/kea6/several-subnets.json

@@ -8,6 +8,13 @@
 # Kea is told to listen on eth0 interface only.
   "interfaces": [ "eth0" ],
 
+# We need to specify lease type. As of May 2014, three backends are supported:
+# memfile, mysql and pgsql. We'll just use memfile, because it doesn't require
+# any prior set up.
+  "lease-database": {
+    "type": "memfile"
+  },
+
 # Addresses will be assigned with preferred and valid lifetimes
 # being 3000 and 4000, respectively. Client is told to start
 # renewing after 1000 seconds. If the server does not repond

+ 3 - 11
src/bin/dhcp4/dhcp4_srv.cc

@@ -90,10 +90,9 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const bool use_bcast,
 
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
     try {
-        // Open sockets only if port is non-zero. Port 0 is used for testing
-        // purposes in two cases:
-        // - when non-socket related testing is performed
-        // - when the particular test supplies its own packet filtering class.
+        // Port 0 is used for testing purposes where we don't open broadcast
+        // capable sockets. So, set the packet filter handling direct traffic
+        // only if we are in non-test mode.
         if (port) {
             // First call to instance() will create IfaceMgr (it's a singleton)
             // it may throw something if things go wrong.
@@ -103,13 +102,6 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const bool use_bcast,
             // may be lacking on some OSes, so there is no guarantee that server
             // will be able to respond directly.
             IfaceMgr::instance().setMatchingPacketFilter(direct_response_desired);
-
-            // Create error handler. This handler will be called every time
-            // the socket opening operation fails. We use this handler to
-            // log a warning.
-            isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
-                boost::bind(&Dhcpv4Srv::ifaceMgrSocket4ErrorHandler, _1);
-            IfaceMgr::instance().openSockets4(port_, use_bcast_, error_handler);
         }
 
         // Instantiate allocation engine

+ 6 - 15
src/bin/dhcp6/dhcp6_srv.cc

@@ -122,21 +122,12 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
 
     // Initialize objects required for DHCP server operation.
     try {
-        // Port 0 is used for testing purposes. It means that the server should
-        // not open any sockets at all. Some tests, e.g. configuration parser,
-        // require Dhcpv6Srv object, but they don't really need it to do
-        // anything. This speed up and simplifies the tests.
-        if (port > 0) {
-            if (IfaceMgr::instance().countIfaces() == 0) {
-                LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
-                return;
-            }
-            // Create error handler. This handler will be called every time
-            // the socket opening operation fails. We use this handler to
-            // log a warning.
-            isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
-                boost::bind(&Dhcpv6Srv::ifaceMgrSocket6ErrorHandler, _1);
-            IfaceMgr::instance().openSockets6(port_, error_handler);
+        // Port 0 is used for testing purposes where in most cases we don't
+        // rely on the physical interfaces. Therefore, it should be possible
+        // to create an object even when there are no usable interfaces.
+        if ((port > 0) && (IfaceMgr::instance().countIfaces() == 0)) {
+            LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
+            return;
         }
 
         string duid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_DUID_FILE);

+ 17 - 15
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);
+                    }
+                }
             }
         }
     }
@@ -545,9 +558,9 @@ IfaceMgr::openSockets6(const uint16_t port,
                 continue;
             }
 
-            // Run OS-specific function to open a socket on link-local address
-            // and join multicast group (non-Linux OSes), or open two sockets and
-            // bind one to link-local, another one to multicast address.
+            // Run OS-specific function to open a socket capable of receiving
+            // packets sent to All_DHCP_Relay_Agents_and_Servers multicast
+            // address.
             if (openMulticastSocket(*iface, *addr, port, error_handler)) {
                 ++count;
             }
@@ -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) {

+ 8 - 6
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.
     ///
@@ -1020,11 +1023,10 @@ private:
 
     /// @brief Open an IPv6 socket with multicast support.
     ///
-    /// This function opens socket(s) to allow reception of the DHCPv6 sent
-    /// to multicast address. It opens an IPv6 socket, binds it to link-local
-    /// address and joins multicast group (on non-Linux systems) or opens two
-    /// IPv6 sockets and binds one of them to link-local address and another
-    /// one to multicast address (on Linux systems).
+    /// This function opens a socket capable of receiving messages sent to
+    /// the All_DHCP_Relay_Agents_and_Servers (ff02::1:2) multicast address.
+    /// The socket is bound to the in6addr_any address and the IPV6_JOIN_GROUP
+    /// option is set to point to the ff02::1:2 multicast address.
     ///
     /// @note This function is intended to be called internally by the
     /// @c IfaceMgr::openSockets6. It is not intended to be called from any

+ 16 - 3
src/lib/dhcp/iface_mgr_bsd.cc

@@ -156,10 +156,9 @@ IfaceMgr::openMulticastSocket(Iface& iface,
                               const uint16_t port,
                               IfaceMgrErrorMsgCallback error_handler) {
     try {
-        // This should open a socket, bound it to link-local address
+        // This should open a socket, bind it to link-local address
         // and join multicast group.
-        openSocket(iface.getName(), addr, port,
-                   iface.flag_multicast_);
+        openSocket(iface.getName(), addr, port, iface.flag_multicast_);
 
     } catch (const Exception& ex) {
         IFACEMGR_ERROR(SocketConfigError, error_handler,
@@ -172,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

+ 13 - 2
src/lib/dhcp/iface_mgr_linux.cc

@@ -557,8 +557,8 @@ IfaceMgr::openMulticastSocket(Iface& iface,
 
     }
 
-    // To receive multicast traffic, Linux requires binding socket to
-    // the multicast address.
+    // 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
@@ -588,6 +588,17 @@ IfaceMgr::openMulticastSocket(Iface& iface,
     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
 

+ 7 - 7
src/lib/dhcp/libdhcp++.dox

@@ -112,14 +112,14 @@ network operations. In particlar, it provides information about existing
 network interfaces See isc::dhcp::IfaceMgr::Iface class and
 isc::dhcp::IfaceMgr::detectIfaces() and isc::dhcp::IfaceMgr::getIface().
 
-Currently there is interface detection is implemented in Linux only. There
-are plans to implement such support for other OSes, but they remain low
-priority for now.
+Currently there is interface detection is implemented in Linux and BSD.
+There are plans to implement such support for other OSes, but they
+remain low priority for now.
 
 Generic parts of the code are isc::dhcp::IfaceMgr class in
 src/lib/dhcp/iface_mgr.cc file. OS-specific code is located in separate
-files, e.g. iface_mgr_linux.cc. Such separation should be maintained when
-additional code will be developed.
+files, e.g. iface_mgr_linux.cc, iface_mgr_bsd. Such separation should be
+maintained when additional code will be developed.
 
 For systems that interface detection is not supported on, there is a stub
 mechanism implemented. It assumes that interface name is read from a text
@@ -132,7 +132,7 @@ should bind to. In theory this mechanism also supports IPv4, but it was
 never tested. The code currently supports only a single interface defined
 that way.
 
-Another useful methods are dedicated to transmission
+Other useful methods are dedicated to transmission
 (isc::dhcp::IfaceMgr::send(), 2 overloads) and reception
 (isc::dhcp::IfaceMgr::receive4() and isc::dhcp::IfaceMgr::receive6()).
 Note that receive4() and receive6() methods may return NULL, e.g.
@@ -196,7 +196,7 @@ the \ref isc::dhcp::IfaceMgr logic.
 The \ref isc::dhcp::IfaceMgr::openSockets6 function examines configuration
 of detected interfaces for their availability to listen DHCPv6 traffic. For
 all running interfaces (except local loopback) it will try to open a socket
-and bind it to the link local or global unicast address. The socket will
+and bind it to the link-local or global unicast address. The socket will
 not be opened on the interface which is down or for which it was explicitly
 specified that it should not be used to listen to DHCPv6 messages. There is
 a substantial amount of logic in this function that has to be unit tested for

+ 8 - 2
src/lib/dhcp/pkt_filter.cc

@@ -41,21 +41,27 @@ PktFilter::openFallbackSocket(const isc::asiolink::IOAddress& addr,
 
     if (bind(sock, reinterpret_cast<struct sockaddr*>(&addr4),
              sizeof(addr4)) < 0) {
+        // Get the error message immediately after the bind because the
+        // invocation to close() below would override the errno.
+        char* errmsg = strerror(errno);
         // Remember to close the socket if we failed to bind it.
         close(sock);
         isc_throw(SocketConfigError, "failed to bind fallback socket to"
                   " address " << addr << ", port " << port
-                  << ", reason: " << strerror(errno)
+                  << ", reason: " << errmsg
                   << " - is another DHCP server running?");
     }
 
     // Set socket to non-blocking mode. This is to prevent the read from the
     // fallback socket to block message processing on the primary socket.
     if (fcntl(sock, F_SETFL, O_NONBLOCK) != 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 set SO_NONBLOCK option on the"
                   " fallback socket, bound to " << addr << ", port "
-                  << port << ", reason: " << strerror(errno));
+                  << port << ", reason: " << errmsg);
     }
     // Successfully created and bound a fallback socket. Return a descriptor.
     return (sock);

+ 3 - 4
src/lib/dhcp/pkt_filter6.h

@@ -125,10 +125,9 @@ public:
 
     /// @brief Joins IPv6 multicast group on a socket.
     ///
-    /// Socket must be created and bound to an address. Note that this
-    /// address is different than the multicast address. For example DHCPv6
-    /// server should bind its socket to link-local address (fe80::1234...)
-    /// and later join ff02::1:2 multicast group.
+    /// This function joins the socket to the specified multicast group.
+    /// The socket descriptor must point to a valid socket bound to the
+    /// in6addr_any prior to calling this function.
     ///
     /// @param sock A socket descriptor (socket must be bound).
     /// @param ifname An interface name (for link-scoped multicast groups).

+ 31 - 6
src/lib/dhcp/pkt_filter_inet6.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -38,11 +38,20 @@ PktFilterInet6::openSocket(const Iface& iface,
     memset(&addr6, 0, sizeof(addr6));
     addr6.sin6_family = AF_INET6;
     addr6.sin6_port = htons(port);
-    if (addr.toText() != "::1") {
+    // 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());
     }
 
-    memcpy(&addr6.sin6_addr, &addr.toBytes()[0], sizeof(addr6.sin6_addr));
+    // 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
@@ -60,13 +69,18 @@ PktFilterInet6::openSocket(const Iface& iface,
     if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
                    (char *)&flag, sizeof(flag)) < 0) {
         close(sock);
-        isc_throw(SocketConfigError, "Can't set SO_REUSEADDR option on dhcpv6 socket.");
+        isc_throw(SocketConfigError, "Can't set SO_REUSEADDR option on IPv6"
+                  " socket.");
     }
 
     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);
+        isc_throw(SocketConfigError, "Failed to bind socket " << sock << " to "
+                  << addr.toText() << "/port=" << port
+                  << ": " << errmsg);
     }
 #ifdef IPV6_RECVPKTINFO
     // RFC3542 - a new way
@@ -169,6 +183,17 @@ PktFilterInet6::receive(const SocketInfo& socket_info) {
         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 {

+ 8 - 2
src/lib/dhcp/pkt_filter_inet6.h

@@ -36,8 +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.
+    /// 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.
@@ -62,6 +64,10 @@ public:
     /// must first check that there is any message on the socket (using
     /// select function) prior to calling this function.
     ///
+    /// If the message is received through the socket bound to "any"
+    /// (in6addr_any) address this function will drop this message if it has
+    /// been sent to an address other than multicast or link-local.
+    ///
     /// @param socket_info A structure holding socket information.
     ///
     /// @return A pointer to received message.

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