Browse Source

[2898] Support for v6 relayed traffic added.

Tomek Mrugalski 12 years ago
parent
commit
b82db9d608

+ 26 - 2
src/bin/dhcp6/config_parser.cc

@@ -1481,11 +1481,27 @@ private:
         std::string iface;
         std::string iface;
         try {
         try {
             iface = string_values_.getParam("interface");
             iface = string_values_.getParam("interface");
-        } catch (DhcpConfigError) {
+        } catch (const DhcpConfigError&) {
             // iface not mandatory so swallow the exception
             // iface not mandatory so swallow the exception
         }
         }
 
 
-        /// @todo: Convert this to logger once the parser is working reliably
+        // Get interface-id option content. For now we support string
+        // represenation only
+        std::string ifaceid;
+        try {
+            ifaceid = string_values_.getParam("interface-id");
+        } catch (DhcpConfigError) {
+            // interface-id is not mandatory
+        }
+
+        if (!iface.empty() && !ifaceid.empty()) {
+            isc_throw(isc::dhcp::DhcpConfigError,
+                      "parser error: interface (defined for locally reachable "
+                      "subnets) and interface-id (defined for subnets reachable"
+                      " via relays) cannot be defined at the same time for "
+                      "subnet " << addr.toText() << "/" << (int)len);
+        }
+
         stringstream tmp;
         stringstream tmp;
         tmp << addr.toText() << "/" << (int)len
         tmp << addr.toText() << "/" << (int)len
             << " with params t1=" << t1 << ", t2=" << t2 << ", pref="
             << " with params t1=" << t1 << ", t2=" << t2 << ", pref="
@@ -1512,6 +1528,13 @@ private:
             subnet_->setIface(iface);
             subnet_->setIface(iface);
         }
         }
 
 
+        // Configure interface-id for remote interfaces, if defined
+        if (!ifaceid.empty()) {
+            OptionBuffer tmp(ifaceid.begin(), ifaceid.end());
+            OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+            subnet_->setInterfaceId(opt);
+        }
+
         // We are going to move configured options to the Subnet object.
         // We are going to move configured options to the Subnet object.
         // Configured options reside in the container where options
         // Configured options reside in the container where options
         // are grouped by space names. Thus we need to get all space names
         // are grouped by space names. Thus we need to get all space names
@@ -1591,6 +1614,7 @@ private:
         factories["pool"] = PoolParser::factory;
         factories["pool"] = PoolParser::factory;
         factories["option-data"] = OptionDataListParser::factory;
         factories["option-data"] = OptionDataListParser::factory;
         factories["interface"] = StringParser::factory;
         factories["interface"] = StringParser::factory;
+        factories["interface-id"] = StringParser::factory;
 
 
         FactoryMap::iterator f = factories.find(config_id);
         FactoryMap::iterator f = factories.find(config_id);
         if (f == factories.end()) {
         if (f == factories.end()) {

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

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

+ 39 - 9
src/bin/dhcp6/dhcp6_srv.cc

@@ -403,8 +403,13 @@ Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
     if (clientid) {
     if (clientid) {
         answer->addOption(clientid);
         answer->addOption(clientid);
     }
     }
+    /// @todo: Should throw if there is no client-id (except anonymous INF-REQUEST)
+
+    // if this is a relayed message, we need to copy relay information
+    if (!question->relay_info_.empty()) {
+        answer->copyRelayInfo(question);
+    }
 
 
-    // TODO: Should throw if there is no client-id (except anonymous INF-REQUEST)
 }
 }
 
 
 void
 void
@@ -523,18 +528,43 @@ Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
 Subnet6Ptr
 Subnet6Ptr
 Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
 Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
 
 
-    /// @todo: pass interface information only if received direct (non-relayed) message
+    Subnet6Ptr subnet;
 
 
-    // Try to find a subnet if received packet from a directly connected client
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getIface());
-    if (subnet) {
+    if (question->relay_info_.empty()) {
+        // This is a 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);
         return (subnet);
-    }
+    } else {
 
 
-    // If no subnet was found, try to find it based on remote address
-    subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+        // This is a relayed message
+        OptionPtr interface_id = question->getAnyRelayOption(D6O_INTERFACE_ID,
+                                                             Pkt6::RELAY_SEARCH_FIRST);
+        if (interface_id) {
+            subnet = CfgMgr::instance().getSubnet6(interface_id);
+        }
+
+        if (subnet) {
+            return (subnet);
+        }
+
+        // If no interface-id was specified (or not configured on server), let's
+        // try address matching
+        IOAddress link_addr = question->relay_info_.back().linkaddr_;
 
 
-    return (subnet);
+        // if relay filled in link_addr field, then let's use it
+        if (link_addr != IOAddress("::")) {
+            subnet = CfgMgr::instance().getSubnet6(link_addr);
+        }
+        return (subnet);
+    }
 }
 }
 
 
 void
 void

+ 104 - 0
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -500,6 +500,110 @@ TEST_F(Dhcp6ParserTest, interfaceGlobal) {
     EXPECT_EQ(1, rcode_);
     EXPECT_EQ(1, rcode_);
 }
 }
 
 
