fqdn_unittest.cc 64.2 KB
Newer Older
1
// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
2
//
3
4
5
// 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/.
6
7
8
9

#include <config.h>
#include <asiolink/io_address.h>
#include <dhcp/option4_client_fqdn.h>
10
#include <dhcp/option_int_array.h>
11
#include <dhcp/tests/iface_mgr_test_config.h>
12
#include <dhcp4/tests/dhcp4_client.h>
13
#include <dhcp4/tests/dhcp4_test_utils.h>
14
#include <dhcp_ddns/ncr_msg.h>
15
#include <dhcpsrv/cfgmgr.h>
16
17
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
18
19
20
21
22
23
24

#include <gtest/gtest.h>
#include <boost/scoped_ptr.hpp>

using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
25
using namespace isc::dhcp::test;
26
using namespace isc::dhcp_ddns;
27
28

namespace {
29

30
31
32
33
34
35
36
37
38
39
40
41
/// @brief Set of JSON configurations used by the FQDN tests.
const char* CONFIGS[] = {
    "{ \"interfaces-config\": {"
        "      \"interfaces\": [ \"*\" ]"
        "},"
        "\"valid-lifetime\": 3000,"
        "\"subnet4\": [ { "
        "    \"subnet\": \"10.0.0.0/24\", "
        "    \"id\": 1,"
        "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
        "    \"option-data\": [ {"
        "        \"name\": \"routers\","
42
        "        \"data\": \"10.0.0.200,10.0.0.201\""
43
44
45
46
47
48
49
50
51
52
        "    } ],"
        "    \"reservations\": ["
        "       {"
        "         \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
        "         \"hostname\":   \"unique-host.example.org\""
        "       }"
        "    ]"
        " }],"
        "\"dhcp-ddns\": {"
            "\"enable-updates\": true,"
53
            "\"qualifying-suffix\": \"\""
54
55
56
57
58
59
60
61
62
63
64
65
        "}"
    "}",
    "{ \"interfaces-config\": {"
        "      \"interfaces\": [ \"*\" ]"
        "},"
        "\"valid-lifetime\": 3000,"
        "\"subnet4\": [ { "
        "    \"subnet\": \"10.0.0.0/24\", "
        "    \"id\": 1,"
        "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
        "    \"option-data\": [ {"
        "        \"name\": \"routers\","
66
        "        \"data\": \"10.0.0.200,10.0.0.201\""
67
68
69
70
        "    } ],"
        "    \"reservations\": ["
        "       {"
        "         \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
71
        "         \"hostname\":   \"foobar\""
72
73
74
75
76
77
78
        "       }"
        "    ]"
        " }],"
        "\"dhcp-ddns\": {"
            "\"enable-updates\": true,"
            "\"qualifying-suffix\": \"fake-suffix.isc.org.\""
        "}"
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
    "}",
    // Simple config with DDNS updates disabled.  Note pool is one address
    // large to ensure we get a specific address back.
    "{ \"interfaces-config\": {"
        "      \"interfaces\": [ \"*\" ]"
        "},"
        "\"valid-lifetime\": 3000,"
        "\"subnet4\": [ { "
        "    \"subnet\": \"10.0.0.0/24\", "
        "    \"id\": 1,"
        "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.10\" } ]"
        " }],"
        "\"dhcp-ddns\": {"
            "\"enable-updates\": false,"
            "\"qualifying-suffix\": \"fake-suffix.isc.org.\""
        "}"
    "}",
    // Simple config with DDNS updates enabled.  Note pool is one address
    // large to ensure we get a specific address back.
    "{ \"interfaces-config\": {"
        "      \"interfaces\": [ \"*\" ]"
        "},"
        "\"valid-lifetime\": 3000,"
        "\"subnet4\": [ { "
        "    \"subnet\": \"10.0.0.0/24\", "
        "    \"id\": 1,"
        "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.10\" } ]"
        " }],"
        "\"dhcp-ddns\": {"
            "\"enable-updates\": true,"
            "\"qualifying-suffix\": \"fake-suffix.isc.org.\""
        "}"
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
    "}",
    // Configuration which disables DNS updates but contains a reservation
    // for a hostname. Reserved hostname should be assigned to a client if
    // the client includes it in the Parameter Request List option.
    "{ \"interfaces-config\": {"
        "      \"interfaces\": [ \"*\" ]"
        "},"
        "\"valid-lifetime\": 3000,"
        "\"subnet4\": [ { "
        "    \"subnet\": \"10.0.0.0/24\", "
        "    \"id\": 1,"
        "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
        "    \"option-data\": [ {"
        "        \"name\": \"routers\","
        "        \"data\": \"10.0.0.200,10.0.0.201\""
        "    } ],"
        "    \"reservations\": ["
        "       {"
        "         \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
        "         \"hostname\":   \"reserved.example.org\""
        "       }"
        "    ]"
        " }],"
        "\"dhcp-ddns\": {"
            "\"enable-updates\": false,"
            "\"qualifying-suffix\": \"\""
        "}"
    "}",
    // Configuration which disables DNS updates but contains a reservation
    // for a hostname and the qualifying-suffix which should be appended to
    // the reserved hostname in the Hostname option returned to a client.
    "{ \"interfaces-config\": {"
        "      \"interfaces\": [ \"*\" ]"
        "},"
        "\"valid-lifetime\": 3000,"
        "\"subnet4\": [ { "
        "    \"subnet\": \"10.0.0.0/24\", "
        "    \"id\": 1,"
        "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
        "    \"option-data\": [ {"
        "        \"name\": \"routers\","
        "        \"data\": \"10.0.0.200,10.0.0.201\""
        "    } ],"
        "    \"reservations\": ["
        "       {"
        "         \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
        "         \"hostname\":   \"foo-bar\""
        "       }"
        "    ]"
        " }],"
        "\"dhcp-ddns\": {"
            "\"enable-updates\": false,"
            "\"qualifying-suffix\": \"example.isc.org\""
        "}"
165
166
167
    "}"
};

168
class NameDhcpv4SrvTest : public Dhcpv4SrvTest {
169
public:
170
171
172
    // Reference to D2ClientMgr singleton
    D2ClientMgr& d2_mgr_;

173
174
175
176
177
178
    /// @brief Pointer to the DHCP server instance.
    NakedDhcpv4Srv* srv_;

