pkt6_unittest.cc 48.5 KB
Newer Older
1
// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
2
3
4
5
6
7
8
9
10
11
12
13
14
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

15
#include <config.h>
16

17
#include <asiolink/io_address.h>
18
#include <dhcp/dhcp6.h>
19
#include <dhcp/option.h>
20
21
22
23
#include <dhcp/option_custom.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
24
#include <dhcp/option_vendor.h>
25
#include <dhcp/iface_mgr.h>
26
#include <dhcp/pkt6.h>
27
#include <dhcp/hwaddr.h>
Tomek Mrugalski's avatar
Tomek Mrugalski committed
28
#include <dhcp/docsis3_option_defs.h>
29
#include <dhcp/tests/pkt_captures.h>
30
#include <util/range_utilities.h>
31

32
#include <boost/bind.hpp>
33
#include <boost/date_time/posix_time/posix_time.hpp>
34
35
#include <boost/scoped_ptr.hpp>
#include <util/encode/hex.h>
36
37
38
39
40
41
#include <gtest/gtest.h>

#include <iostream>
#include <sstream>

#include <arpa/inet.h>
42
43
44

using namespace std;
using namespace isc;
45
using namespace isc::asiolink;
46
using namespace isc::dhcp;
47
using boost::scoped_ptr;
48

49
namespace {
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

/// @brief A class which contains a custom callback function to unpack options.
///
/// This is a class used by the tests which verify that the custom callback
/// functions can be installed to unpack options from a message. When the
/// callback function is called, the executed_ member is set to true to allow
/// verification that the callback was really called. Internally, this class
/// uses libdhcp++ to unpack options so the options parsing algorithm remains
/// unchanged after installation of the callback.
class CustomUnpackCallback {
public:

    /// @brief Constructor
    ///
    /// Marks that callback hasn't been called.
    CustomUnpackCallback()
        : executed_(false) {
    }

    /// @brief A callback
    ///
    /// Contains custom implementation of the callback.
    ///
    /// @param buf a A buffer holding options in on-wire format.
74
75
    /// @param option_space A name of the option space encapsulated by the
    /// option being parsed.
76
77
78
79
80
81
82
83
    /// @param [out] options A reference to the collection where parsed options
    /// will be stored.
    /// @param relay_msg_offset Reference to a size_t structure. If specified,
    /// offset to beginning of relay_msg option will be stored in it.
    /// @param relay_msg_len reference to a size_t structure. If specified,
    /// length of the relay_msg option will be stored in it.
    /// @return An offset to the first byte after last parsed option.
    size_t execute(const OptionBuffer& buf,
84
                   const std::string& option_space,
85
86
87
88
89
90
91
                   isc::dhcp::OptionCollection& options,
                   size_t* relay_msg_offset,
                   size_t* relay_msg_len) {
        // Set the executed_ member to true to allow verification that the
        // callback has been actually called.
        executed_ = true;
        // Use default implementation of the unpack algorithm to parse options.
Francis Dupont's avatar
Francis Dupont committed
92
93
        return (LibDHCP::unpackOptions6(buf, option_space, options,
                                        relay_msg_offset, relay_msg_len));
94
95
96
97
98
99
    }

    /// A flag which indicates if callback function has been called.
    bool executed_;
};

100
101
102
103
class Pkt6Test : public ::testing::Test {
public:
    Pkt6Test() {
    }
104

Francis Dupont's avatar
Francis Dupont committed
105
106
    /// @brief generates an option with given code (and length) and
    /// random content
107
108
109
110
111
112
113
114
115
116
    ///
    /// @param code option code
    /// @param len data length (data will be randomized)
    ///
    /// @return pointer to the new option
    OptionPtr generateRandomOption(uint16_t code, size_t len = 10) {
        OptionBuffer data(len);
        util::fillRandom(data.begin(), data.end());
        return OptionPtr(new Option(Option::V6, code, data));
    }
117
118

    /// @brief Create a wire representation of the test packet and clone it.
119
120
121
122
123
124
125
126
127
128
129
130
131
    ///
    /// The purpose of this function is to create a packet to be used to
    /// check that packet parsing works correctly. The unpack() function
    /// requires that the data_ field of the object holds the data to be
    /// parsed. This function creates an on-wire representation of the
    /// packet by calling pack(). But, the pack() function stores the
    /// on-wire representation into the output buffer (not the data_ field).
    /// For this reason, it is not enough to return the packet on which
    /// pack() is called. This function returns a clone of this packet
    /// which is created using a constructor taking a buffer and buffer
    /// length as an input. This constructor is normally used to parse
    /// received packets. It stores the packet in a data_ field and
    /// therefore unpack() can be called to parse it.
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
    Pkt6Ptr packAndClone() {
        Pkt6Ptr parent(new Pkt6(DHCPV6_SOLICIT, 0x020304));

        OptionPtr opt1(new Option(Option::V6, 1));
        OptionPtr opt2(new Option(Option::V6, 2));
        OptionPtr opt3(new Option(Option::V6, 100));
        // Let's not use zero-length option type 3 as it is IA_NA

        parent->addOption(opt1);
        parent->addOption(opt2);
        parent->addOption(opt3);

        EXPECT_EQ(DHCPV6_SOLICIT, parent->getType());

        // Calculated length should be 16
        EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
                  parent->len());

        EXPECT_NO_THROW(parent->pack());

        EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
                  parent->len());

        // Create second packet,based on assembled data from the first one
        Pkt6Ptr clone(new Pkt6(static_cast<const uint8_t*>
                               (parent->getBuffer().getData()),
                               parent->getBuffer().getLength()));
        return (clone);

    }
162
163
164
};

TEST_F(Pkt6Test, constructor) {
165
    uint8_t data[] = { 0, 1, 2, 3, 4, 5 };
166
    scoped_ptr<Pkt6> pkt1(new Pkt6(data, sizeof(data)));
167

168
169
    EXPECT_EQ(6, pkt1->data_.size());
    EXPECT_EQ(0, memcmp( &pkt1->data_[0], data, sizeof(data)));
170
}
171

