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
// Copyright (C) 2015-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <asiolink/io_address.h>
#include <dhcp/pkt4o6.h>
#include <dhcp/pkt6.h>
#include <dhcp/testutils/iface_mgr_test_config.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4to6_ipc.h>
#include <dhcp4/tests/dhcp4_test_utils.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/testutils/dhcp4o6_test_ipc.h>
#include <stats/stats_mgr.h>
#include <hooks/callout_handle.h>
#include <hooks/hooks_manager.h>

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

using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace isc::dhcp::test;
using namespace isc::stats;
using namespace isc::hooks;
using namespace isc::util;

namespace {

/// @brief Port number used in tests.
const uint16_t TEST_PORT = 32000;

/// @brief Define short name for the test IPC.
typedef Dhcp4o6TestIpc TestIpc;

/// @brief Test fixture class for DHCPv4 endpoint of DHCPv4o6 IPC.
class Dhcp4to6IpcTest : public Dhcpv4SrvTest {
public:

    /// @brief Constructor
    ///
    /// Configures IPC to use a test port. It also provides a fake
    /// configuration of interfaces.
    Dhcp4to6IpcTest()
        : Dhcpv4SrvTest(),
        iface_mgr_test_config_(true) {
        IfaceMgr::instance().openSockets4();
        configurePort(TEST_PORT);
        // Install buffer4_receive_callout
        EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().
                        registerCallout("buffer4_receive",
                                        buffer4_receive_callout));
        // Install buffer4_send_callout
        EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().
                        registerCallout("buffer4_send", buffer4_send_callout));
        // Verify we have a controlled server
        ControlledDhcpv4Srv* srv = NULL;
        EXPECT_NO_THROW(srv = ControlledDhcpv4Srv::getInstance());
        EXPECT_TRUE(srv);
        // Let's wipe all existing statistics.
        StatsMgr::instance().removeAll();

        // Set the flags to false as we expect them to be set in callouts.
        callback_recv_pkt_options_copy_ = std::make_pair(false, false);
        callback_sent_pkt_options_copy_ = std::make_pair(false, false);
    }

    /// @brief Destructor
    ///
    /// Various cleanups.
    virtual ~Dhcp4to6IpcTest() {
        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer4_send");
        callback_recv_pkt_.reset();
        callback_sent_pkt_.reset();
        bool status = HooksManager::unloadLibraries();
        if (!status) {
            cerr << "(fixture dtor) unloadLibraries failed" << endl;
        }
    }

    /// @brief Configure DHCP4o6 port.
    ///
    /// @param port New port.
    void configurePort(uint16_t port);

    /// @brief Creates an instance of the DHCPv4o6 Message option.
    ///
    /// The option will contain an empty DHCPREQUEST message, with
    /// just the Message Type option inside and nothing else.
    ///
    /// @return Pointer to the instance of the DHCPv4-query Message option.
    OptionPtr createDHCPv4MsgOption() const;

    /// @brief Handler for the buffer4_receive hook
    ///
    /// This hook is at the beginning of processPacket
    ///
    /// @param callout_handle handle passed by the hooks framework
    /// @return always 0
    static int buffer4_receive_callout(CalloutHandle& callout_handle) {
        callout_handle.getArgument("query4", callback_recv_pkt_);
        Pkt4o6Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4o6>(callback_recv_pkt_);
        if (pkt4) {
            callback_recv_pkt_options_copy_.first = pkt4->isCopyRetrievedOptions();
            Pkt6Ptr pkt6 = pkt4->getPkt6();
            if (pkt6) {
                callback_recv_pkt_options_copy_.second =
                    pkt6->isCopyRetrievedOptions();
            }
        }
        return (0);
    }

    /// @brief Handler for the buffer4_send hook
    ///
    /// This hook is at the end of the DHCPv4o6 packet handler
    ///
    /// @param callout_handle handle passed by the hooks framework
    /// @return always 0
    static int buffer4_send_callout(CalloutHandle& callout_handle) {
        callout_handle.getArgument("response4", callback_sent_pkt_);
        Pkt4o6Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4o6>(callback_sent_pkt_);
        if (pkt4) {
            callback_sent_pkt_options_copy_.first = pkt4->isCopyRetrievedOptions();
            Pkt6Ptr pkt6 = pkt4->getPkt6();
            if (pkt6) {
                callback_sent_pkt_options_copy_.second =
                    pkt6->isCopyRetrievedOptions();
            }
        }
        return (0);
    }

    /// @brief Response Pkt4 shared pointer returned in the receive callout
    static Pkt4Ptr callback_recv_pkt_;

    /// @brief Response Pkt4 shared pointer returned in the send callout
    static Pkt4Ptr callback_sent_pkt_;

    /// Flags indicating if copying retrieved options was enabled for
    /// a received packet during callout execution.
    static std::pair<bool, bool> callback_recv_pkt_options_copy_;

    /// Flags indicating if copying retrieved options was enabled for
    /// a sent packet during callout execution.
    static std::pair<bool, bool> callback_sent_pkt_options_copy_;

    /// @brief reference to a controlled server
    ///
    /// Dhcp4to6Ipc::handler() uses the instance of the controlled server
    /// so it has to be build. This reference does this.
    ControlledDhcpv4Srv srv_;

