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