172
173
174
175
176
177
178
179
/// @brief returns captured actual SOLICIT packet
///
/// Captured SOLICIT packet with transid=0x3d79fb and options: client-id,
/// in_na, dns-server, elapsed-time, option-request
/// This code was autogenerated (see src/bin/dhcp6/tests/iface_mgr_unittest.c),
/// but we spent some time to make is less ugly than it used to be.
///
/// @return pointer to Pkt6 that represents received SOLICIT
180
Pkt6* capture1() {
181
    Pkt6* pkt;
182
    uint8_t data[98];
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
    data[0]  = 1;
    data[1]  = 1;       data[2]  = 2;     data[3] = 3;      data[4]  = 0;
    data[5]  = 1;       data[6]  = 0;     data[7] = 14;     data[8]  = 0;
    data[9]  = 1;       data[10] = 0;     data[11] = 1;     data[12] = 21;
    data[13] = 158;     data[14] = 60;    data[15] = 22;    data[16] = 0;
    data[17] = 30;      data[18] = 140;   data[19] = 155;   data[20] = 115;
    data[21] = 73;      data[22] = 0;     data[23] = 3;     data[24] = 0;
    data[25] = 40;      data[26] = 0;     data[27] = 0;     data[28] = 0;
    data[29] = 1;       data[30] = 255;   data[31] = 255;   data[32] = 255;
    data[33] = 255;     data[34] = 255;   data[35] = 255;   data[36] = 255;
    data[37] = 255;     data[38] = 0;     data[39] = 5;     data[40] = 0;
    data[41] = 24;      data[42] = 32;    data[43] = 1;     data[44] = 13;
    data[45] = 184;     data[46] = 0;     data[47] = 1;     data[48] = 0;
    data[49] = 0;       data[50] = 0;     data[51] = 0;     data[52] = 0;
    data[53] = 0;       data[54] = 0;     data[55] = 0;     data[56] = 18;
    data[57] = 52;      data[58] = 255;   data[59] = 255;   data[60] = 255;
    data[61] = 255;     data[62] = 255;   data[63] = 255;   data[64] = 255;
    data[65] = 255;     data[66] = 0;     data[67] = 23;    data[68] = 0;
    data[69] = 16;      data[70] = 32;    data[71] = 1;     data[72] = 13;
    data[73] = 184;     data[74] = 0;     data[75] = 1;     data[76] = 0;
    data[77] = 0;       data[78] = 0;     data[79] = 0;     data[80] = 0;
    data[81] = 0;       data[82] = 0;     data[83] = 0;     data[84] = 221;
    data[85] = 221;     data[86] = 0;     data[87] = 8;     data[88] = 0;
    data[89] = 2;       data[90] = 0;     data[91] = 100;   data[92] = 0;
    data[93] = 6;       data[94] = 0;     data[95] = 2;     data[96] = 0;
    data[97] = 23;
209
210
211
212
213
214
215
216
217

    pkt = new Pkt6(data, sizeof(data));
    pkt->setRemotePort(546);
    pkt->setRemoteAddr(IOAddress("fe80::21e:8cff:fe9b:7349"));
    pkt->setLocalPort(0);
    pkt->setLocalAddr(IOAddress("ff02::1:2"));
    pkt->setIndex(2);
    pkt->setIface("eth0");

218
219
220
    return (pkt);
}

Tomek Mrugalski's avatar
Tomek Mrugalski committed
221
/// @brief creates doubly relayed solicit message
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
///
/// This is a traffic capture exported from wireshark. It includes a SOLICIT
/// message that passed through two relays. Each relay include interface-id,
/// remote-id and relay-forw encapsulation. It is especially interesting,
/// because of the following properties:
/// - double encapsulation
/// - first relay inserts relay-msg before extra options
/// - second relay inserts relay-msg after extra options
/// - both relays are from different vendors
/// - interface-id are different for each relay
/// - first relay inserts valid remote-id
/// - second relay inserts remote-id with empty vendor data
/// - the solicit message requests for custom options in ORO
/// - there are option types in RELAY-FORW that do not appear in SOLICIT
/// - there are option types in SOLICT that do not appear in RELAY-FORW
///
/// RELAY-FORW
///  - relay message option
///      - RELAY-FORW
///          - interface-id option
///          - remote-id option
///          - RELAY-FORW
///              SOLICIT
///                  - client-id option
///                  - ia_na option
///                  - elapsed time
///                  - ORO
///  - interface-id option
///  - remote-id option
///
/// The original capture was posted to dibbler users mailing list.
///
/// @return created double relayed SOLICIT message
Pkt6* capture2() {

    // string exported from Wireshark
    string hex_string =
Francis Dupont's avatar
Francis Dupont committed
259
260
261
262
263
264
265
        "0c01200108880db800010000000000000000fe80000000000000020021fffe5c"
        "18a90009007d0c0000000000000000000000000000000000fe80000000000000"
        "020021fffe5c18a9001200154953414d3134342065746820312f312f30352f30"
        "310025000400000de900090036016b4fe20001000e0001000118b03341000021"
        "5c18a90003000c00000001ffffffffffffffff00080002000000060006001700"
        "f200f30012001c4953414d3134347c3239397c697076367c6e743a76703a313a"
        "313130002500120000197f0001000118b033410000215c18a9";
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281

    std::vector<uint8_t> bin;

    // Decode the hex string and store it in bin (which happens
    // to be OptionBuffer format)
    isc::util::encode::decodeHex(hex_string, bin);

    Pkt6* pkt = new Pkt6(&bin[0], bin.size());
    pkt->setRemotePort(547);
    pkt->setRemoteAddr(IOAddress("fe80::1234"));
    pkt->setLocalPort(547);
    pkt->setLocalAddr(IOAddress("ff05::1:3"));
    pkt->setIndex(2);
    pkt->setIface("eth0");
    return (pkt);
}
282

283
TEST_F(Pkt6Test, unpack_solicit1) {
284
    scoped_ptr<Pkt6> sol(capture1());
285

286
    ASSERT_NO_THROW(sol->unpack());
287

288
    // Check for length
289
290
    EXPECT_EQ(98, sol->len() );

291
    // Check for type
292
    EXPECT_EQ(DHCPV6_SOLICIT, sol->getType() );
293

294
    // Check that all present options are returned
295
296
297
298
299
    EXPECT_TRUE(sol->getOption(D6O_CLIENTID)); // client-id is present
    EXPECT_TRUE(sol->getOption(D6O_IA_NA));    // IA_NA is present
    EXPECT_TRUE(sol->getOption(D6O_ELAPSED_TIME));  // elapsed is present
    EXPECT_TRUE(sol->getOption(D6O_NAME_SERVERS));
    EXPECT_TRUE(sol->getOption(D6O_ORO));
300

301
    // Let's check that non-present options are not returned
302
303
304
    EXPECT_FALSE(sol->getOption(D6O_SERVERID)); // server-id is missing
    EXPECT_FALSE(sol->getOption(D6O_IA_TA));
    EXPECT_FALSE(sol->getOption(D6O_IAADDR));
305
306
}

307
TEST_F(Pkt6Test, packUnpack) {
308
309
    // Create an on-wire representation of the test packet and clone it.
    Pkt6Ptr clone = packAndClone();
310

311
    // Now recreate options list
312
    ASSERT_NO_THROW(clone->unpack());
313

314
315
316
    // transid, message-type should be the same as before
    EXPECT_EQ(0x020304, clone->getTransid());
    EXPECT_EQ(DHCPV6_SOLICIT, clone->getType());
317

318
319
320
321
322
    EXPECT_TRUE(clone->getOption(1));
    EXPECT_TRUE(clone->getOption(2));
    EXPECT_TRUE(clone->getOption(100));
    EXPECT_FALSE(clone->getOption(4));
}
323