private:

    /// @brief Provides fake configuration of interfaces.
    IfaceMgrTestConfig iface_mgr_test_config_;

};

Pkt4Ptr Dhcp4to6IpcTest::callback_recv_pkt_;
Pkt4Ptr Dhcp4to6IpcTest::callback_sent_pkt_;
std::pair<bool, bool> Dhcp4to6IpcTest::callback_recv_pkt_options_copy_;
std::pair<bool, bool> Dhcp4to6IpcTest::callback_sent_pkt_options_copy_;

void
Dhcp4to6IpcTest::configurePort(uint16_t port) {
    CfgMgr::instance().getStagingCfg()->setDhcp4o6Port(port);
}

OptionPtr
Dhcp4to6IpcTest::createDHCPv4MsgOption() const {
    // Create the DHCPv4 message.
    Pkt4Ptr pkt(new Pkt4(DHCPREQUEST, 1234));
    // Make a wire representation of the DHCPv4 message.
    pkt->pack();
    const OptionBuffer& option_buffer = pkt->getBuffer().getVector();

    // Create the DHCPv4 Message option holding the created message.
    OptionPtr opt_msg(new Option(Option::V6, D6O_DHCPV4_MSG, option_buffer));
    return (opt_msg);
}

// This test verifies that the IPC returns an error when trying to bind
// to the out of range port.
TEST_F(Dhcp4to6IpcTest, invalidPortError) {<--- syntax error
    // Create instance of the IPC endpoint under test with out-of-range port.
    configurePort(65535);
    Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
    EXPECT_THROW(ipc.open(), isc::OutOfRange);
}

// This test verifies that the DHCPv4 endpoint of the DHCPv4o6 IPC can
// receive messages.
TEST_F(Dhcp4to6IpcTest, receive) {
    // Create instance of the IPC endpoint under test.
    Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
    // Create instance of the IPC endpoint being used as a source of messages.
    TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6);

    // Open both endpoints.
    ASSERT_NO_THROW(ipc.open());
    ASSERT_NO_THROW(src_ipc.open());

    // Create message to be sent over IPC.
    Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 1234));
    pkt->addOption(createDHCPv4MsgOption());
    pkt->setIface("eth0");
    pkt->setIndex(ETH0_INDEX);
    pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
    ASSERT_NO_THROW(pkt->pack());

    // Reset the received packet
    Dhcp4to6IpcTest::callback_recv_pkt_.reset();

    // Send and wait up to 1 second to receive it.
    ASSERT_NO_THROW(src_ipc.send(pkt));
    ASSERT_NO_THROW(IfaceMgr::instance().receive6(1, 0));

    // Make sure that the message has been received.
    // The buffer4_receive hook is at the beginning of processPacket
    // so this proves it was passed to it.
    Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_;
    ASSERT_TRUE(pkt4_received);
    Pkt4o6Ptr pkt_received =
        boost::dynamic_pointer_cast<Pkt4o6>(pkt4_received);
    ASSERT_TRUE(pkt_received);
    Pkt6Ptr pkt6_received = pkt_received->getPkt6();
    ASSERT_TRUE(pkt6_received);
    EXPECT_EQ("eth0", pkt6_received->getIface());
    EXPECT_EQ(ETH0_INDEX, pkt6_received->getIndex());
    EXPECT_EQ("2001:db8:1::123", pkt6_received->getRemoteAddr().toText());

    // Both DHCP4o6 and encapsulated DHCPv6 packet should have the
    // flag enabled.
    EXPECT_TRUE(callback_recv_pkt_options_copy_.first);
    EXPECT_TRUE(callback_recv_pkt_options_copy_.second);
}

