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