+
+// This test checks if it is possible to define a subnet with an
+// interface-id option defined.
+TEST_F(Dhcp6ParserTest, subnetInterfaceId) {
+
+    const string valid_interface_id = "foobar";
+    const string bogus_interface_id = "blah";
+
+    ConstElementPtr status;
+
+    // There should be at least one interface
+
+    string config = "{ "
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"interface-id\": \"" + valid_interface_id + "\","
+        "    \"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_);
+
+    // try to get a subnet based on bogus interface-id option
+    OptionBuffer tmp(bogus_interface_id.begin(), bogus_interface_id.end());
+    OptionPtr ifaceid(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(ifaceid);
+    EXPECT_FALSE(subnet);
+
+    // now try to get subnet for valid interface-id value
+    tmp = OptionBuffer(valid_interface_id.begin(), valid_interface_id.end());
+    ifaceid.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+    subnet = CfgMgr::instance().getSubnet6(ifaceid);
+    ASSERT_TRUE(subnet);
+    EXPECT_TRUE(ifaceid->equal(subnet->getInterfaceId()));
+}
+
+
+// This test checks if it is not allowed to define global interface
+// parameter.
+TEST_F(Dhcp6ParserTest, interfaceIdGlobal) {
+
+    ConstElementPtr status;
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"interface-id\": \"foobar\"," // 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_);
+}
+
+// This test checks if it is not possible to define a subnet with an
+// interface (i.e. local subnet) and interface-id (remote subnet) defined.
+TEST_F(Dhcp6ParserTest, subnetInterfaceAndInterfaceId) {
+
+    ConstElementPtr status;
+
+    string config = "{ \"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"interface\": \"" + valid_iface_ + "\","
+        "    \"interface-id\": \"foobar\","
+        "    \"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(1, rcode_);
+
+}
+
+
+
 // Test verifies that a subnet with pool values that do not belong to that
 // Test verifies that a subnet with pool values that do not belong to that
 // pool are rejected.
 // pool are rejected.
 TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
 TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {

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

@@ -98,6 +98,16 @@ public:
         return (ia);
         return (ia);
     }
     }
 
 
+    /// @brief generates interface-id option, based on text
+    ///
+    /// @param iface_id textual representation of the interface-id content
+    ///
+    /// @return pointer to the option object
+    OptionPtr generateInterfaceId(const string& iface_id) {
+        OptionBuffer tmp(iface_id.begin(), iface_id.end());
+        return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+    }
+
     // Generate client-id option
     // Generate client-id option
     OptionPtr generateClientId(size_t duid_size = 32) {
     OptionPtr generateClientId(size_t duid_size = 32) {
 
 
@@ -678,6 +688,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
     // check that IA_NA was returned and that there's an address included
     // check that IA_NA was returned and that there's an address included
     boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
     boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
                                                 subnet_->getT2());
                                                 subnet_->getT2());
+    ASSERT_TRUE(addr);
 
 
     // Check that the assigned address is indeed from the configured pool
     // Check that the assigned address is indeed from the configured pool
     checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
     checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
@@ -731,6 +742,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
     // check that IA_NA was returned and that there's an address included
     // check that IA_NA was returned and that there's an address included
     boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
     boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
                                                 subnet_->getT2());
                                                 subnet_->getT2());
+    ASSERT_TRUE(addr);
 
 
     // check that we've got the address we requested
     // check that we've got the address we requested
     checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
     checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
@@ -779,6 +791,7 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
     // check that IA_NA was returned and that there's an address included
     // check that IA_NA was returned and that there's an address included
     boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
     boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
                                                 subnet_->getT2());
                                                 subnet_->getT2());
+    ASSERT_TRUE(addr);
 
 
     // Check that the assigned address is indeed from the configured pool
     // Check that the assigned address is indeed from the configured pool
     checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
     checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
@@ -840,6 +853,9 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
                                                 subnet_->getT2());
                                                 subnet_->getT2());
     boost::shared_ptr<Option6IAAddr> addr3 = checkIA_NA(reply3, 3, subnet_->getT1(),
     boost::shared_ptr<Option6IAAddr> addr3 = checkIA_NA(reply3, 3, subnet_->getT1(),
                                                 subnet_->getT2());
                                                 subnet_->getT2());
+    ASSERT_TRUE(addr1);
+    ASSERT_TRUE(addr2);
+    ASSERT_TRUE(addr3);
 
 
     // Check that the assigned address is indeed from the configured pool
     // Check that the assigned address is indeed from the configured pool
     checkIAAddr(addr1, addr1->getAddress(), subnet_->getPreferred(), subnet_->getValid());
     checkIAAddr(addr1, addr1->getAddress(), subnet_->getPreferred(), subnet_->getValid());
@@ -910,6 +926,7 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
     // check that IA_NA was returned and that there's an address included
     // check that IA_NA was returned and that there's an address included
     boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
     boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
                                                 subnet_->getT2());
                                                 subnet_->getT2());
+    ASSERT_TRUE(addr);
 
 
     // check that we've got the address we requested
     // check that we've got the address we requested
     checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
     checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
@@ -1592,6 +1609,113 @@ TEST_F(Dhcpv6SrvTest, selectSubnetIface) {
     EXPECT_EQ(subnet3, srv.selectSubnet(pkt));
     EXPECT_EQ(subnet3, srv.selectSubnet(pkt));
 }
 }
 
 
