1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
// Copyright (C) 2025 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <lease_cmds.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <lease_cmds_func_unittest.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <hooks/hooks_manager.h>
#include <dhcpsrv/lease_mgr.h>
#include <exceptions/exceptions.h>
#include <cc/data.h>
#include <dhcp/dhcp6.h>
#include <dhcp/pkt6.h>

#include <testutils/gtest_utils.h>
#include <testutils/user_context_utils.h>
#include <testutils/multi_threading_utils.h>

#include <gtest/gtest.h>

using namespace std;
using namespace isc;
using namespace isc::dhcp;
using namespace isc::data;
using namespace isc::test;
using namespace isc::asiolink;
using namespace isc::hooks;

using namespace isc::lease_cmds;

namespace {

/// @brief DHCPv6 Test fixture for testing lease commands hook library
/// functions and class methods.
class LeaseCmdsFuncTest6 :  public LeaseCmdsFuncTest {
public:
    /// @brief Constructor
    LeaseCmdsFuncTest6() = default;

    /// @brief Destructor
    virtual ~LeaseCmdsFuncTest6() = default;

    /// @brief Initializes the lease manager and populates it with test leases.
    void initLeaseMgr() {
        LeaseMgrFactory::destroy();
        LeaseMgrFactory::create("type=memfile persist=false "
                                               "universe=6 extended-info-tables=true");
        lmptr_ = &(isc::dhcp::LeaseMgrFactory::instance());
        ASSERT_TRUE(lmptr_);
        lmptr_->addLease(createLease6("2001:db8:1::1", 66, 0x42));
        lmptr_->addLease(createLease6("2001:db8:1::2", 66, 0x56));
        lmptr_->addLease(createLease6("2001:db8:2::1", 99, 0x42));
        lmptr_->addLease(createLease6("2001:db8:2::2", 99, 0x56));
    }

    /// @brief Creates an IPv6 lease
    ///
    /// @param ip_address IP address for the lease.
    /// @param subnet_id subnet identifier
    /// @param duid_address_pattern value to be used for generating DUID by
    /// repeating it 8 times
    /// @return Returns the lease created
    isc::dhcp::Lease6Ptr createLease6(const std::string& ip_address,
                                      const isc::dhcp::SubnetID& subnet_id,
                                      const uint8_t duid_pattern) {
        isc::dhcp::Lease6Ptr lease(new isc::dhcp::Lease6());
        lease->addr_ = isc::asiolink::IOAddress(ip_address);
        lease->type_ = isc::dhcp::Lease::TYPE_NA;
        lease->prefixlen_ = 128;
        lease->iaid_ = 42;
        lease->duid_ = isc::dhcp::DuidPtr(new isc::dhcp::DUID(std::vector<uint8_t>(8, duid_pattern)));
        lease->preferred_lft_ = 1800;
        lease->valid_lft_ = 2400;
        lease->subnet_id_ = subnet_id;
        return (lease);
    }

    /// @brief Check that leases6_committed handler works as expected with
    /// valid inputs.
    void testValidLeases6Committed();

    /// @brief Check that leases6_committed handler does not throw or alter
    /// leases under NOP conditions:
    /// 1. There is no response packet
    /// 2. There are no leases
    /// 3. There are leases but none active
    void testNopLeases6Committed();

