Browse Source

[2596] "Interface" parameter in Subnet6 configuration added.

Tomek Mrugalski 12 years ago
parent
commit
d47e9e008a

+ 6 - 0
ChangeLog

@@ -1,3 +1,9 @@
+5XX.	[func]		tomek
+	b10-dhcp6: It is now possible to specify that a configured subnet
+	is reachable locally over specified interface (see "interface"
+	parameter in Subnet6 configuration).
+	(Trac #2596, git TBD)
+
 545.	[func]		jinmei
 	libdns++: the SOA Rdata class now uses the generic lexer in
 	constructors from text.  This means that the MNAME and RNAME of an

+ 26 - 0
doc/guide/bind10-guide.xml

@@ -3839,6 +3839,32 @@ Dhcp6/subnet6	         []     list    (default)</screen>
           and problems in the DHCPv6 server. See <xref linkend="dhcp6-limit"/>.
         </para>
       </note>
+      <section id="dhcp6-config-subnets">
+        <title>Subnet selection</title>
+        <para>
+          DHCPv6 server may receive requests from local (connected to the same
+          subnet as the server) and remote (connecting via relays)
+          clients. Server may have many subnet configurations defined, so it
+          must select appropriate subnet for a given request. Server first
+          checks if there is only one subnet defined and source of the packet is
+          link-local. If this is the case server assumes that the only subnet
+          defined is local and client is indeed connected to it. This check
+          simplifies small deployments.
+        </para>
+        <para>If there are two or more subnets defined, server can not assume
+        which of those (if any) subnets are local. Therefore an optional
+        "interface" parameter has been defined to designate that a given subnet
+        is local, i.e. reachable directly over specified interface. For example
+        the server that is intended to serve local subnet over eth0 may be configured
+        as follows:
+        <screen>
+&gt; <userinput>config add Dhcp6/subnet6</userinput>
+&gt; <userinput>config set Dhcp6/subnet6[1]/subnet "2001:db8:beef::/48"</userinput>
+&gt; <userinput>config set Dhcp6/subnet6[1]/pool [ "2001:db8:beef::/48" ]</userinput>
+&gt; <userinput>config set Dhcp6/subnet6[1]/interface "eth0"</userinput>
+&gt; <userinput>config commit</userinput></screen>
+        </para>
+      </section>
     </section>
 
     <section id="dhcp6-std">

+ 15 - 0
src/bin/dhcp6/config_parser.cc

@@ -1135,6 +1135,15 @@ private:
         Triplet<uint32_t> pref = getParam("preferred-lifetime");
         Triplet<uint32_t> valid = getParam("valid-lifetime");
 
+        // Get interface name. If it is defined, then the subnet is available
+        // directly over specified network interface.
+
+        string iface;
+        StringStorage::iterator iface_iter = string_values_.find("interface");
+        if (iface_iter != string_values_.end()) {
+            iface = iface_iter->second;
+        }
+
         /// @todo: Convert this to logger once the parser is working reliably
         stringstream tmp;
         tmp << addr.toText() << "/" << (int)len
@@ -1151,6 +1160,11 @@ private:
             subnet_->addPool(*it);
         }
 
+        // Configure interface, if defined
+        if (iface.length()) {
+            subnet_->setIface(iface);
+        }
+
         Subnet::OptionContainerPtr options = subnet_->getOptionDescriptors("dhcp6");
         const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
 
@@ -1205,6 +1219,7 @@ private:
         factories["subnet"] = StringParser::factory;
         factories["pool"] = PoolParser::factory;
         factories["option-data"] = OptionDataListParser::factory;
+        factories["interface"] = StringParser::factory;
 
         FactoryMap::iterator f = factories.find(config_id);
         if (f == factories.end()) {

+ 6 - 0
src/bin/dhcp6/dhcp6.spec

@@ -94,6 +94,12 @@
                   "item_default": ""
                 },
 