+// This test verifies if selectSubnet() selects proper subnet for a given
+// linkaddr in RELAY-FORW message
+TEST_F(Dhcpv6SrvTest, selectSubnetRelayLinkaddr) {
+    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));
+
+    Pkt6::RelayInfo relay;
+    relay.linkaddr_ = IOAddress("2001:db8:2::1234");
+    relay.peeraddr_ = IOAddress("fe80::1");
+
+    // CASE 1: We have only one subnet defined and we received relayed traffic.
+    // The only available subnet should NOT be selected.
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+    Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    pkt->relay_info_.push_back(relay);
+
+    Subnet6Ptr selected = srv.selectSubnet(pkt);
+    EXPECT_FALSE(selected);
+
+    // CASE 2: We have three subnets defined and we received relayed traffic.
+    // Nothing should be selected.
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1);
+    CfgMgr::instance().addSubnet6(subnet2);
+    CfgMgr::instance().addSubnet6(subnet3);
+    selected = srv.selectSubnet(pkt);
+    EXPECT_EQ(selected, subnet2);
+
+    // CASE 3: 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);
+
+    // source of the packet should have no meaning. Selection is based
+    // on linkaddr field in the relay
+    pkt->setRemoteAddr(IOAddress("2001:db8:1::baca"));
+    selected = srv.selectSubnet(pkt);
+    EXPECT_EQ(selected, subnet2);
+
+    // CASE 4: 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->relay_info_.clear();
+    relay.linkaddr_ = IOAddress("2001:db8:4::1234");
+    pkt->relay_info_.push_back(relay);
+    selected = srv.selectSubnet(pkt);
+    EXPECT_FALSE(selected);
+
+}
+
+// This test verifies if selectSubnet() selects proper subnet for a given
+// interface-id option
+TEST_F(Dhcpv6SrvTest, selectSubnetRelayInterfaceId) {
+    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->setInterfaceId(generateInterfaceId("relay1"));
+    subnet2->setInterfaceId(generateInterfaceId("relay2"));
+
+    // CASE 1: We have only one subnet defined and it is for interface-id "relay1"
+    // Packet came with interface-id "relay2". We should not select subnet1
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+    Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    Pkt6::RelayInfo relay;
+    relay.linkaddr_ = IOAddress("2001:db8:2::1234");
+    relay.peeraddr_ = IOAddress("fe80::1");
+    OptionPtr opt = generateInterfaceId("relay2");
+    relay.options_.insert(pair<int, OptionPtr>(opt->getType(), opt));
+    pkt->relay_info_.push_back(relay);
+
+    // There is only one subnet configured and we are outside of that subnet
+    Subnet6Ptr selected = srv.selectSubnet(pkt);
+    EXPECT_FALSE(selected);
+
+    // CASE 2: We have only one subnet defined and it is for interface-id "relay2"
+    // Packet came with interface-id "relay2". We should select it
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet2); // just a single subnet
+    selected = srv.selectSubnet(pkt);
+    EXPECT_EQ(selected, subnet2);
+
+    // CASE 3: We have only 3 subnets defined: one remote for interface-id "relay1",
+    // one remote for interface-id "relay2" and third local
+    // packet comes with interface-id "relay2". We should select subnet2
+    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().addSubnet6(subnet1);
+    CfgMgr::instance().addSubnet6(subnet2);
+    CfgMgr::instance().addSubnet6(subnet3);
+
+    EXPECT_EQ(subnet2, srv.selectSubnet(pkt));
+}
+
 // This test verifies if the server-id disk operations (read, write) are
 // This test verifies if the server-id disk operations (read, write) are
 // working properly.
 // working properly.
 TEST_F(Dhcpv6SrvTest, ServerID) {
 TEST_F(Dhcpv6SrvTest, ServerID) {

+ 81 - 0
src/lib/dhcp/pkt6.cc

@@ -72,6 +72,60 @@ uint16_t Pkt6::len() {
     }
     }
 }
 }
 
 