    /// @brief Checks that errors occurring in leases6Committed() are handled
    /// properly.
    void testLeases6CommittedErrors();
};

void
LeaseCmdsFuncTest6::testValidLeases6Committed() {
    struct Scenario {
        uint32_t line_;
        std::string config_;
        std::string orig_context_;
        std::string exp_context_;
    };

    std::list<Scenario> scenarios = {
    {
        // No variables configured, nothing in lease context.
        __LINE__,
        R"({})",
        R"({})",
        R"({})"
    },
    {
        // Lease context has no binding-variables, two configured.
        __LINE__,
        R"^({"binding-variables":[
            {
                "name": "duid",
                "expression": "hexstring(option[1].hex,':')",
                "source": "query"
            },
            {
                "name": "sub-id",
                "expression": "hexstring(option[38].hex, ':')",
                "source": "response"
            }]})^",
        R"({})",
        R"({"ISC":{
            "binding-variables":{
                "duid": "01:02:03:04",
                "sub-id": "05:06:07:08"
            }
        }})",
    },
    {
        // Lease context has binding-variables, none configured.
        // Current logic leaves lease untouched.
        __LINE__,
        R"({})",
        R"({"ISC":{
            "binding-variables":{
                "duid": "01:02:03:04",
                "sub-id": "05:06:07:08"
            }
        }})",
        R"({"ISC":{
            "binding-variables":{
                "duid": "01:02:03:04",
                "sub-id": "05:06:07:08"
            }
        }})",
    },
    {
        // Evaluated variable value is an empty string.
        __LINE__,
        R"^({"binding-variables":[
            {
                "name": "my-var",
                "expression": "''",
                "source": "query"
            }]})^",
        R"({"ISC":{
            "binding-variables":{
                "my-var": "pre-existing value"
            }
        }})",
        R"({"ISC":{
            "binding-variables":{
                "my-var": ""
            }
        }})",
    }
    };

    // Create packet pair and lease.
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
                                   { 0x01, 0x02, 0x03, 0x04 }));
    query->addOption(client_id);

    Pkt6Ptr response(new Pkt6(DHCPV6_REPLY, 7890));
    OptionPtr subscriber_id(new Option(Option::V6, D6O_SUBSCRIBER_ID,
                                       { 0x05, 0x06, 0x07, 0x08 }));
    response->addOption(subscriber_id);

    // Create a list of the lease addresses.
    std::list<IOAddress> lease_addrs;
    lease_addrs.push_back(IOAddress("2001:db8:1::1"));
    lease_addrs.push_back(IOAddress("2001:db8:1::2"));
    lease_addrs.push_back(IOAddress("2001:db8:2::2"));

    // Iterates over scenarios.
    for (auto const& scenario : scenarios) {
        SCOPED_LINE(scenario.line_);

        // Create and configure the manager.
        BindingVariableMgrPtr mgr;
        ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET6)));
        ConstElementPtr config;
        ASSERT_NO_THROW_LOG(config = Element::fromJSON(scenario.config_));
        ASSERT_NO_THROW_LOG(mgr->configure(config));

        // Fetch the lease and set its user-context to the original content.
        Lease6CollectionPtr orig_leases(new Lease6Collection());
        for (auto const& address : lease_addrs) {
            Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, address);
            ASSERT_TRUE(orig_lease);
            ASSERT_TRUE(orig_lease->valid_lft_ > 0);

            ConstElementPtr orig_context;
            ASSERT_NO_THROW_LOG(orig_context = Element::fromJSON(scenario.orig_context_));
            orig_lease->setContext(orig_context);
            ASSERT_NO_THROW_LOG(lmptr_->updateLease6(orig_lease));
            orig_leases->push_back(orig_lease);
        }

        // Create a callout handle and add the expected arguments.
        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
        callout_handle->setArgument("query6", query);
        callout_handle->setArgument("response6", response);
        callout_handle->setArgument("leases6", orig_leases);

        // Invoke the leases6Committed handler.
        LeaseCmds cmds;
        ASSERT_NO_THROW_LOG(cmds.leases6Committed(*callout_handle, mgr));

        // Iterate over the leases and make sure the user-contexts are as expected.
        for (auto const& lease : *orig_leases) {
            // Fetch the lease.
            Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, lease->addr_);
            ASSERT_TRUE(after_lease);

            // Context contents should match the expected context content.
            ConstElementPtr exp_context;
            ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(scenario.exp_context_));
            ASSERT_EQ(*(after_lease->getContext()), *exp_context);
        }
    }
}

void
LeaseCmdsFuncTest6::testNopLeases6Committed() {
    struct Scenario {
        uint32_t line_;
        DHCPv6MessageType response_type_;
        bool send_lease_;
        uint32_t valid_lft_;
    };

    // Configure a single variable.
    std::string config =
    R"^({"binding-variables":[
    {
        "name": "duid",
        "expression": "hexstring(option[1].hex,':')",
        "source": "query"
    }]})^";

    // Create and configure the manager.
    BindingVariableMgrPtr mgr;
    ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET6)));
    ASSERT_NO_THROW_LOG(mgr->configure(Element::fromJSON(config)));

    // Scenarios should all result in no change to the lease.
    std::list<Scenario> scenarios = {
    {
        // No leases.
        __LINE__,
        DHCPV6_REPLY,
        false,
        0
    },
    {
        // No active leases.
        __LINE__,
        DHCPV6_REPLY,
        true,
        0
    },
    {
        // No response.
        __LINE__,
        DHCPV6_NOTYPE,
        true,
        1000
    }};

    // Create query packet.
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
                                   { 0x01, 0x02, 0x03, 0x04 }));
    query->addOption(client_id);

    IOAddress na_addr("2001:db8:1::1");
    for (auto const& scenario : scenarios) {
        SCOPED_LINE(scenario.line_);

        // Create the response packet, if one.
        Pkt6Ptr response;
        if (scenario.response_type_ != DHCPV6_NOTYPE) {
            response.reset(new Pkt6(scenario.response_type_, 1234));
        }


        // Fetch the lease and verify there is no context content.
        Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, na_addr);
        ASSERT_TRUE(orig_lease);
        ASSERT_FALSE(orig_lease->getContext());
        orig_lease->valid_lft_ =  scenario.valid_lft_;

        Lease6CollectionPtr leases(new Lease6Collection());
        if (scenario.send_lease_) {
            leases->push_back(orig_lease);
        }

        // Create a callout handle and add the expected arguments.
        CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
        callout_handle->setArgument("query6", query);
        callout_handle->setArgument("response6", response);
        callout_handle->setArgument("leases6", leases);

        // Invoke the leases6Committed handler.
        LeaseCmds cmds;
        ASSERT_NO_THROW_LOG(cmds.leases6Committed(*callout_handle, mgr));

        // Fetch the lease. Context should still be empty.
        Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, na_addr);
        ASSERT_TRUE(after_lease);
        ASSERT_FALSE(after_lease->getContext());
    }
}

