1 /* $NetBSD: relay_unittests.c,v 1.4 2022/04/03 01:10:59 christos Exp $ */
2
3 /*
4 * Copyright (C) 2019-2022 Internet Systems Consortium, Inc. ("ISC")
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public
7 * License, v. 2.0. If a copy of the MPL was not distributed with this
8 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
16 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 *
18 * Internet Systems Consortium, Inc.
19 * PO Box 360
20 * Newmarket, NH 03857 USA
21 * <info@isc.org>
22 * https://www.isc.org/
23 *
24 */
25
26 #include <sys/cdefs.h>
27 __RCSID("$NetBSD: relay_unittests.c,v 1.4 2022/04/03 01:10:59 christos Exp $");
28
29 #include "config.h"
30 #include <atf-c.h>
31 #include <omapip/omapip_p.h>
32 #include "dhcpd.h"
33
34 /* @brief Externs for dhcrelay.c functions under test */
35 extern int add_agent_options;
36 extern int add_relay_agent_options(struct interface_info *,
37 struct dhcp_packet *, unsigned,
38 struct in_addr);
39
40 extern int find_interface_by_agent_option(struct dhcp_packet *,
41 struct interface_info **,
42 u_int8_t *, int);
43
44 extern int strip_relay_agent_options(struct interface_info *,
45 struct interface_info **,
46 struct dhcp_packet *, unsigned);
47
48 /* @brief Add the given option data to a DHCPv4 packet
49 *
50 * It first fills the packet.options buffer with the given pad character.
51 * Next it copies the DHCP magic cookie value into the beginning of the
52 * options buffer. Finally it appends the given data after the cookie.
53 *
54 * @param packet pointer to the packet
55 * @param data pointer to the option data to copy into the packet's options
56 * buffer
57 * @param len length of the option data to copy
58 * @param pad byte value with which to initialize the packet's options buffer
59 *
60 * @return returns the new length of the packet
61 */
set_packet_options(struct dhcp_packet * packet,unsigned char * data,unsigned len,unsigned char pad)62 unsigned set_packet_options(struct dhcp_packet *packet,
63 unsigned char* data, unsigned len,
64 unsigned char pad) {
65 unsigned new_len;
66 memset(packet->options, pad, DHCP_MAX_OPTION_LEN);
67
68 // Add the COOKIE
69 new_len = 4;
70 memcpy(packet->options, DHCP_OPTIONS_COOKIE, new_len);
71
72 new_len += len;
73 if (new_len > DHCP_MAX_OPTION_LEN) {
74 return(0);
75 }
76
77 memcpy(&packet->options[4], data, len);
78 return(new_len + DHCP_FIXED_NON_UDP);
79 }
80
81 /* @brief Checks two sets of option data for equalit
82 *
83 * It constructs the expected options content by creating an options buffer
84 * filled with the pad value. Next it copies the DHCP magic cookie value
85 * into the beginning of the buffer and then appends the expected data after
86 * the cookie. It the compares this buffer to the actual buffer passed in
87 * for equality and returns the result.
88 *
89 * @param actual_options pointer to the packet::options to be checked
90 * @param expected_data pointer to the expected options data (everything after
91 * the DHCP cookie)
92 * @param data_len length of the expected options data
93 * @param pad byte value with which to initialize the packet's options buffer
94 *
95 * @return zero it the sets of data match, non-zero otherwise
96 */
check_with_pad(unsigned char * actual_options,unsigned char * expected_data,unsigned data_len,unsigned char pad)97 int check_with_pad(unsigned char* actual_options,
98 unsigned char *expected_data,
99 unsigned data_len, unsigned char pad) {
100
101 unsigned char exp_options[DHCP_MAX_OPTION_LEN];
102 unsigned new_len;
103
104 memset(exp_options, pad, DHCP_MAX_OPTION_LEN);
105 new_len = 4;
106 memcpy(exp_options, DHCP_OPTIONS_COOKIE, new_len);
107
108 new_len += data_len;
109 if (new_len > DHCP_MAX_OPTION_LEN) {
110 return(-1);
111 }
112
113 memcpy(&exp_options[4], expected_data, data_len);
114 return (memcmp(actual_options, exp_options, DHCP_MAX_OPTION_LEN));
115 }
116
117 ATF_TC(strip_relay_agent_options_test);
118
ATF_TC_HEAD(strip_relay_agent_options_test,tc)119 ATF_TC_HEAD(strip_relay_agent_options_test, tc) {
120 atf_tc_set_md_var(tc, "descr", "tests strip_relay-agent_options");
121 }
122
123 /* This Test exercises strip_relay_agent_options() function */
ATF_TC_BODY(strip_relay_agent_options_test,tc)124 ATF_TC_BODY(strip_relay_agent_options_test, tc) {
125
126 struct interface_info ifaces;
127 struct interface_info *matched;
128 struct dhcp_packet packet;
129 unsigned len;
130 int ret;
131
132 memset(&ifaces, 0x0, sizeof(ifaces));
133 matched = 0;
134 memset(&packet, 0x0, sizeof(packet));
135 len = 0;
136
137 /* Make sure an empty packet is harmless. We set add_agent_options = 1
138 * to avoid early return when it's 0. */
139 add_agent_options = 1;
140 ret = strip_relay_agent_options(&ifaces, &matched, &packet, len);
141 if (ret != 0) {
142 atf_tc_fail("empty packet failed");
143 }
144
145 {
146 /*
147 * Uses valid input option data to verify that:
148 * - When add_agent_options is false, the output option data is the
149 * the same as the input option data (i.e. nothing removed)
150 * - When add_agent_options is true, 0 length relay agent option has
151 * been removed from the output option data
152 * - When add_agent_options is true, a relay agent option has
153 * been removed from the output option data
154 *
155 */
156
157 unsigned char input_data[] = {
158 0x35, 0x00, /* DHCP_TYPE = DISCOVER */
159 0x52, 0x08, 0x01, 0x06, 0x65, /* Relay Agent Option, len = 8 */
160 0x6e, 0x70, 0x30, 0x73, 0x4f,
161
162 0x42, 0x02, 0x00, 0x10, /* Opt 0x42, len = 2 */
163 0x43, 0x00 /* Opt 0x43, len = 0 */
164 };
165
166 unsigned char input_data2[] = {
167 0x35, 0x00, /* DHCP_TYPE = DISCOVER */
168 0x52, 0x00, /* Relay Agent Option, len = 0 */
169 0x42, 0x02, 0x00, 0x10, /* Opt 0x42, len = 2 */
170 0x43, 0x00 /* Opt 0x43, len = 0 */
171 };
172
173 unsigned char output_data[] = {
174 0x35, 0x00, /* DHCP_TYPE = DISCOVER */
175 0x42, 0x02, 0x00, 0x10, /* Opt 0x42, len = 2 */
176 0x43, 0x00 /* Opt 0x43, len = 0 */
177 };
178
179 unsigned char pad = 0x0;
180 len = set_packet_options(&packet, input_data, sizeof(input_data), pad);
181 if (len == 0) {
182 atf_tc_fail("input_data: set_packet_options failed");
183 }
184
185 /* When add_agent_options = 0, no change should occur */
186 add_agent_options = 0;
187 ret = strip_relay_agent_options(&ifaces, &matched, &packet, len);
188 if (ret != len) {
189 atf_tc_fail("expected unchanged len %d, returned %d", len, ret);
190 }
191
192 if (check_with_pad(packet.options, input_data, sizeof(input_data),
193 pad) != 0) {
194 atf_tc_fail("expected unchanged data, does not match");
195 }
196
197 /* When add_agent_options = 1, it should remove the eight byte
198 * relay agent option. */
199 add_agent_options = 1;
200
201 /* Beause the inbound option data is less than the BOOTP_MIN_LEN,
202 * the output data should get padded out to BOOTP_MIN_LEN
203 * padded out to BOOTP_MIN_LEN */
204 ret = strip_relay_agent_options(&ifaces, &matched, &packet, len);
205 if (ret != BOOTP_MIN_LEN) {
206 atf_tc_fail("input_data: len of %d, returned %d",
207 BOOTP_MIN_LEN, ret);
208 }
209
210 if (check_with_pad(packet.options, output_data, sizeof(output_data),
211 pad) != 0) {
212 atf_tc_fail("input_data: expected data does not match");
213 }
214
215 /* Now let's repeat it with a relay agent option 0 bytes in length. */
216 len = set_packet_options(&packet, input_data2, sizeof(input_data2), pad);
217 if (len == 0) {
218 atf_tc_fail("input_data2 set_packet_options failed");
219 }
220
221 /* Because the inbound option data is less than the BOOTP_MIN_LEN,
222 * the output data should get padded out to BOOTP_MIN_LEN
223 * padded out to BOOTP_MIN_LEN */
224 ret = strip_relay_agent_options(&ifaces, &matched, &packet, len);
225 if (ret != BOOTP_MIN_LEN) {
226 atf_tc_fail("input_data2: len of %d, returned %d",
227 BOOTP_MIN_LEN, ret);
228 }
229
230 if (check_with_pad(packet.options, output_data, sizeof(output_data),
231 pad) != 0) {
232 atf_tc_fail("input_data2: expected output does not match");
233 }
234 }
235
236 {
237 /* Verify that oversized packet filled with long options does not
238 * cause overrun */
239
240 /* We borrowed this union from discover.c:got_one() */
241 union {
242 unsigned char packbuf [4095]; /* Packet input buffer.
243 * Must be as large as largest
244 * possible MTU. */
245 struct dhcp_packet packet;
246 } u;
247
248 unsigned char input_data[] = {
249 0x35, 0x00, /* DHCP_TYPE = DISCOVER */
250 0x52, 0x00, /* Relay Agent Option, len = 0 */
251 0x42, 0x02, 0x00, 0x10, /* Opt 0x42, len = 2 */
252 0x43, 0x00 /* Opt 0x43, len = 0 */
253 };
254
255 /* We'll pad it 0xFE, that way wherever we hit for "length" we'll
256 * have length of 254. Increasing the odds we'll push over the end. */
257 unsigned char pad = 0xFE;
258 memset(u.packbuf, pad, 4095);
259
260 len = set_packet_options(&u.packet, input_data, sizeof(input_data), pad);
261 if (len == 0) {
262 atf_tc_fail("overrun: set_packet_options failed");
263 }
264
265 /* Enable option stripping by setting add_agent_options = 1 */
266 add_agent_options = 1;
267
268 /* strip_relay_agent_options() should detect the overrun and return 0 */
269 ret = strip_relay_agent_options(&ifaces, &matched, &u.packet, 4095);
270 if (ret != 0) {
271 atf_tc_fail("overrun expected return len = 0, we got %d", ret);
272 }
273 }
274 }
275
276 ATF_TC(add_relay_agent_options_test);
277
ATF_TC_HEAD(add_relay_agent_options_test,tc)278 ATF_TC_HEAD(add_relay_agent_options_test, tc) {
279 atf_tc_set_md_var(tc, "descr", "tests agent_relay-agent_options");
280 }
281
282 /* This Test exercises add_relay_agent_options() function */
ATF_TC_BODY(add_relay_agent_options_test,tc)283 ATF_TC_BODY(add_relay_agent_options_test, tc) {
284
285 struct interface_info ifc;
286 struct dhcp_packet packet;
287 unsigned len;
288 int ret;
289 struct in_addr giaddr;
290
291 giaddr.s_addr = inet_addr("192.0.1.1");
292
293 u_int8_t circuit_id[] = { 0x01,0x02,0x03,0x04,0x05,0x06 };
294 u_int8_t remote_id[] = { 0x11,0x22,0x33,0x44,0x55,0x66 };
295
296 memset(&ifc, 0x0, sizeof(ifc));
297 ifc.circuit_id = circuit_id;
298 ifc.circuit_id_len = sizeof(circuit_id);
299 ifc.remote_id = remote_id;
300 ifc.remote_id_len = sizeof(remote_id);
301
302 memset(&packet, 0x0, sizeof(packet));
303 len = 0;
304
305 /* Make sure an empty packet is harmless */
306 ret = add_relay_agent_options(&ifc, &packet, len, giaddr);
307 if (ret != 0) {
308 atf_tc_fail("empty packet failed");
309 }
310
311 {
312 /*
313 * Uses valid input option data to verify that:
314 * - When add_agent_options is false, the output option data is the
315 * the same as the input option data (i.e. nothing removed)
316 * - When add_agent_options is true, the relay agent option has
317 * been removed from the output option data
318 * - When add_agent_options is true, 0 length relay agent option has
319 * been removed from the output option data
320 *
321 */
322
323 unsigned char input_data[] = {
324 0x35, 0x00, /* DHCP_TYPE = DISCOVER */
325 0x52, 0x08, 0x01, 0x06, 0x65, /* Relay Agent Option, len = 8 */
326 0x6e, 0x70, 0x30, 0x73, 0x4f,
327 0x42, 0x02, 0x00, 0x10, /* Opt 0x42, len = 2 */
328 0x43, 0x00 /* Opt 0x43, len = 0 */
329 };
330
331 unsigned char input_data2[] = {
332 0x35, 0x00, /* DHCP_TYPE = DISCOVER */
333 0x52, 0x00, /* Relay Agent Option, len = 0 */
334 0x42, 0x02, 0x00, 0x10, /* Opt 0x42, len = 2 */
335 0x43, 0x00 /* Opt 0x43, len = 0 */
336 };
337
338 unsigned char output_data[] = {
339 0x35, 0x00, /* DHCP_TYPE = DISCOVER */
340 0x42, 0x02, 0x00, 0x10, /* Opt 0x42, len = 2 */
341 0x43, 0x00, /* Opt 0x43, len = 0 */
342 0x52, 0x10, /* Relay Agent, len = 16 */
343 0x1, 0x6, /* Circuit id, len = 6 */
344 0x1, 0x2, 0x3, 0x4, 0x5, 0x6,
345 0x2, 0x6, /* Remete id, len = 6 */
346 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0xff
347 };
348
349 unsigned char pad = 0x0;
350 len = set_packet_options(&packet, input_data, sizeof(input_data), pad);
351 if (len == 0) {
352 atf_tc_fail("input_data: set_packet_options failed");
353 }
354
355 /* When add_agent_options = 0, no change should occur */
356 add_agent_options = 0;
357 ret = add_relay_agent_options(&ifc, &packet, len, giaddr);
358 if (ret != len) {
359 atf_tc_fail("expected unchanged len %d, returned %d", len, ret);
360 }
361
362 if (check_with_pad(packet.options, input_data, sizeof(input_data),
363 pad) != 0) {
364 atf_tc_fail("expected unchanged data, does not match");
365 }
366
367 /* When add_agent_options = 1, it should remove the eight byte
368 * relay agent option. */
369 add_agent_options = 1;
370
371 /* Because the inbound option data is less than the BOOTP_MIN_LEN,
372 * the output data should get padded out to BOOTP_MIN_LEN
373 * padded out to BOOTP_MIN_LEN */
374 ret = add_relay_agent_options(&ifc, &packet, len, giaddr);
375 if (ret != BOOTP_MIN_LEN) {
376 atf_tc_fail("input_data: len of %d, returned %d",
377 BOOTP_MIN_LEN, ret);
378 }
379
380 if (check_with_pad(packet.options, output_data, sizeof(output_data),
381 pad) != 0) {
382 atf_tc_fail("input_data: expected data does not match");
383 }
384
385 /* Now let's repeat it with a relay agent option 0 bytes in length. */
386 len = set_packet_options(&packet, input_data2, sizeof(input_data2),
387 pad);
388 if (len == 0) {
389 atf_tc_fail("input_data2 set_packet_options failed");
390 }
391
392 /* Because the inbound option data is less than the BOOTP_MIN_LEN,
393 * the output data should get padded out to BOOTP_MIN_LEN
394 * padded out to BOOTP_MIN_LEN */
395 ret = add_relay_agent_options(&ifc, &packet, len, giaddr);
396 if (ret != BOOTP_MIN_LEN) {
397 atf_tc_fail("input_data2: len of %d, returned %d",
398 BOOTP_MIN_LEN, ret);
399 }
400
401 if (check_with_pad(packet.options, output_data, sizeof(output_data),
402 pad) != 0) {
403 atf_tc_fail("input_data2: expected output does not match");
404 }
405 }
406 }
407
408 ATF_TC(gwaddr_override_test);
409
ATF_TC_HEAD(gwaddr_override_test,tc)410 ATF_TC_HEAD(gwaddr_override_test, tc) {
411 atf_tc_set_md_var(tc, "descr", "tests that gateway addr (giaddr) field can be overridden");
412 }
413
414 extern isc_boolean_t use_fake_gw;
415 extern struct in_addr gw;
416
417 /* This basic test checks if the new gwaddr override (-g) option is disabled by default */
ATF_TC_BODY(gwaddr_override_test,tc)418 ATF_TC_BODY(gwaddr_override_test, tc) {
419
420 if (use_fake_gw == ISC_TRUE) {
421 atf_tc_fail("the gwaddr override should be disabled by default");
422 }
423 char txt[16] = {0};
424 inet_ntop(AF_INET, &gw, txt, sizeof(txt));
425 if (strncmp(txt, "0.0.0.0", 8) != 0) {
426 atf_tc_fail("the default gwaddr override value should be 0.0.0.0, but is %s", txt);
427 }
428 }
429
ATF_TP_ADD_TCS(tp)430 ATF_TP_ADD_TCS(tp) {
431 ATF_TP_ADD_TC(tp, strip_relay_agent_options_test);
432 ATF_TP_ADD_TC(tp, add_relay_agent_options_test);
433 ATF_TP_ADD_TC(tp, gwaddr_override_test);
434
435 return (atf_no_error());
436 }
437