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
// Copyright (C) 2022-2023 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Kea Hooks Basic
// Commercial End User License Agreement v2.0. See COPYING file in the premium/
// directory.

/// @file This file contains tests which exercise the ddns[46]_update callouts
/// installed by the ddns tuning hook library. In order to test the callouts
/// one must be able to pass to the load function its hook library parameters
/// because the only way to populate these parameters is by actually loading
/// the library via HooksManager::loadLibraries().

#include <config.h>

#include <asiolink/io_address.h>
#include <callout_unittests.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <database/audit_entry.h>
#include <ddns_tuning.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <dhcp/pkt4.h>
#include <dhcp/pkt6.h>
#include <dhcp/option_string.h>
#include <dhcpsrv/cfgmgr.h>
#include <hooks/callout_manager.h>
#include <hooks/hooks.h>
#include <hooks/hooks_manager.h>
#include <process/daemon.h>
#include <testutils/gtest_utils.h>
#include <testutils/multi_threading_utils.h>

#include <gtest/gtest.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <errno.h><--- Include file:  not found. Please note: Cppcheck does not need standard library headers to get proper results.

using namespace std;
using namespace isc;
using namespace isc::db;
using namespace isc::ddns_tuning;
using namespace isc::asiolink;
using namespace isc::hooks;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::process;
using namespace isc::test;
using namespace isc::ddns_tuning::test;

namespace {

/// @brief Test fixture for testing the DHCPv6 ddns_tuning hook library callouts
class Callout6Test : public CalloutTest {
public:
    /// @brief Constructor
    Callout6Test() : CalloutTest("dhcp6_subnet") {
        CfgMgr::instance().setFamily(AF_INET6);
        Daemon::setProcName("kea-dhcp6");

        // Register hook points.
        hook_index_ddns_update_ = HooksManager::registerHook("ddns6_update");
        hook_index_dhcp_srv_configured_ = HooksManager::registerHook("dhcp6_srv_configured");
        hook_index_cb_updated_ = HooksManager::registerHook("cb6_updated");
    }

    /// @brief Destructor
    virtual ~Callout6Test() {
    }

    /// @brief Invokes ddns6_update callback and verifies the outcome.
    ///
    /// This assumes the library has been loaded and configured as desired
    /// prior to the function call.
    ///
    /// @param subnet subnet to pass into the callout
    /// @param exp_hostname expected hostname returned by the callout
    /// @param skip_ddns when true, SKIP_DDNS class is added to the client
    /// query.
    void callDdns6Update(Subnet6Ptr subnet, const std::string& exp_hostname,
                         bool skip_ddns = false) {
        // Make the client query and server response packets.
        Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 12345));
        if (skip_ddns) {
            query->addClass("SKIP_DDNS");
        }

        OptionPtr bootfile_url(new OptionString(Option::V6, D6O_BOOTFILE_URL, "boot6"));
        query->addOption(bootfile_url);
        Pkt6Ptr response(new Pkt6(DHCPV6_ADVERTISE, 12345));

        // Create DDNS parameters, it needs to be associated with the subnet.
        DdnsParamsPtr ddns_params(new DdnsParams(subnet, true));

        // Get and setup the callout handle.
        ASSERT_TRUE(HooksManager::calloutsPresent(hook_index_ddns_update_));
        CalloutHandlePtr handle = HooksManager::createCalloutHandle();
        handle->setArgument("hostname", std::string("orig_host"));
        handle->setArgument("query6", query);
        handle->setArgument("response6", response);
        handle->setArgument("subnet6", subnet);
        handle->setArgument("ddns-params", ddns_params);
        handle->setArgument("fwd-update", true);
        handle->setArgument("rev-update", true);

        // Execute the callout.
        ASSERT_NO_THROW_LOG(HooksManager::callCallouts(hook_index_ddns_update_, *handle));
        ASSERT_EQ(0, handle->getStatus());

        // Hostname should match the expected hostname.
        std::string calculated_hostname;
        handle->getArgument("hostname", calculated_hostname);
        ASSERT_EQ(calculated_hostname, exp_hostname);

        // Verify the DDNS direction flags.
        bool fwd_update;
        handle->getArgument("fwd-update", fwd_update);
        EXPECT_EQ(!skip_ddns, fwd_update);

