1 // Copyright (C) 2015-2020 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7 #include <config.h>
8 #include <asiolink/io_address.h>
9 #include <cc/data.h>
10 #include <dhcp/dhcp4.h>
11 #include <dhcp/tests/iface_mgr_test_config.h>
12 #include <dhcpsrv/cfgmgr.h>
13 #include <dhcpsrv/subnet_id.h>
14 #include <dhcp4/tests/dhcp4_test_utils.h>
15 #include <dhcp4/tests/dhcp4_client.h>
16 #include <stats/stats_mgr.h>
17 #include <boost/shared_ptr.hpp>
18 #include <sstream>
19
20 using namespace isc;
21 using namespace isc::asiolink;
22 using namespace isc::data;
23 using namespace isc::dhcp;
24 using namespace isc::dhcp::test;
25 using namespace isc::stats;
26
27 namespace {
28
29 /// @brief Set of JSON configurations used throughout the Release tests.
30 ///
31 /// - Configuration 0:
32 /// - Used for testing Release message processing
33 /// - 1 subnet: 10.0.0.0/24
34 /// - 1 pool: 10.0.0.10-10.0.0.100
35 /// - Router option present: 10.0.0.200 and 10.0.0.201
36 const char* RELEASE_CONFIGS[] = {
37 // Configuration 0
38 "{ \"interfaces-config\": {"
39 " \"interfaces\": [ \"*\" ]"
40 "},"
41 "\"valid-lifetime\": 600,"
42 "\"subnet4\": [ { "
43 " \"subnet\": \"10.0.0.0/24\", "
44 " \"id\": 1,"
45 " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
46 " \"option-data\": [ {"
47 " \"name\": \"routers\","
48 " \"data\": \"10.0.0.200,10.0.0.201\""
49 " } ]"
50 " } ]"
51 "}"
52 };
53
54 /// @brief Test fixture class for testing 4-way (DORA) exchanges.
55 ///
56 /// @todo Currently there is a limit number of test cases covered here.
57 /// In the future it is planned that the tests from the
58 /// dhcp4_srv_unittest.cc will be migrated here and will use the
59 /// @c Dhcp4Client class.
60 class ReleaseTest : public Dhcpv4SrvTest {
61 public:
62
63 enum ExpectedResult {
64 SHOULD_PASS,
65 SHOULD_FAIL
66 };
67
68 /// @brief Constructor.
69 ///
70 /// Sets up fake interfaces.
ReleaseTest()71 ReleaseTest()
72 : Dhcpv4SrvTest(),
73 iface_mgr_test_config_(true) {
74 }
75
76 /// @brief Performs 4-way exchange to obtain new lease.
77 ///
78 /// @param client Client to be used to obtain a lease.
79 void acquireLease(Dhcp4Client& client);
80
81 /// @brief Tests if the acquired lease is or is not released.
82 ///
83 /// @param hw_address_1 HW Address to be used to acquire the lease.
84 /// @param client_id_1 Client id to be used to acquire the lease.
85 /// @param hw_address_2 HW Address to be used to release the lease.
86 /// @param client_id_2 Client id to be used to release the lease.
87 /// @param expected_result SHOULD_PASS if the lease is expected to
88 /// be successfully released, or SHOULD_FAIL if the lease is expected
89 /// to not be released.
90 void acquireAndRelease(const std::string& hw_address_1,
91 const std::string& client_id_1,
92 const std::string& hw_address_2,
93 const std::string& client_id_2,
94 ExpectedResult expected_result);
95
96 /// @brief Interface Manager's fake configuration control.
97 IfaceMgrTestConfig iface_mgr_test_config_;
98
99 };
100
101 void
acquireLease(Dhcp4Client & client)102 ReleaseTest::acquireLease(Dhcp4Client& client) {
103 // Perform 4-way exchange with the server but to not request any
104 // specific address in the DHCPDISCOVER message.
105 ASSERT_NO_THROW(client.doDORA());
106 // Make sure that the server responded.
107 ASSERT_TRUE(client.getContext().response_);
108 Pkt4Ptr resp = client.getContext().response_;
109 // Make sure that the server has responded with DHCPACK.
110 ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
111 // Response must not be relayed.
112 EXPECT_FALSE(resp->isRelayed());
113 // Make sure that the server id is present.
114 EXPECT_EQ("10.0.0.1", client.config_.serverid_.toText());
115 // Make sure that the client has got the lease with the requested address.
116 ASSERT_NE(client.config_.lease_.addr_.toText(), "0.0.0.0");
117 Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(client.config_.lease_.addr_);
118 ASSERT_TRUE(lease);
119 }
120
121 void
acquireAndRelease(const std::string & hw_address_1,const std::string & client_id_1,const std::string & hw_address_2,const std::string & client_id_2,ExpectedResult expected_result)122 ReleaseTest::acquireAndRelease(const std::string& hw_address_1,
123 const std::string& client_id_1,
124 const std::string& hw_address_2,
125 const std::string& client_id_2,
126 ExpectedResult expected_result) {
127 CfgMgr::instance().clear();
128 Dhcp4Client client(Dhcp4Client::SELECTING);
129 // Configure DHCP server.
130 configure(RELEASE_CONFIGS[0], *client.getServer());
131 // Explicitly set the client id.
132 client.includeClientId(client_id_1);
133 // Explicitly set the HW address.
134 client.setHWAddress(hw_address_1);
135 // Perform 4-way exchange to obtain a new lease.
136 acquireLease(client);
137
138 std::stringstream name;
139
140 // Let's get the subnet-id and generate statistics name out of it
141 const Subnet4Collection* subnets =
142 CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
143 ASSERT_EQ(1, subnets->size());
144 name << "subnet[" << (*subnets->begin())->getID() << "].assigned-addresses";
145
146 ObservationPtr assigned_cnt = StatsMgr::instance().getObservation(name.str());
147 ASSERT_TRUE(assigned_cnt);
148 uint64_t before = assigned_cnt->getInteger().first;
149
150 // Remember the acquired address.
151 IOAddress leased_address = client.config_.lease_.addr_;
152
153 // Explicitly set the client id for DHCPRELEASE.
154 client.includeClientId(client_id_2);
155 // Explicitly set the HW address for DHCPRELEASE.
156 client.setHWAddress(hw_address_2);
157
158 // Send the release and make sure that the lease is removed from the
159 // server.
160 ASSERT_NO_THROW(client.doRelease());
161 Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(leased_address);
162
163 assigned_cnt = StatsMgr::instance().getObservation(name.str());
164 ASSERT_TRUE(assigned_cnt);
165 uint64_t after = assigned_cnt->getInteger().first;
166
167 // We check if the release process was successful by checking if the
168 // lease is in the database. It is expected that it is not present,
169 // i.e. has been deleted with the release.
170 if (expected_result == SHOULD_PASS) {
171 EXPECT_FALSE(lease);
172
173 // The removal succeeded, so the assigned-addresses statistic should
174 // be decreased by one
175 EXPECT_EQ(before, after + 1);
176 } else {
177 EXPECT_TRUE(lease);
178
179 // The removal failed, so the assigned-address should be the same
180 // as before
181 EXPECT_EQ(before, after);
182 }
183 }
184
185 // This test checks that the client can acquire and release the lease.
TEST_F(ReleaseTest,releaseNoIdentifierChange)186 TEST_F(ReleaseTest, releaseNoIdentifierChange) {
187 acquireAndRelease("01:02:03:04:05:06", "12:14",
188 "01:02:03:04:05:06", "12:14",
189 SHOULD_PASS);
190 }
191
192 // This test verifies the release correctness in the following case:
193 // - Client acquires new lease using HW address only
194 // - Client sends the DHCPRELEASE with valid HW address and without
195 // client identifier.
196 // - The server successfully releases the lease.
TEST_F(ReleaseTest,releaseHWAddressOnly)197 TEST_F(ReleaseTest, releaseHWAddressOnly) {
198 acquireAndRelease("01:02:03:04:05:06", "",
199 "01:02:03:04:05:06", "",
200 SHOULD_PASS);
201 }
202
203 // This test verifies the release correctness in the following case:
204 // - Client acquires new lease using the client identifier and HW address
205 // - Client sends the DHCPRELEASE with valid HW address but with no
206 // client identifier.
207 // - The server successfully releases the lease.
TEST_F(ReleaseTest,releaseNoClientId)208 TEST_F(ReleaseTest, releaseNoClientId) {
209 acquireAndRelease("01:02:03:04:05:06", "12:14",
210 "01:02:03:04:05:06", "",
211 SHOULD_PASS);
212 }
213
214 // This test verifies the release correctness in the following case:
215 // - Client acquires new lease using HW address
216 // - Client sends the DHCPRELEASE with valid HW address and some
217 // client identifier.
218 // - The server identifies the lease using HW address and releases
219 // this lease.
TEST_F(ReleaseTest,releaseNoClientId2)220 TEST_F(ReleaseTest, releaseNoClientId2) {
221 acquireAndRelease("01:02:03:04:05:06", "",
222 "01:02:03:04:05:06", "12:14",
223 SHOULD_PASS);
224 }
225
226 // This test checks the server's behavior in the following case:
227 // - Client acquires new lease using the client identifier and HW address
228 // - Client sends the DHCPRELEASE with the valid HW address but with invalid
229 // client identifier.
230 // - The server should not remove the lease.
TEST_F(ReleaseTest,releaseNonMatchingClientId)231 TEST_F(ReleaseTest, releaseNonMatchingClientId) {
232 acquireAndRelease("01:02:03:04:05:06", "12:14",
233 "01:02:03:04:05:06", "12:15:16",
234 SHOULD_FAIL);
235 }
236
237 // This test checks the server's behavior in the following case:
238 // - Client acquires new lease using client identifier and HW address
239 // - Client sends the DHCPRELEASE with the same client identifier but
240 // different HW address.
241 // - The server uses client identifier to find the client's lease and
242 // releases it.
TEST_F(ReleaseTest,releaseNonMatchingHWAddress)243 TEST_F(ReleaseTest, releaseNonMatchingHWAddress) {
244 acquireAndRelease("01:02:03:04:05:06", "12:14",
245 "06:06:06:06:06:06", "12:14",
246 SHOULD_PASS);
247 }
248
249 // This test checks the server's behavior in the following case:
250 // - Client acquires new lease.
251 // - Client sends DHCPRELEASE with the ciaddr set to a different
252 // address than it has acquired from the server.
253 // - Server determines that the client is trying to release a
254 // wrong address and will refuse to release.
TEST_F(ReleaseTest,releaseNonMatchingIPAddress)255 TEST_F(ReleaseTest, releaseNonMatchingIPAddress) {
256 Dhcp4Client client(Dhcp4Client::SELECTING);
257 // Configure DHCP server.
258 configure(RELEASE_CONFIGS[0], *client.getServer());
259 // Perform 4-way exchange to obtain a new lease.
260 acquireLease(client);
261
262 // Remember the acquired address.
263 IOAddress leased_address = client.config_.lease_.addr_;
264
265 // Modify the client's address to force it to release a different address
266 // than it has obtained from the server.
267 client.config_.lease_.addr_ = IOAddress(leased_address.toUint32() + 1);
268
269 // Send DHCPRELEASE and make sure it was unsuccessful, i.e. the lease
270 // remains in the database.
271 ASSERT_NO_THROW(client.doRelease());
272 Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(leased_address);
273 ASSERT_TRUE(lease);
274 }
275
276 // This test verifies that an incoming RELEASE for an address within
277 // a subnet that has been removed can still be released.
TEST_F(ReleaseTest,releaseNoSubnet)278 TEST_F(ReleaseTest, releaseNoSubnet) {
279 Dhcp4Client client(Dhcp4Client::SELECTING);
280 // Configure DHCP server.
281 configure(RELEASE_CONFIGS[0], *client.getServer());
282 // Perform 4-way exchange to obtain a new lease.
283 acquireLease(client);
284
285 // Remember the acquired address.
286 IOAddress leased_address = client.config_.lease_.addr_;
287
288 // Release is as it was relayed
289 client.useRelay(true);
290
291 // Send the release
292 ASSERT_NO_THROW(client.doRelease());
293
294 // Check that the lease was removed
295 Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(leased_address);
296 EXPECT_FALSE(lease);
297 }
298
299 } // end of anonymous namespace
300