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
|
// Copyright (C) 2012-2024 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 <iostream>
#include <sstream>
#include <arpa/inet.h>
#include <gtest/gtest.h>
#include <asiolink/io_address.h>
#include <dhcp/option.h>
#include <dhcp/dhcp4.h>
#include <boost/scoped_ptr.hpp>
#include "../localized_option.h"
#include "../perf_pkt4.h"
using namespace std;
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace isc::perfdhcp;
typedef PerfPkt4::LocalizedOptionPtr LocalizedOptionPtr;
namespace {
// A dummy MAC address, padded with 0s
const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0 };
// Let's use some creative test content here (128 chars + \0)
const uint8_t dummyFile[] = "Lorem ipsum dolor sit amet, consectetur "
"adipiscing elit. Proin mollis placerat metus, at "
"lacinia orci ornare vitae. Mauris amet.";
// Yet another type of test content (64 chars + \0)
const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
"adipiscing elit posuere.";
class PerfPkt4Test : public ::testing::Test {
public:
PerfPkt4Test() {
}
/// \brief Returns buffer with sample DHCPDISCOVER message.
///
/// This method creates buffer containing on-wire data of
/// DHCPDICOSVER message. This buffer is used by tests below
/// to create DHCPv4 test packets.
///
/// \return vector containing on-wire data
std::vector<uint8_t>& capture() {
// That is only part of the header. It contains all "short" fields,
// larger fields are constructed separately.
uint8_t hdr[] = {
1, 6, 6, 13, // op, htype, hlen, hops,
0x12, 0x34, 0x56, 0x78, // transaction-id
0, 42, 0x80, 0x00, // 42 secs, BROADCAST flags
192, 0, 2, 1, // ciaddr
1, 2, 3, 4, // yiaddr
192, 0, 2, 255, // siaddr
255, 255, 255, 255, // giaddr
};
// cppcheck-suppress variableScope
uint8_t v4Opts[] = {
DHO_HOST_NAME, 3, 0, 1, 2, // Host name option.
DHO_BOOT_SIZE, 3, 10, 11, 12, // Boot file size option
DHO_MERIT_DUMP, 3, 20, 21, 22, // Merit dump file
DHO_DHCP_MESSAGE_TYPE, 1, 1, // DHCP message type.
128, 3, 30, 31, 32,
254, 3, 40, 41, 42,
};
// Initialize the vector with the header fields defined above.
static std::vector<uint8_t> buf(hdr, hdr + sizeof(hdr));
// If this is a first call to this function. Initialize
// remaining data.
if (buf.size() == sizeof(hdr)) {
// Append the large header fields.
std::copy(dummyChaddr, dummyChaddr + Pkt4::MAX_CHADDR_LEN,
back_inserter(buf));
std::copy(dummySname, dummySname + Pkt4::MAX_SNAME_LEN,
back_inserter(buf));
std::copy(dummyFile, dummyFile + Pkt4::MAX_FILE_LEN,
back_inserter(buf));
// Append magic cookie.
buf.push_back(0x63);
buf.push_back(0x82);
buf.push_back(0x53);
buf.push_back(0x63);
// Append options.
std::copy(v4Opts, v4Opts + sizeof(v4Opts), back_inserter(buf));
}
return buf;
}
};
TEST_F(PerfPkt4Test, Constructor) {
// Initialize some dummy payload.
uint8_t data[250];
for (uint8_t i = 0; i < 250; ++i) {
data[i] = i;
}
// Test constructor to be used for incoming messages.
// Use default (1) offset value and don't specify transaction id.
const size_t offset_transid[] = { 1, 10 };
boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(data,
sizeof(data),
offset_transid[0]));
EXPECT_EQ(1, pkt1->getTransidOffset());
// Test constructor to be used for outgoing messages.
// Use non-zero offset and specify transaction id.
const uint32_t transid = 0x010203;
boost::scoped_ptr<PerfPkt4> pkt2(new PerfPkt4(data, sizeof(data),
offset_transid[1],
transid));
EXPECT_EQ(transid, pkt2->getTransid());
EXPECT_EQ(offset_transid[1], pkt2->getTransidOffset());
// Test default constructor. Transaction id offset is expected to be 1.
boost::scoped_ptr<PerfPkt4> pkt3(new PerfPkt4(data, sizeof(data)));
EXPECT_EQ(1, pkt3->getTransidOffset());
}
TEST_F(PerfPkt4Test, RawPack) {
// Create new packet.
std::vector<uint8_t> buf = capture();
boost::scoped_ptr<PerfPkt4> pkt(new PerfPkt4(&buf[0], buf.size()));
// Initialize options data.
uint8_t buf_hostname[] = { DHO_HOST_NAME, 3, 4, 5, 6 };
uint8_t buf_boot_filesize[] = { DHO_BOOT_SIZE, 3, 1, 2, 3 };
OptionBuffer vec_hostname(buf_hostname + 2,
buf_hostname + sizeof(buf_hostname));
OptionBuffer vec_boot_filesize(buf_boot_filesize + 2,
buf_boot_filesize + sizeof(buf_hostname));
// Create options objects.
const size_t offset_hostname = 240;
LocalizedOptionPtr pkt_hostname(new LocalizedOption(Option::V4,
DHO_HOST_NAME,
vec_hostname,
offset_hostname));
const size_t offset_boot_filesize = 245;
LocalizedOptionPtr pkt_boot_filesize(new LocalizedOption(Option::V4,
DHO_BOOT_SIZE,
vec_boot_filesize,
offset_boot_filesize));
// Try to add options to packet.
ASSERT_NO_THROW(pkt->addOption(pkt_boot_filesize));
ASSERT_NO_THROW(pkt->addOption(pkt_hostname));
// We have valid options addedwith valid offsets so
// pack operation should succeed.
ASSERT_TRUE(pkt->rawPack());
// Buffer should now contain new values of DHO_HOST_NAME and
// DHO_BOOT_SIZE options.
util::OutputBuffer pkt_output = pkt->getBuffer();
ASSERT_EQ(buf.size(), pkt_output.getLength());
const uint8_t* out_buf_data = pkt_output.getData();
// Check if options we read from buffer is valid.
EXPECT_EQ(0, memcmp(buf_hostname,
out_buf_data + offset_hostname,
sizeof(buf_hostname)));
EXPECT_EQ(0, memcmp(buf_boot_filesize,
out_buf_data + offset_boot_filesize,
sizeof(buf_boot_filesize)));
}
TEST_F(PerfPkt4Test, RawUnpack) {
// Create new packet.
std::vector<uint8_t> buf = capture();
boost::scoped_ptr<PerfPkt4> pkt(new PerfPkt4(&buf[0], buf.size()));
// Create options (existing in the packet) and specify their offsets.
const size_t offset_merit = 250;
LocalizedOptionPtr opt_merit(new LocalizedOption(Option::V4,
DHO_MERIT_DUMP,
OptionBuffer(),
offset_merit));
const size_t offset_msg_type = 255;
LocalizedOptionPtr opt_msg_type(new LocalizedOption(Option::V4,
DHO_DHCP_MESSAGE_TYPE,
OptionBuffer(),
offset_msg_type));
// Addition should be successful
ASSERT_NO_THROW(pkt->addOption(opt_merit));
ASSERT_NO_THROW(pkt->addOption(opt_msg_type));
// Option fit to packet boundaries and offsets are valid,
// so this should unpack successfully.
ASSERT_TRUE(pkt->rawUnpack());
// At this point we should have updated options data (read from buffer).
// Let's try to retrieve them.
opt_merit = boost::dynamic_pointer_cast<LocalizedOption>
(pkt->getOption(DHO_MERIT_DUMP));
opt_msg_type = boost::dynamic_pointer_cast<LocalizedOption>
(pkt->getOption(DHO_DHCP_MESSAGE_TYPE));
ASSERT_TRUE(opt_merit);
ASSERT_TRUE(opt_msg_type);
// Get first option payload.
OptionBuffer opt_merit_data = opt_merit->getData();
// Define reference data.
uint8_t buf_merit[] = { 20, 21, 22 };
// Validate first option data.
ASSERT_EQ(sizeof(buf_merit), opt_merit_data.size());
EXPECT_TRUE(std::equal(opt_merit_data.begin(),
opt_merit_data.end(),
buf_merit));
// Get second option payload.
OptionBuffer opt_msg_type_data = opt_msg_type->getData();
// Expect one byte of message type payload.
ASSERT_EQ(1, opt_msg_type_data.size());
EXPECT_EQ(1, opt_msg_type_data[0]);
}
TEST_F(PerfPkt4Test, InvalidOptions) {
// Create new packet.
std::vector<uint8_t> buf = capture();
boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(&buf[0], buf.size()));
// Create option with invalid offset.
// This option is at offset 250 (not 251).
const size_t offset_merit = 251;
LocalizedOptionPtr opt_merit(new LocalizedOption(Option::V4,
DHO_MERIT_DUMP,
OptionBuffer(),
offset_merit));
ASSERT_NO_THROW(pkt1->addOption(opt_merit));
cout << "Testing unpack of invalid options. "
<< "This may produce spurious errors." << endl;
// Unpack is expected to fail because it is supposed to read
// option type from buffer and match it with DHO_MERIT_DUMP.
// It will not match because option is shifted by on byte.
ASSERT_FALSE(pkt1->rawUnpack());
// Create another packet.
boost::scoped_ptr<PerfPkt4> pkt2(new PerfPkt4(&buf[0], buf.size()));
// Create DHO_DHCP_MESSAGE_TYPE option that has the wrong offset.
// With this offset, option goes beyond packet size (268).
const size_t offset_msg_type = 266;
LocalizedOptionPtr opt_msg_type(new LocalizedOption(Option::V4,
DHO_DHCP_MESSAGE_TYPE,
OptionBuffer(1, 2),
offset_msg_type));
// Adding option is expected to be successful because no
// offset validation takes place at this point.
ASSERT_NO_THROW(pkt2->addOption(opt_msg_type));
// This is expected to fail because option is out of bounds.
ASSERT_FALSE(pkt2->rawPack());
}
TEST_F(PerfPkt4Test, TruncatedPacket) {
// Get the whole packet and truncate it to 249 bytes.
std::vector<uint8_t> buf = capture();
buf.resize(249);
boost::scoped_ptr<PerfPkt4> pkt(new PerfPkt4(&buf[0], buf.size()));
// Option DHO_BOOT_SIZE is now truncated because whole packet
// is truncated. This option ends at 249 while last index of
// truncated packet is now 248.
const size_t offset_boot_filesize = 245;
LocalizedOptionPtr opt_boot_filesize(new LocalizedOption(Option::V4,
DHO_BOOT_SIZE,
OptionBuffer(3, 1),
offset_boot_filesize));
ASSERT_NO_THROW(pkt->addOption(opt_boot_filesize));
cout << "Testing pack and unpack of options in truncated "
<< "packet. This may produce spurious errors." << endl;
// Both pack and unpack are expected to fail because
// added option is out of bounds.
EXPECT_FALSE(pkt->rawUnpack());
EXPECT_FALSE(pkt->rawPack());
}
TEST_F(PerfPkt4Test, PackTransactionId) {
// Create dummy packet that consists of zeros.
std::vector<uint8_t> buf(268, 0);
const size_t offset_transid[] = { 10, 265 };
const uint32_t transid = 0x0102;
// Initialize transaction id 0x00000102 at offset 10.
boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(&buf[0], buf.size(),
offset_transid[0],
transid));
// Pack will inject transaction id at offset 10 into the
// packet buffer.
ASSERT_TRUE(pkt1->rawPack());
// Get packet's output buffer and make sure it has valid size.
util::OutputBuffer out_buf = pkt1->getBuffer();
ASSERT_EQ(buf.size(), out_buf.getLength());
const uint8_t *out_buf_data = out_buf.getData();
// Initialize reference data for transaction id.
const uint8_t ref_data[] = { 0, 0, 1, 2 };
// Expect that reference transaction id matches what we have
// read from buffer.
EXPECT_EQ(0, memcmp(ref_data, out_buf_data + offset_transid[0], 4));
cout << "Testing pack with invalid transaction id offset. "
<< "This may produce spurious errors" << endl;
// Create packet with invalid transaction id offset.
// Packet length is 268, transaction id is 4 bytes long so last byte of
// transaction id is out of bounds.
boost::scoped_ptr<PerfPkt4> pkt2(new PerfPkt4(&buf[0], buf.size(),
offset_transid[1],
transid));
EXPECT_FALSE(pkt2->rawPack());
}
TEST_F(PerfPkt4Test, UnpackTransactionId) {
// Initialize packet data, length 268, zeros only.
std::vector<uint8_t> in_data(268, 0);
// Assume that transaction id is at offset 100.
// Fill 4 bytes at offset 100 with dummy transaction id.
for (uint8_t i = 100; i < 104; ++i) {
in_data[i] = i - 99;
}
// Create packet from initialized buffer.
const size_t offset_transid[] = { 100, 270 };
boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(&in_data[0],
in_data.size(),
offset_transid[0]));
ASSERT_TRUE(pkt1->rawUnpack());
// Get unpacked transaction id and compare with reference.
EXPECT_EQ(0x01020304, pkt1->getTransid());
// Create packet with transaction id at invalid offset.
boost::scoped_ptr<PerfPkt4> pkt2(new PerfPkt4(&in_data[0],
in_data.size(),
offset_transid[1]));
cout << "Testing unpack of transaction id at invalid offset. "
<< "This may produce spurious errors." << endl;
// Unpack is supposed to fail because transaction id is at
// out of bounds offset.
EXPECT_FALSE(pkt2->rawUnpack());
}
TEST_F(PerfPkt4Test, Writes) {
// Initialize input buffer with 260 elements set to value 1.
dhcp::OptionBuffer in_data(260, 1);
// Initialize buffer to be used for write: 1,2,3,4,...,9
dhcp::OptionBuffer write_buf(10);
for (size_t i = 0; i < write_buf.size(); ++i) {
write_buf[i] = i;
}
// Create packet from the input buffer.
const size_t transid_offset = 4;
boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(&in_data[0],
in_data.size(),
transid_offset));
// Write numbers 4,5,6,7 to the packet's input buffer at position 10.
pkt1->writeAt(10, write_buf.begin() + 3, write_buf.begin() + 7);
// We have to pack data to output buffer here because Pkt4 provides no
// way to retrieve input buffer. If we pack data it will go to
// output buffer that has getter available.
ASSERT_TRUE(pkt1->rawPack());
const util::OutputBuffer& out_buf = pkt1->getBuffer();
ASSERT_EQ(in_data.size(), out_buf.getLength());
// Verify that 4,5,6,7 has been written to the packet's buffer.
const uint8_t* out_data = out_buf.getData();
EXPECT_TRUE(std::equal(write_buf.begin() + 3, write_buf.begin() + 7,
out_data + 10));
// Write 1 octet (0x51) at position 10.
pkt1->writeValueAt<uint8_t>(10, 0x51);
ASSERT_TRUE(pkt1->rawPack());
ASSERT_EQ(in_data.size(), pkt1->getBuffer().getLength());
EXPECT_EQ(0x51, pkt1->getBuffer()[10]);
// Write 2 octets (0x5251) at position 20.
pkt1->writeValueAt<uint16_t>(20, 0x5251);
ASSERT_TRUE(pkt1->rawPack());
ASSERT_EQ(in_data.size(), pkt1->getBuffer().getLength());
EXPECT_EQ(0x52, pkt1->getBuffer()[20]);
EXPECT_EQ(0x51, pkt1->getBuffer()[21]);
// Write 4 octets (0x54535251) at position 30.
pkt1->writeValueAt<uint32_t>(30, 0x54535251);
ASSERT_TRUE(pkt1->rawPack());
ASSERT_EQ(in_data.size(), pkt1->getBuffer().getLength());
EXPECT_EQ(0x54, pkt1->getBuffer()[30]);
EXPECT_EQ(0x53, pkt1->getBuffer()[31]);
EXPECT_EQ(0x52, pkt1->getBuffer()[32]);
EXPECT_EQ(0x51, pkt1->getBuffer()[33]);
}
}
|