+OptionPtr Pkt6::getAnyRelayOption(uint16_t opt_type, RelaySearchOrder order) {
+    int start = 0;
+    int end = 0;
+    int direction = 0;
+
+    if (relay_info_.empty()) {
+        // there's no relay info, this is a direct message
+        return (OptionPtr());
+    }
+
+    switch (order) {
+    case RELAY_SEARCH_FROM_CLIENT:
+        // search backwards
+        start = relay_info_.size() - 1;
+        end = 0;
+        direction = -1;
+        break;
+    case RELAY_SEARCH_FROM_SERVER:
+        // search forward
+        start = 0;
+        end = relay_info_.size() - 1;
+        direction = 1;
+        break;
+    case RELAY_SEARCH_FIRST:
+        // look at the innermost relay only
+        start = relay_info_.size() - 1;
+        end = start;
+        direction = 0;
+        break;
+    case RELAY_SEARCH_LAST:
+        // look at the outermost relay only
+        start = 0;
+        end = 0;
+        direction = 0;
+    }
+
+    // this is a tricky loop. It must go from start to end, but it must work in
+    // both directions (start > end; or start < end). We can't use regular
+    // exit condition, because we don't know whether to use i <= end or i >= end
+    for (int i = start; ; i += direction) {
+        OptionPtr opt = getRelayOption(opt_type, i);
+        if (opt) {
+            return (opt);
+        }
+
+        if (i == end) {
+            break;
+        }
+    }
+
+    return getRelayOption(opt_type, end);
+}
+
+
 OptionPtr Pkt6::getRelayOption(uint16_t opt_type, uint8_t relay_level) {
 OptionPtr Pkt6::getRelayOption(uint16_t opt_type, uint8_t relay_level) {
     if (relay_level >= relay_info_.size()) {
     if (relay_level >= relay_info_.size()) {
         isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)."
         isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)."
@@ -483,5 +537,32 @@ const char* Pkt6::getName() const {
     return (getName(getType()));
     return (getName(getType()));
 }
 }
 
 
+void Pkt6::copyRelayInfo(const Pkt6Ptr& question) {
+
+    // we use index rather than iterator, because we need that as a parameter
+    // passed to getRelayOption()
+    for (int i = 0; i < question->relay_info_.size(); ++i) {
+        RelayInfo x;
+        x.msg_type_ = DHCPV6_RELAY_REPL;
+        x.hop_count_ = question->relay_info_[i].hop_count_;
+        x.linkaddr_ = question->relay_info_[i].linkaddr_;
+        x.peeraddr_ = question->relay_info_[i].peeraddr_;
+
+        // Is there interface-id option in this nesting level if there is,
+        // we need to echo it back
+        OptionPtr opt = question->getRelayOption(D6O_INTERFACE_ID, i);
+        // taken from question->RelayInfo_[i].options_
+        if (opt) {
+            x.options_.insert(pair<int, boost::shared_ptr<Option> >(opt->getType(), opt));
+        }
+
+        /// @todo: implement support for ERO (Echo Request Option, RFC4994)
+
+        // add this relay-repl info to our message
+        relay_info_.push_back(x);
+    }
+}
+
+
 } // end of isc::dhcp namespace
 } // end of isc::dhcp namespace
 } // end of isc namespace
 } // end of isc namespace

+ 45 - 2
src/lib/dhcp/pkt6.h

@@ -30,6 +30,9 @@ namespace isc {
 
 
 namespace dhcp {
 namespace dhcp {
 
 
+class Pkt6;
+typedef boost::shared_ptr<Pkt6> Pkt6Ptr;
+
 class Pkt6 {
 class Pkt6 {
 public:
 public:
     /// specifies non-relayed DHCPv6 packet header length (over UDP)
     /// specifies non-relayed DHCPv6 packet header length (over UDP)
@@ -44,6 +47,28 @@ public:
         TCP = 1  // there are TCP DHCPv6 packets (bulk leasequery, failover)
         TCP = 1  // there are TCP DHCPv6 packets (bulk leasequery, failover)
     };
     };
 
 
+    /// @brief defines relay search pattern
+    ///
+    /// Defines order in which options are searched in a message that
+    /// passed through mulitple relays. RELAY_SEACH_FROM_CLIENT will
+    /// start search from the relay that was the closest to the client
+    /// (i.e. innermost in the encapsulated message, which also means
+    /// this was the first relay that forwarded packet received by the
+    /// server and this will be the last relay that will handle the
+    /// response that server sent towards the client.).
+    /// RELAY_SEARCH_FROM_SERVER is the opposite. This will be the
+    /// relay closest to the server (i.e. outermost in the encapsulated
+    /// message, which also means it was the last relay that relayed
+    /// the received message and will be the first one to process
+    /// server's response). RELAY_SEARCH_FIRST is option from the first
+    /// relay (closest to the client), RELAY_SEARCH_LAST is the
+    /// last relay (closest to the server).
+    enum RelaySearchOrder {
+        RELAY_SEARCH_FROM_CLIENT = 1,
+        RELAY_SEARCH_FROM_SERVER = 2,
+        RELAY_SEARCH_FIRST = 3,
+        RELAY_SEARCH_LAST = 4
+    };
 
 
     /// @brief structure that describes a single relay information
     /// @brief structure that describes a single relay information
     ///
     ///
@@ -201,6 +226,18 @@ public:
     /// @return pointer to the option (or NULL if there is no such option)
     /// @return pointer to the option (or NULL if there is no such option)
     OptionPtr getRelayOption(uint16_t option_code, uint8_t nesting_level);
     OptionPtr getRelayOption(uint16_t option_code, uint8_t nesting_level);
 
 
+    /// @brief returns first instance of a specified option
+    ///
+    /// When client's packet traverses multiple relays, each passing relay
+    /// may insert extra options. This method allows getting specific instance
+    /// of a given option (closest to the client, closest to the server, etc.)
+    /// See @ref RelaySearchOrder for detailed description.
+    ///
+    /// @param option_code searched option
+    /// @param order option search order (see @ref RelaySearchOrder)
+    /// @return option pointer (or NULL if no option matches specified criteria)
+    OptionPtr getAnyRelayOption(uint16_t option_code, RelaySearchOrder order);
+
     /// @brief Returns all instances of specified type.
     /// @brief Returns all instances of specified type.
     ///
     ///
     /// Returns all instances of options of the specified type. DHCPv6 protocol
     /// Returns all instances of options of the specified type. DHCPv6 protocol
@@ -356,6 +393,14 @@ public:
     ///         be freed by the caller.
     ///         be freed by the caller.
     const char* getName() const;
     const char* getName() const;
 
 
+    /// @brief copies relay information from client's packet to server's response
+    ///
+    /// This information is not simply copied over. Some parameter are
+    /// removed, msg_type_is updated (RELAY-FORW => RELAY-REPL), etc.
+    ///
+    /// @param question client's packet
+    void copyRelayInfo(const Pkt6Ptr& question);
+
     /// relay information
     /// relay information
     ///
     ///
     /// this is a public field. Otherwise we hit one of the two problems:
     /// this is a public field. Otherwise we hit one of the two problems:
@@ -494,8 +539,6 @@ protected:
     boost::posix_time::ptime timestamp_;
     boost::posix_time::ptime timestamp_;
 }; // Pkt6 class
 }; // Pkt6 class
 
 
-typedef boost::shared_ptr<Pkt6> Pkt6Ptr;
-
 } // isc::dhcp namespace
 } // isc::dhcp namespace
 
 
 } // isc namespace
 } // isc namespace