324
325
// Checks if the code is able to handle malformed packet
TEST_F(Pkt6Test, unpackMalformed) {
Francis Dupont's avatar
Francis Dupont committed
326
327
    // Get a packet. We're really interested in its on-wire
    // representation only.
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
    scoped_ptr<Pkt6> donor(capture1());

    // That's our original content. It should be sane.
    OptionBuffer orig = donor->data_;

    Pkt6Ptr success(new Pkt6(&orig[0], orig.size()));
    EXPECT_NO_THROW(success->unpack());

    // Insert trailing garbage.
    OptionBuffer malform1 = orig;
    malform1.push_back(123);

    // Let's check a truncated packet. Moderately sane DHCPv6 packet should at
    // least have four bytes header. Zero bytes is definitely not a valid one.
    OptionBuffer empty(1); // Let's allocate one byte, so we won't be
                           // dereferencing and empty buffer.

    Pkt6Ptr empty_pkt(new Pkt6(&empty[0], 0));
    EXPECT_THROW(empty_pkt->unpack(), isc::BadValue);

    // Neither is 3 bytes long.
    OptionBuffer shorty;
    shorty.push_back(DHCPV6_SOLICIT);
    shorty.push_back(1);
    shorty.push_back(2);
    Pkt6Ptr too_short_pkt(new Pkt6(&shorty[0], shorty.size()));
    EXPECT_THROW(too_short_pkt->unpack(), isc::BadValue);

    // The code should complain about remaining bytes that can't
    // be parsed.
    Pkt6Ptr trailing_garbage(new Pkt6(&malform1[0], malform1.size()));
    EXPECT_NO_THROW(trailing_garbage->unpack());

    // A strict approach would assume the code will reject the whole packet,
362
363
    // but we decided to follow Jon Postel's law and be silent about
    // received malformed or truncated options.
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381

    // Add an option that is truncated
    OptionBuffer malform2 = orig;
    malform2.push_back(0);
    malform2.push_back(123); // 0, 123 - option code = 123
    malform2.push_back(0);
    malform2.push_back(1);   // 0, 1 - option length = 1
    // Option content would go here, but it's missing

    Pkt6Ptr trunc_option(new Pkt6(&malform2[0], malform2.size()));

    // The unpack() operation should succeed...
    EXPECT_NO_THROW(trunc_option->unpack());

    // ... but there should be no option 123 as it was malformed.
    EXPECT_FALSE(trunc_option->getOption(123));
}

382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
// This test verifies that it is possible to specify custom implementation of
// the option parsing algorithm by installing a callback function.
TEST_F(Pkt6Test, packUnpackWithCallback) {
    // Create an on-wire representation of the test packet and clone it.
    Pkt6Ptr clone = packAndClone();

    // Install the custom callback function. We expect that this function
    // will be called to parse options in the packet instead of
    // LibDHCP::unpackOptions6.
    CustomUnpackCallback cb;
    clone->setCallback(boost::bind(&CustomUnpackCallback::execute, &cb,
                                   _1, _2, _3, _4, _5));
    // Make sure that the flag which indicates if the callback function has
    // been called is not set. Otherwise, our test doesn't make sense.
    ASSERT_FALSE(cb.executed_);
397

398
    // Now recreate options list
399
    ASSERT_NO_THROW(clone->unpack());
400
401
402
403

    // An object which holds a callback should now have a flag set which
    // indicates that callback has been called.
    EXPECT_TRUE(cb.executed_);
404
405

    // transid, message-type should be the same as before
406
    EXPECT_EQ(0x020304, clone->getTransid());
407
408
    EXPECT_EQ(DHCPV6_SOLICIT, clone->getType());

409
410
411
412
    EXPECT_TRUE(clone->getOption(1));
    EXPECT_TRUE(clone->getOption(2));
    EXPECT_TRUE(clone->getOption(100));
    EXPECT_FALSE(clone->getOption(4));
413
414
415
416
417
418

    // Reset the indicator to perform another check: uninstall the callback.
    cb.executed_ = false;
    // By setting the callback to NULL we effectively uninstall the callback.
    clone->setCallback(NULL);
    // Do another unpack.
419
    ASSERT_NO_THROW(clone->unpack());
420
421
    // Callback should not be executed.
    EXPECT_FALSE(cb.executed_);
422
423
}

424
425
// This test verifies that options can be added (addOption()), retrieved
// (getOption(), getOptions()) and deleted (delOption()).
426
TEST_F(Pkt6Test, addGetDelOptions) {
427
    scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_SOLICIT, random()));
428

429
430
431
    OptionPtr opt1(new Option(Option::V6, 1));
    OptionPtr opt2(new Option(Option::V6, 2));
    OptionPtr opt3(new Option(Option::V6, 2));
432
433
434
435
436
437
438
439

    parent->addOption(opt1);
    parent->addOption(opt2);

    // getOption() test
    EXPECT_EQ(opt1, parent->getOption(1));
    EXPECT_EQ(opt2, parent->getOption(2));

440
    // Expect NULL
441
    EXPECT_EQ(OptionPtr(), parent->getOption(4));
442

443
    // Now there are 2 options of type 2
444
445
    parent->addOption(opt3);

446
    OptionCollection options = parent->getOptions(2);
447
448
    EXPECT_EQ(2, options.size()); // there should be 2 instances

449
    // Both options must be of type 2 and there must not be
450
    // any other type returned
451
    for (OptionCollection::const_iterator x= options.begin();
452
453
454
455
456
457
458
459
460
461
462
463
464
         x != options.end(); ++x) {
        EXPECT_EQ(2, x->second->getType());
    }

    // Try to get a single option. Normally for singular options
    // it is better to use getOption(), but getOptions() must work
    // as well
    options = parent->getOptions(1);
    ASSERT_EQ(1, options.size());

    EXPECT_EQ(1, (*options.begin()).second->getType());
    EXPECT_EQ(opt1, options.begin()->second);

465
    // Let's delete one of them
466
467
    EXPECT_EQ(true, parent->delOption(2));

468
    // There still should be the other option 2
469
    EXPECT_NE(OptionPtr(), parent->getOption(2));
470

471
    // Let's delete the other option 2
472
473
    EXPECT_EQ(true, parent->delOption(2));

474
    // No more options with type=2
475
    EXPECT_EQ(OptionPtr(), parent->getOption(2));
476

477
    // Let's try to delete - should fail
478
479
    EXPECT_TRUE(false ==  parent->delOption(2));

480
481
482
    // Finally try to get a non-existent option
    options = parent->getOptions(1234);
    EXPECT_EQ(0, options.size());
483
484
}

485
TEST_F(Pkt6Test, Timestamp) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
486
    boost::scoped_ptr<Pkt6> pkt(new Pkt6(DHCPV6_SOLICIT, 0x020304));
487
488
489
490

    // Just after construction timestamp is invalid
    ASSERT_TRUE(pkt->getTimestamp().is_not_a_date_time());

491
492
493
494
    // Update packet time.
    pkt->updateTimestamp();

    // Get updated packet time.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
495
    boost::posix_time::ptime ts_packet = pkt->getTimestamp();
496
497
498
499
500

    // After timestamp is updated it should be date-time.
    ASSERT_FALSE(ts_packet.is_not_a_date_time());

    // Check current time.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
501
502
    boost::posix_time::ptime ts_now =
        boost::posix_time::microsec_clock::universal_time();
503
504

    // Calculate period between packet time and now.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
505
    boost::posix_time::time_period ts_period(ts_packet, ts_now);
506
507
508

    // Duration should be positive or zero.
    EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
509
510
}

511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
// This test verifies that getName() method returns proper
// packet type names.
TEST_F(Pkt6Test, getName) {
    // Check all possible packet types
    for (int itype = 0; itype < 256; ++itype) {
        uint8_t type = itype;

        switch (type) {
        case DHCPV6_CONFIRM:
            EXPECT_STREQ("CONFIRM", Pkt6::getName(type));
            break;

        case DHCPV6_DECLINE:
            EXPECT_STREQ("DECLINE", Pkt6::getName(type));
            break;

        case DHCPV6_INFORMATION_REQUEST:
            EXPECT_STREQ("INFORMATION_REQUEST",
                         Pkt6::getName(type));
            break;

        case DHCPV6_REBIND:
            EXPECT_STREQ("REBIND", Pkt6::getName(type));
            break;

        case DHCPV6_RELEASE:
            EXPECT_STREQ("RELEASE", Pkt6::getName(type));
            break;

        case DHCPV6_RENEW:
            EXPECT_STREQ("RENEW", Pkt6::getName(type));
            break;

        case DHCPV6_REQUEST:
            EXPECT_STREQ("REQUEST", Pkt6::getName(type));
            break;

        case DHCPV6_SOLICIT:
            EXPECT_STREQ("SOLICIT", Pkt6::getName(type));
            break;

        default:
            EXPECT_STREQ("UNKNOWN", Pkt6::getName(type));
        }
    }
}