void
LeaseCmdsFuncTest6::testLeases6CommittedErrors() {
    // Create a config with two binding variables.
    std::string config_str =
        R"^({"binding-variables":[
            {
                "name": "duid",
                "expression": "hexstring(option[1].hex,':')",
                "source": "query"
            },
            {
                "name": "sub-id",
                "expression": "hexstring(option[38].hex, ':')",
                "source": "response"
            }]})^";

    ConstElementPtr config;
    ASSERT_NO_THROW_LOG(config = Element::fromJSON(config_str));

    // Create the expected context contents.
    std::string exp_context_str =
        R"({"ISC":{
            "binding-variables":{
                "duid": "01:02:03:04",
                "sub-id": "05:06:07:08"
            }
        }})";


    ConstElementPtr exp_context;
    ASSERT_NO_THROW_LOG(exp_context = Element::fromJSON(exp_context_str));

    // Create packet pair and lease.
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID,
                                   { 0x01, 0x02, 0x03, 0x04 }));
    query->addOption(client_id);

    Pkt6Ptr response(new Pkt6(DHCPV6_REPLY, 7890));
    OptionPtr subscriber_id(new Option(Option::V6, D6O_SUBSCRIBER_ID,
                                       { 0x05, 0x06, 0x07, 0x08 }));
    response->addOption(subscriber_id);

    // Create and configure the manager.
    BindingVariableMgrPtr mgr;
    ASSERT_NO_THROW_LOG(mgr.reset(new BindingVariableMgr(AF_INET6)));
    ASSERT_NO_THROW_LOG(mgr->configure(config));

    // Fetch the leases.
    std::list<IOAddress> lease_addrs;
    lease_addrs.push_back(IOAddress("2001:db8:1::1"));
    lease_addrs.push_back(IOAddress("2001:db8:1::2"));
    lease_addrs.push_back(IOAddress("2001:db8:2::2"));

    Lease6CollectionPtr orig_leases(new Lease6Collection());
    for (auto const& address : lease_addrs) {
        Lease6Ptr orig_lease = lmptr_->getLease6(Lease::TYPE_NA, address);
        ASSERT_TRUE(orig_lease);
        orig_leases->push_back(orig_lease);
    }

    // Delete the middle lease from the back end. This should cause a conflict error.
    ASSERT_NO_THROW_LOG(lmptr_->deleteLease((*orig_leases)[1]));

    // Create a callout handle and add the expected arguments.
    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
    callout_handle->setArgument("query6", query);
    callout_handle->setArgument("response6", response);
    callout_handle->setArgument("leases6", orig_leases);

    // Invoke the leases6Committed handler.
    LeaseCmds cmds;
    ASSERT_THROW_MSG(cmds.leases6Committed(*callout_handle, mgr),
                     Unexpected,
                     "1 out of 3 leases failed to update for "
                     "duid=[01:02:03:04], [no hwaddr info], tid=0x4d2");

    for (auto const& lease : *orig_leases) {
        // Fetch the lease.
        Lease6Ptr after_lease = lmptr_->getLease6(Lease::TYPE_NA, lease->addr_);
        if (after_lease) {
            // Context contents should match the expected context content.
            ASSERT_EQ(*(after_lease->getContext()), *exp_context);
        } else {
            // Middle lease should not be found.
            EXPECT_EQ(lease->addr_, (*orig_leases)[1]->addr_);
        }
    }
}

TEST_F(LeaseCmdsFuncTest6, validLeases6Committed) {
    testValidLeases6Committed();
}

TEST_F(LeaseCmdsFuncTest6, validLeases6CommittedMultiThreading) {
    MultiThreadingTest mt(true);
    testValidLeases6Committed();
}

TEST_F(LeaseCmdsFuncTest6, nopLeases6Committed) {
    testNopLeases6Committed();
}

TEST_F(LeaseCmdsFuncTest6, nopLeases6CommittedMultiThreading) {
    MultiThreadingTest mt(true);
    testNopLeases6Committed();
}

TEST_F(LeaseCmdsFuncTest6, leases6CommittedErrors) {
    testLeases6CommittedErrors();
}

TEST_F(LeaseCmdsFuncTest6, leases6CommittedErrorsMultiThreading) {
    MultiThreadingTest mt(true);
    testLeases6CommittedErrors();
}

} // end of anonymous namespace