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
67static 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
73static const uint32_t botable2[] = { 1500, 1750, 5000 /* in case dog slow */ };
74static const lws_retry_bo_t bo2 = {
75	botable2, LWS_ARRAY_SIZE(botable2), LWS_RETRY_CONCEAL_ALWAYS, 0, 0, 20 };
76
77static int
78lws_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
140static int
141callback_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 */
246bcast:
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
270retry_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
284struct lws_protocols lws_system_protocol_dhcpc4 =
285	{ "lws-dhcp4client", callback_dhcpc4, 0, 128, 0, NULL, 0 };
286
287void
288lws_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
324static void
325lws_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
331int
332lws_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;
460get_ipv4:
461			if (l >= 4)
462				r->is.nums[n] = ntohl(lws_ser_ru32be(p));
463			p += l;
464			continue;
465broken:
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