558
559
560
561
// This test verifies that a fancy solicit that passed through two
// relays can be parsed properly. See capture2() method description
// for details regarding the packet.
TEST_F(Pkt6Test, relayUnpack) {
Tomek Mrugalski's avatar
Tomek Mrugalski committed
562
    boost::scoped_ptr<Pkt6> msg(capture2());
563
564
565
566
567
568
569
570
571
572

    EXPECT_NO_THROW(msg->unpack());

    EXPECT_EQ(DHCPV6_SOLICIT, msg->getType());
    EXPECT_EQ(217, msg->len());

    ASSERT_EQ(2, msg->relay_info_.size());

    OptionPtr opt;

573
    // Part 1: Check options inserted by the first relay
574
575
576
577
578
579
580
581
582
583
584
585

    // There should be 2 options in first relay
    EXPECT_EQ(2, msg->relay_info_[0].options_.size());

    // There should be interface-id option
    ASSERT_TRUE(opt = msg->getRelayOption(D6O_INTERFACE_ID, 0));
    OptionBuffer data = opt->getData();
    EXPECT_EQ(32, opt->len()); // 28 bytes of data + 4 bytes header
    EXPECT_EQ(data.size(), 28);
    // That's a strange interface-id, but this is a real life example
    EXPECT_TRUE(0 == memcmp("ISAM144|299|ipv6|nt:vp:1:110", &data[0], 28));

586
    // Get the remote-id option
587
588
    ASSERT_TRUE(opt = msg->getRelayOption(D6O_REMOTE_ID, 0));
    EXPECT_EQ(22, opt->len()); // 18 bytes of data + 4 bytes header
Tomek Mrugalski's avatar
Tomek Mrugalski committed
589
    boost::shared_ptr<OptionCustom> custom = boost::dynamic_pointer_cast<OptionCustom>(opt);
590
591
592
593

    uint32_t vendor_id = custom->readInteger<uint32_t>(0);
    EXPECT_EQ(6527, vendor_id); // 6527 = Panthera Networks

Francis Dupont's avatar
Francis Dupont committed
594
595
596
    uint8_t expected_remote_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0,
                                     0x33, 0x41, 0x00, 0x00, 0x21, 0x5c,
                                     0x18, 0xa9 };
597
598
599
600
    OptionBuffer remote_id = custom->readBinary(1);
    ASSERT_EQ(sizeof(expected_remote_id), remote_id.size());
    ASSERT_EQ(0, memcmp(expected_remote_id, &remote_id[0], remote_id.size()));

601
    // Part 2: Check options inserted by the second relay
602

603
    // Get the interface-id from the second relay
604
605
606
607
608
609
    ASSERT_TRUE(opt = msg->getRelayOption(D6O_INTERFACE_ID, 1));
    data = opt->getData();
    EXPECT_EQ(25, opt->len()); // 21 bytes + 4 bytes header
    EXPECT_EQ(data.size(), 21);
    EXPECT_TRUE(0 == memcmp("ISAM144 eth 1/1/05/01", &data[0], 21));

610
    // Get the remote-id option
611
612
    ASSERT_TRUE(opt = msg->getRelayOption(D6O_REMOTE_ID, 1));
    EXPECT_EQ(8, opt->len());
Tomek Mrugalski's avatar
Tomek Mrugalski committed
613
    custom = boost::dynamic_pointer_cast<OptionCustom>(opt);
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631

    vendor_id = custom->readInteger<uint32_t>(0);
    EXPECT_EQ(3561, vendor_id); // 3561 = Broadband Forum
    // @todo: See if we can validate empty remote-id field

    // Let's check if there is no leak between options stored in
    // the SOLICIT message and the relay.
    EXPECT_FALSE(opt = msg->getRelayOption(D6O_IA_NA, 1));


    // Part 3: Let's check options in the message itself
    // This is not redundant compared to other direct messages tests,
    // as we parsed it differently
    EXPECT_EQ(DHCPV6_SOLICIT, msg->getType());
    EXPECT_EQ(0x6b4fe2, msg->getTransid());

    ASSERT_TRUE(opt = msg->getOption(D6O_CLIENTID));
    EXPECT_EQ(18, opt->len()); // 14 bytes of data + 4 bytes of header
Francis Dupont's avatar
Francis Dupont committed
632
633
634
    uint8_t expected_client_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0,
                                     0x33, 0x41, 0x00, 0x00, 0x21, 0x5c,
                                     0x18, 0xa9 };
635
636
637
638
639
    data = opt->getData();
    ASSERT_EQ(data.size(), sizeof(expected_client_id));
    ASSERT_EQ(0, memcmp(&data[0], expected_client_id, data.size()));

    ASSERT_TRUE(opt = msg->getOption(D6O_IA_NA));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
640
641
    boost::shared_ptr<Option6IA> ia =
        boost::dynamic_pointer_cast<Option6IA>(opt);
642
643
644
645
646
647
648
    ASSERT_TRUE(ia);
    EXPECT_EQ(1, ia->getIAID());
    EXPECT_EQ(0xffffffff, ia->getT1());
    EXPECT_EQ(0xffffffff, ia->getT2());

    ASSERT_TRUE(opt = msg->getOption(D6O_ELAPSED_TIME));
    EXPECT_EQ(6, opt->len()); // 2 bytes of data + 4 bytes of header
Tomek Mrugalski's avatar
Tomek Mrugalski committed
649
650
    boost::shared_ptr<OptionInt<uint16_t> > elapsed =
        boost::dynamic_pointer_cast<OptionInt<uint16_t> > (opt);
651
652
653
654
    ASSERT_TRUE(elapsed);
    EXPECT_EQ(0, elapsed->getValue());

    ASSERT_TRUE(opt = msg->getOption(D6O_ORO));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
655
656
    boost::shared_ptr<OptionIntArray<uint16_t> > oro =
        boost::dynamic_pointer_cast<OptionIntArray<uint16_t> > (opt);
657
658
659
660
661
662
663
664
665
666
667
    const std::vector<uint16_t> oro_list = oro->getValues();
    EXPECT_EQ(3, oro_list.size());
    EXPECT_EQ(23, oro_list[0]);
    EXPECT_EQ(242, oro_list[1]);
    EXPECT_EQ(243, oro_list[2]);
}

// This test verified that message with relay information can be
// packed and then unpacked.
TEST_F(Pkt6Test, relayPack) {

668
    scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_ADVERTISE, 0x020304));
669
670
671
672
673
674
675
676

    Pkt6::RelayInfo relay1;
    relay1.msg_type_ = DHCPV6_RELAY_REPL;
    relay1.hop_count_ = 17; // not very miningful, but useful for testing
    relay1.linkaddr_ = IOAddress("2001:db8::1");
    relay1.peeraddr_ = IOAddress("fe80::abcd");

    uint8_t relay_opt_data[] = { 1, 2, 3, 4, 5, 6, 7, 8};
677
678
    vector<uint8_t> relay_data(relay_opt_data,
                               relay_opt_data + sizeof(relay_opt_data));