    /// @brief Interface Manager's fake configuration control.
    IfaceMgrTestConfig iface_mgr_test_config_;

179
    // Bit Constants for turning on and off DDNS configuration options.
180
181
182
183
    static const uint16_t ALWAYS_INCLUDE_FQDN = 1;
    static const uint16_t OVERRIDE_NO_UPDATE = 2;
    static const uint16_t OVERRIDE_CLIENT_UPDATE = 4;
    static const uint16_t REPLACE_CLIENT_NAME = 8;
184

185
186
187
188
189
190
191
192
193
194
195
196
197
198
    // Enum used to specify whether a client (packet) should include
    // the hostname option
    enum ClientNameFlag {
        CLIENT_NAME_PRESENT,
        CLIENT_NAME_NOT_PRESENT
    };

    // Enum used to specify whether the server should replace/supply
    // the hostname or not
    enum ReplacementFlag {
        NAME_REPLACED,
        NAME_NOT_REPLACED
    };

199
200
201
202
203
204
    NameDhcpv4SrvTest()
        : Dhcpv4SrvTest(),
          d2_mgr_(CfgMgr::instance().getD2ClientMgr()),
          srv_(NULL),
          iface_mgr_test_config_(true)
    {
205
        srv_ = new NakedDhcpv4Srv(0);
206
        IfaceMgr::instance().openSockets4();
207
208
        // Config DDNS to be enabled, all controls off
        enableD2();
209
    }
210

211
    virtual ~NameDhcpv4SrvTest() {
212
        delete srv_;
213
        // CfgMgr singleton doesn't get wiped between tests, so  we'll
Francis Dupont's avatar
Francis Dupont committed
214
        // disable D2 explicitly between tests.
215
        disableD2();
216
217
    }

218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
    /// @brief Sets the server's DDNS configuration to ddns updates disabled.
    void disableD2() {
        // Default constructor creates a config with DHCP-DDNS updates
        // disabled.
        D2ClientConfigPtr cfg(new D2ClientConfig());
        CfgMgr::instance().setD2ClientConfig(cfg);
    }

    /// @brief Enables DHCP-DDNS updates with the given options enabled.
    ///
    /// Replaces the current D2ClientConfiguration with a configuration
    /// which as updates enabled and the control options set based upon
    /// the bit mask of options.
    ///
    /// @param mask Bit mask of configuration options that should be enabled.
    void enableD2(const uint16_t mask = 0) {
        D2ClientConfigPtr cfg;

        ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
237
                                  isc::asiolink::IOAddress("127.0.0.1"), 53001,
238
                                  isc::asiolink::IOAddress("0.0.0.0"), 0, 1024,
239
240
241
242
                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                  (mask & ALWAYS_INCLUDE_FQDN),
                                  (mask & OVERRIDE_NO_UPDATE),
                                  (mask & OVERRIDE_CLIENT_UPDATE),
243
                                  ((mask & REPLACE_CLIENT_NAME) ?
244
245
                                    D2ClientConfig::RCM_WHEN_PRESENT
                                   : D2ClientConfig::RCM_NEVER),
246
                                  "myhost", "example.com")));
247
248
        ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(cfg));
        ASSERT_NO_THROW(srv_->startD2());
249
250
    }

251
252
253
254
255
    // Create a lease to be used by various tests.
    Lease4Ptr createLease(const isc::asiolink::IOAddress& addr,
                          const std::string& hostname,
                          const bool fqdn_fwd,
                          const bool fqdn_rev) {
256
        const uint8_t hwaddr_data[] = { 0, 1, 2, 3, 4, 5, 6 };
257
258
        HWAddrPtr hwaddr(new HWAddr(hwaddr_data, sizeof(hwaddr_data),
                                    HTYPE_ETHER));
259
        Lease4Ptr lease(new Lease4(addr, hwaddr,
260
261
262
263
264
265
266
267
268
269
270
                                   &generateClientId()->getData()[0],
                                   generateClientId()->getData().size(),
                                   100, 50, 75, time(NULL), subnet_->getID()));
        // @todo Set this through the Lease4 constructor.
        lease->hostname_ = hostname;
        lease->fqdn_fwd_ = fqdn_fwd;
        lease->fqdn_rev_ = fqdn_rev;

        return (lease);
    }

