Commit 0f7a029e authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac3768'

parents 1a04bbea 38d6de2b
......@@ -49,6 +49,7 @@ Dhcp4Client::Dhcp4Client(const Dhcp4Client::State& state) :
curr_transid_(0),
dest_addr_("255.255.255.255"),
hwaddr_(generateHWAddr()),
clientid_(),
iface_name_("eth0"),
relay_addr_("192.0.2.2"),
requested_options_(),
......@@ -66,6 +67,7 @@ Dhcp4Client::Dhcp4Client(boost::shared_ptr<NakedDhcpv4Srv> srv,
dest_addr_("255.255.255.255"),
fqdn_(),
hwaddr_(generateHWAddr()),
clientid_(),
iface_name_("eth0"),
relay_addr_("192.0.2.2"),
requested_options_(),
......@@ -84,6 +86,54 @@ Dhcp4Client::addRequestedAddress(const asiolink::IOAddress& addr) {
}
}
void
Dhcp4Client::appendClientId() {
if (!context_.query_) {
isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
" when adding Client Identifier option");
}
if (clientid_) {
OptionPtr opt(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
clientid_->getClientId()));
context_.query_->addOption(opt);
}
}
void
Dhcp4Client::appendName() {
if (!context_.query_) {
isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
" when adding FQDN or Hostname option");
}
if (fqdn_) {
context_.query_->addOption(fqdn_);
} else if (hostname_) {
context_.query_->addOption(hostname_);
}
}
void
Dhcp4Client::appendPRL() {
if (!context_.query_) {
isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
" when adding option codes to the PRL option");
} else if (!requested_options_.empty()) {
// Include Parameter Request List if at least one option code
// has been specified to be requested.
OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
DHO_DHCP_PARAMETER_REQUEST_LIST));
for (std::set<uint8_t>::const_iterator opt = requested_options_.begin();
opt != requested_options_.end(); ++opt) {
prl->addValue(*opt);
}
context_.query_->addOption(prl);
}
}
void
Dhcp4Client::applyConfiguration() {
Pkt4Ptr resp = context_.response_;
......@@ -150,9 +200,11 @@ void
Dhcp4Client::doDiscover(const boost::shared_ptr<IOAddress>& requested_addr) {
context_.query_ = createMsg(DHCPDISCOVER);
// Request options if any.
includePRL();
appendPRL();
// Include FQDN or Hostname.
includeName();
appendName();
// Include Client Identifier
appendClientId();
if (requested_addr) {
addRequestedAddress(*requested_addr);
}
......@@ -178,7 +230,7 @@ void
Dhcp4Client::doInform(const bool set_ciaddr) {
context_.query_ = createMsg(DHCPINFORM);
// Request options if any.
includePRL();
appendPRL();
// The client sending a DHCPINFORM message has an IP address obtained
// by some other means, e.g. static configuration. The lease which we
// are using here is most likely set by the createLease method.
......@@ -241,9 +293,11 @@ Dhcp4Client::doRequest() {
}
// Request options if any.
includePRL();
appendPRL();
// Include FQDN or Hostname.
includeName();
appendName();
// Include Client Identifier
appendClientId();
// Send the message to the server.
sendMsg(context_.query_);
// Expect response.
......@@ -254,6 +308,16 @@ Dhcp4Client::doRequest() {
}
}
void
Dhcp4Client::includeClientId(const std::string& clientid) {
if (clientid.empty()) {
clientid_.reset();
} else {
clientid_ = ClientId::fromText(clientid);
}
}
void
Dhcp4Client::includeFQDN(const uint8_t flags, const std::string& fqdn_name,
Option4ClientFqdn::DomainNameType fqdn_type) {
......@@ -266,39 +330,6 @@ Dhcp4Client::includeHostname(const std::string& name) {
hostname_.reset(new OptionString(Option::V4, DHO_HOST_NAME, name));
}
void
Dhcp4Client::includeName() {
if (!context_.query_) {
isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
" when adding FQDN or Hostname option");
}
if (fqdn_) {
context_.query_->addOption(fqdn_);
} else if (hostname_) {
context_.query_->addOption(hostname_);
}
}
void
Dhcp4Client::includePRL() {
if (!context_.query_) {
isc_throw(Dhcp4ClientError, "pointer to the query must not be NULL"
" when adding option codes to the PRL option");
} else if (!requested_options_.empty()) {
// Include Parameter Request List if at least one option code
// has been specified to be requested.
OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
DHO_DHCP_PARAMETER_REQUEST_LIST));
for (std::set<uint8_t>::const_iterator opt = requested_options_.begin();
opt != requested_options_.end(); ++opt) {
prl->addValue(*opt);
}
context_.query_->addOption(prl);
}
}
HWAddrPtr
Dhcp4Client::generateHWAddr(const uint8_t htype) const {
......@@ -339,7 +370,6 @@ Dhcp4Client::requestOptions(const uint8_t option1, const uint8_t option2,
requestOption(option3);
}
Pkt4Ptr
Dhcp4Client::receiveOneMsg() {
// Return empty pointer if server hasn't responded.
......@@ -385,7 +415,11 @@ Dhcp4Client::sendMsg(const Pkt4Ptr& msg) {
void
Dhcp4Client::setHWAddress(const std::string& hwaddr_str) {
hwaddr_.reset(new HWAddr(HWAddr::fromText(hwaddr_str)));
if (hwaddr_str.empty()) {
hwaddr_.reset();
} else {
hwaddr_.reset(new HWAddr(HWAddr::fromText(hwaddr_str)));
}
}
} // end of namespace isc::dhcp::test
......
......@@ -217,6 +217,15 @@ public:
return (srv_);
}
/// @brief Creates the client id from the client id in the textual format.
///
/// The generated client id will be added to the client's messages to the
/// server.
///
/// @param clientid Client id in the textual format. Use the empty client id
/// value to not include the client id.
void includeClientId(const std::string& clientid);
/// @brief Creates an instance of the Client FQDN option to be included
/// in the client's message.
///
......@@ -285,7 +294,8 @@ public:
/// @brief Sets the explicit hardware address for the client.
///
/// @param hwaddr_str String representation of the HW address.
/// @param hwaddr_str String representation of the HW address. Use an
/// empty string to set the NULL hardware address.
void setHWAddress(const std::string& hwaddr_str);
/// @brief Sets the interface over which the messages should be sent.
......@@ -360,12 +370,19 @@ private:
/// @return An instance of the message created.
Pkt4Ptr createMsg(const uint8_t msg_type);
/// @brief Includes the Client Identifier option in the client's message.
///
/// This function creates an instance of the Client Identifier option
/// if the client identifier has been specified and includes this
/// option in the client's message to the server.
void appendClientId();
/// @brief Includes FQDN or Hostname option in the client's message.
///
/// This method checks if @c fqdn_ or @c hostname_ is specified and
/// includes it in the client's message. If both are specified, the
/// @c fqdn_ will be used.
void includeName();
void appendName();
/// @brief Include PRL Option in the query message.
///
......@@ -373,7 +390,7 @@ private:
/// option and adds option codes from the @c requested_options_ to it.
/// It later adds the PRL option to the @c context_.query_ message
/// if it is non-NULL.
void includePRL();
void appendPRL();
/// @brief Simulates reception of the message from the server.
///
......@@ -407,6 +424,9 @@ private:
/// @brief Current hardware address of the client.
HWAddrPtr hwaddr_;
/// @brief Current client identifier.
ClientIdPtr clientid_;
/// @brief Interface to be used to send the messages.
std::string iface_name_;
......
......@@ -173,6 +173,71 @@ public:
IfaceMgr::instance().openSockets4();
}
/// @brief Test that server doesn't allocate the lease for a client
/// which has the same address or client identifier as another client.
///
/// This test checks the server's behavior in the following scenario:
/// - Client A identifies itself to the server using the hardware address
/// and client identifier or only one of those.
/// - Client A performs the 4-way exchange and obtains a lease from the server.
/// - Client B uses the same HW address or client identifier as the client A.
/// - Client B uses both HW address and client identifier if the client A is using
/// only one of them. Client B uses one of the HW address or client
/// identifier if the client A is using both.
/// - Client B sends the DHCPDISCOVER to the server.
/// The server determines that there is a lease for the client A using the
/// same HW address as the client B. Server discards the client's message and
/// doesn't offer the lease for the client B to prevent allocation of the
/// lease without a unique identifier.
/// - The client sends the DHCPREQUEST and the server sends the DHCPNAK for the
/// same reason.
/// - The client A renews its address successfully.
///
/// The specific test cases using this test must make sure that one of the
/// provided parameters is an empty string. This simulates the situation where
/// one of the clients has only one of the identifiers and the other one has
/// two.
///
/// @param hwaddr_a HW address of client A.
/// @param clientid_a Client id of client A.
/// @param hwaddr_b HW address of client B.
/// @param clientid_b Client id of client B.
void oneAllocationOverlapTest(const std::string& hwaddr_a,
const std::string& clientid_a,
const std::string& hwaddr_b,
const std::string& clientid_b);
/// @brief Test that server can allocate the lease for a client having
/// the same HW Address or client id as another client.
///
/// This test checks the server behavior in the following situation:
/// - Client A identifies itself to the server using client identifier
/// and the hardware address and requests allocation of the new lease.
/// - Server allocates the lease to the client.
/// - Client B has a different hardware address or client identifier than
/// the client A, but the other identifier is equal to the corresponding
/// identifier of the client A.
/// - Client B sends DHCPDISCOVER.
/// - Server should determine that the client B is not client A, because
/// it is using a different hadrware address or client identifier.
/// As a consequence, the server should offer a different address to the
/// client B.
/// - The client B performs the 4-way exchange again, and the server
/// allocates a new address to the client, which should be different
/// than the address used by the client A.
/// - Client B is in the renewing state and it successfully renews its
/// address.
/// - The client A also renews its address successfully.
///
/// @param hwaddr_a HW address of client A.
/// @param clientid_a Client id of client A.
/// @param hwaddr_b HW address of client B.
/// @param clientid_b Client id of client B.
void twoAllocationsOverlapTest(const std::string& hwaddr_a,
const std::string& clientid_a,
const std::string& hwaddr_b,
const std::string& clientid_b);
/// @brief Interface Manager's fake configuration control.
IfaceMgrTestConfig iface_mgr_test_config_;
......@@ -441,6 +506,188 @@ TEST_F(DORATest, ciaddr) {
EXPECT_EQ("0.0.0.0", resp->getCiaddr().toText());
}
void
DORATest::twoAllocationsOverlapTest(const std::string& hwaddr_a,
const std::string& clientid_a,
const std::string& hwaddr_b,
const std::string& clientid_b) {
// Allocate a lease by client A using the 4-way exchange.
Dhcp4Client client_a(Dhcp4Client::SELECTING);
client_a.includeClientId(clientid_a);
client_a.setHWAddress(hwaddr_a);
configure(DORA_CONFIGS[0], *client_a.getServer());
ASSERT_NO_THROW(client_a.doDORA());
// Make sure that the server responded.
ASSERT_TRUE(client_a.getContext().response_);
Pkt4Ptr resp_a = client_a.getContext().response_;
// Make sure that the server has responded with DHCPACK.
ASSERT_EQ(DHCPACK, static_cast<int>(resp_a->getType()));
// Make sure that the lease has been recorded by the server.
Lease4Ptr lease_a = LeaseMgrFactory::instance().getLease4(client_a.config_.lease_.addr_);
ASSERT_TRUE(lease_a);
// Create client B.
Dhcp4Client client_b(client_a.getServer(), Dhcp4Client::SELECTING);
client_b.setHWAddress(hwaddr_b);
client_b.includeClientId(clientid_b);
// Send DHCPDISCOVER and expect the response.
ASSERT_NO_THROW(client_b.doDiscover());
Pkt4Ptr resp_b = client_b.getContext().response_;
// Make sure that the server has responded with DHCPOFFER.
ASSERT_EQ(DHCPOFFER, static_cast<int>(resp_b->getType()));
// The offered address should be different than the address which
// was obtained by the client A.
ASSERT_NE(resp_b->getYiaddr(), client_a.config_.lease_.addr_);
// Make sure that the client A lease hasn't been modified.
lease_a = LeaseMgrFactory::instance().getLease4(client_a.config_.lease_.addr_);
ASSERT_TRUE(lease_a);
// Now that we know that the server will avoid assigning the same
// address that the client A has, use the 4-way exchange to actually
// allocate some address.
ASSERT_NO_THROW(client_b.doDORA());
// Make sure that the server responded.
ASSERT_TRUE(client_b.getContext().response_);
resp_b = client_b.getContext().response_;
// Make sure that the server has responded with DHCPACK.
ASSERT_EQ(DHCPACK, static_cast<int>(resp_b->getType()));
// Again, make sure the assigned addresses are different.
ASSERT_NE(client_b.config_.lease_.addr_, client_a.config_.lease_.addr_);
// Make sure that the client A still has a lease.
lease_a = LeaseMgrFactory::instance().getLease4(client_a.config_.lease_.addr_);
ASSERT_TRUE(lease_a);
// Make sure that the client B has a lease.
Lease4Ptr lease_b = LeaseMgrFactory::instance().getLease4(client_b.config_.lease_.addr_);
ASSERT_TRUE(lease_b);
// Client B should be able to renew its address.
client_b.setState(Dhcp4Client::RENEWING);
ASSERT_NO_THROW(client_b.doRequest());
ASSERT_TRUE(client_b.getContext().response_);
resp_b = client_b.getContext().response_;
ASSERT_EQ(DHCPACK, static_cast<int>(resp_b->getType()));
ASSERT_NE(client_b.config_.lease_.addr_, client_a.config_.lease_.addr_);
// Client A should also be able to renew its address.
client_a.setState(Dhcp4Client::RENEWING);
ASSERT_NO_THROW(client_a.doRequest());
ASSERT_TRUE(client_a.getContext().response_);
resp_b = client_a.getContext().response_;
ASSERT_EQ(DHCPACK, static_cast<int>(resp_b->getType()));
ASSERT_NE(client_a.config_.lease_.addr_, client_b.config_.lease_.addr_);
}
void
DORATest::oneAllocationOverlapTest(const std::string& hwaddr_a,
const std::string& clientid_a,
const std::string& hwaddr_b,
const std::string& clientid_b) {
// Allocate a lease by client A using the 4-way exchange.
Dhcp4Client client_a(Dhcp4Client::SELECTING);
client_a.includeClientId(clientid_a);
client_a.setHWAddress(hwaddr_a);
configure(DORA_CONFIGS[0], *client_a.getServer());
ASSERT_NO_THROW(client_a.doDORA());
// Make sure that the server responded.
ASSERT_TRUE(client_a.getContext().response_);
Pkt4Ptr resp_a = client_a.getContext().response_;
// Make sure that the server has responded with DHCPACK.
ASSERT_EQ(DHCPACK, static_cast<int>(resp_a->getType()));
Lease4Ptr lease_a = LeaseMgrFactory::instance().getLease4(client_a.config_.lease_.addr_);
ASSERT_TRUE(lease_a);
// Client B sends a DHCPDISCOVER.
Dhcp4Client client_b(client_a.getServer(), Dhcp4Client::SELECTING);
client_b.setHWAddress(hwaddr_b);
client_b.includeClientId(clientid_b);
// Client A and Client B have one common identifier (HW address
// or client identifier) and one of them is missing the other
// identifier. The allocation engine can't offer an address for
// the client which has the same identifier as the other client and
// which doesn't have any other (unique) identifier. It should
// discard the DHCPDISCOVER.
ASSERT_NO_THROW(client_b.doDiscover());
Pkt4Ptr resp_b = client_b.getContext().response_;
ASSERT_FALSE(resp_b);
// Now repeat the same test but send the DHCPREQUEST. This time the
// server should send the DHCPNAK.
client_b.config_.lease_.addr_ = IOAddress::IPV4_ZERO_ADDRESS();
client_b.setState(Dhcp4Client::INIT_REBOOT);
ASSERT_NO_THROW(client_b.doRequest());
resp_b = client_b.getContext().response_;
ASSERT_TRUE(resp_b);
ASSERT_EQ(DHCPNAK, static_cast<int>(resp_b->getType()));
// Client A should also be able to renew its address.
client_a.setState(Dhcp4Client::RENEWING);
ASSERT_NO_THROW(client_a.doRequest());
ASSERT_TRUE(client_a.getContext().response_);
resp_b = client_a.getContext().response_;
ASSERT_EQ(DHCPACK, static_cast<int>(resp_b->getType()));
ASSERT_NE(client_a.config_.lease_.addr_, client_b.config_.lease_.addr_);
}
// This test checks the server behavior in the following situation:
// - Client A identifies itself to the server using client identifier
// and the hardware address and requests allocation of the new lease.
// - Server allocates the lease to the client.
// - Client B has a different hardware address but is using the same
// client identifier as Client A.
// - Client B sends DHCPDISCOVER.
// - Server should determine that the client B is not client A, because
// it is using a different hadrware address, even though they use the
// same client identifier. As a consequence, the server should offer
// a different address to the client B.
// - The client B performs the 4-way exchange again and the server
// allocates a new address to the client, which should be different
// than the address used by the client A.
// - Client B is in the renewing state and it successfully renews its
// address.
// - Client A also renews its address successfully.
TEST_F(DORATest, twoAllocationsOverlap1) {
twoAllocationsOverlapTest("01:02:03:04:05:06", "12:34",
"02:02:03:03:04:04", "12:34");
}
// This test is similar to twoAllocationsOverlap1, but the
// clients differ by client identifier.
TEST_F(DORATest, twoAllocationsOverlap2) {
twoAllocationsOverlapTest("01:02:03:04:05:06", "12:34",
"01:02:03:04:05:06", "22:34");
}
// This test checks the server behavior in the following situation:
// - Client A identifies itself to the server using the hardware address
// and client identifier.
// - Client A performs the 4-way exchange and obtains a lease from the server.
// - Client B uses the same HW address as the client A, but it doesn't use
// the client identifier.
// - Client B sends the DHCPDISCOVER to the server.
// The server determines that there is a lease for the client A using the
// same HW address as the client B. Server discards the client's message and
// doesn't offer the lease for the client B to prevent allocation of the
// lease without a unique identifier.
// - The client sends the DHCPREQUEST and the server sends the DHCPNAK for the
// same reason.
// - Client A renews its address successfully.
TEST_F(DORATest, oneAllocationOverlap1) {
oneAllocationOverlapTest("01:02:03:04:05:06", "12:34",
"01:02:03:04:05:06", "");
}
// This test is similar to oneAllocationOverlap2 but this time the client A
// uses no client identifier, and the client B uses the HW address and the
// client identifier. The server behaves as previously.
TEST_F(DORATest, oneAllocationOverlap2) {
oneAllocationOverlapTest("01:02:03:04:05:06", "",
"01:02:03:04:05:06", "12:34");
}
// This is a simple test for the host reservation. It creates a reservation
// for an address for a single client, identified by the HW address. The
// test verifies that the client using this HW address will obtain a
......
......@@ -1235,6 +1235,71 @@ hasAddressReservation(const AllocEngine::ClientContext4& ctx) {
return (ctx.host_ && !ctx.host_->getIPv4Reservation().isV4Zero());
}
/// @brief Check if there is a lease for the client which message is processed.
///
/// This function searches the lease database to find existing lease for the client.
/// It finds the lease using the client's HW address first. If the lease exists and
/// appears to belong to the client the lease is returned. Otherwise, the function
/// will search for the lease using the client identifier (if supplied). If the
/// lease exists and appears to belong to the client, it is returned.
///
/// This function also identifies the conflicts between existing leases and the
/// lease to be allocated for the client, when the client is using a HW address
/// or client identifier which is already in use by the client having a lease in
/// the database. If the client uses an identifier which is already used by another
/// client and no other unique identifier which could be used to identify the client's
/// lease this function signals the conflict by returning 'true'.
///
/// @param ctx Client context.
/// @param [out] client_lease Client's lease found in the database.
///
/// @return true if there is a conflict of identifiers (HW address or client id)
/// between the client which message is being processed and the client which has
/// a lease in the database. When the value 'true' is returned, the caller should
/// cease the lease allocation for the client.
bool matchClientLease(const AllocEngine::ClientContext4& ctx, Lease4Ptr& client_lease) {
// Obtain the sole instance of the LeaseMgr.
LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
// The server should hand out existing lease to the client, so we have to check
// if there is one. First, try to use the client's HW address.
client_lease = lease_mgr.getLease4(*ctx.hwaddr_, ctx.subnet_->getID());
// If there is no lease for this HW address or the lease doesn't seem to be ours,
// we will have to use the client identifier. Note that in some situations two
// clients may use the same HW address so even if we find the lease for the HW
// address it doesn't mean it is ours, because client identifier may not match.
if (ctx.clientid_ && ((!client_lease) || (client_lease && !ctx.myLease(*client_lease)))) {
// Check if the lease is in conflict with the lease that we want to allocate.
// If the lease is in conflict because of using overlapping HW address or
// client identifier, we can't allocate the lease for this client.
if (client_lease && ctx.isInConflict(*client_lease)) {
return (true);
}
// There is no lease or the lease we found is not conflicting with the lease
// which we have found for the HW address, so there is still a chance that
// we will allocate the lease. Check if there is a lease using the client
// identifier.
client_lease = lease_mgr.getLease4(*ctx.clientid_, ctx.subnet_->getID());
}
// Check if the lease we have found belongs to us.
if (client_lease && !ctx.myLease(*client_lease)) {
// If the lease doesn't belong to us, check if we can add new lease for
// the client which message we're processing, or its identifiers are
// in conflict with this lease.
if (ctx.isInConflict(*client_lease)) {
return (true);
}
// If there is no conflict we can proceed and try to find the appropriate
// lease but we don't use the one we found, because it is assigned to
// someone else. Reset the pointer to indicate that we're not
// renewing this lease.
client_lease.reset();
}
return (false);
}
} // end of anonymous namespace
namespace isc {
......@@ -1286,6 +1351,14 @@ AllocEngine::ClientContext4::myLease(const Lease4& lease) const {
return (true);
}
bool
AllocEngine::ClientContext4::isInConflict(const Lease4& lease) const {
return ((!(hwaddr_ && lease.hwaddr_) && (clientid_ && lease.client_id_) &&
(*clientid_ == *lease.client_id_)) ||
(!(clientid_ && lease.client_id_) && (hwaddr_ && lease.hwaddr_) &&
(hwaddr_->hwaddr_ == lease.hwaddr_->hwaddr_)));
}
Lease4Ptr
AllocEngine::allocateLease4(ClientContext4& ctx) {
// The NULL pointer indicates that the old lease didn't exist. It may
......@@ -1340,15 +1413,13 @@ AllocEngine::findReservation(ClientContext4& ctx) {
Lease4Ptr
AllocEngine::discoverLease4(AllocEngine::ClientContext4& ctx) {
// Obtain the sole instance of the LeaseMgr.
LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
// Check if the client has any lease already. This information is needed
// to either return this lease to the client or to return it as an old
// (existing) lease if a different one is offered.
Lease4Ptr client_lease = lease_mgr.getLease4(*ctx.hwaddr_, ctx.subnet_->getID());
if (!client_lease && ctx.clientid_) {
client_lease = lease_mgr.getLease4(*ctx.clientid_, ctx.subnet_->getID());
// Find an existing lease for this client. This function will return true
// if there is a conflict with existing lease and the allocation should
// not be continued.
Lease4Ptr client_lease;
bool conflict = matchClientLease(ctx, client_lease);
if (conflict) {
return (Lease4Ptr());
}