1 // Copyright (C) 2015-2020 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
9 #include <asiolink/io_address.h>
10 #include <dhcp/pkt4o6.h>
11 #include <dhcp/pkt6.h>
12 #include <dhcp/tests/iface_mgr_test_config.h>
13 #include <dhcp4/ctrl_dhcp4_srv.h>
14 #include <dhcp4/dhcp4to6_ipc.h>
15 #include <dhcp4/tests/dhcp4_test_utils.h>
16 #include <dhcpsrv/cfgmgr.h>
17 #include <dhcpsrv/testutils/dhcp4o6_test_ipc.h>
18 #include <stats/stats_mgr.h>
19 #include <hooks/callout_handle.h>
20 #include <hooks/hooks_manager.h>
21 
22 #include <gtest/gtest.h>
23 #include <stdint.h>
24 #include <utility>
25 
26 using namespace isc;
27 using namespace isc::asiolink;
28 using namespace isc::dhcp;
29 using namespace isc::dhcp::test;
30 using namespace isc::stats;
31 using namespace isc::hooks;
32 using namespace isc::util;
33 
34 namespace {
35 
36 /// @brief Port number used in tests.
37 const uint16_t TEST_PORT = 32000;
38 
39 /// @brief Define short name for the test IPC.
40 typedef Dhcp4o6TestIpc TestIpc;
41 
42 /// @brief Test fixture class for DHCPv4 endpoint of DHCPv4o6 IPC.
43 class Dhcp4to6IpcTest : public Dhcpv4SrvTest {
44 public:
45 
46     /// @brief Constructor
47     ///
48     /// Configures IPC to use a test port. It also provides a fake
49     /// configuration of interfaces.
Dhcp4to6IpcTest()50     Dhcp4to6IpcTest()
51         : Dhcpv4SrvTest(),
52         iface_mgr_test_config_(true) {
53         IfaceMgr::instance().openSockets4();
54         configurePort(TEST_PORT);
55         // Install buffer4_receive_callout
56         EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().
57                         registerCallout("buffer4_receive",
58                                         buffer4_receive_callout));
59         // Install buffer4_send_callout
60         EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().
61                         registerCallout("buffer4_send", buffer4_send_callout));
62         // Verify we have a controlled server
63         ControlledDhcpv4Srv* srv = NULL;
64         EXPECT_NO_THROW(srv = ControlledDhcpv4Srv::getInstance());
65         EXPECT_TRUE(srv);
66         // Let's wipe all existing statistics.
67         StatsMgr::instance().removeAll();
68 
69         // Set the flags to false as we expect them to be set in callouts.
70         callback_recv_pkt_options_copy_ = std::make_pair(false, false);
71         callback_sent_pkt_options_copy_ = std::make_pair(false, false);
72     }
73 
74     /// @brief Destructor
75     ///
76     /// Various cleanups.
~Dhcp4to6IpcTest()77     virtual ~Dhcp4to6IpcTest() {
78         HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer4_send");
79         callback_recv_pkt_.reset();
80         callback_sent_pkt_.reset();
81         bool status = HooksManager::unloadLibraries();
82         if (!status) {
83             cerr << "(fixture dtor) unloadLibraries failed" << endl;
84         }
85     }
86 
87     /// @brief Configure DHCP4o6 port.
88     ///
89     /// @param port New port.
90     void configurePort(uint16_t port);
91 
92     /// @brief Creates an instance of the DHCPv4o6 Message option.
93     ///
94     /// The option will contain an empty DHCPREQUEST message, with
95     /// just the Message Type option inside and nothing else.
96     ///
97     /// @return Pointer to the instance of the DHCPv4-query Message option.
98     OptionPtr createDHCPv4MsgOption() const;
99 
100     /// @brief Handler for the buffer4_receive hook
101     ///
102     /// This hook is at the beginning of processPacket
103     ///
104     /// @param callout_handle handle passed by the hooks framework
105     /// @return always 0
buffer4_receive_callout(CalloutHandle & callout_handle)106     static int buffer4_receive_callout(CalloutHandle& callout_handle) {
107         callout_handle.getArgument("query4", callback_recv_pkt_);
108         Pkt4o6Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4o6>(callback_recv_pkt_);
109         if (pkt4) {
110             callback_recv_pkt_options_copy_.first = pkt4->isCopyRetrievedOptions();
111             Pkt6Ptr pkt6 = pkt4->getPkt6();
112             if (pkt6) {
113                 callback_recv_pkt_options_copy_.second =
114                     pkt6->isCopyRetrievedOptions();
115             }
116         }
117         return (0);
118     }
119 
120     /// @brief Handler for the buffer4_send hook
121     ///
122     /// This hook is at the end of the DHCPv4o6 packet handler
123     ///
124     /// @param callout_handle handle passed by the hooks framework
125     /// @return always 0
buffer4_send_callout(CalloutHandle & callout_handle)126     static int buffer4_send_callout(CalloutHandle& callout_handle) {
127         callout_handle.getArgument("response4", callback_sent_pkt_);
128         Pkt4o6Ptr pkt4 = boost::dynamic_pointer_cast<Pkt4o6>(callback_sent_pkt_);
129         if (pkt4) {
130             callback_sent_pkt_options_copy_.first = pkt4->isCopyRetrievedOptions();
131             Pkt6Ptr pkt6 = pkt4->getPkt6();
132             if (pkt6) {
133                 callback_sent_pkt_options_copy_.second =
134                     pkt6->isCopyRetrievedOptions();
135             }
136         }
137         return (0);
138     }
139 
140     /// @brief Response Pkt4 shared pointer returned in the receive callout
141     static Pkt4Ptr callback_recv_pkt_;
142 
143     /// @brief Response Pkt4 shared pointer returned in the send callout
144     static Pkt4Ptr callback_sent_pkt_;
145 
146     /// Flags indicating if copying retrieved options was enabled for
147     /// a received packet during callout execution.
148     static std::pair<bool, bool> callback_recv_pkt_options_copy_;
149 
150     /// Flags indicating if copying retrieved options was enabled for
151     /// a sent packet during callout execution.
152     static std::pair<bool, bool> callback_sent_pkt_options_copy_;
153 
154     /// @brief reference to a controlled server
155     ///
156     /// Dhcp4to6Ipc::handler() uses the instance of the controlled server
157     /// so it has to be build. This reference does this.
158     ControlledDhcpv4Srv srv_;
159 
160 private:
161 
162     /// @brief Provides fake configuration of interfaces.
163     IfaceMgrTestConfig iface_mgr_test_config_;
164 
165 };
166 
167 Pkt4Ptr Dhcp4to6IpcTest::callback_recv_pkt_;
168 Pkt4Ptr Dhcp4to6IpcTest::callback_sent_pkt_;
169 std::pair<bool, bool> Dhcp4to6IpcTest::callback_recv_pkt_options_copy_;
170 std::pair<bool, bool> Dhcp4to6IpcTest::callback_sent_pkt_options_copy_;
171 
172 void
configurePort(uint16_t port)173 Dhcp4to6IpcTest::configurePort(uint16_t port) {
174     CfgMgr::instance().getStagingCfg()->setDhcp4o6Port(port);
175 }
176 
177 OptionPtr
createDHCPv4MsgOption() const178 Dhcp4to6IpcTest::createDHCPv4MsgOption() const {
179     // Create the DHCPv4 message.
180     Pkt4Ptr pkt(new Pkt4(DHCPREQUEST, 1234));
181     // Make a wire representation of the DHCPv4 message.
182     pkt->pack();
183     OutputBuffer& output_buffer = pkt->getBuffer();
184     const uint8_t* data = static_cast<const uint8_t*>(output_buffer.getData());
185     OptionBuffer option_buffer(data, data + output_buffer.getLength());
186 
187     // Create the DHCPv4 Message option holding the created message.
188     OptionPtr opt_msg(new Option(Option::V6, D6O_DHCPV4_MSG, option_buffer));
189     return (opt_msg);
190 }
191 
192 // This test verifies that the IPC returns an error when trying to bind
193 // to the out of range port.
TEST_F(Dhcp4to6IpcTest,invalidPortError)194 TEST_F(Dhcp4to6IpcTest, invalidPortError) {
195     // Create instance of the IPC endpoint under test with out-of-range port.
196     configurePort(65535);
197     Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
198     EXPECT_THROW(ipc.open(), isc::OutOfRange);
199 }
200 
201 // This test verifies that the DHCPv4 endpoint of the DHCPv4o6 IPC can
202 // receive messages.
TEST_F(Dhcp4to6IpcTest,receive)203 TEST_F(Dhcp4to6IpcTest, receive) {
204     // Create instance of the IPC endpoint under test.
205     Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
206     // Create instance of the IPC endpoint being used as a source of messages.
207     TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6);
208 
209     // Open both endpoints.
210     ASSERT_NO_THROW(ipc.open());
211     ASSERT_NO_THROW(src_ipc.open());
212 
213     // Create message to be sent over IPC.
214     Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 1234));
215     pkt->addOption(createDHCPv4MsgOption());
216     pkt->setIface("eth0");
217     pkt->setIndex(ETH0_INDEX);
218     pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
219     ASSERT_NO_THROW(pkt->pack());
220 
221     // Reset the received packet
222     Dhcp4to6IpcTest::callback_recv_pkt_.reset();
223 
224     // Send and wait up to 1 second to receive it.
225     ASSERT_NO_THROW(src_ipc.send(pkt));
226     ASSERT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
227 
228     // Make sure that the message has been received.
229     // The buffer4_receive hook is at the beginning of processPacket
230     // so this proves it was passed to it.
231     Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_;
232     ASSERT_TRUE(pkt4_received);
233     Pkt4o6Ptr pkt_received =
234         boost::dynamic_pointer_cast<Pkt4o6>(pkt4_received);
235     ASSERT_TRUE(pkt_received);
236     Pkt6Ptr pkt6_received = pkt_received->getPkt6();
237     ASSERT_TRUE(pkt6_received);
238     EXPECT_EQ("eth0", pkt6_received->getIface());
239     EXPECT_EQ(ETH0_INDEX, pkt6_received->getIndex());
240     EXPECT_EQ("2001:db8:1::123", pkt6_received->getRemoteAddr().toText());
241 
242     // Both DHCP4o6 and encapsulated DHCPv6 packet should have the
243     // flag enabled.
244     EXPECT_TRUE(callback_recv_pkt_options_copy_.first);
245     EXPECT_TRUE(callback_recv_pkt_options_copy_.second);
246 }
247 
248 // This test verifies that message with multiple DHCPv4 query options
249 // is rejected.
TEST_F(Dhcp4to6IpcTest,receiveMultipleQueries)250 TEST_F(Dhcp4to6IpcTest, receiveMultipleQueries) {
251     // Create instance of the IPC endpoint under test.
252     Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
253     // Create instance of the IPC endpoint being used as a source of messages.
254     TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6);
255 
256     // Open both endpoints.
257     ASSERT_NO_THROW(ipc.open());
258     ASSERT_NO_THROW(src_ipc.open());
259 
260     // Create message to be sent over IPC.
261     Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 1234));
262     // Add two DHCPv4 query options.
263     pkt->addOption(createDHCPv4MsgOption());
264     pkt->addOption(createDHCPv4MsgOption());
265     pkt->setIface("eth0");
266     pkt->setIndex(ETH0_INDEX);
267     pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
268     ASSERT_NO_THROW(pkt->pack());
269 
270     // Reset the received packet
271     Dhcp4to6IpcTest::callback_recv_pkt_.reset();
272 
273     // Send and wait up to 1 second to receive it.
274     ASSERT_NO_THROW(src_ipc.send(pkt));
275     EXPECT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
276 
277     // No message should has been sent.
278     Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_;
279     EXPECT_FALSE(pkt4_received);
280 }
281 
282 // This test verifies that message with no DHCPv4 query options is rejected.
TEST_F(Dhcp4to6IpcTest,receiveNoQueries)283 TEST_F(Dhcp4to6IpcTest, receiveNoQueries) {
284     // Create instance of the IPC endpoint under test.
285     Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
286     // Create instance of the IPC endpoint being used as a source of messages.
287     TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6);
288 
289     // Open both endpoints.
290     ASSERT_NO_THROW(ipc.open());
291     ASSERT_NO_THROW(src_ipc.open());
292 
293     // Create message to be sent over IPC without DHCPv4 query option.
294     Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 1234));
295     pkt->setIface("eth0");
296     pkt->setIndex(ETH0_INDEX);
297     pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
298     ASSERT_NO_THROW(pkt->pack());
299 
300     // Reset the received packet
301     Dhcp4to6IpcTest::callback_recv_pkt_.reset();
302 
303     // Send and wait up to 1 second to receive it.
304     ASSERT_NO_THROW(src_ipc.send(pkt));
305     EXPECT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
306 
307     // No message should has been sent.
308     Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_;
309     EXPECT_FALSE(pkt4_received);
310 }
311 
312 // This test verifies that the DHCPv4 endpoint of the DHCPv4o6 IPC can
313 // process messages.
TEST_F(Dhcp4to6IpcTest,process)314 TEST_F(Dhcp4to6IpcTest, process) {
315     // Create instance of the IPC endpoint under test.
316     Dhcp4to6Ipc& ipc = Dhcp4to6Ipc::instance();
317     // Create instance of the IPC endpoint being used as a source of messages.
318     TestIpc src_ipc(TEST_PORT, TestIpc::ENDPOINT_TYPE_V6);
319 
320     // Open both endpoints.
321     ASSERT_NO_THROW(ipc.open());
322     ASSERT_NO_THROW(src_ipc.open());
323 
324     // Get statistics
325     StatsMgr& mgr = StatsMgr::instance();
326     ObservationPtr pkt4_snd = mgr.getObservation("pkt4-sent");
327     ObservationPtr pkt4_ack = mgr.getObservation("pkt4-ack-sent");
328     EXPECT_FALSE(pkt4_snd);
329     EXPECT_FALSE(pkt4_ack);
330 
331     // Create an information request message
332     Pkt4Ptr infreq(new Pkt4(DHCPINFORM, 1234));
333     infreq->setHWAddr(generateHWAddr(6));
334     infreq->setCiaddr(IOAddress("192.0.1.2"));
335     // Make a wire representation of the DHCPv4 message.
336     infreq->pack();
337     OutputBuffer& output_buffer = infreq->getBuffer();
338     const uint8_t* data = static_cast<const uint8_t*>(output_buffer.getData());
339     OptionBuffer option_buffer(data, data + output_buffer.getLength());
340 
341     // Create the DHCPv4 Message option holding the created message.
342     OptionPtr opt_msg(new Option(Option::V6, D6O_DHCPV4_MSG, option_buffer));
343 
344     // Create message to be sent over IPC.
345     Pkt6Ptr pkt(new Pkt6(DHCPV6_DHCPV4_QUERY, 1234));
346     pkt->addOption(opt_msg);
347     pkt->setIface("eth0");
348     pkt->setIndex(ETH0_INDEX);
349     pkt->setRemoteAddr(IOAddress("2001:db8:1::123"));
350     ASSERT_NO_THROW(pkt->pack());
351 
352     // Reset the received packet
353     Dhcp4to6IpcTest::callback_recv_pkt_.reset();
354 
355     // Send and wait up to 1 second to receive it.
356     ASSERT_NO_THROW(src_ipc.send(pkt));
357     ASSERT_NO_THROW(IfaceMgr::instance().receive6(1, 0));
358 
359     // Make sure that the message has been received.
360     Pkt4Ptr pkt4_received = Dhcp4to6IpcTest::callback_recv_pkt_;
361     ASSERT_TRUE(pkt4_received);
362     Pkt4o6Ptr pkt_received =
363         boost::dynamic_pointer_cast<Pkt4o6>(pkt4_received);
364     ASSERT_TRUE(pkt_received);
365     Pkt6Ptr pkt6_received = pkt_received->getPkt6();
366     ASSERT_TRUE(pkt6_received);
367     EXPECT_EQ("eth0", pkt6_received->getIface());
368     EXPECT_EQ(ETH0_INDEX, pkt6_received->getIndex());
369     EXPECT_EQ("2001:db8:1::123", pkt6_received->getRemoteAddr().toText());
370 
371     // Make sure that the message has been processed.
372     // Using the buffer4_send hook
373     Pkt4Ptr pkt4_sent = Dhcp4to6IpcTest::callback_sent_pkt_;
374     ASSERT_TRUE(pkt4_sent);
375     EXPECT_EQ(DHCPACK, pkt4_sent->getType());
376     Pkt4o6Ptr pkt_sent = boost::dynamic_pointer_cast<Pkt4o6>(pkt4_sent);
377     ASSERT_TRUE(pkt_sent);
378     Pkt6Ptr pkt6_sent = pkt_sent->getPkt6();
379     ASSERT_TRUE(pkt6_sent);
380     EXPECT_EQ(DHCPV6_DHCPV4_RESPONSE, pkt6_sent->getType());
381     EXPECT_EQ("eth0", pkt6_sent->getIface());
382     EXPECT_EQ(ETH0_INDEX, pkt6_sent->getIndex());
383     EXPECT_EQ("2001:db8:1::123", pkt6_sent->getRemoteAddr().toText());
384 
385     // Both DHCP4o6 and encapsulated DHCPv6 packet should have the
386     // flag enabled.
387     EXPECT_TRUE(callback_sent_pkt_options_copy_.first);
388     EXPECT_TRUE(callback_sent_pkt_options_copy_.second);
389 
390     // Verify the 4o6 part
391     OptionCollection sent_msgs = pkt6_sent->getOptions(D6O_DHCPV4_MSG);
392     ASSERT_EQ(1, sent_msgs.size());
393     OptionPtr sent_msg = sent_msgs.begin()->second;
394     ASSERT_TRUE(sent_msg);
395     const OptionBuffer sent_buf = sent_msg->getData();
396     Pkt4Ptr pkt4_opt;
397     ASSERT_NO_THROW(pkt4_opt.reset(new Pkt4(&sent_buf[0], sent_buf.size())));
398     ASSERT_NO_THROW(pkt4_opt->unpack());
399     EXPECT_EQ(DHCPACK, pkt4_sent->getType());
400     EXPECT_EQ(pkt4_sent->len(), pkt4_opt->len());
401 
402     // Verify statistics
403     pkt4_snd = mgr.getObservation("pkt4-sent");
404     pkt4_ack = mgr.getObservation("pkt4-ack-sent");
405     ASSERT_TRUE(pkt4_snd);
406     ASSERT_TRUE(pkt4_ack);
407     EXPECT_EQ(1, pkt4_snd->getInteger().first);
408     EXPECT_EQ(1, pkt4_ack->getInteger().first);
409 }
410 
411 } // end of anonymous namespace
412