271
272
273
274
    // Create an instance of the DHCPv4 Client FQDN Option.
    Option4ClientFqdnPtr
    createClientFqdn(const uint8_t flags,
                     const std::string& fqdn_name,
275
                     Option4ClientFqdn::DomainNameType fqdn_type) {
276
277
278
279
280
        return (Option4ClientFqdnPtr(new Option4ClientFqdn(flags,
                                                           Option4ClientFqdn::
                                                           RCODE_CLIENT(),
                                                           fqdn_name,
                                                           fqdn_type)));
281
282
283
   }

    // Create an instance of the Hostname option.
284
    OptionStringPtr
285
    createHostname(const std::string& hostname) {
286
287
288
        OptionStringPtr opt_hostname(new OptionString(Option::V4,
                                                      DHO_HOST_NAME,
                                                      hostname));
289
        return (opt_hostname);
290
291
    }

292
293
294
295
296
297
298
299
    /// @brief Convenience method for generating an FQDN from an IP address.
    ///
    /// This is just a wrapper method around the D2ClientMgr's method for
    /// generating domain names from the configured prefix, suffix, and a
    /// given IP address.  This is useful for verifying that fully generated
    /// names are correct.
    ///
    /// @param addr IP address used in the lease.
300
301
    /// @param trailing_dot A boolean flag which indicates whether the
    /// trailing dot should be appended to the end of the hostname.
Francis Dupont's avatar
Francis Dupont committed
302
    /// The default value is "true" which means that it should.
303
304
    ///
    /// @return An std::string contained the generated FQDN.
305
306
307
308
    std::string generatedNameFromAddress(const IOAddress& addr,
                                         const bool trailing_dot = true) {
        return(CfgMgr::instance().getD2ClientMgr()
               .generateFqdn(addr, trailing_dot));
309
310
    }

311
    // Get the Client FQDN Option from the given message.
312
313
314
315
316
    Option4ClientFqdnPtr getClientFqdnOption(const Pkt4Ptr& pkt) {
        return (boost::dynamic_pointer_cast<
                Option4ClientFqdn>(pkt->getOption(DHO_FQDN)));
    }

317
    // get the Hostname option from the given message.
318
    OptionStringPtr getHostnameOption(const Pkt4Ptr& pkt) {
319
        return (boost::dynamic_pointer_cast<
320
                OptionString>(pkt->getOption(DHO_HOST_NAME)));
321
322
    }

323
324
325
326
    // Create a message holding DHCPv4 Client FQDN Option.
    Pkt4Ptr generatePktWithFqdn(const uint8_t msg_type,
                                const uint8_t fqdn_flags,
                                const std::string& fqdn_domain_name,
327
                                Option4ClientFqdn::DomainNameType fqdn_type,
328
329
                                const bool include_prl,
                                const bool include_clientid = true) {
330
331
        Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(msg_type, 1234));
        pkt->setRemoteAddr(IOAddress("192.0.2.3"));
332
        pkt->setIface("eth1");
333
334
335
336
337
        // For DISCOVER we don't include server id, because client broadcasts
        // the message to all servers.
        if (msg_type != DHCPDISCOVER) {
            pkt->addOption(srv_->getServerID());
        }
338
339
340
341

        if (include_clientid) {
            pkt->addOption(generateClientId());
        }
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359

        // Create Client FQDN Option with the specified flags and
        // domain-name.
        pkt->addOption(createClientFqdn(fqdn_flags, fqdn_domain_name,
                                        fqdn_type));

        // Control whether or not to request that server returns the FQDN
        // option. Server may be configured to always return it or return
        // only in case client requested it.
        if (include_prl) {
            OptionUint8ArrayPtr option_prl =
                OptionUint8ArrayPtr(new OptionUint8Array(Option::V4,
                                    DHO_DHCP_PARAMETER_REQUEST_LIST));
            option_prl->addValue(DHO_FQDN);
        }
        return (pkt);
    }

360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
    // Create a message holding a Hostname option.
    Pkt4Ptr generatePktWithHostname(const uint8_t msg_type,
                                    const std::string& hostname) {

        Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(msg_type, 1234));
        pkt->setRemoteAddr(IOAddress("192.0.2.3"));
        // For DISCOVER we don't include server id, because client broadcasts
        // the message to all servers.
        if (msg_type != DHCPDISCOVER) {
            pkt->addOption(srv_->getServerID());
        }

        pkt->addOption(generateClientId());


        // Create Client FQDN Option with the specified flags and
        // domain-name.
        pkt->addOption(createHostname(hostname));

        return (pkt);

381
    }
382

383
384
385
386
387
388
389
390
391
392
393
394
395
396
    // Create a message holding of a given type
    Pkt4Ptr generatePkt(const uint8_t msg_type) {
        Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(msg_type, 1234));
        pkt->setRemoteAddr(IOAddress("192.0.2.3"));
        // For DISCOVER we don't include server id, because client broadcasts
        // the message to all servers.
        if (msg_type != DHCPDISCOVER) {
            pkt->addOption(srv_->getServerID());
        }

        pkt->addOption(generateClientId());
        return (pkt);
    }