679
680
681

    OptionPtr optRelay1(new Option(Option::V6, 200, relay_data));

682
    relay1.options_.insert(make_pair(optRelay1->getType(), optRelay1));
683
684
685
686

    OptionPtr opt1(new Option(Option::V6, 100));
    OptionPtr opt2(new Option(Option::V6, 101));
    OptionPtr opt3(new Option(Option::V6, 102));
687
    // Let's not use zero-length option type 3 as it is IA_NA
688
689
690
691
692
693
694
695
696

    parent->addRelayInfo(relay1);

    parent->addOption(opt1);
    parent->addOption(opt2);
    parent->addOption(opt3);

    EXPECT_EQ(DHCPV6_ADVERTISE, parent->getType());

697
    EXPECT_NO_THROW(parent->pack());
698

Francis Dupont's avatar
Francis Dupont committed
699
700
    EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN
              + 3 * Option::OPTION6_HDR_LEN // ADVERTISE
701
702
              + Pkt6::DHCPV6_RELAY_HDR_LEN // Relay header
              + Option::OPTION6_HDR_LEN // Relay-msg
703
704
705
              + optRelay1->len(),
              parent->len());

706
707
708
709
    // Create second packet,based on assembled data from the first one
    scoped_ptr<Pkt6> clone(new Pkt6(static_cast<const uint8_t*>(
                                    parent->getBuffer().getData()),
                                    parent->getBuffer().getLength()));
710

711
    // Now recreate options list
712
    EXPECT_NO_THROW( clone->unpack() );
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737

    // transid, message-type should be the same as before
    EXPECT_EQ(parent->getTransid(), parent->getTransid());
    EXPECT_EQ(DHCPV6_ADVERTISE, clone->getType());

    EXPECT_TRUE( clone->getOption(100));
    EXPECT_TRUE( clone->getOption(101));
    EXPECT_TRUE( clone->getOption(102));
    EXPECT_FALSE(clone->getOption(103));

    // Now check relay info
    ASSERT_EQ(1, clone->relay_info_.size());
    EXPECT_EQ(DHCPV6_RELAY_REPL, clone->relay_info_[0].msg_type_);
    EXPECT_EQ(17, clone->relay_info_[0].hop_count_);
    EXPECT_EQ("2001:db8::1", clone->relay_info_[0].linkaddr_.toText());
    EXPECT_EQ("fe80::abcd", clone->relay_info_[0].peeraddr_.toText());

    // There should be exactly one option
    EXPECT_EQ(1, clone->relay_info_[0].options_.size());
    OptionPtr opt = clone->getRelayOption(200, 0);
    EXPECT_TRUE(opt);
    EXPECT_EQ(opt->getType() , optRelay1->getType());
    EXPECT_EQ(opt->len(), optRelay1->len());
    OptionBuffer data = opt->getData();
    ASSERT_EQ(data.size(), sizeof(relay_opt_data));
Francis Dupont's avatar
Francis Dupont committed
738
739
    EXPECT_EQ(0,
              memcmp(relay_opt_data, relay_opt_data, sizeof(relay_opt_data)));
740
}
741

742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757

// This test verified that options added by relays to the message can be
// accessed and retrieved properly
TEST_F(Pkt6Test, getAnyRelayOption) {

    boost::scoped_ptr<Pkt6> msg(new Pkt6(DHCPV6_ADVERTISE, 0x020304));
    msg->addOption(generateRandomOption(300));

    // generate options for relay1
    Pkt6::RelayInfo relay1;

    // generate 3 options with code 200,201,202 and random content
    OptionPtr relay1_opt1(generateRandomOption(200));
    OptionPtr relay1_opt2(generateRandomOption(201));
    OptionPtr relay1_opt3(generateRandomOption(202));

758
759
760
    relay1.options_.insert(make_pair(200, relay1_opt1));
    relay1.options_.insert(make_pair(201, relay1_opt2));
    relay1.options_.insert(make_pair(202, relay1_opt3));
761
762
763
764
765
766
767
    msg->addRelayInfo(relay1);

    // generate options for relay2
    Pkt6::RelayInfo relay2;
    OptionPtr relay2_opt1(new Option(Option::V6, 100));
    OptionPtr relay2_opt2(new Option(Option::V6, 101));
    OptionPtr relay2_opt3(new Option(Option::V6, 102));
Francis Dupont's avatar
Francis Dupont committed
768
769
    OptionPtr relay2_opt4(new Option(Option::V6, 200));
    // the same code as relay1_opt3
770
771
772
773
    relay2.options_.insert(make_pair(100, relay2_opt1));
    relay2.options_.insert(make_pair(101, relay2_opt2));
    relay2.options_.insert(make_pair(102, relay2_opt3));
    relay2.options_.insert(make_pair(200, relay2_opt4));
774
775
776
777
778
    msg->addRelayInfo(relay2);

    // generate options for relay3
    Pkt6::RelayInfo relay3;
    OptionPtr relay3_opt1(generateRandomOption(200, 7));
779
    relay3.options_.insert(make_pair(200, relay3_opt1));
780
781
782
783
784
785
786
787
    msg->addRelayInfo(relay3);

    // Ok, so we now have a packet that traversed the following network:
    // client---relay3---relay2---relay1---server

    // First check that the getAnyRelayOption does not confuse client options
    // and relay options
    // 300 is a client option, present in the message itself.
Francis Dupont's avatar
Francis Dupont committed
788
789
    OptionPtr opt =
        msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_CLIENT);
790
791
792
793
794
795
    EXPECT_FALSE(opt);
    opt = msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_SERVER);
    EXPECT_FALSE(opt);
    opt = msg->getAnyRelayOption(300, Pkt6::RELAY_GET_FIRST);
    EXPECT_FALSE(opt);
    opt = msg->getAnyRelayOption(300, Pkt6::RELAY_GET_LAST);
796
797
798
799
800
801
802
803
    EXPECT_FALSE(opt);

    // Option 200 is added in every relay.

    // We want to get that one inserted by relay3 (first match, starting from
    // closest to the client.
    opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
    ASSERT_TRUE(opt);
804
    EXPECT_TRUE(opt->equals(relay3_opt1));
805
806
807
808
809

    // We want to ge that one inserted by relay1 (first match, starting from
    // closest to the server.
    opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER);
    ASSERT_TRUE(opt);
810
    EXPECT_TRUE(opt->equals(relay1_opt1));
811
812

    // We just want option from the first relay (closest to the client)
813
    opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_FIRST);
814
    ASSERT_TRUE(opt);
815
    EXPECT_TRUE(opt->equals(relay3_opt1));
816
817

    // We just want option from the last relay (closest to the server)
818
    opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_LAST);
819
    ASSERT_TRUE(opt);
820
    EXPECT_TRUE(opt->equals(relay1_opt1));
821
822
823
824
825

    // Let's try to ask for something that is inserted by the middle relay
    // only.
    opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_SERVER);
    ASSERT_TRUE(opt);
826
    EXPECT_TRUE(opt->equals(relay2_opt1));
827
828
829

    opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_CLIENT);
    ASSERT_TRUE(opt);
830
    EXPECT_TRUE(opt->equals(relay2_opt1));
831

832
833
834
835
836
837
838
839
    opt = msg->getAnyRelayOption(100, Pkt6::RELAY_GET_FIRST);
    EXPECT_FALSE(opt);

    opt = msg->getAnyRelayOption(100, Pkt6::RELAY_GET_LAST);
    EXPECT_FALSE(opt);

    // Finally, try to get an option that does not exist
    opt = msg->getAnyRelayOption(500, Pkt6::RELAY_GET_FIRST);