+ 103 - 0
src/lib/dhcp/tests/pkt6_unittest.cc

@@ -22,6 +22,7 @@
 #include <dhcp/option_int.h>
 #include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/pkt6.h>
+#include <util/range_utilities.h>
 
 
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
@@ -44,6 +45,18 @@ class Pkt6Test : public ::testing::Test {
 public:
 public:
     Pkt6Test() {
     Pkt6Test() {
     }
     }
+
+    /// @brief generates an option with given code (and length) and random content
+    ///
+    /// @param code option code
+    /// @param len data length (data will be randomized)
+    ///
+    /// @return pointer to the new option
+    OptionPtr generateRandomOption(uint16_t code, size_t len = 10) {
+        OptionBuffer data(len);
+        util::fillRandom(data.begin(), data.end());
+        return OptionPtr(new Option(Option::V6, code, data));
+    }
 };
 };
 
 
 TEST_F(Pkt6Test, constructor) {
 TEST_F(Pkt6Test, constructor) {
@@ -552,4 +565,94 @@ TEST_F(Pkt6Test, relayPack) {
     EXPECT_EQ(0, memcmp(relay_opt_data, relay_opt_data, sizeof(relay_opt_data)));
     EXPECT_EQ(0, memcmp(relay_opt_data, relay_opt_data, sizeof(relay_opt_data)));
 }
 }
 
 
+
+// This test verified that options added by relays to the message can be
+// accessed and retrieved properly
+TEST_F(Pkt6Test, getAnyRelayOption) {
+
+    boost::scoped_ptr<Pkt6> msg(new Pkt6(DHCPV6_ADVERTISE, 0x020304));
+    msg->addOption(generateRandomOption(300));
+
+    // generate options for relay1
+    Pkt6::RelayInfo relay1;
+
+    // generate 3 options with code 200,201,202 and random content
+    OptionPtr relay1_opt1(generateRandomOption(200));
+    OptionPtr relay1_opt2(generateRandomOption(201));
+    OptionPtr relay1_opt3(generateRandomOption(202));
+
+    relay1.options_.insert(pair<int, boost::shared_ptr<Option> >(200, relay1_opt1));
+    relay1.options_.insert(pair<int, boost::shared_ptr<Option> >(201, relay1_opt2));
+    relay1.options_.insert(pair<int, boost::shared_ptr<Option> >(202, relay1_opt3));
+    msg->addRelayInfo(relay1);
+
+    // generate options for relay2
+    Pkt6::RelayInfo relay2;
+    OptionPtr relay2_opt1(new Option(Option::V6, 100));
+    OptionPtr relay2_opt2(new Option(Option::V6, 101));
+    OptionPtr relay2_opt3(new Option(Option::V6, 102));
+    OptionPtr relay2_opt4(new Option(Option::V6, 200)); // the same code as relay1_opt3
+    relay2.options_.insert(pair<int, boost::shared_ptr<Option> >(100, relay2_opt1));
+    relay2.options_.insert(pair<int, boost::shared_ptr<Option> >(101, relay2_opt2));
+    relay2.options_.insert(pair<int, boost::shared_ptr<Option> >(102, relay2_opt3));
+    relay2.options_.insert(pair<int, boost::shared_ptr<Option> >(200, relay2_opt4));
+    msg->addRelayInfo(relay2);
+
+    // generate options for relay3
+    Pkt6::RelayInfo relay3;
+    OptionPtr relay3_opt1(generateRandomOption(200, 7));
+    relay3.options_.insert(pair<int, boost::shared_ptr<Option> >(200, relay3_opt1));
+    msg->addRelayInfo(relay3);
+
+    // Ok, so we now have a packet that traversed the following network:
+    // client---relay3---relay2---relay1---server
+
+    // First check that the getAnyRelayOption does not confuse client options
+    // and relay options
+    OptionPtr opt = msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+    // 300 is a client option, present in the message itself.
+    EXPECT_FALSE(opt);
+
+    // Option 200 is added in every relay.
+
+    // We want to get that one inserted by relay3 (first match, starting from
+    // closest to the client.
+    opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+    ASSERT_TRUE(opt);
+    EXPECT_TRUE(opt->equal(relay3_opt1));
+
+    // We want to ge that one inserted by relay1 (first match, starting from
+    // closest to the server.
+    opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER);
+    ASSERT_TRUE(opt);
+    EXPECT_TRUE(opt->equal(relay1_opt1));
+
+    // We just want option from the first relay (closest to the client)
+    opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FIRST);
+    ASSERT_TRUE(opt);
+    EXPECT_TRUE(opt->equal(relay3_opt1));
+
+    // We just want option from the last relay (closest to the server)
+    opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_LAST);
+    ASSERT_TRUE(opt);
+    EXPECT_TRUE(opt->equal(relay1_opt1));
+
+    // Let's try to ask for something that is inserted by the middle relay
+    // only.
+    opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_SERVER);
+    ASSERT_TRUE(opt);
+    EXPECT_TRUE(opt->equal(relay2_opt1));
+
+    opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+    ASSERT_TRUE(opt);
+    EXPECT_TRUE(opt->equal(relay2_opt1));
+
+    opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FIRST);
+    EXPECT_FALSE(opt);
+
+    opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_LAST);
+    EXPECT_FALSE(opt);
+
+}
+
 }
 }