+                { "item_name": "interface",
+                  "item_type": "string",
+                  "item_optional": false,
+                  "item_default": ""
+                },
+
                 { "item_name": "renew-timer",
                   "item_type": "integer",
                   "item_optional": false,

+ 11 - 1
src/bin/dhcp6/dhcp6_srv.cc

@@ -428,7 +428,17 @@ void Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
 }
 
 Subnet6Ptr Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+
+    /// @todo: pass interface information only if received direct (non-relayed) message
+
+    // Try to find a subnet if received packet from a directly connected client
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getIface());
+    if (subnet) {
+        return (subnet);
+    }
+
+    // If no subnet was found, try to find it based on remote address
+    subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
 
     return (subnet);
 }

+ 60 - 4
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -272,9 +272,8 @@ TEST_F(Dhcp6ParserTest, bogusCommand) {
     EXPECT_EQ(1, rcode_);
 }
 
-/// The goal of this test is to verify if wrongly defined subnet will
-/// be rejected. Properly defined subnet must include at least one
-/// pool definition.
+/// The goal of this test is to verify if configuration without any
+/// subnets defined can be accepted.
 TEST_F(Dhcp6ParserTest, emptySubnet) {
 
     ConstElementPtr status;
@@ -365,6 +364,64 @@ TEST_F(Dhcp6ParserTest, subnetLocal) {
     EXPECT_EQ(4, subnet->getValid());
 }
 
+// This test checks if it is possible to define a subnet with an
+// interface defined.
+TEST_F(Dhcp6ParserTest, subnetInterface) {
+
+    ConstElementPtr status;
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"interface\": \"eth0\","
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+    cout << config << endl;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+    // returned value should be 0 (configuration success)
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(0, rcode_);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("eth0", subnet->getIface());
+}
+
+// This test checks if it is not allowed to define global interface
+// parameter.
+TEST_F(Dhcp6ParserTest, interfaceGlobal) {
+
+    ConstElementPtr status;
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"interface\": \"eth0\"," // Not valid. Can be defined in subnet only
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+    cout << config << endl;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+    // returned value should be 1 (parse error)
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(1, rcode_);
+}
+
 // Test verifies that a subnet with pool values that do not belong to that
 // pool are rejected.
 TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
@@ -389,7 +446,6 @@ TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
     // as the pool does not belong to that subnet
     ASSERT_TRUE(status);
     comment_ = parseAnswer(rcode_, status);
-
     EXPECT_EQ(1, rcode_);
 }
 

+ 114 - 0
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -1294,6 +1294,120 @@ TEST_F(Dhcpv6SrvTest, sanityCheck) {
                  RFCViolation);
 }
 
