1 /*
2 * libwebsockets - small server side websockets and web server implementation
3 *
4 * Copyright (C) 2010 - 2021 Andy Green <andy@warmcat.com>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to
8 * deal in the Software without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 * IN THE SOFTWARE.
23 *
24 * The protocol part of dhcp4 client
25 */
26
27 #include "private-lib-core.h"
28 #include "private-lib-system-dhcpclient.h"
29
30 #define LDHC_OP_BOOTREQUEST 1
31 #define LDHC_OP_BOOTREPLY 2
32
33 /*
34 * IPv4... max total 576
35 *
36 * 0 1 2 3
37 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
38 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
39 * | op (1) | htype (1) | hlen (1) | hops (1) |
40 * +---------------+---------------+---------------+---------------+
41 * | +04 xid (4) |
42 * +-------------------------------+-------------------------------+
43 * | +08 secs (2) | +0a flags (2) |
44 * +-------------------------------+-------------------------------+
45 * | +0C ciaddr (4) client IP |
46 * +---------------------------------------------------------------+
47 * | +10 yiaddr (4) your IP |
48 * +---------------------------------------------------------------+
49 * | +14 siaddr (4) server IP |
50 * +---------------------------------------------------------------+
51 * | +18 giaddr (4) gateway IP |
52 * +---------------------------------------------------------------+
53 * | |
54 * | +1C chaddr (16) client HWADDR |
55 * +---------------------------------------------------------------+
56 * | |
57 * | +2C sname (64) |
58 * +---------------------------------------------------------------+
59 * | |
60 * | +6C file (128) |
61 * +---------------------------------------------------------------+
62 * | |
63 * | +EC options (variable) |
64 * +---------------------------------------------------------------+
65 */
66
67 static const uint8_t rawdisc4[] = {
68 0x45, 0x00, 0, 0, 0, 0, 0x40, 0, 0x2e, IPPROTO_UDP,
69 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff,
70 0, 68, 0, 67, 0, 0, 0, 0
71 };
72
73 static const uint32_t botable2[] = { 1500, 1750, 5000 /* in case dog slow */ };
74 static const lws_retry_bo_t bo2 = {
75 botable2, LWS_ARRAY_SIZE(botable2), LWS_RETRY_CONCEAL_ALWAYS, 0, 0, 20 };
76
77 static int
lws_dhcpc4_prep(uint8_t * start,unsigned int bufsiz,lws_dhcpc_req_t * r,int op)78 lws_dhcpc4_prep(uint8_t *start, unsigned int bufsiz, lws_dhcpc_req_t *r, int op)
79 {
80 uint8_t *p = start;
81
82 memset(start, 0, bufsiz);
83
84 *p++ = 1;
85 *p++ = 1;
86 *p++ = 6; /* sizeof ethernet MAC */
87
88 memcpy(p + 1, r->xid, 4);
89
90 // p[7] = 0x80; /* broadcast flag */
91
92 p += 0x1c - 3;
93
94 if (lws_plat_ifname_to_hwaddr(r->wsi_raw->desc.sockfd,
95 (const char *)&r[1], r->is.mac, 6) < 0)
96 return -1;
97
98 memcpy(p, r->is.mac, 6);
99
100 p += 16 + 64 + 128;
101
102 *p++ = 0x63; /* RFC2132 Magic Cookie indicates start of options */
103 *p++ = 0x82;
104 *p++ = 0x53;
105 *p++ = 0x63;
106
107 *p++ = LWSDHC4POPT_MESSAGE_TYPE;
108 *p++ = 1; /* length */
109 *p++ = (uint8_t)op;
110
111 switch (op) {
112 case LWSDHC4PDISCOVER:
113 *p++ = LWSDHC4POPT_PARAM_REQ_LIST;
114 *p++ = 4; /* length */
115 *p++ = LWSDHC4POPT_SUBNET_MASK;
116 *p++ = LWSDHC4POPT_ROUTER;
117 *p++ = LWSDHC4POPT_DNSERVER;
118 *p++ = LWSDHC4POPT_DOMAIN_NAME;
119 break;
120
121 case LWSDHC4PREQUEST:
122 if (r->is.sa46[LWSDH_SA46_IP].sa4.sin_family != AF_INET)
123 break;
124 *p++ = LWSDHC4POPT_REQUESTED_ADS;
125 *p++ = 4; /* length */
126 lws_ser_wu32be(p, r->is.sa46[LWSDH_SA46_IP].sa4.sin_addr.s_addr);
127 p += 4;
128 *p++ = LWSDHC4POPT_SERVER_ID;
129 *p++ = 4; /* length */
130 lws_ser_wu32be(p, r->is.sa46[LWSDH_SA46_DHCP_SERVER].sa4.sin_addr.s_addr);
131 p += 4;
132 break;
133 }
134
135 *p++ = LWSDHC4POPT_END_OPTIONS;
136
137 return lws_ptr_diff(p, start);
138 }
139
140 static int
callback_dhcpc4(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)141 callback_dhcpc4(struct lws *wsi, enum lws_callback_reasons reason, void *user,
142 void *in, size_t len)
143 {
144 lws_dhcpc_req_t *r = (lws_dhcpc_req_t *)user;
145 uint8_t pkt[LWS_PRE + 576], *p = pkt + LWS_PRE;
146 int n, m;
147
148 switch (reason) {
149
150 case LWS_CALLBACK_RAW_ADOPT:
151 lwsl_debug("%s: LWS_CALLBACK_RAW_ADOPT\n", __func__);
152 lws_callback_on_writable(wsi);
153 break;
154
155 case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
156 lwsl_err("%s: udp conn failed\n", __func__);
157
158 /* fallthru */
159 case LWS_CALLBACK_RAW_CLOSE:
160 lwsl_debug("%s: LWS_CALLBACK_RAW_CLOSE\n", __func__);
161 if (!r)
162 break;
163 r->wsi_raw = NULL;
164 lws_sul_cancel(&r->sul_write);
165 if (r->state != LDHC_BOUND) {
166 r->state = LDHC_INIT;
167 lws_retry_sul_schedule(r->context, 0, &r->sul_conn,
168 &bo2, lws_dhcpc4_retry_conn,
169 &r->retry_count_conn);
170 }
171 break;
172
173 case LWS_CALLBACK_RAW_RX:
174
175 if (lws_dhcpc4_parse(r, in, len))
176 break;
177
178 /*
179 * that's it... commit to the configuration
180 */
181
182 /* set up our network interface as offered */
183
184 if (lws_plat_ifconfig(r->wsi_raw->desc.sockfd, &r->is))
185 /*
186 * Problem setting the IP... maybe something
187 * transient like racing with NetworkManager?
188 * Since the sul retries are still around it
189 * will retry
190 */
191 return -1;
192
193 /* clear timeouts related to the broadcast socket */
194
195 lws_sul_cancel(&r->sul_write);
196 lws_sul_cancel(&r->sul_conn);
197
198 lwsl_notice("%s: DHCP configured %s\n", __func__,
199 (const char *)&r[1]);
200 r->state = LDHC_BOUND;
201
202 lws_state_transition_steps(&wsi->a.context->mgr_system,
203 LWS_SYSTATE_OPERATIONAL);
204
205 r->cb(r->opaque, &r->is);
206
207 r->wsi_raw = NULL;
208
209 return -1; /* close the broadcast wsi */
210
211 case LWS_CALLBACK_RAW_WRITEABLE:
212
213 if (!r)
214 break;
215
216 /*
217 * UDP is not reliable, it can be locally dropped, or dropped
218 * by any intermediary or the remote peer. So even though we
219 * will do the write in a moment, we schedule another request
220 * for rewrite according to the wsi retry policy.
221 *
222 * If the result came before, we'll cancel it in the close flow.
223 *
224 * If we have already reached the end of our concealed retries
225 * in the policy, just close without another write.
226 */
227 if (lws_dll2_is_detached(&r->sul_write.list) &&
228 lws_retry_sul_schedule_retry_wsi(wsi, &r->sul_write,
229 lws_dhcpc_retry_write,
230 &r->retry_count_write)) {
231 /* we have reached the end of our concealed retries */
232 lwsl_warn("%s: concealed retries done, failing\n",
233 __func__);
234 goto retry_conn;
235 }
236
237 switch (r->state) {
238 case LDHC_INIT:
239 n = LWSDHC4PDISCOVER;
240 goto bcast;
241
242 case LDHC_REQUESTING:
243 n = LWSDHC4PREQUEST;
244
245 /* fallthru */
246 bcast:
247 n = lws_dhcpc4_prep(p + 28, (unsigned int)
248 (sizeof(pkt) - LWS_PRE - 28), r, n);
249 if (n < 0) {
250 lwsl_err("%s: failed to prep\n", __func__);
251 break;
252 }
253
254 m = lws_plat_rawudp_broadcast(p, rawdisc4,
255 LWS_ARRAY_SIZE(rawdisc4),
256 (size_t)(n + 28),
257 r->wsi_raw->desc.sockfd,
258 (const char *)&r[1]);
259 if (m < 0)
260 lwsl_err("%s: Failed to write dhcp client req: "
261 "%d %d, errno %d\n", __func__,
262 n, m, LWS_ERRNO);
263 break;
264 default:
265 break;
266 }
267
268 return 0;
269
270 retry_conn:
271 lws_retry_sul_schedule(wsi->a.context, 0, &r->sul_conn, &bo2,
272 lws_dhcpc4_retry_conn,
273 &r->retry_count_conn);
274
275 return -1;
276
277 default:
278 break;
279 }
280
281 return 0;
282 }
283
284 struct lws_protocols lws_system_protocol_dhcpc4 =
285 { "lws-dhcp4client", callback_dhcpc4, 0, 128, 0, NULL, 0 };
286
287 void
lws_dhcpc4_retry_conn(struct lws_sorted_usec_list * sul)288 lws_dhcpc4_retry_conn(struct lws_sorted_usec_list *sul)
289 {
290 lws_dhcpc_req_t *r = lws_container_of(sul, lws_dhcpc_req_t, sul_conn);
291
292 if (r->wsi_raw || !lws_dll2_is_detached(&r->sul_conn.list))
293 return;
294
295 /* create the UDP socket aimed at the server */
296
297 r->retry_count_write = 0;
298 r->wsi_raw = lws_create_adopt_udp(r->context->vhost_system, "0.0.0.0",
299 68, LWS_CAUDP_PF_PACKET |
300 LWS_CAUDP_BROADCAST,
301 "lws-dhcp4client", (const char *)&r[1],
302 NULL, NULL, &bo2, "dhcpc");
303 lwsl_debug("%s: created wsi_raw: %s\n", __func__, lws_wsi_tag(r->wsi_raw));
304 if (!r->wsi_raw) {
305 lwsl_err("%s: unable to create udp skt\n", __func__);
306
307 lws_retry_sul_schedule(r->context, 0, &r->sul_conn, &bo2,
308 lws_dhcpc4_retry_conn,
309 &r->retry_count_conn);
310
311 return;
312 }
313
314 /* force the network if up */
315 lws_plat_if_up((const char *)&r[1], r->wsi_raw->desc.sockfd, 0);
316 lws_plat_if_up((const char *)&r[1], r->wsi_raw->desc.sockfd, 1);
317
318 r->wsi_raw->user_space = r;
319 r->wsi_raw->user_space_externally_allocated = 1;
320
321 lws_get_random(r->wsi_raw->a.context, r->xid, 4);
322 }
323
324 static void
lws_sa46_set_ipv4(lws_dhcpc_req_t * r,unsigned int which,uint8_t * p)325 lws_sa46_set_ipv4(lws_dhcpc_req_t *r, unsigned int which, uint8_t *p)
326 {
327 r->is.sa46[which].sa4.sin_family = AF_INET;
328 r->is.sa46[which].sa4.sin_addr.s_addr = ntohl(lws_ser_ru32be(p));
329 }
330
331 int
lws_dhcpc4_parse(lws_dhcpc_req_t * r,void * in,size_t len)332 lws_dhcpc4_parse(lws_dhcpc_req_t *r, void *in, size_t len)
333 {
334 uint8_t pkt[LWS_PRE + 576], *p = pkt + LWS_PRE, *end;
335 int n, m;
336
337 switch (r->state) {
338 case LDHC_INIT: /* expect DHCPOFFER */
339 case LDHC_REQUESTING: /* expect DHCPACK */
340 /*
341 * We should check carefully if we like what we were
342 * sent... anything can spam us with crafted replies
343 */
344 if (len < 0x100)
345 break;
346
347 p = (uint8_t *)in + 28; /* skip to UDP payload */
348 if (p[0] != 2 || p[1] != 1 || p[2] != 6)
349 break;
350
351 if (memcmp(&p[4], r->xid, 4)) /* must be our xid */
352 break;
353
354 if (memcmp(&p[0x1c], r->is.mac, 6)) /* our netif mac? */
355 break;
356
357 /* the DHCP magic cookie must be in place */
358 if (lws_ser_ru32be(&p[0xec]) != 0x63825363)
359 break;
360
361 /* "your" client IP address */
362 lws_sa46_set_ipv4(r, LWSDH_SA46_IP, p + 0x10);
363 /* IP of next server used in bootstrap */
364 lws_sa46_set_ipv4(r, LWSDH_SA46_DHCP_SERVER, p + 0x14);
365
366 /* it looks legit so far... look at the options */
367
368 end = (uint8_t *)in + len;
369 p += 0xec + 4;
370 while (p < end) {
371 uint8_t c = *p++;
372 uint8_t l = 0;
373
374 if (c && c != 0xff) {
375 /* pad 0 and EOT 0xff have no length */
376 l = *p++;
377 if (!l) {
378 lwsl_err("%s: zero length\n",
379 __func__);
380 goto broken;
381 }
382 if (p + l > end) {
383 /* ...nice try... */
384 lwsl_err("%s: bad len\n",
385 __func__);
386 goto broken;
387 }
388 }
389
390 if (c == 0xff) /* end of options */
391 break;
392
393 m = 0;
394 switch (c) {
395 case LWSDHC4POPT_SUBNET_MASK:
396 n = LWSDH_IPV4_SUBNET_MASK;
397 goto get_ipv4;
398
399 case LWSDHC4POPT_ROUTER:
400 lws_sa46_set_ipv4(r, LWSDH_SA46_IPV4_ROUTER, p);
401 break;
402
403 case LWSDHC4POPT_TIME_SERVER:
404 lws_sa46_set_ipv4(r, LWSDH_SA46_NTP_SERVER, p);
405 break;
406
407 case LWSDHC4POPT_BROADCAST_ADS:
408 n = LWSDH_IPV4_BROADCAST;
409 goto get_ipv4;
410
411 case LWSDHC4POPT_LEASE_TIME:
412 n = LWSDH_LEASE_SECS;
413 goto get_ipv4;
414
415 case LWSDHC4POPT_RENEWAL_TIME: /* AKA T1 */
416 n = LWSDH_RENEWAL_SECS;
417 goto get_ipv4;
418
419 case LWSDHC4POPT_REBINDING_TIME: /* AKA T2 */
420 n = LWSDH_REBINDING_SECS;
421 goto get_ipv4;
422
423 case LWSDHC4POPT_DNSERVER:
424 if (l & 3)
425 break;
426 m = LWSDH_SA46_DNS_SRV_1;
427 while (l && m - LWSDH_SA46_DNS_SRV_1 < 4) {
428 lws_sa46_set_ipv4(r, (unsigned int)m++, p);
429 l = (uint8_t)(l - 4);
430 p += 4;
431 }
432 break;
433
434 case LWSDHC4POPT_DOMAIN_NAME:
435 m = l;
436 if (m > (int)sizeof(r->is.domain) - 1)
437 m = sizeof(r->is.domain) - 1;
438 lws_strnncpy(r->is.domain, (const char *)p,
439 (unsigned int)m, sizeof(r->is.domain));
440 break;
441
442 case LWSDHC4POPT_MESSAGE_TYPE:
443 /*
444 * Confirm this is the right message
445 * for the state of the negotiation
446 */
447 if (r->state == LDHC_INIT && *p != LWSDHC4POFFER)
448 goto broken;
449 if (r->state == LDHC_REQUESTING &&
450 *p != LWSDHC4PACK)
451 goto broken;
452 break;
453
454 default:
455 break;
456 }
457
458 p += l;
459 continue;
460 get_ipv4:
461 if (l >= 4)
462 r->is.nums[n] = ntohl(lws_ser_ru32be(p));
463 p += l;
464 continue;
465 broken:
466 memset(r->is.sa46, 0, sizeof(r->is.sa46));
467 break;
468 }
469
470 #if defined(_DEBUG)
471 /* dump what we have parsed out */
472
473 for (n = 0; n < (int)_LWSDH_NUMS_COUNT; n++) {
474 m = (int)ntohl(r->is.nums[n]);
475 lwsl_info("%s: %d: 0x%x\n", __func__, n, m);
476 }
477
478 for (n = 0; n < (int)_LWSDH_SA46_COUNT; n++) {
479 lws_sa46_write_numeric_address(&r->is.sa46[n],
480 (char *)pkt, 48);
481 lwsl_info("%s: %d: %s\n", __func__, n, pkt);
482 }
483 #endif
484
485 /*
486 * Having seen everything in there... do we really feel
487 * we could use it? Everything critical is there?
488 */
489
490 if (!r->is.sa46[LWSDH_SA46_IP].sa4.sin_family ||
491 !r->is.sa46[LWSDH_SA46_DHCP_SERVER].sa4.sin_family ||
492 !r->is.sa46[LWSDH_SA46_IPV4_ROUTER].sa4.sin_family ||
493 !r->is.nums[LWSDH_IPV4_SUBNET_MASK] ||
494 !r->is.nums[LWSDH_LEASE_SECS] ||
495 !r->is.sa46[LWSDH_SA46_DNS_SRV_1].sa4.sin_family) {
496 lwsl_notice("%s: rejecting on incomplete\n", __func__);
497 memset(r->is.sa46, 0, sizeof(r->is.sa46));
498 break;
499 }
500
501 /*
502 * Network layout has to be internally consistent...
503 * DHCP server has to be reachable by broadcast and
504 * default route has to be on same subnet
505 */
506
507 if ((r->is.sa46[LWSDH_SA46_IP].sa4.sin_addr.s_addr &
508 r->is.nums[LWSDH_IPV4_SUBNET_MASK]) !=
509 (r->is.sa46[LWSDH_SA46_DHCP_SERVER].sa4.sin_addr.s_addr &
510 r->is.nums[LWSDH_IPV4_SUBNET_MASK])) {
511 lwsl_notice("%s: rejecting on srv %x reachable on mask %x\n",
512 __func__, r->is.sa46[LWSDH_SA46_IP].sa4.sin_addr.s_addr,
513 r->is.nums[LWSDH_IPV4_SUBNET_MASK]);
514 break;
515 }
516
517 if (r->state == LDHC_INIT) {
518 lwsl_info("%s: moving to REQ\n", __func__);
519 r->state = LDHC_REQUESTING;
520 lws_callback_on_writable(r->wsi_raw);
521 //break;
522 }
523
524 return 0;
525
526 default:
527 break;
528 }
529
530 return 1;
531 }
532
533