+ 18 - 3
src/lib/dhcpsrv/cfgmgr.cc

@@ -178,14 +178,29 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
     return (Subnet6Ptr());
     return (Subnet6Ptr());
 }
 }
 
 
-Subnet6Ptr CfgMgr::getSubnet6(OptionPtr /*interfaceId*/) {
-    /// @todo: Implement get subnet6 by interface-id (for relayed traffic)
-    isc_throw(NotImplemented, "Relayed DHCPv6 traffic is not supported yet.");
+Subnet6Ptr CfgMgr::getSubnet6(OptionPtr iface_id_option) {
+    if (!iface_id_option) {
+        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 ( (*subnet)->getInterfaceId() &&
+             ((*subnet)->getInterfaceId()->equal(iface_id_option))) {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+                      DHCPSRV_CFGMGR_SUBNET6_IFACE_ID)
+                .arg((*subnet)->toText());
+            return (*subnet);
+        }
+    }
+    return (Subnet6Ptr());
 }
 }
 
 
 void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
 void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
     /// @todo: Check that this new subnet does not cross boundaries of any
     /// @todo: Check that this new subnet does not cross boundaries of any
     /// other already defined subnet.
     /// other already defined subnet.
+    /// @todo: Check that there is no subnet with the same interface-id
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET6)
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET6)
               .arg(subnet->toText());
               .arg(subnet->toText());
     subnets6_.push_back(subnet);
     subnets6_.push_back(subnet);

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

@@ -174,7 +174,6 @@ public:
     /// @param interface_id content of interface-id option returned by a relay
     /// @param interface_id content of interface-id option returned by a relay
     ///
     ///
     /// @return a subnet object
     /// @return a subnet object
-    /// @todo This method is not currently supported.
     Subnet6Ptr getSubnet6(OptionPtr interface_id);
     Subnet6Ptr getSubnet6(OptionPtr interface_id);
 
 
     /// @brief adds an IPv6 subnet
     /// @brief adds an IPv6 subnet

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

@@ -107,6 +107,13 @@ given interface.  This particular subnet was selected, because it
 was specified as being directly reachable over given interface. (see
 was specified as being directly reachable over given interface. (see
 'interface' parameter in subnet6 definition).
 'interface' parameter in subnet6 definition).
 
 
+% DHCPSRV_CFGMGR_SUBNET6_IFACE_ID selected subnet %1 (interface-id match) for incoming packet
+This is a debug message reporting that the DHCP configuration manager
+has returned the specified IPv6 subnet for a received packet. This particular
+subnet was selected, because value of interface-id option matched what was
+configured in server's interface-id option for that selected subnet6.
+(see 'interface-id' parameter in subnet6 definition).
+
 % DHCPSRV_CLOSE_DB closing currently open %1 database
 % DHCPSRV_CLOSE_DB closing currently open %1 database
 This is a debug message, issued when the DHCP server closes the currently
 This is a debug message, issued when the DHCP server closes the currently
 open lease database.  It is issued at program shutdown and whenever
 open lease database.  It is issued at program shutdown and whenever

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

@@ -463,6 +463,19 @@ public:
     /// @return network interface name for directly attached subnets or ""
     /// @return network interface name for directly attached subnets or ""
     std::string getIface() const;
     std::string getIface() const;
 
 
+    /// @brief sets interface-id option (if defined)
+    ///
+    /// @param ifaceid pointer to interface-id option
+    void setInterfaceId(const OptionPtr& ifaceid) {
+        interface_id_ = ifaceid;
+    }
+
+    /// @brief returns interface-id value (if specified)
+    /// @return interface-id option (if defined)
+    OptionPtr getInterfaceId() const {
+        return interface_id_;
+    }
+
 protected:
 protected:
 
 
     /// @brief Check if option is valid and can be added to a subnet.
     /// @brief Check if option is valid and can be added to a subnet.
@@ -478,6 +491,9 @@ protected:
         return (isc::asiolink::IOAddress("::"));
         return (isc::asiolink::IOAddress("::"));
     }
     }
 
 
+    /// @brief specifies optional interface-id
+    OptionPtr interface_id_;
+
     /// @brief collection of pools in that list
     /// @brief collection of pools in that list
     Pool6Collection pools_;
     Pool6Collection pools_;
 
 