840
841
    EXPECT_FALSE(opt);

842
    opt = msg->getAnyRelayOption(500, Pkt6::RELAY_GET_LAST);
843
844
    EXPECT_FALSE(opt);

845
846
847
848
849
    opt = msg->getAnyRelayOption(500, Pkt6::RELAY_SEARCH_FROM_SERVER);
    EXPECT_FALSE(opt);

    opt = msg->getAnyRelayOption(500, Pkt6::RELAY_SEARCH_FROM_CLIENT);
    EXPECT_FALSE(opt);
850
851
}

852
853
854
855
856
857
// Tests whether a packet can be assigned to a class and later
// checked if it belongs to a given class
TEST_F(Pkt6Test, clientClasses) {
    Pkt6 pkt(DHCPV6_ADVERTISE, 1234);

    // Default values (do not belong to any class)
Tomek Mrugalski's avatar
Tomek Mrugalski committed
858
859
    EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
    EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
860
861
862
    EXPECT_TRUE(pkt.classes_.empty());

    // Add to the first class
Tomek Mrugalski's avatar
Tomek Mrugalski committed
863
864
865
    pkt.addClass(DOCSIS3_CLASS_EROUTER);
    EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
    EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
866
867
868
    ASSERT_FALSE(pkt.classes_.empty());

    // Add to a second class
Tomek Mrugalski's avatar
Tomek Mrugalski committed
869
870
871
    pkt.addClass(DOCSIS3_CLASS_MODEM);
    EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
    EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_MODEM));
872
873
874
875
876

    // Check that it's ok to add to the same class repeatedly
    EXPECT_NO_THROW(pkt.addClass("foo"));
    EXPECT_NO_THROW(pkt.addClass("foo"));
    EXPECT_NO_THROW(pkt.addClass("foo"));
Tomek Mrugalski's avatar
Tomek Mrugalski committed
877
878
879

    // Check that the packet belongs to 'foo'
    EXPECT_TRUE(pkt.inClass("foo"));
880
881
}

882
883
884
885
886
887
// Tests whether MAC can be obtained and that MAC sources are not
// confused.
TEST_F(Pkt6Test, getMAC) {
    Pkt6 pkt(DHCPV6_ADVERTISE, 1234);

    // DHCPv6 packet by default doens't have MAC address specified.
Tomek Mrugalski's avatar
Tomek Mrugalski committed
888
889
    EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY));
    EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW));
890

Francis Dupont's avatar
Francis Dupont committed
891
892
    // We haven't specified source IPv6 address, so this method should
    // fail, too
Tomek Mrugalski's avatar
Tomek Mrugalski committed
893
    EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL));
894
895
896
897

    // Let's check if setting IPv6 address improves the situation.
    IOAddress linklocal_eui64("fe80::204:06ff:fe08:0a0c");
    pkt.setRemoteAddr(linklocal_eui64);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
898
899
900
901
    EXPECT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY));
    EXPECT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL));
    EXPECT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL |
                           HWAddr::HWADDR_SOURCE_RAW));
902
903
    pkt.setRemoteAddr(IOAddress("::"));

904
905
906
907
908
909
910
911
912
    // Let's invent a MAC
    const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
    const uint8_t hw_type = 123; // hardware type
    HWAddrPtr dummy_hwaddr(new HWAddr(hw, sizeof(hw), hw_type));

    // Now let's pretend that we obtained it from raw sockets
    pkt.setRemoteHWAddr(dummy_hwaddr);

    // Now we should be able to get something
Tomek Mrugalski's avatar
Tomek Mrugalski committed
913
914
915
916
    ASSERT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY));
    ASSERT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW));
    EXPECT_TRUE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL |
                           HWAddr::HWADDR_SOURCE_RAW));
917
918

    // Check that the returned MAC is indeed the expected one
Tomek Mrugalski's avatar
Tomek Mrugalski committed
919
920
    ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_ANY));
    ASSERT_TRUE(*dummy_hwaddr == *pkt.getMAC(HWAddr::HWADDR_SOURCE_RAW));
921
922
}

923
// Test checks whether getMACFromIPv6LinkLocal() returns the hardware (MAC)
924
925
// address properly (for direct message).
TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_direct) {
926
927
928
929
930
931
932
933
934
935
936
    Pkt6 pkt(DHCPV6_ADVERTISE, 1234);

    // Let's get the first interface
    Iface* iface = IfaceMgr::instance().getIface(1);
    ASSERT_TRUE(iface);

    // and set source interface data properly. getMACFromIPv6LinkLocal attempts
    // to use source interface to obtain hardware type
    pkt.setIface(iface->getName());
    pkt.setIndex(iface->getIndex());

Francis Dupont's avatar
Francis Dupont committed
937
938
939
    // Note that u and g bits (the least significant ones of the most
    // significant byte) have special meaning and must not be set in MAC.
    // u bit is always set in EUI-64. g is always cleared.
940
    IOAddress global("2001:db8::204:06ff:fe08:0a:0c");
Tomek Mrugalski's avatar
Tomek Mrugalski committed
941
942
    IOAddress linklocal_eui64("fe80::f204:06ff:fe08:0a0c");
    IOAddress linklocal_noneui64("fe80::f204:0608:0a0c:0e10");
943
944
945

    // If received from a global address, this method should fail
    pkt.setRemoteAddr(global);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
946
    EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL));
947
948
949

    // If received from link-local that is EUI-64 based, it should succeed
    pkt.setRemoteAddr(linklocal_eui64);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
950
    HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL);
951
952
953
    ASSERT_TRUE(found);

    stringstream tmp;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
954
    tmp << "hwtype=" << (int)iface->getHWType() << " f0:04:06:08:0a:0c";
955
956
957
    EXPECT_EQ(tmp.str(), found->toText(true));
}

958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
// Test checks whether getMACFromIPv6LinkLocal() returns the hardware (MAC)
// address properly (for relayed message).
TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_singleRelay) {

    // Let's create a Solicit first...
    Pkt6 pkt(DHCPV6_SOLICIT, 1234);

    // ... and pretend it was relayed by a single relay.
    Pkt6::RelayInfo info;
    pkt.addRelayInfo(info);
    ASSERT_EQ(1, pkt.relay_info_.size());

    // Let's get the first interface
    Iface* iface = IfaceMgr::instance().getIface(1);
    ASSERT_TRUE(iface);

    // and set source interface data properly. getMACFromIPv6LinkLocal attempts
    // to use source interface to obtain hardware type
    pkt.setIface(iface->getName());
    pkt.setIndex(iface->getIndex());

    IOAddress global("2001:db8::204:06ff:fe08:0a:0c"); // global address
Tomek Mrugalski's avatar
Tomek Mrugalski committed
980
981
    IOAddress linklocal_noneui64("fe80::f204:0608:0a0c:0e10"); // no fffe
    IOAddress linklocal_eui64("fe80::f204:06ff:fe08:0a0c"); // valid EUI-64
982
983
984

    // If received from a global address, this method should fail
    pkt.relay_info_[0].peeraddr_ = global;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
985
    EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL));
986

Francis Dupont's avatar
Francis Dupont committed
987
    // If received from a link-local that does not use EUI-64, it should fail
988
    pkt.relay_info_[0].peeraddr_ = linklocal_noneui64;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
989
    EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL));
