1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
|
// Copyright (C) 2014-2023 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/pkt4.h>
#include <dhcp/classify.h>
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/cfg_subnets4.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/subnet.h>
#include <dhcp4/json_config_parser.h>
#include <dhcp4/tests/dhcp4_client.h>
#include <dhcp4/tests/dhcp4_test_utils.h>
#include <gtest/gtest.h>
#include <string>
using namespace isc;
using namespace isc::asiolink;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::dhcp::test;
namespace {
/// @brief Test fixture class for testing message processing from directly
/// connected clients.
///
/// This class provides mechanisms for testing processing of DHCPv4 messages
/// from directly connected clients.
class DirectClientTest : public Dhcpv4SrvTest {
public:
/// @brief Constructor.
///
/// Initializes DHCPv4 server object used by various tests.
DirectClientTest();
/// @brief Configures the server with one subnet.
///
/// This creates new configuration for the DHCPv4 with one subnet having
/// a specified prefix.
///
/// The subnet parameters (such as options, timers etc.) are arbitrarily
/// selected. The subnet and pool mask is always /24. The real configuration
/// would exclude .0 (network address) and .255 (broadcast address), but we
/// ignore that fact for the sake of test simplicity.
///
/// @param prefix Prefix for a subnet.
void configureSubnet(const std::string& prefix);
/// @brief Configures the server with two subnets.
///
/// This function configures DHCPv4 server with two different subnets.
/// The subnet parameters (such as options, timers etc.) are arbitrarily
/// selected. The subnet and pool mask is /24. The real configuration
/// would exclude .0 (network address) and .255 (broadcast address), but we
/// ignore that fact for the sake of test simplicity.
///
/// @param prefix1 Prefix of the first subnet to be configured.
/// @param prefix2 Prefix of the second subnet to be configured.
void configureTwoSubnets(const std::string& prefix1,
const std::string& prefix2);
/// @brief Creates simple message from a client.
///
/// This function creates a DHCPv4 message having a specified type
/// (e.g. Discover, Request) and sets some properties of this
/// message: client identifier, address and interface. The copy of
/// this message is then created by parsing wire data of the original
/// message. This simulates the case when the message is received and
/// parsed by the server.
///
/// @param msg_type Type of the message to be created.
/// @param iface Name of the interface on which the message has been
/// "received" by the server.
/// @param ifindex Index of the interface on which the message has been
/// "received" by the server.
///
/// @return Generated message.
Pkt4Ptr createClientMessage(const uint16_t msg_type,
const std::string& iface,
const unsigned int ifindex);
/// @brief Creates simple message from a client.
///
/// This function configures a client's message by adding client identifier,
/// setting interface and addresses. The copy of this message is then
/// created by parsing wire data of the original message. This simulates the
/// case when the message is received and parsed by the server.
///
/// @param msg Caller supplied message to be configured. This object must
/// not be NULL.
/// @param iface Name of the interface on which the message has been
/// "received" by the server.
/// @param ifindex Index of the interface on which the message has been
/// "received" by the server.
///
/// @return Configured and parsed message.
Pkt4Ptr createClientMessage(const Pkt4Ptr &msg,
const std::string& iface,
const unsigned int ifindex);
/// @brief This test checks that the message from directly connected client
/// is processed and that client is offered IPv4 address from the subnet
/// which is suitable for the local interface on which the client's message
/// is received. This test uses two subnets, with two active interfaces
/// which IP addresses belong to these subnets. The address offered to the
/// client which message has been sent over eth0 should belong to a
/// different subnet than the address offered for the client sending its
/// message via eth1.
void twoSubnets();
/// @brief This test checks that server selects a subnet when receives a
/// message through an interface for which the subnet has been configured.
/// This interface has IPv4 address assigned which belongs to this subnet.
/// This test also verifies that when the message is received through the
/// interface for which there is no suitable subnet, the message is
/// discarded.
void oneSubnet();
/// @brief This test verifies that the server uses ciaddr to select a subnet
/// for a client which renews its lease.
void renew();
/// This test verifies that when a client in the Rebinding state broadcasts
/// a Request message through an interface for which a subnet is configured,
/// the server responds to this Request. It also verifies that when such a
/// Request is sent through the interface for which there is no subnet
/// configured the client's message is discarded.
void rebind();
/// @brief classes the client belongs to
///
/// This is empty in most cases, but it is needed as a parameter for all
/// getSubnet4() calls.
ClientClasses classify_;
};
DirectClientTest::DirectClientTest() : Dhcpv4SrvTest() {
}
void
DirectClientTest::configureSubnet(const std::string& prefix) {
std::ostringstream config;
config << "{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"option-data\": [ ],"
"\"subnet4\": [ { "
" \"id\": 1, "
" \"pools\": [ { \"pool\": \"" << prefix << "/24\" } ],"
" \"subnet\": \"" << prefix << "/24\", "
" \"rebind-timer\": 2000, "
" \"renew-timer\": 1000, "
" \"valid-lifetime\": 4000"
"} ],"
"\"valid-lifetime\": 4000 }";
configure(config.str());
}
void
DirectClientTest::configureTwoSubnets(const std::string& prefix1,
const std::string& prefix2) {
std::ostringstream config;
config << "{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"option-data\": [ ],"
"\"subnet4\": [ { "
" \"id\": 1, "
" \"pools\": [ { \"pool\": \"" << prefix1 << "/24\" } ],"
" \"subnet\": \"" << prefix1 << "/24\", "
" \"rebind-timer\": 2000, "
" \"renew-timer\": 1000, "
" \"valid-lifetime\": 4000"
" },"
"{ "
" \"id\": 2, "
" \"pools\": [ { \"pool\": \"" << prefix2 << "/24\" } ],"
" \"subnet\": \"" << prefix2 << "/24\", "
" \"rebind-timer\": 2000, "
" \"renew-timer\": 1000, "
" \"valid-lifetime\": 4000"
"} ],"
"\"valid-lifetime\": 4000 }";
configure(config.str());
}
Pkt4Ptr
DirectClientTest::createClientMessage(const uint16_t msg_type,
const std::string& iface,
const unsigned int ifindex) {
// Create a source packet.
Pkt4Ptr msg = Pkt4Ptr(new Pkt4(msg_type, 1234));
return (createClientMessage(msg, iface, ifindex));
}
Pkt4Ptr
DirectClientTest::createClientMessage(const Pkt4Ptr& msg,
const std::string& iface,
const unsigned int ifindex) {
msg->setRemoteAddr(IOAddress("255.255.255.255"));
msg->addOption(generateClientId());
msg->setIface(iface);
msg->setIndex(ifindex);
// Create copy of this packet by parsing its wire data. Make sure that the
// local and remote address are set like it was a message sent from the
// directly connected client.
Pkt4Ptr received;
createPacketFromBuffer(msg, received);
received->setIface(iface);
received->setIndex(ifindex);
received->setLocalAddr(IOAddress("255.255.255.255"));
received->setRemoteAddr(IOAddress("0.0.0.0"));
return (received);
}
void
DirectClientTest::twoSubnets() {
// Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
IfaceMgrTestConfig iface_config(true);
// After creating interfaces we have to open sockets as it is required
// by the message processing code.
ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
// Add two subnets: address on eth0 belongs to the second subnet,
// address on eth1 belongs to the first subnet.
ASSERT_NO_FATAL_FAILURE(configureTwoSubnets("192.0.2.0", "10.0.0.0"));
// Create Discover and simulate reception of this message through eth0.
Pkt4Ptr dis = createClientMessage(DHCPDISCOVER, "eth0", ETH0_INDEX);
srv_.fakeReceive(dis);
// Create Request and simulate reception of this message through eth1.
Pkt4Ptr req = createClientMessage(DHCPREQUEST, "eth1", ETH1_INDEX);
srv_.fakeReceive(req);
// Process clients' messages.
srv_.run();
// Check that the server did send responses.
ASSERT_EQ(2, srv_.fake_sent_.size());
// In multi-threading responses can be received out of order.
Pkt4Ptr offer;
Pkt4Ptr ack;
while (srv_.fake_sent_.size()) {
// Make sure that we received a response.
Pkt4Ptr response = srv_.fake_sent_.front();
ASSERT_TRUE(response);
srv_.fake_sent_.pop_front();
if (response->getType() == DHCPOFFER) {
offer = response;
} else if (response->getType() == DHCPACK) {
ack = response;
}
}
// Client should get an Offer (not a NAK).
ASSERT_TRUE(offer);
// Client should get an Ack (not a NAK).
ASSERT_TRUE(ack);
// Check that the offered address belongs to the suitable subnet.
Subnet4Ptr subnet = CfgMgr::instance().getCurrentCfg()->
getCfgSubnets4()->selectSubnet(offer->getYiaddr());
ASSERT_TRUE(subnet);
EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
// Check that the offered address belongs to the suitable subnet.
subnet = CfgMgr::instance().getCurrentCfg()->
getCfgSubnets4()->selectSubnet(ack->getYiaddr());
ASSERT_TRUE(subnet);
EXPECT_EQ("192.0.2.0", subnet->get().first.toText());
}
TEST_F(DirectClientTest, twoSubnets) {
Dhcpv4SrvMTTestGuard guard(*this, false);
twoSubnets();
}
TEST_F(DirectClientTest, twoSubnetsMultiThreading) {
Dhcpv4SrvMTTestGuard guard(*this, true);
twoSubnets();
}
void
DirectClientTest::oneSubnet() {
// Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
IfaceMgrTestConfig iface_config(true);
// After creating interfaces we have to open sockets as it is required
// by the message processing code.
ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
// Add a subnet which will be selected when a message from directly
// connected client is received through interface eth0.
ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
// Create Discover and simulate reception of this message through eth0.
Pkt4Ptr dis = createClientMessage(DHCPDISCOVER, "eth0", ETH0_INDEX);
srv_.fakeReceive(dis);
// Create Request and simulate reception of this message through eth1.
Pkt4Ptr req = createClientMessage(DHCPDISCOVER, "eth1", ETH1_INDEX);
srv_.fakeReceive(req);
// Process clients' messages.
srv_.run();
// Check that the server sent one response for the message received
// through eth0. The other client's message should be discarded.
ASSERT_EQ(1, srv_.fake_sent_.size());
// Check the response. The first Discover was sent via eth0 for which
// the subnet has been configured.
Pkt4Ptr response = srv_.fake_sent_.front();
ASSERT_TRUE(response);
srv_.fake_sent_.pop_front();
// Since Discover has been received through the interface for which
// the subnet has been configured, the server should respond with
// an Offer message.
ASSERT_EQ(DHCPOFFER, response->getType());
// Check that the offered address belongs to the suitable subnet.
Subnet4Ptr subnet = CfgMgr::instance().getCurrentCfg()->
getCfgSubnets4()->selectSubnet(response->getYiaddr());
ASSERT_TRUE(subnet);
EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
}
TEST_F(DirectClientTest, oneSubnet) {
Dhcpv4SrvMTTestGuard guard(*this, false);
oneSubnet();
}
TEST_F(DirectClientTest, oneSubnetMultiThreading) {
Dhcpv4SrvMTTestGuard guard(*this, true);
oneSubnet();
}
void
DirectClientTest::renew() {
// Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
IfaceMgrTestConfig iface_config(true);
// After creating interfaces we have to open sockets as it is required
// by the message processing code.
ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
// Add a subnet.
ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
// Create the DHCPv4 client.
Dhcp4Client client;
client.useRelay(false);
// Obtain the lease using the 4-way exchange.
ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("10.0.0.10"))));
ASSERT_EQ("10.0.0.10", client.config_.lease_.addr_.toText());
// Put the client into the renewing state.
client.setState(Dhcp4Client::RENEWING);
// Renew, and make sure we have obtained the same address.
ASSERT_NO_THROW(client.doRequest());
ASSERT_TRUE(client.getContext().response_);
EXPECT_EQ(DHCPACK, static_cast<int>(client.getContext().response_->getType()));
EXPECT_EQ("10.0.0.10", client.config_.lease_.addr_.toText());
}
TEST_F(DirectClientTest, renew) {
Dhcpv4SrvMTTestGuard guard(*this, false);
renew();
}
TEST_F(DirectClientTest, renewMultiThreading) {
Dhcpv4SrvMTTestGuard guard(*this, true);
renew();
}
void
DirectClientTest::rebind() {
// Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
IfaceMgrTestConfig iface_config(true);
// After creating interfaces we have to open sockets as it is required
// by the message processing code.
ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
// Add a subnet.
ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
// Create the DHCPv4 client.
Dhcp4Client client;
client.useRelay(false);
// Obtain the lease using the 4-way exchange.
ASSERT_NO_THROW(client.doDORA(boost::shared_ptr<IOAddress>(new IOAddress("10.0.0.10"))));
ASSERT_EQ("10.0.0.10", client.config_.lease_.addr_.toText());
// Put the client into the rebinding state.
client.setState(Dhcp4Client::REBINDING);
// Broadcast Request through an interface for which there is no subnet
// configured. This message should be discarded by the server.
client.setIfaceName("eth1");
client.setIfaceIndex(ETH1_INDEX);
ASSERT_NO_THROW(client.doRequest());
EXPECT_FALSE(client.getContext().response_);
// Send Rebind over the correct interface, and make sure we have obtained
// the same address.
client.setIfaceName("eth0");
client.setIfaceIndex(ETH0_INDEX);
ASSERT_NO_THROW(client.doRequest());
ASSERT_TRUE(client.getContext().response_);
EXPECT_EQ(DHCPACK, static_cast<int>(client.getContext().response_->getType()));
EXPECT_EQ("10.0.0.10", client.config_.lease_.addr_.toText());
}
TEST_F(DirectClientTest, rebind) {
Dhcpv4SrvMTTestGuard guard(*this, false);
rebind();
}
TEST_F(DirectClientTest, rebindMultiThreading) {
Dhcpv4SrvMTTestGuard guard(*this, true);
rebind();
}
}
|