397
398
399
    // Test that server generates the appropriate FQDN option in response to
    // client's FQDN option.
    void testProcessFqdn(const Pkt4Ptr& query, const uint8_t exp_flags,
400
                         const std::string& exp_domain_name,
401
                         Option4ClientFqdn::DomainNameType
402
                         exp_domain_type = Option4ClientFqdn::FULL) {
403
404
405
406
407
408
409
410
411
412
        ASSERT_TRUE(getClientFqdnOption(query));

        Pkt4Ptr answer;
        if (query->getType() == DHCPDISCOVER) {
            answer.reset(new Pkt4(DHCPOFFER, 1234));

        } else {
            answer.reset(new Pkt4(DHCPACK, 1234));

        }
413
        Dhcpv4Exchange ex = createExchange(query);
414
        ASSERT_NO_THROW(srv_->processClientName(ex));
415

416
        Option4ClientFqdnPtr fqdn = getClientFqdnOption(ex.getResponse());
417
418
        ASSERT_TRUE(fqdn);

419
        checkFqdnFlags(ex.getResponse(), exp_flags);
420
421
422
423
424
425

        EXPECT_EQ(exp_domain_name, fqdn->getDomainName());
        EXPECT_EQ(exp_domain_type, fqdn->getDomainNameType());

    }

426
427
428
429
    // Test that the server's processes the hostname (or lack thereof)
    // in a client request correctly, according to the replace-client-name
    // mode configuration parameter.
    //
430
    // @param mode - value to use for replace-client-name
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
    // @param client_name_flag - specifies whether or not the client request
    // should contain a hostname option
    // @param exp_replacement_flag - specifies whether or not the server is
    // expected to replace (or supply) the hostname in its response
    void testReplaceClientNameMode(const char* mode,
                                   enum ClientNameFlag client_name_flag,
                                   enum ReplacementFlag exp_replacement_flag) {
        // Configuration "template" with a replaceable mode parameter
        const char* config_template =
            "{ \"interfaces-config\": {"
            "      \"interfaces\": [ \"*\" ]"
            "},"
            "\"valid-lifetime\": 3000,"
            "\"subnet4\": [ { "
            "    \"subnet\": \"10.0.0.0/24\", "
            "    \"id\": 1,"
            "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.10\" } ]"
            " }],"
            "\"dhcp-ddns\": {"
            "\"enable-updates\": true,"
            "\"qualifying-suffix\": \"fake-suffix.isc.org.\","
452
            "\"replace-client-name\": \"%s\""
453
454
455
456
457
            "}}";

        // Create the configuration and configure the server
        char config_buf[1024];
        sprintf(config_buf, config_template, mode);
458
        ASSERT_NO_THROW(configure(config_buf, srv_)) << "configuration failed";
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475

        // Build our client packet
        Pkt4Ptr query;
        if (client_name_flag == CLIENT_NAME_PRESENT) {
            ASSERT_NO_THROW(query = generatePktWithHostname(DHCPREQUEST,
                                                            "my.example.com."));
        } else {
            ASSERT_NO_THROW(query = generatePkt(DHCPREQUEST));
        }

        // Run the packet through the server, extracting the hostname option
        // from the response.  If the option isn't present the returned pointer
        // will be null.
        OptionStringPtr hostname;
        ASSERT_NO_THROW(
            hostname = processHostname(query,
                                       client_name_flag == CLIENT_NAME_PRESENT)
476
        ) << "processHostname throw an exception";
477
478
479

        // Verify the contents (or lack thereof) of the hostname
        if (exp_replacement_flag == NAME_REPLACED) {
480
481
            ASSERT_TRUE(hostname)
                << "No host name, it should have the replacement name \".\"";
482
483
484
            EXPECT_EQ(".", hostname->getValue());
        } else {
            if (client_name_flag == CLIENT_NAME_PRESENT) {
485
486
                ASSERT_TRUE(hostname)
                    << "No host name, expected original from client";
487
488
                EXPECT_EQ("my.example.com.", hostname->getValue());
            } else {
489
490
491
                ASSERT_FALSE(hostname)
                    << "Host name is: " << hostname
                    << ", it should have been null";
492
493
494
495
            }
        }
    }

496
497
498
499
500
501
502
503
    /// @brief Checks the packet's FQDN option flags against a given mask
    ///
    /// @param pkt IPv4 packet whose FQDN flags should be checked.
    /// @param exp_flags Bit mask of flags that are expected to be true.
    void checkFqdnFlags(const Pkt4Ptr& pkt, const uint8_t exp_flags) {
        Option4ClientFqdnPtr fqdn = getClientFqdnOption(pkt);
        ASSERT_TRUE(fqdn);

504
505
506
507
508
509
510
511
512
513
514
        const bool flag_n = (exp_flags & Option4ClientFqdn::FLAG_N) != 0;
        const bool flag_s = (exp_flags & Option4ClientFqdn::FLAG_S) != 0;
        const bool flag_o = (exp_flags & Option4ClientFqdn::FLAG_O) != 0;
        const bool flag_e = (exp_flags & Option4ClientFqdn::FLAG_E) != 0;

        EXPECT_EQ(flag_n, fqdn->getFlag(Option4ClientFqdn::FLAG_N));
        EXPECT_EQ(flag_s, fqdn->getFlag(Option4ClientFqdn::FLAG_S));
        EXPECT_EQ(flag_o, fqdn->getFlag(Option4ClientFqdn::FLAG_O));
        EXPECT_EQ(flag_e, fqdn->getFlag(Option4ClientFqdn::FLAG_E));
    }

515

516
    /// @brief  Invokes Dhcpv4Srv::processHostname on the given packet