990
991
992

    // If received from link-local that is EUI-64 based, it should succeed
    pkt.relay_info_[0].peeraddr_ = linklocal_eui64;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
993
    HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL);
994
995
996
    ASSERT_TRUE(found);

    stringstream tmp;
Tomek Mrugalski's avatar
Tomek Mrugalski committed
997
    tmp << "hwtype=" << (int)iface->getHWType() << " f0:04:06:08:0a:0c";
998
999
1000
1001
1002
1003
1004
1005
1006
1007
    EXPECT_EQ(tmp.str(), found->toText(true));
}

// Test checks whether getMACFromIPv6LinkLocal() returns the hardware (MAC)
// address properly (for a message relayed multiple times).
TEST_F(Pkt6Test, getMACFromIPv6LinkLocal_multiRelay) {

    // Let's create a Solicit first...
    Pkt6 pkt(DHCPV6_SOLICIT, 1234);

Francis Dupont's avatar
Francis Dupont committed
1008
1009
1010
    // ... and pretend it was relayed via 3 relays. Keep in mind that
    // the relays are stored in relay_info_ in the encapsulation order
    // rather than in traverse order. The following simulates:
1011
    // client --- relay1 --- relay2 --- relay3 --- server
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1012
1013
1014
    IOAddress linklocal1("fe80::200:ff:fe00:1"); // valid EUI-64
    IOAddress linklocal2("fe80::200:ff:fe00:2"); // valid EUI-64
    IOAddress linklocal3("fe80::200:ff:fe00:3"); // valid EUI-64
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045

    // Let's add info about relay3. This was the last relay, so it added the
    // outermost encapsulation layer, so it was parsed first during reception.
    // Its peer-addr field contains an address of relay2, so it's useless for
    // this method.
    Pkt6::RelayInfo info;
    info.peeraddr_ = linklocal3;
    pkt.addRelayInfo(info);

    // Now add info about relay2. Its peer-addr contains an address of the
    // previous relay (relay1). Still useless for us.
    info.peeraddr_ = linklocal2;
    pkt.addRelayInfo(info);

    // Finally add the first relay. This is the relay that received the packet
    // from the client directly, so its peer-addr field contains an address of
    // the client. The method should get that address and build MAC from it.
    info.peeraddr_ = linklocal1;
    pkt.addRelayInfo(info);
    ASSERT_EQ(3, pkt.relay_info_.size());

    // Let's get the first interface
    Iface* iface = IfaceMgr::instance().getIface(1);
    ASSERT_TRUE(iface);

    // and set source interface data properly. getMACFromIPv6LinkLocal attempts
    // to use source interface to obtain hardware type
    pkt.setIface(iface->getName());
    pkt.setIndex(iface->getIndex());

    // The method should return MAC based on the first relay that was closest
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1046
    HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL);
1047
1048
1049
1050
1051
1052
1053
1054
    ASSERT_TRUE(found);

    // Let's check the info now.
    stringstream tmp;
    tmp << "hwtype=" << iface->getHWType() << " 00:00:00:00:00:01";
    EXPECT_EQ(tmp.str(), found->toText(true));
}

1055
1056
1057
1058
1059
1060
1061
1062
// Test checks whether getMACFromIPv6RelayOpt() returns the hardware (MAC)
// address properly from a single relayed message.
TEST_F(Pkt6Test, getMACFromIPv6RelayOpt_singleRelay) {

    // Let's create a Solicit first...
    Pkt6 pkt(DHCPV6_SOLICIT, 1234);

    // Packets that are not relayed should fail
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1063
    EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION));
1064
1065
1066
1067
1068

    // Now pretend it was relayed by a single relay.
    Pkt6::RelayInfo info;

    // generate options with code 79 and client link layer address
1069
    const uint8_t opt_data[] = {
1070
1071
1072
1073
        0x00, 0x01,  // Ethertype
        0x0a, 0x1b, 0x0b, 0x01, 0xca, 0xfe // MAC
    };
    OptionPtr relay_opt(new Option(Option::V6, 79,
1074
                            OptionBuffer(opt_data, opt_data + sizeof(opt_data))));
1075
1076
1077
1078
1079
    info.options_.insert(make_pair(relay_opt->getType(), relay_opt));

    pkt.addRelayInfo(info);
    ASSERT_EQ(1, pkt.relay_info_.size());

Tomek Mrugalski's avatar
Tomek Mrugalski committed
1080
    HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION);
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
    ASSERT_TRUE(found);

    stringstream tmp;
    tmp << "hwtype=1 0a:1b:0b:01:ca:fe";
    EXPECT_EQ(tmp.str(), found->toText(true));
}

// Test checks whether getMACFromIPv6RelayOpt() returns the hardware (MAC)
// address properly from a message relayed by multiple servers.
TEST_F(Pkt6Test, getMACFromIPv6RelayOpt_multipleRelay) {

    // Let's create a Solicit first...
    Pkt6 pkt(DHCPV6_SOLICIT, 1234);

    // Now pretend it was relayed two times. The relay closest to the server
    // adds link-layer-address information against the RFC, the process fails.
    Pkt6::RelayInfo info1;
1098
    uint8_t opt_data[] = {
1099
1100
1101
        0x00, 0x01,  // Ethertype
        0x1a, 0x30, 0x0b, 0xfa, 0xc0, 0xfe // MAC
    };
1102
    OptionPtr relay_opt1(new Option(Option::V6, D6O_CLIENT_LINKLAYER_ADDR,
1103
                            OptionBuffer(opt_data, opt_data + sizeof(opt_data))));
1104
1105
1106
1107
1108
1109
1110
1111
1112

    info1.options_.insert(make_pair(relay_opt1->getType(), relay_opt1));
    pkt.addRelayInfo(info1);

    // Second relay, closest to the client has not implemented RFC6939
    Pkt6::RelayInfo info2;
    pkt.addRelayInfo(info2);
    ASSERT_EQ(2, pkt.relay_info_.size());

Tomek Mrugalski's avatar
Tomek Mrugalski committed
1113
    EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION));
1114
1115
1116
1117

    // Let's envolve the packet with a third relay (now the closest to the client)
    // that inserts the correct client_linklayer_addr option.
    Pkt6::RelayInfo info3;
1118

1119
1120
    // We reuse the option and modify the MAC to be sure we get the right address
    opt_data[2] = 0xfa;
1121
    OptionPtr relay_opt3(new Option(Option::V6, D6O_CLIENT_LINKLAYER_ADDR,
1122
                            OptionBuffer(opt_data, opt_data + sizeof(opt_data))));
1123
1124
1125
1126
1127
    info3.options_.insert(make_pair(relay_opt3->getType(), relay_opt3));
    pkt.addRelayInfo(info3);
    ASSERT_EQ(3, pkt.relay_info_.size());

    // Now extract the MAC address from the relayed option
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1128
    HWAddrPtr found = pkt.getMAC(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION);
1129
1130
1131
1132
1133
1134
    ASSERT_TRUE(found);

    stringstream tmp;
    tmp << "hwtype=1 fa:30:0b:fa:c0:fe";
    EXPECT_EQ(tmp.str(), found->toText(true));
}
1135

