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