517
518
519
520
521
522
523
524
525
526
527
528
    ///
    /// Processes the Hostname option in the client's message and returns
    /// the hostname option which would be sent to the client. It will
    /// return empty if the hostname option is not to be included
    /// server's response.
    /// @param query - client packet to process
    /// @param must_have_host - flag indicating whether or not the client
    /// packet must contain the hostname option
    ///
    /// @return a pointer to the hostname option constructed by the server
    OptionStringPtr processHostname(const Pkt4Ptr& query,
                                    bool must_have_host = true) {
529
        if (!getHostnameOption(query) && must_have_host) {
530
531
            ADD_FAILURE() << "Hostname option not carried in the query";
        }
532
533
534
535
536
537
538
539
540

        Pkt4Ptr answer;
        if (query->getType() == DHCPDISCOVER) {
            answer.reset(new Pkt4(DHCPOFFER, 1234));

        } else {
            answer.reset(new Pkt4(DHCPACK, 1234));

        }
541
        Dhcpv4Exchange ex = createExchange(query);
542
        srv_->processClientName(ex);
543

544
        OptionStringPtr hostname = getHostnameOption(ex.getResponse());
545
        return (hostname);
546
547
548

    }

549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
    ///@brief Verify that NameChangeRequest holds valid values.
    ///
    /// Pulls the NCR from the top of the send queue and checks it's content
    ///  against a number of expected parameters.
    ///
    /// @param type - expected NCR change type, CHG_ADD or CHG_REMOVE
    /// @param reverse - flag indicating whether or not the NCR specifies
    /// reverse change
    /// @param forward - flag indication whether or not the NCR specifies
    /// forward change
    /// @param addr  - expected lease address in the NCR
    /// @param fqdn  - expected FQDN in the NCR
    /// @param dhcid - expected DHCID in the NCR (comparison is performed only
    /// if the value supplied is not empty):w
    /// @param cltt - cltt value from the lease the NCR for which the NCR
    /// was generated expected value for
    /// @param len - expected lease length in the NCR
    /// @param not_strict_expire_check - when true the comparison of the NCR
    /// lease expiration time is conducted as greater than or equal to rather
    /// equal to CLTT plus lease lenght.
569
570
571
    void verifyNameChangeRequest(const isc::dhcp_ddns::NameChangeType type,
                                 const bool reverse, const bool forward,
                                 const std::string& addr,
572
                                 const std::string& fqdn,
573
                                 const std::string& dhcid,
574
575
576
                                 const time_t cltt,
                                 const uint16_t len,
                                 const bool not_strict_expire_check = false) {
577
578
579
580
581
582
583
584
585
        NameChangeRequestPtr ncr;
        ASSERT_NO_THROW(ncr = d2_mgr_.peekAt(0));
        ASSERT_TRUE(ncr);

        EXPECT_EQ(type, ncr->getChangeType());
        EXPECT_EQ(forward, ncr->isForwardChange());
        EXPECT_EQ(reverse, ncr->isReverseChange());
        EXPECT_EQ(addr, ncr->getIpAddress());
        EXPECT_EQ(fqdn, ncr->getFqdn());
586
587
588
        // Compare dhcid if it is not empty. In some cases, the DHCID is
        // not known in advance and can't be compared.
        if (!dhcid.empty()) {
589
            EXPECT_EQ(dhcid, ncr->getDhcid().toStr());
590
        }
591
592
        // In some cases, the test doesn't have access to the last transmission
        // time for the particular client. In such cases, the test can use the
593
594
595
        // current time as cltt but the it may not check the lease expiration
        // time for equality but rather check that the lease expiration time
        // is not greater than the current time + lease lifetime.
596
        if (not_strict_expire_check) {
597
            EXPECT_GE(cltt + len, ncr->getLeaseExpiresOn());
598
        } else {
599
            EXPECT_EQ(cltt + len, ncr->getLeaseExpiresOn());
600
        }
601
602
603
604
605
        EXPECT_EQ(len, ncr->getLeaseLength());
        EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr->getStatus());

        // Process the message off the queue
        ASSERT_NO_THROW(d2_mgr_.runReadyIO());
606
607
    }

608
609
610
611
612
613
614
615
616
617
618
619
620
621

    /// @brief Tests processing a request with the given client flags
    ///
    /// This method creates a request with its FQDN flags set to the given
    /// value and submits it to the server for processing.  It then checks
    /// the following:
    /// 1. Did the server generate an ACK with the correct FQDN flags
    /// 2. If the server should have generated an NCR, did it? and If
    /// so was it correct?
    ///
    /// @param client_flags Mask of client FQDN flags which are true
    /// @param response_flags Mask of expected FQDN flags in the response
    void flagVsConfigScenario(const uint8_t client_flags,
                       const uint8_t response_flags) {
622
623
624
625
        // Create fake interfaces and open fake sockets.
        IfaceMgrTestConfig iface_config(true);
        IfaceMgr::instance().openSockets4();

626
627
628
629
630
631
632
633
634
635
636
637
        Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, client_flags,
                                          "myhost.example.com.",
                                          Option4ClientFqdn::FULL, true);

        // Process the request.
        Pkt4Ptr reply;
        ASSERT_NO_THROW(reply = srv_->processRequest(req));

        // Verify the response and flags.
        checkResponse(reply, DHCPACK, 1234);
        checkFqdnFlags(reply, response_flags);