        bool rev_update;
        handle->getArgument("rev-update", rev_update);
        EXPECT_EQ(!skip_ddns, rev_update);
    }

    /// @brief Verifies the outcome of invoking the ddns_update6 callout for
    /// several scenarios.
    ///
    /// Each scenario is described by an instance of @c Scenario.  Prior to running
    /// the scenarios the ddns_tuning library ls loaded with a configuration that
    /// specifies a valid, literal host expression.
    void testDdns6UpdateScenarios() {
        // Test scenarios to execute.
        const std::vector<Scenario> scenarios {
            {
            "1. Empty expression, with suffix, should not make changes",
            empty_expr,
            "example.com",
            "orig_host"
            },
            {
            "2. Non-empty expression, with suffix, should change",
            "'subnet_host'",
            "example.com",
            "subnet_host.example.com."
            },
            {
            "3. Non-empty expression, no suffix, should change but without suffix",
            "'subnet_host'",
            no_suffix,
            "subnet_host."
            },
            {
            "4. Non-empty expression, suffix, should change",
            "'subnet_host'",
            "example.com",
            "subnet_host.example.com."
            },
            {
            "5. No expression, with suffix, should use global expression",
            no_expr,
            "example.com",
            "global_host.example.com."
            },
            {
            "6. V6 specific expression, with suffix, should change",
            "option[bootfile-url].hex",
            "example.com",
            "boot6.example.com."
            },
        };

        // Configure the hook library with a global expression.
        ElementPtr config = Element::createMap();
        config->set("hostname-expr", Element::create("'global_host'"));
        addLib(DDNS_TUNING_LIB_SO, config);
        loadLibs();

        // Run the scenarios.
        SubnetID subnet_id = 100;
        for (auto const& scenario : scenarios) {
            SCOPED_TRACE(scenario.desc_);

            // Make a new subnet. We use a new subnet ID so each scenario gets
            // a subnet that the cache has not seen. We don't care that the
            // subnets all have the same prefix.
            Subnet6Ptr subnet(new Subnet6(IOAddress("3001:1::"), 64, 30, 40, 50, 60, ++subnet_id));

            // Add its expression.
            if (scenario.subnet_exp_ != "<null>") {
                ElementPtr user_context = Element::createMap();
                ElementPtr ddns_tuning = Element::createMap();
                ddns_tuning->set("hostname-expr", Element::create(scenario.subnet_exp_));
                user_context->set("ddns-tuning", ddns_tuning);
                subnet->setContext(user_context);
            }

            // Set the qualifying suffix if not empty.
            if (!scenario.suffix_.empty()) {
                subnet->setDdnsQualifyingSuffix(scenario.suffix_);
            }

            // Run the ddns6_update() callback and verify the calculated host name.
            ASSERT_NO_FATAL_FAILURE(callDdns6Update(subnet, scenario.exp_hostname_));
        }
    }

    /// @brief Verifies that dhcp6_srv_configured() callout functions properly.
    ///
    /// -# Load the library with a global expression
    /// -# Add subnet without an expression to the configuration
    /// -# Call dhcp6_srv_configured
    /// -# Call ddns6_update() callout for the subnet, verify global expression is used
    /// -# Add an expression to subnet
    /// -# Call dhcp6_srv_configured
    /// -# Call ddns6_update() callout for the subnet, verify subnet expression is used
    /// -# Add an invalid expression to subnet
    /// -# Call dhcp6_srv_configured
    /// -# Call ddns6_update() callout for the subnet, verify global expression is used
    void testDhcp6SrvConfigured() {
        // Configure the hook library with a global expression.
        ElementPtr config = Element::createMap();
        config->set("hostname-expr", Element::create("'global_host'"));
        addLib(DDNS_TUNING_LIB_SO, config);
        loadLibs();

        // Make sure dhcp6_srv_configured callout exists.
        ASSERT_TRUE(HooksManager::calloutsPresent(hook_index_dhcp_srv_configured_));

        // Create a IPv6 subnet.
        Subnet6Ptr subnet(new Subnet6(IOAddress("3001:1::"), 64, 30, 40, 50, 60, 100));

        // Add the subnet to server configuration.
        auto srv_config = CfgMgr::instance().getStagingCfg();
        srv_config->getCfgSubnets6()->add(subnet);

        // Invoke the callout.
        callDhcpSrvConfigured(srv_config);

        // Verify that a call to ddns6_update() uses the  global expression.
        callDdns6Update(subnet, "global_host.");

        // Add a valid expression to the subnet.
        setSubnetExpression(subnet, "'subnet_100'");

        // Invoke the callout. This should add the subnet's expression to the cache.
        callDhcpSrvConfigured(srv_config);

        // Verify that a call to ddns6_update() uses the subnet's expression.
        callDdns6Update(subnet, "subnet_100.");

        // Make the subnet expression invalid.
        setSubnetExpression(subnet, "invalid expression");

        // Execute the callout. The subnet expression should fail to parse (without
        // causing a throw), leaving the subnet without an expression.
        callDhcpSrvConfigured(srv_config, CalloutHandle::NEXT_STEP_DROP);

        // Verify that a call to ddns6_update() now uses the global expression.
        callDdns6Update(subnet, "global_host.");
    }

    /// @brief Verifies that cb6_updated() callout functions properly.
    ///
    /// -# Load the library with a global expression
    /// -# Add subnet without expression to the configuration
    /// -# Call ddns6_update() callout for the subnet, verify global expression is used
    /// -# Add an expression to subnet
    /// -# Call cb6_updated with no audit entries
    /// -# Call ddns6_update() callout for the subnet, verify global expression is used
    /// -# Call cb6_updated with an audit entry for the subnet
    /// -# Call ddns6_update() callout for the subnet, verify subnet expression is used
    void testCb6Updated() {
        // Configure the hook library with a global expression.
        ElementPtr config = Element::createMap();
        config->set("hostname-expr", Element::create("'global_host'"));
        addLib(DDNS_TUNING_LIB_SO, config);
        loadLibs();

        // Make sure cb6_updated callout exists.
        ASSERT_TRUE(HooksManager::calloutsPresent(hook_index_cb_updated_));

        // Create a IPv6 subnet.
        Subnet6Ptr subnet(new Subnet6(IOAddress("3001:1::"), 64, 30, 40, 50, 60, 100));
        auto srv_config = CfgMgr::instance().getStagingCfg();
        srv_config->getCfgSubnets6()->add(subnet);
        CfgMgr::instance().commit();

        // Verify that we use the global expression, as our subnet currently has no expression.
        callDdns6Update(subnet, "global_host.");

        // Add an expression to the subnet.
        setSubnetExpression(subnet, "'subnet_100'");

        // Call cb update with no audit-entries.
        callCbUpdated(Subnet6Ptr());

        // We should not yet see the subnet's expression.
        callDdns6Update(subnet, "global_host.");

        // Call cb update with an audit-entry for the subnet.
        callCbUpdated(subnet);

        // We should see the subnet's expression.
        callDdns6Update(subnet, "subnet_100.");
    }

    /// @brief Verifies that ddns6_update correctly handles SKIP_DDNS queries.
    void testSkipDdns6() {
        // Configure the hook library with a global expression.
        ElementPtr config = Element::createMap();
        addLib(DDNS_TUNING_LIB_SO, config);
        loadLibs();

        // Create a IPv6 subnet.
        Subnet6Ptr subnet(new Subnet6(IOAddress("3001:1::"), 64, 30, 40, 50, 60, 100));

        // Verify hostname isn't changed but we do set DDNS direction flags false.
        callDdns6Update(subnet, "orig_host", true);

        setSubnetExpression(subnet, "'subnet_100'");

        // Verify hostname is calculated and we set the DDNS direction flags false.
        callDdns6Update(subnet, "orig_host", true);
    }
};

TEST_F(Callout6Test, ddns6UpdateScenarios) {<--- syntax error
    testDdns6UpdateScenarios();
}

TEST_F(Callout6Test, ddns6UpdateScenariosMultiThreading) {
    MultiThreadingTest mt(true);
    testDdns6UpdateScenarios();
}

TEST_F(Callout6Test, dhcp6SrvConfigured) {
    testDhcp6SrvConfigured();
};

TEST_F(Callout6Test, dhcp6SrvConfiguredMultiThreading) {
    MultiThreadingTest mt(true);
    testDhcp6SrvConfigured();
};

TEST_F(Callout6Test, cb6Updated) {
    testCb6Updated();
};

TEST_F(Callout6Test, cb6UpdatedMultiThreading) {
    MultiThreadingTest mt(true);
    testCb6Updated();
};


TEST_F(Callout6Test, skipDdns) {
    testSkipDdns6();
}

TEST_F(Callout6Test, skipDdnsMultiThreading) {
    MultiThreadingTest mt(true);
    testSkipDdns6();
}

} // end of anonymous namespace