// This test verifies that message with multiple DHCPv4 query options
// is rejected.
TEST_F(Dhcp4to6IpcTest, receiveMultipleQueries) {
    // Create instance of the IPC endpoint under test.
    Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
    // Create instance of the IPC endpoint being used as a source of messages.
    TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6);

    // Open both endpoints.
    ASSERT_NO_THROW(ipc.open());
    ASSERT_NO_THROW(src_ipc.open());

    // Create message to be sent over IPC.
    Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 1234));
    // Add two DHCPv4 query options.
    pkt->addOption(createDHCPv4MsgOption());
    pkt->addOption(createDHCPv4MsgOption());
    pkt->setIface("eth0");
    pkt->setIndex(ETH0_INDEX);
    pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
    ASSERT_NO_THROW(pkt->pack());

    // Reset the received packet
    Dhcp4to6IpcTest::callback_recv_pkt_.reset();

    // Send and wait up to 1 second to receive it.
    ASSERT_NO_THROW(src_ipc.send(pkt));
    EXPECT_NO_THROW(IfaceMgr::instance().receive6(1, 0));

    // No message should has been sent.
    Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_;
    EXPECT_FALSE(pkt4_received);
}

// This test verifies that message with no DHCPv4 query options is rejected.
TEST_F(Dhcp4to6IpcTest, receiveNoQueries) {
    // Create instance of the IPC endpoint under test.
    Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
    // Create instance of the IPC endpoint being used as a source of messages.
    TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6);

    // Open both endpoints.
    ASSERT_NO_THROW(ipc.open());
    ASSERT_NO_THROW(src_ipc.open());

    // Create message to be sent over IPC without DHCPv4 query option.
    Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 1234));
    pkt->setIface("eth0");
    pkt->setIndex(ETH0_INDEX);
    pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
    ASSERT_NO_THROW(pkt->pack());

    // Reset the received packet
    Dhcp4to6IpcTest::callback_recv_pkt_.reset();

    // Send and wait up to 1 second to receive it.
    ASSERT_NO_THROW(src_ipc.send(pkt));
    EXPECT_NO_THROW(IfaceMgr::instance().receive6(1, 0));

    // No message should has been sent.
    Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_;
    EXPECT_FALSE(pkt4_received);
}