+// This test verifies if selectSubnet() selects proper subnet for a given
+// source address.
+TEST_F(Dhcpv6SrvTest, selectSubnetAddr) {
+    NakedDhcpv6Srv srv(0);
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
+
+    // CASE 1: We have only one subnet defined and we received local traffic.
+    // The only available subnet should be selected
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+    Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    pkt->setRemoteAddr(IOAddress("fe80::abcd"));
+
+    Subnet6Ptr selected = srv.selectSubnet(pkt);
+    EXPECT_EQ(selected, subnet1);
+
+    // CASE 2: We have only one subnet defined and we received relayed traffic.
+    // We should NOT select it.
+
+    // Identical steps as in case 1, but repeated for clarity
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+    pkt->setRemoteAddr(IOAddress("2001:db8:abcd::2345"));
+    selected = srv.selectSubnet(pkt);
+    EXPECT_FALSE(selected);
+
+    // CASE 3: We have three subnets defined and we received local traffic.
+    // Nothing should be selected.
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1);
+    CfgMgr::instance().addSubnet6(subnet2);
+    CfgMgr::instance().addSubnet6(subnet3);
+    pkt->setRemoteAddr(IOAddress("fe80::abcd"));
+    selected = srv.selectSubnet(pkt);
+    EXPECT_FALSE(selected);
+
+    // CASE 4: We have three subnets defined and we received relayed traffic
+    // that came out of subnet 2. We should select subnet2 then
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1);
+    CfgMgr::instance().addSubnet6(subnet2);
+    CfgMgr::instance().addSubnet6(subnet3);
+    pkt->setRemoteAddr(IOAddress("2001:db8:2::baca"));
+    selected = srv.selectSubnet(pkt);
+    EXPECT_EQ(selected, subnet2);
+
+    // CASE 5: We have three subnets defined and we received relayed traffic
+    // that came out of undefined subnet. We should select nothing
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1);
+    CfgMgr::instance().addSubnet6(subnet2);
+    CfgMgr::instance().addSubnet6(subnet3);
+    pkt->setRemoteAddr(IOAddress("2001:db8:4::baca"));
+    selected = srv.selectSubnet(pkt);
+    EXPECT_FALSE(selected);
+
+}
+
+// This test verifies if selectSubnet() selects proper subnet for a given
+// network interface name.
+TEST_F(Dhcpv6SrvTest, selectSubnetIface) {
+    NakedDhcpv6Srv srv(0);
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
+
+    subnet1->setIface("eth0");
+    subnet3->setIface("wifi1");
+
+    // CASE 1: We have only one subnet defined and it is available via eth0.
+    // Packet came from eth0. The only available subnet should be selected
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+    Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    pkt->setIface("eth0");
+
+    Subnet6Ptr selected = srv.selectSubnet(pkt);
+    EXPECT_EQ(selected, subnet1);
+
+    // CASE 2: We have only one subnet defined and it is available via eth0.
+    // Packet came from eth1. We should not select it
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+    pkt->setIface("eth1");
+
+    selected = srv.selectSubnet(pkt);
+    EXPECT_FALSE(selected);
+
+    // CASE 3: We have only 3 subnets defined, one over eth0, one remote and
+    // one over wifi1.
+    // Packet came from eth1. We should not select it
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1);
+    CfgMgr::instance().addSubnet6(subnet2);
+    CfgMgr::instance().addSubnet6(subnet3);
+
+    pkt->setIface("eth0");
+    EXPECT_EQ(subnet1, srv.selectSubnet(pkt));
+
+    pkt->setIface("eth3"); // no such interface
+    EXPECT_EQ(Subnet6Ptr(), srv.selectSubnet(pkt)); // nothing selected
+
+    pkt->setIface("wifi1");
+    EXPECT_EQ(subnet3, srv.selectSubnet(pkt));
+
+}
+
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.
 

+ 21 - 0
src/lib/dhcpsrv/cfgmgr.cc

@@ -138,6 +138,26 @@ CfgMgr::getOptionDef(const std::string& option_space,
 }
 
 Subnet6Ptr
+CfgMgr::getSubnet6(const std::string& iface) {
+
+    if (!iface.length()) {
+        return (Subnet6Ptr());
+    }
+
+    // If there is more than one, we need to choose the proper one
+    for (Subnet6Collection::iterator subnet = subnets6_.begin();
+         subnet != subnets6_.end(); ++subnet) {
+        if (iface == (*subnet)->getIface()) {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+                      DHCPSRV_CFGMGR_SUBNET6_IFACE)
+                .arg((*subnet)->toText()).arg(iface);
+            return (*subnet);
+        }
+    }
+    return (Subnet6Ptr());
+}
+
+Subnet6Ptr
 CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
 
     // If there's only one subnet configured, let's just use it
@@ -158,6 +178,7 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
     // If there is more than one, we need to choose the proper one
     for (Subnet6Collection::iterator subnet = subnets6_.begin();
          subnet != subnets6_.end(); ++subnet) {
+
         if ((*subnet)->inRange(hint)) {
             LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
                       DHCPSRV_CFGMGR_SUBNET6)

+ 10 - 1
src/lib/dhcpsrv/cfgmgr.h

@@ -153,9 +153,18 @@ public:
     ///
     /// @param hint an address that belongs to a searched subnet
     ///
-    /// @return a subnet object
+    /// @return a subnet object (or NULL if no suitable match was fount)
     Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint);
 
