1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <net/if_arp.h>
5 #include <stdbool.h>
6 #include <stdio.h>
7 #include <string.h>
8
9 #include "alloc-util.h"
10 #include "dhcp-internal.h"
11 #include "dhcp-protocol.h"
12 #include "macro.h"
13 #include "memory-util.h"
14
15 struct option_desc {
16 uint8_t sname[64];
17 int snamelen;
18 uint8_t file[128];
19 int filelen;
20 uint8_t options[128];
21 int len;
22 bool success;
23 int filepos;
24 int snamepos;
25 int pos;
26 };
27
28 static bool verbose = false;
29
30 static struct option_desc option_tests[] = {
31 { {}, 0, {}, 0, { 42, 5, 65, 66, 67, 68, 69 }, 7, false, },
32 { {}, 0, {}, 0, { 42, 5, 65, 66, 67, 68, 69, 0, 0,
33 SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_ACK }, 12, true, },
34 { {}, 0, {}, 0, { 8, 255, 70, 71, 72 }, 5, false, },
35 { {}, 0, {}, 0, { 0x35, 0x01, 0x05, 0x36, 0x04, 0x01, 0x00, 0xa8,
36 0xc0, 0x33, 0x04, 0x00, 0x01, 0x51, 0x80, 0x01,
37 0x04, 0xff, 0xff, 0xff, 0x00, 0x03, 0x04, 0xc0,
38 0xa8, 0x00, 0x01, 0x06, 0x04, 0xc0, 0xa8, 0x00,
39 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
40 40, true, },
41 { {}, 0, {}, 0, { SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_OFFER,
42 42, 3, 0, 0, 0 }, 8, true, },
43 { {}, 0, {}, 0, { 42, 2, 1, 2, 44 }, 5, false, },
44
45 { {}, 0,
46 { 222, 3, 1, 2, 3, SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_NAK }, 8,
47 { SD_DHCP_OPTION_OVERLOAD, 1, DHCP_OVERLOAD_FILE }, 3, true, },
48
49 { { 1, 4, 1, 2, 3, 4, SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_ACK }, 9,
50 { 222, 3, 1, 2, 3 }, 5,
51 { SD_DHCP_OPTION_OVERLOAD, 1,
52 DHCP_OVERLOAD_FILE|DHCP_OVERLOAD_SNAME }, 3, true, },
53 };
54
dhcp_type(int type)55 static const char *dhcp_type(int type) {
56 switch (type) {
57 case DHCP_DISCOVER:
58 return "DHCPDISCOVER";
59 case DHCP_OFFER:
60 return "DHCPOFFER";
61 case DHCP_REQUEST:
62 return "DHCPREQUEST";
63 case DHCP_DECLINE:
64 return "DHCPDECLINE";
65 case DHCP_ACK:
66 return "DHCPACK";
67 case DHCP_NAK:
68 return "DHCPNAK";
69 case DHCP_RELEASE:
70 return "DHCPRELEASE";
71 default:
72 return "unknown";
73 }
74 }
75
test_invalid_buffer_length(void)76 static void test_invalid_buffer_length(void) {
77 DHCPMessage message;
78
79 assert_se(dhcp_option_parse(&message, 0, NULL, NULL, NULL) == -EINVAL);
80 assert_se(dhcp_option_parse(&message, sizeof(DHCPMessage) - 1, NULL, NULL, NULL) == -EINVAL);
81 }
82
test_message_init(void)83 static void test_message_init(void) {
84 _cleanup_free_ DHCPMessage *message = NULL;
85 size_t optlen = 4, optoffset;
86 size_t len = sizeof(DHCPMessage) + optlen;
87 uint8_t *magic;
88
89 message = malloc0(len);
90
91 assert_se(dhcp_message_init(message, BOOTREQUEST, 0x12345678,
92 DHCP_DISCOVER, ARPHRD_ETHER, ETH_ALEN, (uint8_t[16]){},
93 optlen, &optoffset) >= 0);
94
95 assert_se(message->xid == htobe32(0x12345678));
96 assert_se(message->op == BOOTREQUEST);
97
98 magic = (uint8_t*)&message->magic;
99
100 assert_se(magic[0] == 99);
101 assert_se(magic[1] == 130);
102 assert_se(magic[2] == 83);
103 assert_se(magic[3] == 99);
104
105 assert_se(dhcp_option_parse(message, len, NULL, NULL, NULL) >= 0);
106 }
107
create_message(uint8_t * options,uint16_t optlen,uint8_t * file,uint8_t filelen,uint8_t * sname,uint8_t snamelen)108 static DHCPMessage *create_message(uint8_t *options, uint16_t optlen,
109 uint8_t *file, uint8_t filelen,
110 uint8_t *sname, uint8_t snamelen) {
111 DHCPMessage *message;
112 size_t len = sizeof(DHCPMessage) + optlen;
113
114 message = malloc0(len);
115 assert_se(message);
116
117 memcpy_safe(&message->options, options, optlen);
118 memcpy_safe(&message->file, file, filelen);
119 memcpy_safe(&message->sname, sname, snamelen);
120
121 return message;
122 }
123
test_ignore_opts(uint8_t * descoption,int * descpos,int * desclen)124 static void test_ignore_opts(uint8_t *descoption, int *descpos, int *desclen) {
125 assert_se(*descpos >= 0);
126
127 while (*descpos < *desclen) {
128 switch (descoption[*descpos]) {
129 case SD_DHCP_OPTION_PAD:
130 *descpos += 1;
131 break;
132
133 case SD_DHCP_OPTION_MESSAGE_TYPE:
134 case SD_DHCP_OPTION_OVERLOAD:
135 *descpos += 3;
136 break;
137
138 default:
139 return;
140 }
141 }
142 }
143
test_options_cb(uint8_t code,uint8_t len,const void * option,void * userdata)144 static int test_options_cb(uint8_t code, uint8_t len, const void *option, void *userdata) {
145 struct option_desc *desc = userdata;
146 uint8_t *descoption = NULL;
147 int *desclen = NULL, *descpos = NULL;
148 uint8_t optcode = 0;
149 uint8_t optlen = 0;
150
151 assert_se((!desc && !code && !len) || desc);
152
153 if (!desc)
154 return -EINVAL;
155
156 assert_se(code != SD_DHCP_OPTION_PAD);
157 assert_se(code != SD_DHCP_OPTION_END);
158 assert_se(code != SD_DHCP_OPTION_MESSAGE_TYPE);
159 assert_se(code != SD_DHCP_OPTION_OVERLOAD);
160
161 while (desc->pos >= 0 || desc->filepos >= 0 || desc->snamepos >= 0) {
162
163 if (desc->pos >= 0) {
164 descoption = &desc->options[0];
165 desclen = &desc->len;
166 descpos = &desc->pos;
167 } else if (desc->filepos >= 0) {
168 descoption = &desc->file[0];
169 desclen = &desc->filelen;
170 descpos = &desc->filepos;
171 } else if (desc->snamepos >= 0) {
172 descoption = &desc->sname[0];
173 desclen = &desc->snamelen;
174 descpos = &desc->snamepos;
175 }
176
177 assert_se(descoption && desclen && descpos);
178
179 if (*desclen)
180 test_ignore_opts(descoption, descpos, desclen);
181
182 if (*descpos < *desclen)
183 break;
184
185 if (*descpos == *desclen)
186 *descpos = -1;
187 }
188
189 assert_se(descpos);
190 assert_se(*descpos != -1);
191
192 optcode = descoption[*descpos];
193 optlen = descoption[*descpos + 1];
194
195 if (verbose)
196 printf("DHCP code %2d(%2d) len %2d(%2d) ", code, optcode,
197 len, optlen);
198
199 assert_se(code == optcode);
200 assert_se(len == optlen);
201
202 for (unsigned i = 0; i < len; i++) {
203 if (verbose)
204 printf("0x%02x(0x%02x) ",
205 ((uint8_t*) option)[i],
206 descoption[*descpos + 2 + i]);
207
208 assert_se(((uint8_t*) option)[i] == descoption[*descpos + 2 + i]);
209 }
210
211 if (verbose)
212 printf("\n");
213
214 *descpos += optlen + 2;
215
216 test_ignore_opts(descoption, descpos, desclen);
217
218 if (desc->pos != -1 && desc->pos == desc->len)
219 desc->pos = -1;
220
221 if (desc->filepos != -1 && desc->filepos == desc->filelen)
222 desc->filepos = -1;
223
224 if (desc->snamepos != -1 && desc->snamepos == desc->snamelen)
225 desc->snamepos = -1;
226
227 return 0;
228 }
229
test_options(struct option_desc * desc)230 static void test_options(struct option_desc *desc) {
231 uint8_t *options = NULL;
232 uint8_t *file = NULL;
233 uint8_t *sname = NULL;
234 int optlen = 0;
235 int filelen = 0;
236 int snamelen = 0;
237 int buflen = 0;
238 _cleanup_free_ DHCPMessage *message = NULL;
239 int res;
240
241 if (desc) {
242 file = &desc->file[0];
243 filelen = desc->filelen;
244 if (!filelen)
245 desc->filepos = -1;
246
247 sname = &desc->sname[0];
248 snamelen = desc->snamelen;
249 if (!snamelen)
250 desc->snamepos = -1;
251
252 options = &desc->options[0];
253 optlen = desc->len;
254 desc->pos = 0;
255 }
256 message = create_message(options, optlen, file, filelen,
257 sname, snamelen);
258
259 buflen = sizeof(DHCPMessage) + optlen;
260
261 if (!desc) {
262 assert_se((res = dhcp_option_parse(message, buflen, test_options_cb, NULL, NULL)) == -ENOMSG);
263 } else if (desc->success) {
264 assert_se((res = dhcp_option_parse(message, buflen, test_options_cb, desc, NULL)) >= 0);
265 assert_se(desc->pos == -1 && desc->filepos == -1 && desc->snamepos == -1);
266 } else
267 assert_se((res = dhcp_option_parse(message, buflen, test_options_cb, desc, NULL)) < 0);
268
269 if (verbose)
270 printf("DHCP type %s\n", dhcp_type(res));
271 }
272
test_option_removal(struct option_desc * desc)273 static void test_option_removal(struct option_desc *desc) {
274 _cleanup_free_ DHCPMessage *message = create_message(&desc->options[0], desc->len, NULL, 0, NULL, 0);
275
276 assert_se(dhcp_option_parse(message, sizeof(DHCPMessage) + desc->len, NULL, NULL, NULL) >= 0);
277 assert_se((desc->len = dhcp_option_remove_option(message->options, desc->len, SD_DHCP_OPTION_MESSAGE_TYPE)) >= 0);
278 assert_se(dhcp_option_parse(message, sizeof(DHCPMessage) + desc->len, NULL, NULL, NULL) < 0);
279 }
280
281 static uint8_t the_options[64] = {
282 'A', 'B', 'C', 'D',
283 160, 2, 0x11, 0x12,
284 0,
285 31, 8, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
286 0,
287 55, 3, 0x51, 0x52, 0x53,
288 17, 7, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
289 255
290 };
291
test_option_set(void)292 static void test_option_set(void) {
293 _cleanup_free_ DHCPMessage *result = NULL;
294 size_t offset = 0, len, pos;
295
296 result = malloc0(sizeof(DHCPMessage) + 11);
297 assert_se(result);
298
299 result->options[0] = 'A';
300 result->options[1] = 'B';
301 result->options[2] = 'C';
302 result->options[3] = 'D';
303
304 assert_se(dhcp_option_append(result, 0, &offset, 0, SD_DHCP_OPTION_PAD,
305 0, NULL) == -ENOBUFS);
306 assert_se(offset == 0);
307
308 offset = 4;
309 assert_se(dhcp_option_append(result, 5, &offset, 0, SD_DHCP_OPTION_PAD,
310 0, NULL) == -ENOBUFS);
311 assert_se(offset == 4);
312 assert_se(dhcp_option_append(result, 6, &offset, 0, SD_DHCP_OPTION_PAD,
313 0, NULL) >= 0);
314 assert_se(offset == 5);
315
316 offset = pos = 4;
317 len = 11;
318 while (pos < len && the_options[pos] != SD_DHCP_OPTION_END) {
319 assert_se(dhcp_option_append(result, len, &offset, DHCP_OVERLOAD_SNAME,
320 the_options[pos],
321 the_options[pos + 1],
322 &the_options[pos + 2]) >= 0);
323
324 if (the_options[pos] == SD_DHCP_OPTION_PAD)
325 pos++;
326 else
327 pos += 2 + the_options[pos + 1];
328
329 if (pos < len)
330 assert_se(offset == pos);
331 }
332
333 for (unsigned i = 0; i < 9; i++) {
334 if (verbose)
335 printf("%2u: 0x%02x(0x%02x) (options)\n", i, result->options[i],
336 the_options[i]);
337 assert_se(result->options[i] == the_options[i]);
338 }
339
340 if (verbose)
341 printf("%2d: 0x%02x(0x%02x) (options)\n", 9, result->options[9],
342 SD_DHCP_OPTION_END);
343
344 assert_se(result->options[9] == SD_DHCP_OPTION_END);
345
346 if (verbose)
347 printf("%2d: 0x%02x(0x%02x) (options)\n", 10, result->options[10],
348 SD_DHCP_OPTION_PAD);
349
350 assert_se(result->options[10] == SD_DHCP_OPTION_PAD);
351
352 for (unsigned i = 0; i < pos - 8; i++) {
353 if (verbose)
354 printf("%2u: 0x%02x(0x%02x) (sname)\n", i, result->sname[i],
355 the_options[i + 9]);
356 assert_se(result->sname[i] == the_options[i + 9]);
357 }
358
359 if (verbose)
360 printf ("\n");
361 }
362
main(int argc,char * argv[])363 int main(int argc, char *argv[]) {
364 test_invalid_buffer_length();
365 test_message_init();
366
367 test_options(NULL);
368
369 for (unsigned i = 0; i < ELEMENTSOF(option_tests); i++)
370 test_options(&option_tests[i]);
371
372 test_option_set();
373
374 for (unsigned i = 0; i < ELEMENTSOF(option_tests); i++) {
375 struct option_desc *desc = &option_tests[i];
376 if (!desc->success || desc->snamelen > 0 || desc->filelen > 0)
377 continue;
378 test_option_removal(desc);
379 }
380
381 return 0;
382 }
383