638
639
        // NCRs cannot be sent to the d2_mgr unless updates are enabled.
        if (d2_mgr_.ddnsEnabled()) {
640
641
642
643
            // There should be an NCR if response S flag is 1 or N flag is 0.
            bool exp_fwd = (response_flags & Option4ClientFqdn::FLAG_S);
            bool exp_rev = (!(response_flags & Option4ClientFqdn::FLAG_N));
            if (!exp_fwd && !exp_rev) {
644
645
646
647
                ASSERT_EQ(0, d2_mgr_.getQueueSize());
            } else {
                // Verify that there is one NameChangeRequest as expected.
                ASSERT_EQ(1, d2_mgr_.getQueueSize());
648
649
                verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD,
                                        exp_rev, exp_fwd,
650
651
652
653
654
655
                                        reply->getYiaddr().toText(),
                                        "myhost.example.com.",
                                        "", // empty DHCID means don't check it
                                        time(NULL) + subnet_->getValid(),
                                        subnet_->getValid(), true);
            }
656
657
        }
    }
658
659
};

660
661
662
663
664
665
// Tests the following scenario:
//  - Updates are enabled
//  - All overrides are off
//  - Client requests forward update  (N = 0, S = 1)
//
//  Server should perform the update:
Francis Dupont's avatar
Francis Dupont committed
666
//  - Response flags should N = 0, S = 1, O = 0
667
668
669
670
671
672
673
//  - Should queue an NCR
TEST_F(NameDhcpv4SrvTest, updatesEnabled) {
    flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
                          Option4ClientFqdn::FLAG_S),
                          (Option4ClientFqdn::FLAG_E |
                           Option4ClientFqdn::FLAG_S));
}
674

675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
// Tests the following scenario
//  - Updates are disabled
//  - Client requests forward update  (N = 0, S = 1)
//
//  Server should NOT perform updates:
//   - Response flags should N = 1, S = 0, O = 1
//   - Should not queue any NCRs
TEST_F(NameDhcpv4SrvTest, updatesDisabled) {
    disableD2();
    flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
                          Option4ClientFqdn::FLAG_S),
                          (Option4ClientFqdn::FLAG_E |
                           Option4ClientFqdn::FLAG_N |
                           Option4ClientFqdn::FLAG_O));
}
690

691
692
693
694
695
696
697
698
699
700
701
702
703
704
// Tests the following scenario:
//  - Updates are enabled
//  - All overrides are off.
//  - Client requests no updates  (N = 1, S = 0)
//
//  Server should NOT perform updates:
//  - Response flags should N = 1, S = 0, O = 0
//  - Should not queue any NCRs
TEST_F(NameDhcpv4SrvTest, respectNoUpdate) {
    flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
                          Option4ClientFqdn::FLAG_N),
                          (Option4ClientFqdn::FLAG_E |
                           Option4ClientFqdn::FLAG_N));
}
705

706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
// Tests the following scenario:
//  - Updates are enabled
//  - override-no-update is on
//  - Client requests no updates  (N = 1, S = 0)
//
// Server should override "no update" request and perform updates:
// - Response flags should be  N = 0, S = 1, O = 1
// - Should queue an NCR
TEST_F(NameDhcpv4SrvTest, overrideNoUpdate) {
    enableD2(OVERRIDE_NO_UPDATE);
    flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
                          Option4ClientFqdn::FLAG_N),
                          (Option4ClientFqdn::FLAG_E |
                           Option4ClientFqdn::FLAG_S |
                           Option4ClientFqdn::FLAG_O));
}

// Tests the following scenario:
//  - Updates are enabled
//  - All overrides are off.
//  - Client requests delegation  (N = 0, S = 0)
//
// Server should respect client's delegation request and NOT do updates:

730
// - Response flags should be  N = 0, S = 0, O = 0
731
732
733
734
// - Should not queue any NCRs
TEST_F(NameDhcpv4SrvTest, respectClientDelegation) {

    flagVsConfigScenario(Option4ClientFqdn::FLAG_E,
735
                         Option4ClientFqdn::FLAG_E);
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
}

// Tests the following scenario:
//  - Updates are enabled
//  - override-client-update is on.
//  - Client requests delegation  (N = 0, S = 0)
//
// Server should override client's delegation request and do updates:
// - Response flags should be  N = 0, S = 1, O = 1
// - Should queue an NCR
TEST_F(NameDhcpv4SrvTest, overrideClientDelegation) {
    // Turn on override-client-update.
    enableD2(OVERRIDE_CLIENT_UPDATE);

    flagVsConfigScenario(Option4ClientFqdn::FLAG_E,
                         (Option4ClientFqdn::FLAG_E |
                          Option4ClientFqdn::FLAG_S |
                          Option4ClientFqdn::FLAG_O));
754
755
}

756
// Test that server processes the Hostname option sent by a client and
757
758
// responds with the Hostname option to confirm that the server has
// taken responsibility for the update.
759
TEST_F(NameDhcpv4SrvTest, serverUpdateHostname) {
760
761
762
    Pkt4Ptr query;
    ASSERT_NO_THROW(query = generatePktWithHostname(DHCPREQUEST,
                                                    "myhost.example.com."));
763
    OptionStringPtr hostname;
764
765
766
    ASSERT_NO_THROW(hostname = processHostname(query));

    ASSERT_TRUE(hostname);
767
    EXPECT_EQ("myhost.example.com.", hostname->getValue());
768
769
770
771
772
773
774
775

}

