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