+ 106 - 10
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -17,6 +17,7 @@
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcpsrv/dhcp_config_parser.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
+#include <dhcp/dhcp6.h>
 
 
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
@@ -37,7 +38,7 @@ using boost::scoped_ptr;
 
 
 namespace {
 namespace {
 
 
-// This test verifies that BooleanStorage functions properly. 
+// This test verifies that BooleanStorage functions properly.
 TEST(ValueStorageTest, BooleanTesting) {
 TEST(ValueStorageTest, BooleanTesting) {
     BooleanStorage testStore;
     BooleanStorage testStore;
 
 
@@ -48,7 +49,7 @@ TEST(ValueStorageTest, BooleanTesting) {
     EXPECT_FALSE(testStore.getParam("firstBool"));
     EXPECT_FALSE(testStore.getParam("firstBool"));
     EXPECT_TRUE(testStore.getParam("secondBool"));
     EXPECT_TRUE(testStore.getParam("secondBool"));
 
 
-    // Verify that we can update paramaters. 
+    // Verify that we can update paramaters.
     testStore.setParam("firstBool", true);
     testStore.setParam("firstBool", true);
     testStore.setParam("secondBool", false);
     testStore.setParam("secondBool", false);
 
 
@@ -65,7 +66,7 @@ TEST(ValueStorageTest, BooleanTesting) {
     // Verify that looking for a parameter that never existed throws.
     // Verify that looking for a parameter that never existed throws.
     ASSERT_THROW(testStore.getParam("bogusBool"), isc::dhcp::DhcpConfigError);
     ASSERT_THROW(testStore.getParam("bogusBool"), isc::dhcp::DhcpConfigError);
 
 
-    // Verify that attempting to delete a parameter that never existed does not throw. 
+    // Verify that attempting to delete a parameter that never existed does not throw.
     EXPECT_NO_THROW(testStore.delParam("bogusBool"));
     EXPECT_NO_THROW(testStore.delParam("bogusBool"));
 
 
     // Verify that we can empty the list.
     // Verify that we can empty the list.
@@ -74,21 +75,21 @@ TEST(ValueStorageTest, BooleanTesting) {
 
 
 }
 }
 
 
-// This test verifies that Uint32Storage functions properly. 
+// This test verifies that Uint32Storage functions properly.
 TEST(ValueStorageTest, Uint32Testing) {
 TEST(ValueStorageTest, Uint32Testing) {
     Uint32Storage testStore;
     Uint32Storage testStore;
 
 
     uint32_t intOne = 77;
     uint32_t intOne = 77;
     uint32_t intTwo = 33;
     uint32_t intTwo = 33;
 
 
-    // Verify that we can add and retrieve parameters. 
+    // Verify that we can add and retrieve parameters.
     testStore.setParam("firstInt", intOne);
     testStore.setParam("firstInt", intOne);
     testStore.setParam("secondInt", intTwo);
     testStore.setParam("secondInt", intTwo);
 
 
     EXPECT_EQ(testStore.getParam("firstInt"), intOne);
     EXPECT_EQ(testStore.getParam("firstInt"), intOne);
     EXPECT_EQ(testStore.getParam("secondInt"), intTwo);
     EXPECT_EQ(testStore.getParam("secondInt"), intTwo);
 
 
-    // Verify that we can update parameters. 
+    // Verify that we can update parameters.
     testStore.setParam("firstInt", --intOne);
     testStore.setParam("firstInt", --intOne);
     testStore.setParam("secondInt", ++intTwo);
     testStore.setParam("secondInt", ++intTwo);
 
 
@@ -105,7 +106,7 @@ TEST(ValueStorageTest, Uint32Testing) {
     // Verify that looking for a parameter that never existed throws.
     // Verify that looking for a parameter that never existed throws.
     ASSERT_THROW(testStore.getParam("bogusInt"), isc::dhcp::DhcpConfigError);
     ASSERT_THROW(testStore.getParam("bogusInt"), isc::dhcp::DhcpConfigError);
 
 
-    // Verify that attempting to delete a parameter that never existed does not throw. 
+    // Verify that attempting to delete a parameter that never existed does not throw.
     EXPECT_NO_THROW(testStore.delParam("bogusInt"));
     EXPECT_NO_THROW(testStore.delParam("bogusInt"));
 
 
     // Verify that we can empty the list.
     // Verify that we can empty the list.
@@ -113,7 +114,7 @@ TEST(ValueStorageTest, Uint32Testing) {
     EXPECT_THROW(testStore.getParam("secondInt"), isc::dhcp::DhcpConfigError);
     EXPECT_THROW(testStore.getParam("secondInt"), isc::dhcp::DhcpConfigError);
 }
 }
 
 
-// This test verifies that StringStorage functions properly. 
+// This test verifies that StringStorage functions properly.
 TEST(ValueStorageTest, StringTesting) {
 TEST(ValueStorageTest, StringTesting) {
     StringStorage testStore;
     StringStorage testStore;
 
 
@@ -127,7 +128,7 @@ TEST(ValueStorageTest, StringTesting) {
     EXPECT_EQ(testStore.getParam("firstString"), stringOne);
     EXPECT_EQ(testStore.getParam("firstString"), stringOne);
     EXPECT_EQ(testStore.getParam("secondString"), stringTwo);
     EXPECT_EQ(testStore.getParam("secondString"), stringTwo);
 
 
-    // Verify that we can update parameters. 
+    // Verify that we can update parameters.
     stringOne.append("-boo");
     stringOne.append("-boo");
     stringTwo.append("-boo");
     stringTwo.append("-boo");
 
 
@@ -147,7 +148,7 @@ TEST(ValueStorageTest, StringTesting) {
     // Verify that looking for a parameter that never existed throws.
     // Verify that looking for a parameter that never existed throws.
     ASSERT_THROW(testStore.getParam("bogusString"), isc::dhcp::DhcpConfigError);
     ASSERT_THROW(testStore.getParam("bogusString"), isc::dhcp::DhcpConfigError);
 
 
-    // Verify that attempting to delete a parameter that never existed does not throw. 
+    // Verify that attempting to delete a parameter that never existed does not throw.
     EXPECT_NO_THROW(testStore.delParam("bogusString"));
     EXPECT_NO_THROW(testStore.delParam("bogusString"));
 
 
     // Verify that we can empty the list.
     // Verify that we can empty the list.
@@ -165,6 +166,16 @@ public:
         CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().deleteSubnets6();
     }
     }
 
 
+    /// @brief generates interface-id option based on provided text
+    ///
+    /// @param text content of the option to be created
+    ///
+    /// @return pointer to the option object created
+    OptionPtr generateInterfaceId(const string& text) {
+        OptionBuffer buffer(text.begin(), text.end());
+        return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, buffer));
+    }
+
     ~CfgMgrTest() {
     ~CfgMgrTest() {
         // clean up after the test
         // clean up after the test
         CfgMgr::instance().deleteSubnets4();
         CfgMgr::instance().deleteSubnets4();
@@ -406,6 +417,91 @@ TEST_F(CfgMgrTest, subnet6) {
     EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::123")));
     EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::123")));
 }
 }
 
 
+// This test verifies if the configuration manager is able to hold, select
+// and return valid subnets, based on interface names.
+TEST_F(CfgMgrTest, subnet6Interface) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+    subnet1->setIface("foo");
+    subnet2->setIface("bar");
+    subnet3->setIface("foobar");
+
+    // There shouldn't be any subnet configured at this stage
+    EXPECT_FALSE(cfg_mgr.getSubnet6("foo"));
+
+    cfg_mgr.addSubnet6(subnet1);
+
+    // Now we have only one subnet, any request will be served from it
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6("foo"));
+
+    // If we have only a single subnet and the request came from a local
+    // address, let's use that subnet
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef")));
+
+    cfg_mgr.addSubnet6(subnet2);
+    cfg_mgr.addSubnet6(subnet3);
+
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6("foobar"));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6("bar"));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("xyzzy")); // no such interface
+
+    // Check that deletion of the subnets works.
+    cfg_mgr.deleteSubnets6();
+    EXPECT_FALSE(cfg_mgr.getSubnet6("foo"));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("bar"));
+    EXPECT_FALSE(cfg_mgr.getSubnet6("foobar"));
+}
+
+// This test verifies if the configuration manager is able to hold, select
+// and return valid leases, based on interface-id option values
+TEST_F(CfgMgrTest, subnet6InterfaceId) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+
+    // interface-id options used in subnets 1,2, and 3
+    OptionPtr ifaceid1 = generateInterfaceId("relay1.eth0");
+    OptionPtr ifaceid2 = generateInterfaceId("VL32");
+    // That's a strange interface-id, but this is a real life example
+    OptionPtr ifaceid3 = generateInterfaceId("ISAM144|299|ipv6|nt:vp:1:110");
+
+    // bogus interface-id
+    OptionPtr ifaceid_bogus = generateInterfaceId("non-existent");
+
+    subnet1->setInterfaceId(ifaceid1);
+    subnet2->setInterfaceId(ifaceid2);
+    subnet3->setInterfaceId(ifaceid3);
+
+    // There shouldn't be any subnet configured at this stage
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1));
+
+    cfg_mgr.addSubnet6(subnet1);
+
+    // If we have only a single subnet and the request came from a local
+    // address, let's use that subnet
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(ifaceid1));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2));
+
+    cfg_mgr.addSubnet6(subnet2);
+    cfg_mgr.addSubnet6(subnet3);
+
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(ifaceid3));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(ifaceid2));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid_bogus));
+
+    // Check that deletion of the subnets works.
+    cfg_mgr.deleteSubnets6();
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2));
+    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid3));
+}
+
+
 // This test verifies that new DHCPv4 option spaces can be added to
 // This test verifies that new DHCPv4 option spaces can be added to
 // the configuration manager and that duplicated option space is
 // the configuration manager and that duplicated option space is
 // rejected.
 // rejected.

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

@@ -16,6 +16,7 @@
 
 
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
 #include <dhcp/option.h>
 #include <dhcp/option.h>
+#include <dhcp/dhcp6.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
@@ -516,4 +517,19 @@ TEST(Subnet6Test, iface) {
     EXPECT_EQ("en1", subnet.getIface());
     EXPECT_EQ("en1", subnet.getIface());
 }
 }
 
 
+// This trivial test checks if the interface-id option can be set and
+// later retrieved for a subnet6 object.
+TEST(Subnet6Test, interfaceId) {
+    // Create as subnet to add options to it.
+    Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+
+    EXPECT_FALSE(subnet->getInterfaceId());
+
+    OptionPtr option(new Option(Option::V6, D6O_INTERFACE_ID, OptionBuffer(10, 0xFF)));
+    subnet->setInterfaceId(option);
+
+    EXPECT_EQ(option, subnet->getInterfaceId());
+
+}
+
 };
 };