// Test that the server skips processing of a wrong Hostname option.
TEST_F(NameDhcpv4SrvTest, serverUpdateWrongHostname) {
    Pkt4Ptr query;
    ASSERT_NO_THROW(query = generatePktWithHostname(DHCPREQUEST,
                                                    "abc..example.com"));
776
    OptionStringPtr hostname;
777
778
    ASSERT_NO_THROW(hostname = processHostname(query));
    EXPECT_FALSE(hostname);
779
780
}

781

782
783
// Test that server generates the fully qualified domain name for the client
// if client supplies the partial name.
784
TEST_F(NameDhcpv4SrvTest, serverUpdateForwardPartialNameFqdn) {
785
786
787
788
789
790
791
792
793
794
795
796
797
    Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST,
                                        Option4ClientFqdn::FLAG_E |
                                        Option4ClientFqdn::FLAG_S,
                                        "myhost",
                                        Option4ClientFqdn::PARTIAL,
                                        true);

    testProcessFqdn(query,
                    Option4ClientFqdn::FLAG_E | Option4ClientFqdn::FLAG_S,
                    "myhost.example.com.");

}

798
799
800
// Test that server generates the fully qualified domain name for the client
// if client supplies the unqualified name in the Hostname option.
TEST_F(NameDhcpv4SrvTest, serverUpdateUnqualifiedHostname) {
801
802
    Pkt4Ptr query;
    ASSERT_NO_THROW(query = generatePktWithHostname(DHCPREQUEST, "myhost"));
803
    OptionStringPtr hostname;
804
805
806
    ASSERT_NO_THROW(hostname =  processHostname(query));

    ASSERT_TRUE(hostname);
807
    EXPECT_EQ("myhost.example.com", hostname->getValue());
808

809
810
}

811
812
813
814
// Test that server sets empty domain-name in the FQDN option when client
// supplied no domain-name. The domain-name is supposed to be set after the
// lease is acquired. The domain-name is then generated from the IP address
// assigned to a client.
815
TEST_F(NameDhcpv4SrvTest, serverUpdateForwardNoNameFqdn) {
816
817
818
819
820
821
822
823
824
    Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST,
                                        Option4ClientFqdn::FLAG_E |
                                        Option4ClientFqdn::FLAG_S,
                                        "",
                                        Option4ClientFqdn::PARTIAL,
                                        true);

    testProcessFqdn(query,
                    Option4ClientFqdn::FLAG_E | Option4ClientFqdn::FLAG_S,
825
                    "", Option4ClientFqdn::PARTIAL);
826
827
828

}

829
830
// Test that exactly one NameChangeRequest is generated when the new lease
// has been acquired (old lease is NULL).
831
TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsNewLease) {
832
833
834
835
836
    Lease4Ptr lease = createLease(IOAddress("192.0.2.3"), "myhost.example.com.",
                                  true, true);
    Lease4Ptr old_lease;

    ASSERT_NO_THROW(srv_->createNameChangeRequests(lease, old_lease));
837
    ASSERT_EQ(1, d2_mgr_.getQueueSize());
838
839

    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
840
841
842
                            "192.0.2.3", "myhost.example.com.",
                            "00010132E91AA355CFBB753C0F0497A5A940436965"
                            "B68B6D438D98E680BF10B09F3BCF",
843
                            lease->cltt_, 100);
844
845
846
847
}

// Test that no NameChangeRequest is generated when a lease is renewed and
// the FQDN data hasn't changed.
848
TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsRenewNoChange) {
849
850
851
852
853
854
855
    Lease4Ptr lease = createLease(IOAddress("192.0.2.3"), "myhost.example.com.",
                                  true, true);
    Lease4Ptr old_lease = createLease(IOAddress("192.0.2.3"),
                                      "myhost.example.com.", true, true);
    old_lease->valid_lft_ += 100;

    ASSERT_NO_THROW(srv_->createNameChangeRequests(lease, old_lease));
856
    ASSERT_EQ(0, d2_mgr_.getQueueSize());
857
}
858

859
860
// Test that the OFFER message generated as a result of the DISCOVER message
// processing will not result in generation of the NameChangeRequests.
861
TEST_F(NameDhcpv4SrvTest, processDiscover) {
862
863
864
    IfaceMgrTestConfig test_config(true);
    IfaceMgr::instance().openSockets4();

865
866
867
868
869
870
871
872
873
    Pkt4Ptr req = generatePktWithFqdn(DHCPDISCOVER, Option4ClientFqdn::FLAG_S |
                                      Option4ClientFqdn::FLAG_E,
                                      "myhost.example.com.",
                                      Option4ClientFqdn::FULL, true);

    Pkt4Ptr reply;
    ASSERT_NO_THROW(reply = srv_->processDiscover(req));
    checkResponse(reply, DHCPOFFER, 1234);

874
    EXPECT_EQ(0, d2_mgr_.getQueueSize());
875
876
}

