diff --git a/src/bin/dhcp4/json_config_parser.cc b/src/bin/dhcp4/json_config_parser.cc index da621485239c12788c913f6ff34f41edb5ade040..59862038371aa2f1d3f8751ce51b24e79ca0594a 100644 --- a/src/bin/dhcp4/json_config_parser.cc +++ b/src/bin/dhcp4/json_config_parser.cc @@ -448,6 +448,8 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id, parser = new ExpirationConfigParser(); } else if (config_id.compare("client-classes") == 0) { parser = new ClientClassDefListParser(config_id, globalContext()); + } else if (config_id.compare("host-reservation-identifiers") == 0) { + parser = new HostReservationIdsParser4(); } else { isc_throw(DhcpConfigError, "unsupported global configuration parameter: " diff --git a/src/bin/dhcp4/tests/dora_unittest.cc b/src/bin/dhcp4/tests/dora_unittest.cc index 8f69c67eb7e5b73918e5e7aa9dc3564a0d6b7c5b..3ee053f24800af83a50a2f6257243d77cca7b3e1 100644 --- a/src/bin/dhcp4/tests/dora_unittest.cc +++ b/src/bin/dhcp4/tests/dora_unittest.cc @@ -57,6 +57,11 @@ namespace { /// - 1 pool: 10.0.0.10-10.0.0.100 /// - match-client-id flag is set to false, thus the server /// uses HW address for lease lookup, rather than client id +/// +/// - Configuration 4: +/// - The same as configuration 2, but using different values in +/// 'host-reservation-identifiers' +/// const char* DORA_CONFIGS[] = { // Configuration 0 "{ \"interfaces-config\": {" @@ -154,6 +159,32 @@ const char* DORA_CONFIGS[] = { " } ]" " } ]" "}", + +// Configuration 4 + "{ \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"host-reservation-identifiers\": [ \"circuit-id\", \"hw-address\" ]," + "\"valid-lifetime\": 600," + "\"subnet4\": [ { " + " \"subnet\": \"10.0.0.0/24\", " + " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]," + " \"reservations\": [ " + " {" + " \"hw-address\": \"aa:bb:cc:dd:ee:ff\"," + " \"ip-address\": \"10.0.0.7\"" + " }," + " {" + " \"duid\": \"01:02:03:04:05\"," + " \"ip-address\": \"10.0.0.8\"" + " }," + " {" + " \"circuit-id\": \"'charter950'\"," + " \"ip-address\": \"10.0.0.9\"" + " }" + " ]" + "} ]" + "}" }; /// @brief Test fixture class for testing 4-way (DORA) exchanges. @@ -697,9 +728,8 @@ TEST_F(DORATest, reservation) { } // This test checks that it is possible to make a reservation by -// circuit-id inserted by the relay agent.. +// circuit-id inserted by the relay agent. TEST_F(DORATest, reservationByCircuitId) { - // Client A is a one which will have a reservation. Dhcp4Client client(Dhcp4Client::SELECTING); // Use relay agent so as the circuit-id can be inserted. client.useRelay(true, IOAddress("10.0.0.1"), IOAddress("10.0.0.2")); @@ -721,6 +751,47 @@ TEST_F(DORATest, reservationByCircuitId) { ASSERT_EQ("10.0.0.9", client.config_.lease_.addr_.toText()); } +// This test verifies that order in which host identifiers are used to +// retrieve host reservations can be controlled. +TEST_F(DORATest, hostIdentifiersOrder) { + Dhcp4Client client(Dhcp4Client::SELECTING); + client.setHWAddress("aa:bb:cc:dd:ee:ff"); + // Use relay agent so as the circuit-id can be inserted. + client.useRelay(true, IOAddress("10.0.0.1"), IOAddress("10.0.0.2")); + // Specify circuit-id. + client.setCircuitId("charter950"); + + // Configure DHCP server. + configure(DORA_CONFIGS[2], *client.getServer()); + // Perform 4-way exchange to obtain reserved address. + // The client has in fact two reserved addresses, but the one assigned + // should be by hw-address. + ASSERT_NO_THROW(client.doDORA(boost::shared_ptr< + IOAddress>(new IOAddress("0.0.0.0")))); + // Make sure that the server responded. + ASSERT_TRUE(client.getContext().response_); + Pkt4Ptr resp = client.getContext().response_; + // Make sure that the server has responded with DHCPACK. + ASSERT_EQ(DHCPACK, static_cast(resp->getType())); + // Make sure that the client has got the lease for the reserved address. + ASSERT_EQ("10.0.0.7", client.config_.lease_.addr_.toText()); + + // Reconfigure the server to change the preference order of the + // host identifiers. The 'circuit-id' should now take precedence over + // the hw-address. + configure(DORA_CONFIGS[4], *client.getServer()); + ASSERT_NO_THROW(client.doDORA(boost::shared_ptr< + IOAddress>(new IOAddress("0.0.0.0")))); + // Make sure that the server responded. + ASSERT_TRUE(client.getContext().response_); + resp = client.getContext().response_; + // Make sure that the server has responded with DHCPACK. + ASSERT_EQ(DHCPACK, static_cast(resp->getType())); + // Make sure that the client has got the lease for the reserved address. + ASSERT_EQ("10.0.0.9", client.config_.lease_.addr_.toText()); + +} + // This test checks that setting the match-client-id value to false causes // the server to ignore changing client identifier when the client is // using consistent HW address. diff --git a/src/bin/dhcp6/json_config_parser.cc b/src/bin/dhcp6/json_config_parser.cc index 8618c1dba3573c1df43bfbe665f084cb8d048554..aecf72f452a89dfad5e0769eaae0daf27db025d8 100644 --- a/src/bin/dhcp6/json_config_parser.cc +++ b/src/bin/dhcp6/json_config_parser.cc @@ -702,6 +702,8 @@ DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id, parser = new ClientClassDefListParser(config_id, globalContext()); } else if (config_id.compare("server-id") == 0) { parser = new DUIDConfigParser(); + } else if (config_id.compare("host-reservation-identifiers") == 0) { + parser = new HostReservationIdsParser6(); } else { isc_throw(DhcpConfigError, "unsupported global configuration parameter: " diff --git a/src/bin/dhcp6/tests/host_unittest.cc b/src/bin/dhcp6/tests/host_unittest.cc index a4f73f1eda176ed627b225c3073b49d77ffa237f..3a5e65c3b93c06a47eec825103e94771c3685704 100644 --- a/src/bin/dhcp6/tests/host_unittest.cc +++ b/src/bin/dhcp6/tests/host_unittest.cc @@ -23,6 +23,9 @@ namespace { /// Single subnet with two reservations, one with a hostname, one without /// - Configuration 1: /// Multiple reservations using different host identifiers. +/// - Configuration 2: +/// Same as configuration 1 but 'host-reservation-identifiers' specified +/// in non-default order. const char* CONFIGS[] = { // Configuration 0: "{ " @@ -76,8 +79,35 @@ const char* CONFIGS[] = { " \"ip-addresses\": [ \"2001:db8:1::2\" ]" " } ]" " } ]" - "}" + "}", + // Configuration 2: + "{ " + "\"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "}," + "\"host-reservation-identifiers\": [ \"duid\", \"hw-address\" ]," + "\"valid-lifetime\": 4000, " + "\"preferred-lifetime\": 3000," + "\"rebind-timer\": 2000, " + "\"renew-timer\": 1000, " + "\"mac-sources\": [ \"ipv6-link-local\" ], " + "\"subnet6\": [ " + " { " + " \"subnet\": \"2001:db8:1::/48\", " + " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ]," + " \"interface\" : \"eth0\" , " + " \"reservations\": [" + " {" + " \"hw-address\": \"38:60:77:d5:ff:ee\"," + " \"ip-addresses\": [ \"2001:db8:1::1\" ]" + " }," + " {" + " \"duid\": \"01:02:03:05\"," + " \"ip-addresses\": [ \"2001:db8:1::2\" ]" + " } ]" + " } ]" + "}" }; /// @brief Test fixture class for testing host reservations @@ -97,11 +127,14 @@ public: /// @param client Reference to a client to be used in the test. /// The client should be preconfigured to insert a specific identifier /// into the message, e.g. DUID, HW address etc. + /// @param config_index Index of the configuration to use in the CONFIGS + /// table. /// @param exp_ip_address Expected IPv6 address in the returned /// reservation. void testReservationByIdentifier(Dhcp6Client& client, + const unsigned int config_index, const std::string exp_ip_address) { - configure(CONFIGS[1], *client.getServer()); + configure(CONFIGS[config_index], *client.getServer()); const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()-> getCfgSubnets6()->getAll(); @@ -315,7 +348,7 @@ TEST_F(HostTest, reservationByDUID) { // Set DUID matching the one used to create host reservations. client.setDUID("01:02:03:05"); // Run the actual test. - testReservationByIdentifier(client, "2001:db8:1::2"); + testReservationByIdentifier(client, 1, "2001:db8:1::2"); } // This test verfies that the host reservation by HW address is found @@ -327,9 +360,20 @@ TEST_F(HostTest, reservationByHWAddress) { // decoded address will be used to search for host reservations. client.setLinkLocal(IOAddress("fe80::3a60:77ff:fed5:ffee")); // Run the actual test. - testReservationByIdentifier(client, "2001:db8:1::1"); + testReservationByIdentifier(client, 1, "2001:db8:1::1"); } - +// This test verifies that order in which host identifiers are used to +// retrieve host reservations can be controlled. +TEST_F(HostTest, hostIdentifiersOrder) { + Dhcp6Client client; + // Set DUID matching the one used to create host reservations. + client.setDUID("01:02:03:05"); + // Set link local address for the client which the server will + // use to decode the HW address as 38:60:77:d5:ff:ee. This + // decoded address will be used to search for host reservations. + client.setLinkLocal(IOAddress("fe80::3a60:77ff:fed5:ffee")); + testReservationByIdentifier(client, 2, "2001:db8:1::2"); +} } // end of anonymous namespace diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index 7c7feec4511bc55bdc04593ab82fbcbc79e5a7cc..aa5c703ae5d82729ca0100b922de9f54b12e1711 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -27,6 +28,7 @@ #include +#include #include #include #include @@ -301,6 +303,40 @@ AllocEngine::AllocatorPtr AllocEngine::getAllocator(Lease::Type type) { return (alloc->second); } +template +void +AllocEngine::findReservationInternal(ContextType& ctx, + const ConstCfgHostReservationsPtr& cfg, + const AllocEngine::HostGetFunc& host_get) { + ctx.host_.reset(); + + // We can only search for the reservation if a subnet has been selected. + if (ctx.subnet_) { + // Check which host reservation mode is supported in this subnet. + Subnet::HRMode hr_mode = ctx.subnet_->getHostReservationMode(); + + // Check if there is a host reseravtion for this client. Attempt to + // get host information + if (hr_mode != Subnet::HR_DISABLED) { + // Iterate over configured identifiers in the order of preference + // and try to use each of them to search for the reservations. + BOOST_FOREACH(const Host::IdentifierType& id, cfg->getIdentifierTypes()) { + IdentifierMap::const_iterator id_ctx = ctx.host_identifiers_.find(id); + if (id_ctx != ctx.host_identifiers_.end()) { + // Attempt to find a host using a specified identifier. + ctx.host_ = host_get(ctx.subnet_->getID(), id, + &id_ctx->second[0], id_ctx->second.size()); + // If we found matching host, return. + if (ctx.host_) { + return; + } + } + } + } + } +} + + // ########################################################################## // # DHCPv6 lease allocation code starts here. // ########################################################################## @@ -339,30 +375,12 @@ AllocEngine::ClientContext6::ClientContext6(const Subnet6Ptr& subnet, const Duid } -void AllocEngine::findReservation(ClientContext6& ctx) const { - if (!ctx.subnet_ || !ctx.duid_) { - return; - } - - ctx.host_.reset(); - - // Check which host reservation mode is supported in this subnet. - Subnet::HRMode hr_mode = ctx.subnet_->getHostReservationMode(); - - // Check if there's a host reservation for this client. Attempt to get - // host info only if reservations are not disabled. - if (hr_mode != Subnet::HR_DISABLED) { - - BOOST_FOREACH(const IdentifierPair& id, ctx.host_identifiers_) { - ctx.host_ = HostMgr::instance().get6(ctx.subnet_->getID(), - id.first, &id.second[0], - id.second.size()); - // If we found matching host, return. - if (ctx.host_) { - return; - } - } - } +void AllocEngine::findReservation(ClientContext6& ctx) { + ConstCfgHostReservationsPtr cfg = + CfgMgr::instance().getCurrentCfg()->getCfgHostReservations6(); + findReservationInternal(ctx, cfg, boost::bind(&HostMgr::get6, + &HostMgr::instance(), + _1, _2, _3, _4)); } Lease6Collection @@ -2117,27 +2135,11 @@ AllocEngine::allocateLease4(ClientContext4& ctx) { void AllocEngine::findReservation(ClientContext4& ctx) { - ctx.host_.reset(); - - // We can only search for the reservation if a subnet has been selected. - if (ctx.subnet_) { - // Check which host reservation mode is supported in this subnet. - Subnet::HRMode hr_mode = ctx.subnet_->getHostReservationMode(); - - // Check if there is a host reseravtion for this client. Attempt to - // get host information - if (hr_mode != Subnet::HR_DISABLED) { - BOOST_FOREACH(const IdentifierPair& id, ctx.host_identifiers_) { - ctx.host_ = HostMgr::instance().get4(ctx.subnet_->getID(), - id.first, &id.second[0], - id.second.size()); - // If we found matching host, return. - if (ctx.host_) { - return; - } - } - } - } + ConstCfgHostReservationsPtr cfg = + CfgMgr::instance().getCurrentCfg()->getCfgHostReservations4(); + findReservationInternal(ctx, cfg, boost::bind(&HostMgr::get4, + &HostMgr::instance(), + _1, _2, _3, _4)); } Lease4Ptr diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h index b23f0ad1bfb4f15a6e5fd3a1b660e635fe1640f8..25d2c975f34c7173e466b448e2ccc5fde6bcfa9a 100644 --- a/src/lib/dhcpsrv/alloc_engine.h +++ b/src/lib/dhcpsrv/alloc_engine.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -617,10 +618,33 @@ public: /// Attempts to find appropriate host reservation in HostMgr. If found, it /// will be set in ctx.host_. /// @param ctx Client context that contains all necessary information. - void findReservation(ClientContext6& ctx) const; + static void findReservation(ClientContext6& ctx); private: + /// @brief Type of the function used by @ref findReservationInternal to + /// retrieve reservations by subnet identifier and host identifier. + typedef boost::function HostGetFunc; + + /// @brief Common function for searching host reservations. + /// + /// This is a common function called by variants of @ref findReservation + /// functions. + /// + /// @param ctx Reference to a @ref ClientContext6 or @ref ClientContext4. + /// @param cfg Pointer to object holding general configuration for host + /// reservations. It uses this object to read the desired order of + /// host identifiers to be used to search for reservations. + /// @param host_get Pointer to the @ref HostMgr functions to be used + /// to retrieve reservation by subnet identifier and host identifier. + /// @tparam ContextType Either @ref ClientContext6 or @ref ClientContext4. + template + static void findReservationInternal(ContextType& ctx, + const ConstCfgHostReservationsPtr& cfg, + const HostGetFunc& host_get); + /// @brief creates a lease and inserts it in LeaseMgr if necessary /// /// Creates a lease based on specified parameters and tries to insert it