From: Marcin Siodelski Date: Sat, 3 Sep 2016 10:09:39 +0000 (+0200) Subject: [4765] DHCPv6 server now uses client classes specified in HR db. X-Git-Tag: trac5006_base~14^2~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5db1edfb8e74e8b5a4e3eb1099d9f96f66c31d01;p=thirdparty%2Fkea.git [4765] DHCPv6 server now uses client classes specified in HR db. --- diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc index 58893bb15a..9ad93a1387 100644 --- a/src/bin/dhcp6/dhcp6_srv.cc +++ b/src/bin/dhcp6/dhcp6_srv.cc @@ -67,6 +67,8 @@ #include #include #include +#include +#include #include #include @@ -2273,6 +2275,7 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) { // Let's create a simplified client context here. AllocEngine::ClientContext6 ctx; initContext(solicit, ctx); + setReservedClientClasses(solicit, ctx); Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid())); @@ -2318,6 +2321,7 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) { // Let's create a simplified client context here. AllocEngine::ClientContext6 ctx; initContext(request, ctx); + setReservedClientClasses(request, ctx); Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid())); @@ -2344,6 +2348,7 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) { // Let's create a simplified client context here. AllocEngine::ClientContext6 ctx; initContext(renew, ctx); + setReservedClientClasses(renew, ctx); Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid())); @@ -2370,6 +2375,7 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) { // Let's create a simplified client context here. AllocEngine::ClientContext6 ctx; initContext(rebind, ctx); + setReservedClientClasses(rebind, ctx); Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid())); @@ -2396,6 +2402,7 @@ Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) { // Let's create a simplified client context here. AllocEngine::ClientContext6 ctx; initContext(confirm, ctx); + setReservedClientClasses(confirm, ctx); // Get IA_NAs from the Confirm. If there are none, the message is // invalid and must be discarded. There is nothing more to do. @@ -2488,6 +2495,7 @@ Dhcpv6Srv::processRelease(const Pkt6Ptr& release) { // Let's create a simplified client context here. AllocEngine::ClientContext6 ctx; initContext(release, ctx); + setReservedClientClasses(release, ctx); Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid())); @@ -2516,6 +2524,7 @@ Dhcpv6Srv::processDecline(const Pkt6Ptr& decline) { // Let's create a simplified client context here. AllocEngine::ClientContext6 ctx; initContext(decline, ctx); + setReservedClientClasses(decline, ctx); // Copy client options (client-id, also relay information if present) copyClientOptions(decline, reply); @@ -2796,6 +2805,7 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& inf_request) { // Let's create a simplified client context here. AllocEngine::ClientContext6 ctx; initContext(inf_request, ctx); + setReservedClientClasses(inf_request, ctx); // Create a Reply packet, with the same trans-id as the client's. Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, inf_request->getTransid())); @@ -2907,11 +2917,24 @@ void Dhcpv6Srv::classifyPacket(const Pkt6Ptr& pkt) { .arg("get exception?"); } } +} + +void +Dhcpv6Srv::setReservedClientClasses(const Pkt6Ptr& pkt, + const AllocEngine::ClientContext6& ctx) { + if (ctx.host_ && pkt) { + BOOST_FOREACH(const std::string& client_class, + ctx.host_->getClientClasses6()) { + pkt->addClass(client_class); + } + } + const ClientClasses& classes = pkt->getClasses(); if (!classes.empty()) { + std::string joined_classes = boost::algorithm::join(classes, ", "); LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_ASSIGNED) .arg(pkt->getLabel()) - .arg(classes); + .arg(joined_classes); } } diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h index 7513f909bf..183d3a8ff6 100644 --- a/src/bin/dhcp6/dhcp6_srv.h +++ b/src/bin/dhcp6/dhcp6_srv.h @@ -629,6 +629,13 @@ protected: /// @param pkt packet to be classified void classifyPacket(const Pkt6Ptr& pkt); + /// @brief Assigns classes retrieved from host reservation database. + /// + /// @param pkt Pointer to the packet to which classes will be assigned. + /// @param ctx Reference to the client context. + void setReservedClientClasses(const Pkt6Ptr& pkt, + const AllocEngine::ClientContext6& ctx); + /// @brief Attempts to get a MAC/hardware address using configred sources /// /// Tries to extract MAC/hardware address information from the packet diff --git a/src/bin/dhcp6/tests/classify_unittests.cc b/src/bin/dhcp6/tests/classify_unittests.cc index 64197a8a54..c86652d453 100644 --- a/src/bin/dhcp6/tests/classify_unittests.cc +++ b/src/bin/dhcp6/tests/classify_unittests.cc @@ -14,11 +14,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include using namespace isc; @@ -31,21 +33,19 @@ namespace { /// @brief Set of JSON configurations used by the classification unit tests. /// /// - Configuration 0: -/// - one subnet 3000::/32 used on eth0 interface -/// - prefixes of length 64, delegated from the pool: 2001:db8:3::/48 -/// - the delegated prefix was intentionally selected to not match the -/// subnet prefix, to test that the delegated prefix doesn't need to -/// match the subnet prefix -/// -/// - Configuration 1: -/// - two subnets 2001:db8:1::/48 and 2001:db8:2::/48 -/// - first subnet assigned to interface eth0, another one assigned to eth1 -/// - one pool for subnet in a range of 2001:db8:X::1 - 2001:db8:X::10, -/// where X is 1 or 2 -/// - enables Rapid Commit for the first subnet and disables for the second -/// one -/// - DNS updates enabled -/// +/// - Specifies 3 classes: 'router', 'reserved-class1' and 'reserved-class2'. +/// - 'router' class is assigned when the client sends option 1234 (string) +/// equal to 'foo'. +/// - The other two classes are reserved for the client having +/// DUID '01:02:03:04' +/// - Class 'router' includes option 'ipv6-forwarding'. +/// - Class 'reserved-class1' includes option DNS servers. +/// - Class 'reserved-class2' includes option NIS servers. +/// - All three options are sent when client has reservations for the +/// 'reserved-class1', 'reserved-class2' and sends option 1234 with +/// the 'foo' value. +/// - There is one subnet specified 2001:db8:1::/48 with pool of +/// IPv6 addresses. const char* CONFIGS[] = { // Configuration 0 "{ \"interfaces-config\": {" @@ -54,46 +54,64 @@ const char* CONFIGS[] = { "\"preferred-lifetime\": 3000," "\"rebind-timer\": 2000, " "\"renew-timer\": 1000, " - "\"subnet6\": [ { " - " \"pd-pools\": [" - " { \"prefix\": \"2001:db8:3::\", " - " \"prefix-len\": 48, " - " \"delegated-len\": 64" - " } ]," - " \"subnet\": \"3000::/32\", " - " \"interface-id\": \"\"," - " \"interface\": \"eth0\"" - " } ]," - "\"valid-lifetime\": 4000 }", - -// Configuration 1 - "{ \"interfaces-config\": {" - " \"interfaces\": [ \"*\" ]" + "\"option-def\": [ " + "{" + " \"name\": \"host-name\"," + " \"code\": 1234," + " \"type\": \"string\"" "}," - "\"preferred-lifetime\": 3000," - "\"rebind-timer\": 2000, " - "\"renew-timer\": 1000, " - "\"subnet6\": [ { " - " \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::10\" } ]," + "{" + " \"name\": \"ipv6-forwarding\"," + " \"code\": 2345," + " \"type\": \"boolean\"" + "} ]," + "\"client-classes\": [" + "{" + " \"name\": \"router\"," + " \"test\": \"option[host-name].text == 'foo'\"," + " \"option-data\": [" + " {" + " \"name\": \"ipv6-forwarding\", " + " \"data\": \"true\"" + " } ]" + "}," + "{" + " \"name\": \"reserved-class1\"," + " \"option-data\": [" + " {" + " \"name\": \"dns-servers\"," + " \"data\": \"2001:db8:1::50\"" + " }" + " ]" + "}," + "{" + " \"name\": \"reserved-class2\"," + " \"option-data\": [" + " {" + " \"name\": \"nis-servers\"," + " \"data\": \"2001:db8:1::100\"" + " }" + " ]" + "}" + "]," + "\"subnet6\": [ " + "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], " " \"subnet\": \"2001:db8:1::/48\", " - " \"interface\": \"eth0\"," - " \"rapid-commit\": True" - " }," - " {" - " \"pools\": [ { \"pool\": \"2001:db8:2::1 - 2001:db8:2::10\" } ]," - " \"subnet\": \"2001:db8:2::/48\", " " \"interface\": \"eth1\"," - " \"rapid-commit\": False" + " \"reservations\": [" + " {" + " \"duid\": \"01:02:03:04\"," + " \"client-classes\": [ \"reserved-class1\", \"reserved-class2\" ]" + " } ]" " } ]," - "\"valid-lifetime\": 4000," - " \"dhcp-ddns\" : {" - " \"enable-updates\" : True, " - " \"qualifying-suffix\" : \"example.com\" }" - "}" + "\"valid-lifetime\": 4000 }" }; /// @brief Test fixture class for testing client classification by the /// DHCPv6 server. +/// +/// @todo There are numerous tests not using Dhcp6Client class. They should be +/// migrated to use it one day. class ClassifyTest : public Dhcpv6SrvTest { public: /// @brief Constructor. @@ -104,6 +122,60 @@ public: iface_mgr_test_config_(true) { } + /// @brief Verify values of options returned by the server when the server + /// uses configuration with index 0. + /// + /// @param config Reference to DHCP client's configuration received. + /// @param ip_forwarding Expected value of IP forwarding option. This option + /// is expected to always be present. + /// @param dns_servers String holding an address carried within DNS + /// servers option. If this value is empty, the option is expected to not + /// be included in the response. + /// @param nis_servers String holding an address carried within NIS + /// servers option. If this value is empty, the option is expected to not + /// be included in the response. + void verifyConfig0Options(const Dhcp6Client::Configuration& config, + const uint8_t ip_forwarding = 1, + const std::string& dns_servers = "", + const std::string& nis_servers = "") { + // IP forwarding option should always exist. + OptionPtr ip_forwarding_opt = config.findOption(2345); + ASSERT_TRUE(ip_forwarding_opt); + // The option comprises 2 bytes of option code, 2 bytes of option length, + // and a single 1 byte value. This makes it 5 bytes of a total length. + ASSERT_EQ(5, ip_forwarding_opt->len()); + ASSERT_EQ(static_cast(ip_forwarding), + static_cast(ip_forwarding_opt->getUint8())); + + // DNS servers. + Option6AddrLstPtr dns_servers_opt = boost::dynamic_pointer_cast< + Option6AddrLst>(config.findOption(D6O_NAME_SERVERS)); + if (!dns_servers.empty()) { + ASSERT_TRUE(dns_servers_opt); + Option6AddrLst::AddressContainer addresses = dns_servers_opt->getAddresses(); + // For simplicity, we expect only a single address. + ASSERT_EQ(1, addresses.size()); + EXPECT_EQ(dns_servers, addresses[0].toText()); + + } else { + EXPECT_FALSE(dns_servers_opt); + } + + // NIS servers. + Option6AddrLstPtr nis_servers_opt = boost::dynamic_pointer_cast< + Option6AddrLst>(config.findOption(D6O_NIS_SERVERS)); + if (!nis_servers.empty()) { + ASSERT_TRUE(nis_servers_opt); + Option6AddrLst::AddressContainer addresses = nis_servers_opt->getAddresses(); + // For simplicity, we expect only a single address. + ASSERT_EQ(1, addresses.size()); + EXPECT_EQ(nis_servers, addresses[0].toText()); + + } else { + EXPECT_FALSE(nis_servers_opt); + } + } + /// @brief Interface Manager's fake configuration control. IfaceMgrTestConfig iface_mgr_test_config_; }; @@ -601,5 +673,77 @@ TEST_F(ClassifyTest, relayOverrideAndClientClass) { EXPECT_TRUE(subnet1 == srv_.selectSubnet(sol)); } +// This test checks that it is possible to specify static reservations for +// client classes. +TEST_F(ClassifyTest, clientClassesInHostReservations) { + Dhcp6Client client; + // Initially use a DUID for which there are no reservations. As a result, + // the client should be assigned a single class "router". + client.setDUID("01:02:03:05"); + client.setInterface("eth1"); + client.requestAddress(); + // Request all options we may potentially get. Otherwise, the server will + // not return them, even when the client is assigned to the classes for + // which these options should be sent. + client.requestOption(2345); + client.requestOption(D6O_NAME_SERVERS); + client.requestOption(D6O_NIS_SERVERS); + + ASSERT_NO_THROW(configure(CONFIGS[0], *client.getServer())); + + // Adding this option to the client's message will cause the client to + // belong to the 'router' class. + OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo")); + client.addExtraOption(hostname); + + // Send a message to the server. + ASSERT_NO_THROW(client.doSolicit(true)); + + // IP forwarding should be present, but DNS and NIS servers should not. + ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_)); + + // Modify the DUID of our client to the one for which class reservations + // have been made. + client.setDUID("01:02:03:04"); + ASSERT_NO_THROW(client.doSolicit(true)); + + // This time, the client should obtain options from all three classes. + ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1, + "2001:db8:1::50", + "2001:db8:1::100")); + + // This should also work for Request case. + ASSERT_NO_THROW(client.doSARR()); + ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1, + "2001:db8:1::50", + "2001:db8:1::100")); + + // Renew case. + ASSERT_NO_THROW(client.doRenew()); + ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1, + "2001:db8:1::50", + "2001:db8:1::100")); + + // Rebind case. + ASSERT_NO_THROW(client.doRebind()); + ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1, + "2001:db8:1::50", + "2001:db8:1::100")); + + // Confirm case. This must be before Information-request because the + // client must have an address to confirm from one of the transactions + // involving address assignment, i.e. Request, Renew or Rebind. + ASSERT_NO_THROW(client.doConfirm()); + ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1, + "2001:db8:1::50", + "2001:db8:1::100")); + + // Information-request case. + ASSERT_NO_THROW(client.doInfRequest()); + ASSERT_NO_FATAL_FAILURE(verifyConfig0Options(client.config_, 1, + "2001:db8:1::50", + "2001:db8:1::100")); +} + } // end of anonymous namespace diff --git a/src/bin/dhcp6/tests/dhcp6_client.cc b/src/bin/dhcp6/tests/dhcp6_client.cc index 9299f11506..e19d5dab31 100644 --- a/src/bin/dhcp6/tests/dhcp6_client.cc +++ b/src/bin/dhcp6/tests/dhcp6_client.cc @@ -561,7 +561,7 @@ Dhcp6Client::doConfirm() { // Set the global status code to default: success and not received. config_.resetGlobalStatusCode(); if (context_.response_) { - config_.resetGlobalStatusCode(); + config_.options_.clear(); applyRcvdConfiguration(context_.response_); } } diff --git a/src/bin/dhcp6/tests/dhcp6_client.h b/src/bin/dhcp6/tests/dhcp6_client.h index f20e8e32e5..4e40c94ef1 100644 --- a/src/bin/dhcp6/tests/dhcp6_client.h +++ b/src/bin/dhcp6/tests/dhcp6_client.h @@ -110,6 +110,7 @@ public: leases_.clear(); status_codes_.clear(); resetGlobalStatusCode(); + options_.clear(); } /// @brief Clears global status code.