877
878
879
// Test that server generates client's hostname from the IP address assigned
// to it when DHCPv4 Client FQDN option specifies an empty domain-name.
TEST_F(NameDhcpv4SrvTest, processRequestFqdnEmptyDomainName) {
880
881
882
    IfaceMgrTestConfig test_config(true);
    IfaceMgr::instance().openSockets4();

883
884
885
886
887
888
889
890
891
892
    Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                      Option4ClientFqdn::FLAG_E,
                                      "", Option4ClientFqdn::PARTIAL, true);

    Pkt4Ptr reply;
    ASSERT_NO_THROW(reply = srv_->processRequest(req));

    checkResponse(reply, DHCPACK, 1234);

    // Verify that there is one NameChangeRequest generated.
893
    ASSERT_EQ(1, d2_mgr_.getQueueSize());
894

895
    // The hostname is generated from the IP address acquired (yiaddr).
896
897
    std::string hostname = generatedNameFromAddress(reply->getYiaddr());

898
    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
899
                            reply->getYiaddr().toText(), hostname,
900
                            "", // empty DHCID forces that it is not checked
901
902
                            time(NULL) + subnet_->getValid(),
                            subnet_->getValid(), true);
903
904
}

905
906
907
908
// Test that server generates client's hostname from the IP address assigned
// to it when DHCPv4 Client FQDN option specifies an empty domain-name  AND
// ddns updates are disabled.
TEST_F(NameDhcpv4SrvTest, processRequestEmptyDomainNameDisabled) {
909
910
911
912
    // Create fake interfaces and open fake sockets.
    IfaceMgrTestConfig test_config(true);
    IfaceMgr::instance().openSockets4();

913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
    disableD2();
    Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                      Option4ClientFqdn::FLAG_E,
                                      "", Option4ClientFqdn::PARTIAL, true);
    Pkt4Ptr reply;
    ASSERT_NO_THROW(reply = srv_->processRequest(req));

    checkResponse(reply, DHCPACK, 1234);

    Option4ClientFqdnPtr fqdn = getClientFqdnOption(reply);
    ASSERT_TRUE(fqdn);

    // The hostname is generated from the IP address acquired (yiaddr).
    std::string hostname = generatedNameFromAddress(reply->getYiaddr());

    EXPECT_EQ(hostname, fqdn->getDomainName());
    EXPECT_EQ(Option4ClientFqdn::FULL, fqdn->getDomainNameType());
}


933
934
// Test that server generates client's hostname from the IP address assigned
// to it when Hostname option carries the top level domain-name.
935
TEST_F(NameDhcpv4SrvTest, processRequestTopLevelHostname) {
936
937
938
    IfaceMgrTestConfig test_config(true);
    IfaceMgr::instance().openSockets4();

939
    Pkt4Ptr req = generatePktWithHostname(DHCPREQUEST, ".");
940
941
    // Set interface for the incoming packet. The server requires it to
    // generate client id.
942
    req->setIface("eth1");
943
944
945
946
947
948
949

    Pkt4Ptr reply;
    ASSERT_NO_THROW(reply = srv_->processRequest(req));

    checkResponse(reply, DHCPACK, 1234);

    // Verify that there is one NameChangeRequest generated.
950
    ASSERT_EQ(1, d2_mgr_.getQueueSize());
951

952
    // The hostname is generated from the IP address acquired (yiaddr).
953
954
    std::string hostname = generatedNameFromAddress(reply->getYiaddr());

955
    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
956
                            reply->getYiaddr().toText(), hostname,
957
                            "", // empty DHCID forces that it is not checked
958
                            time(NULL), subnet_->getValid(), true);
959
960
}

961
962
963
964
// Test that client may send two requests, each carrying FQDN option with
// a different domain-name. Server should use existing lease for the second
// request but modify the DNS entries for the lease according to the contents
// of the FQDN sent in the second request.
965
TEST_F(NameDhcpv4SrvTest, processTwoRequestsFqdn) {
966
967
968
    IfaceMgrTestConfig test_config(true);
    IfaceMgr::instance().openSockets4();

969
970
971
972
973
974
975
976
977
978
979
    Pkt4Ptr req1 = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                       Option4ClientFqdn::FLAG_E,
                                       "myhost.example.com.",
                                       Option4ClientFqdn::FULL, true);

    Pkt4Ptr reply;
    ASSERT_NO_THROW(reply = srv_->processRequest(req1));

    checkResponse(reply, DHCPACK, 1234);

    // Verify that there is one NameChangeRequest generated.
980
    ASSERT_EQ(1, d2_mgr_.getQueueSize());
981
982
983
984
    verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                            reply->getYiaddr().toText(), "myhost.example.com.",
                            "00010132E91AA355CFBB753C0F0497A5A940436"
                            "965B68B6D438D98E680BF10B09F3BCF",
985
                            time(NULL), subnet_->getValid(), true);
986
987
988
989
990
991
992
993
994
995
996
997
998

    // Create another Request message but with a different FQDN. Server
    // should generate two NameChangeRequests: one to remove existing entry,
    // another one to add new entry with updated domain-name.
    Pkt4Ptr req2 = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                       Option4ClientFqdn::FLAG_E,
                                       "otherhost.example.com.",
                                       Option4ClientFqdn::FULL, true);

    ASSERT_NO_THROW(reply = srv_->processRequest(req2));

    checkResponse(reply, DHCPACK, 1234);

999
    // There should be two NameChangeRequests. Verify that they are valid.
1000
    ASSERT_EQ(2, d2_mgr_.getQueueSize());