// This test verifies that the DHCPv4 endpoint of the DHCPv4o6 IPC can
// process messages.
TEST_F(Dhcp4to6IpcTest, process) {
    // Create instance of the IPC endpoint under test.
    Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
    // Create instance of the IPC endpoint being used as a source of messages.
    TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6);

    // Open both endpoints.
    ASSERT_NO_THROW(ipc.open());
    ASSERT_NO_THROW(src_ipc.open());

    // Get statistics
    StatsMgr& mgr = StatsMgr::instance();
    ObservationPtr pkt4_snd = mgr.getObservation("pkt4-sent");
    ObservationPtr pkt4_ack = mgr.getObservation("pkt4-ack-sent");
    EXPECT_FALSE(pkt4_snd);
    EXPECT_FALSE(pkt4_ack);

    // Create an information request message
    Pkt4Ptr infreq(new Pkt4(DHCPINFORM, 1234));
    infreq->setHWAddr(generateHWAddr(6));
    infreq->setCiaddr(IOAddress("192.0.1.2"));
    // Make a wire representation of the DHCPv4 message.
    infreq->pack();
    const OptionBuffer& option_buffer = infreq->getBuffer().getVector();

    // Create the DHCPv4 Message option holding the created message.
    OptionPtr opt_msg(new Option(Option::V6, D6O_DHCPV4_MSG, option_buffer));

    // Create message to be sent over IPC.
    Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 1234));
    pkt->addOption(opt_msg);
    pkt->setIface("eth0");
    pkt->setIndex(ETH0_INDEX);
    pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
    ASSERT_NO_THROW(pkt->pack());

    // Reset the received packet
    Dhcp4to6IpcTest::callback_recv_pkt_.reset();

    // Send and wait up to 1 second to receive it.
    ASSERT_NO_THROW(src_ipc.send(pkt));
    ASSERT_NO_THROW(IfaceMgr::instance().receive6(1, 0));

    // Make sure that the message has been received.
    Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_;
    ASSERT_TRUE(pkt4_received);
    Pkt4o6Ptr pkt_received =
        boost::dynamic_pointer_cast<Pkt4o6>(pkt4_received);
    ASSERT_TRUE(pkt_received);
    Pkt6Ptr pkt6_received = pkt_received->getPkt6();
    ASSERT_TRUE(pkt6_received);
    EXPECT_EQ("eth0", pkt6_received->getIface());
    EXPECT_EQ(ETH0_INDEX, pkt6_received->getIndex());
    EXPECT_EQ("2001:db8:1::123", pkt6_received->getRemoteAddr().toText());

    // Make sure that the message has been processed.
    // Using the buffer4_send hook
    Pkt4Ptr pkt4_sent = Dhcp4to6IpcTest::callback_sent_pkt_;
    ASSERT_TRUE(pkt4_sent);
    EXPECT_EQ(DHCPACK, pkt4_sent->getType());
    Pkt4o6Ptr pkt_sent = boost::dynamic_pointer_cast<Pkt4o6>(pkt4_sent);
    ASSERT_TRUE(pkt_sent);
    Pkt6Ptr pkt6_sent = pkt_sent->getPkt6();
    ASSERT_TRUE(pkt6_sent);
    EXPECT_EQ(DHCPV6_DHCPV4_RESPONSE, pkt6_sent->getType());
    EXPECT_EQ("eth0", pkt6_sent->getIface());
    EXPECT_EQ(ETH0_INDEX, pkt6_sent->getIndex());
    EXPECT_EQ("2001:db8:1::123", pkt6_sent->getRemoteAddr().toText());

    // Both DHCP4o6 and encapsulated DHCPv6 packet should have the
    // flag enabled.
    EXPECT_TRUE(callback_sent_pkt_options_copy_.first);
    EXPECT_TRUE(callback_sent_pkt_options_copy_.second);

    // Verify the 4o6 part
    OptionCollection sent_msgs = pkt6_sent->getOptions(D6O_DHCPV4_MSG);
    ASSERT_EQ(1, sent_msgs.size());
    OptionPtr sent_msg = sent_msgs.begin()->second;
    ASSERT_TRUE(sent_msg);
    const OptionBuffer sent_buf = sent_msg->getData();
    Pkt4Ptr pkt4_opt;
    ASSERT_NO_THROW(pkt4_opt.reset(new Pkt4(&sent_buf[0], sent_buf.size())));
    ASSERT_NO_THROW(pkt4_opt->unpack());
    EXPECT_EQ(DHCPACK, pkt4_sent->getType());
    EXPECT_EQ(pkt4_sent->len(), pkt4_opt->len());

    // Verify statistics
    pkt4_snd = mgr.getObservation("pkt4-sent");
    pkt4_ack = mgr.getObservation("pkt4-ack-sent");
    ASSERT_TRUE(pkt4_snd);
    ASSERT_TRUE(pkt4_ack);
    EXPECT_EQ(1, pkt4_snd->getInteger().first);
    EXPECT_EQ(1, pkt4_ack->getInteger().first);
}

} // end of anonymous namespace