1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
TEST_F(Pkt6Test, getMACFromDUID) {
    Pkt6 pkt(DHCPV6_ADVERTISE, 1234);

    // Although MACs are typically 6 bytes long, let's make this test a bit
    // more challenging and use odd MAC lengths.

    uint8_t duid_llt[] = { 0, 1, // type (DUID-LLT)
                           0, 7, // hwtype (7 - just a randomly picked value)
                           1, 2, 3, 4, // timestamp
                           0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10 // MAC address (7 bytes)
    };

    uint8_t duid_ll[] = { 0, 3, // type (DUID-LL)
                        0, 11, // hwtype (11 - just a randomly picked value)
                        0xa, 0xb, 0xc, 0xd, 0xe // MAC address (5 bytes)
    };

    uint8_t duid_en[] = { 0, 2, // type (DUID-EN)
                        1, 2, 3, 4, // enterprise-id
                        0xa, 0xb, 0xc // opaque data
    };

    OptionPtr clientid1(new Option(Option::V6, D6O_CLIENTID, OptionBuffer(
                                       duid_llt, duid_llt + sizeof(duid_llt))));
    OptionPtr clientid2(new Option(Option::V6, D6O_CLIENTID, OptionBuffer(
                                       duid_ll, duid_ll + sizeof(duid_ll))));
    OptionPtr clientid3(new Option(Option::V6, D6O_CLIENTID, OptionBuffer(
                                       duid_en, duid_en + sizeof(duid_en))));

    // Packet does not have any client-id, this call should fail
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1166
    EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_DUID));
1167
1168
1169

    // Let's test DUID-LLT. This should work.
    pkt.addOption(clientid1);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1170
    HWAddrPtr mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_DUID);
1171
    ASSERT_TRUE(mac);
1172
    EXPECT_EQ("hwtype=7 0a:0b:0c:0d:0e:0f:10", mac->toText(true));
1173
1174
1175
1176

    // Let's test DUID-LL. This should work.
    ASSERT_TRUE(pkt.delOption(D6O_CLIENTID));
    pkt.addOption(clientid2);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1177
    mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_DUID);
1178
    ASSERT_TRUE(mac);
1179
    EXPECT_EQ("hwtype=11 0a:0b:0c:0d:0e", mac->toText(true));
1180
1181
1182
1183
1184

    // Finally, let's try DUID-EN. This should fail, as EN type does not
    // contain any MAC address information.
    ASSERT_TRUE(pkt.delOption(D6O_CLIENTID));
    pkt.addOption(clientid3);
Tomek Mrugalski's avatar
Tomek Mrugalski committed
1185
    EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_DUID));
1186
1187
}

1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
// Test checks whether getMAC(DOCSIS_MODEM) is working properly.
// We only have a small number of actual traffic captures from
// cable networks, so the scope of unit-tests is somewhat limited.
TEST_F(Pkt6Test, getMAC_DOCSIS_Modem) {

    // Let's use a captured traffic. The one we have comes from a
    // modem with MAC address 10:0d:7f:00:07:88.
    Pkt6Ptr pkt = isc::test::PktCaptures::captureDocsisRelayedSolicit();
    ASSERT_NO_THROW(pkt->unpack());

    // The method should return MAC based on the vendor-specific info,
    // suboption 36, which is inserted by the modem itself.
1200
    HWAddrPtr found = pkt->getMAC(HWAddr::HWADDR_SOURCE_DOCSIS_MODEM);
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
    ASSERT_TRUE(found);

    // Let's check the info.
    EXPECT_EQ("hwtype=1 10:0d:7f:00:07:88", found->toText(true));

    // Now let's remove the option
    OptionVendorPtr vendor = boost::dynamic_pointer_cast<
        OptionVendor>(pkt->getOption(D6O_VENDOR_OPTS));
    ASSERT_TRUE(vendor);
    ASSERT_TRUE(vendor->delOption(DOCSIS3_V6_DEVICE_ID));

    // Ok, there's no more suboption 36. Now getMAC() should fail.
1213
    EXPECT_FALSE(pkt->getMAC(HWAddr::HWADDR_SOURCE_DOCSIS_MODEM));
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
}

// Test checks whether getMAC(DOCSIS_CMTS) is working properly.
// We only have a small number of actual traffic captures from
// cable networks, so the scope of unit-tests is somewhat limited.
TEST_F(Pkt6Test, getMAC_DOCSIS_CMTS) {

    // Let's use a captured traffic. The one we have comes from a
    // modem with MAC address 20:e5:2a:b8:15:14.
    Pkt6Ptr pkt = isc::test::PktCaptures::captureeRouterRelayedSolicit();
    ASSERT_NO_THROW(pkt->unpack());

    // The method should return MAC based on the vendor-specific info,
    // suboption 36, which is inserted by the modem itself.
1228
    HWAddrPtr found = pkt->getMAC(HWAddr::HWADDR_SOURCE_DOCSIS_CMTS);
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
    ASSERT_TRUE(found);

    // Let's check the info.
    EXPECT_EQ("hwtype=1 20:e5:2a:b8:15:14", found->toText(true));

    // Now let's remove the suboption 1026 that is inserted by the
    // relay.
    OptionVendorPtr vendor = boost::dynamic_pointer_cast<
        OptionVendor>(pkt->getAnyRelayOption(D6O_VENDOR_OPTS,
                          isc::dhcp::Pkt6::RELAY_SEARCH_FROM_CLIENT));
    ASSERT_TRUE(vendor);
    EXPECT_TRUE(vendor->delOption(DOCSIS3_V6_CMTS_CM_MAC));

1242
    EXPECT_FALSE(pkt->getMAC(HWAddr::HWADDR_SOURCE_DOCSIS_CMTS));
1243
1244
}

1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
// Test checks whether getMACFromRemoteIdRelayOption() returns the hardware (MAC)
// address properly from a relayed message.
TEST_F(Pkt6Test, getMACFromRemoteIdRelayOption) {

    // Create a solicit message.
    Pkt6 pkt(DHCPV6_SOLICIT, 1234);

    // This should fail as the message is't relayed yet.
    EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID));

    // Let's get the first interface
    Iface* iface = IfaceMgr::instance().getIface(1);
    ASSERT_TRUE(iface);

    // and set source interface data properly. getMACFromIPv6LinkLocal attempts
    // to use source interface to obtain hardware type
    pkt.setIface(iface->getName());
    pkt.setIndex(iface->getIndex());

    // Generate option data with randomly picked enterprise number and MAC address
    const uint8_t opt_data[] = {
        1, 2, 3, 4,  // enterprise-number
        0xa, 0xb, 0xc, 0xd, 0xe, 0xf // MAC
    };

    // Create option with number 37 (remote-id relay agent option)
    OptionPtr relay_opt(new Option(Option::V6, D6O_REMOTE_ID,
                        OptionBuffer(opt_data, opt_data + sizeof(opt_data))));

    // First simulate relaying message without adding remote-id option
    Pkt6::RelayInfo info;
    pkt.addRelayInfo(info);
    ASSERT_EQ(1, pkt.relay_info_.size());

    // This should fail as the remote-id option isn't there
    EXPECT_FALSE(pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID));

    // Now add this option to the relayed message
    info.options_.insert(make_pair(relay_opt->getType(), relay_opt));
    pkt.addRelayInfo(info);
    ASSERT_EQ(2, pkt.relay_info_.size());

    // This should work now
    HWAddrPtr mac = pkt.getMAC(HWAddr::HWADDR_SOURCE_REMOTE_ID);
    ASSERT_TRUE(mac);

    stringstream tmp;
    tmp << "hwtype=" << (int)iface->getHWType() << " 0a:0b:0c:0d:0e:0f";

    EXPECT_EQ(tmp.str(), mac->toText(true));
}

1297
}