+    /// @brief get IPv6 subnet by interface name
+    ///
+    /// Finds a matching local subnet, based on interface name. This
+    /// is used for selecting subnets that were explicitly marked by the
+    /// user as reachable over specified network interface.
+    /// @param iface_name interface name
+    /// @return a subnet object (or NULL if no suitable match was fount)
+    Subnet6Ptr getSubnet6(const std::string& iface_name);
+
     /// @brief get IPv6 subnet by interface-id
     ///
     /// Another possibility to find a subnet is based on interface-id.

+ 6 - 0
src/lib/dhcpsrv/dhcpsrv_messages.mes

@@ -60,6 +60,12 @@ This is a debug message reporting that the DHCP configuration manager has
 returned the specified IPv6 subnet when given the address hint specified
 as the address is within the subnet.
 
+% DHCPSRV_CFGMGR_SUBNET6_IFACE selected subnet %1 for packet received over interface %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv6 subnet for a packet received over given interface.
+This particular subnet was selected, because it was specified as being directly
+reachable over given interface. (see 'interface' parameter in subnet6 definition).
+
 % DHCPSRV_INVALID_ACCESS invalid database access string: %1
 This is logged when an attempt has been made to parse a database access string
 and the attempt ended in error.  The access string in question - which

+ 10 - 0
src/lib/dhcpsrv/subnet.cc

@@ -202,5 +202,15 @@ Subnet6::validateOption(const OptionPtr& option) const {
     }
 }
 
+
+void Subnet6::setIface(const std::string& iface_name) {
+    iface_ = iface_name;
+}
+
+std::string Subnet6::getIface() {
+    return (iface_);
+}
+
+
 } // end of isc::dhcp namespace
 } // end of isc namespace

+ 14 - 0
src/lib/dhcpsrv/subnet.h

@@ -415,6 +415,9 @@ protected:
     /// fully trusted.
     isc::asiolink::IOAddress last_allocated_;
 
+    /// @brief Name of the network interface (if connected directly)
+    std::string iface_;
+
 private:
 
     /// Container holding options grouped by option space names.
@@ -496,6 +499,17 @@ public:
         return (preferred_);
     }
 
+    /// @brief sets name of the network interface for directly attached networks
+    ///
+    /// A subnet may be reachable directly (not via relays). In DHCPv6 it is not
+    /// possible to decide that based on addresses assigned to network interfaces,
+    /// as DHCPv6 operates on link-local (and site local) addresses.
+    void setIface(const std::string& iface_name);
+
+    /// @brief network interface name used to reach subnet (or "" for remote subnets)
+    /// @return network interface name for directly attached subnets or ""
+    std::string getIface();
+
 protected:
 
     /// @brief Check if option is valid and can be added to a subnet.

+ 4 - 0
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -351,4 +351,8 @@ TEST_F(CfgMgrTest, optionSpace6) {
     // @todo decide if a duplicate vendor space is allowed.
 }
 
+// No specific tests for getSubnet6. That method (2 overloaded versions) is tested
+// in Dhcpv6SrvTest.selectSubnetAddr and Dhcpv6SrvTest.selectSubnetIface
+// (see src/bin/dhcp6/tests/dhcp6_srv_unittest.cc)
+
 } // end of anonymous namespace

+ 11 - 0
src/lib/dhcpsrv/tests/subnet_unittest.cc

@@ -505,4 +505,15 @@ TEST(Subnet6Test, get) {
     EXPECT_EQ(32, subnet.get().second);
 }
 
+// This trivial test checks if interface name is stored properly
+// in Subnet6 objects.
+TEST(Subnet6Test, iface) {
+    Subnet6 subnet(IOAddress("2001:db8::"), 32, 1, 2, 3, 4);
+
+    EXPECT_EQ("", subnet.getIface());
+
+    subnet.setIface("en1");
+    EXPECT_EQ("en1", subnet.getIface());
+}
+
 };