/* * libwebsockets - small server side websockets and web server implementation * * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "private-lib-core.h" #include "private-lib-system-dhcpclient.h" typedef enum { LDHC_INIT_REBOOT, LDHC_REBOOTING, /* jitterwait */ LDHC_INIT, /* issue DHCPDISCOVER */ LDHC_SELECTING, LDHC_REQUESTING, LDHC_REBINDING, LDHC_BOUND, LDHC_RENEWING } lws_dhcpc_state_t; enum { LWSDHCPDISCOVER = 1, LWSDHCPOFFER, LWSDHCPREQUEST, LWSDHCPDECLINE, LWSDHCPACK, LWSDHCPNACK, LWSDHCPRELEASE, IPV4_PROPOSED = 0, IPV4_SERVER, IPV4_ROUTER, IPV4_SUBNET_MASK, IPV4_BROADCAST, IPV4_TIME_SERVER, IPV4_DNS_SRV_1, IPV4_DNS_SRV_2, IPV4_DNS_SRV_3, IPV4_DNS_SRV_4, IPV4_LEASE_SECS, IPV4_REBINDING_SECS, IPV4_RENEWAL_SECS, _IPV4_COUNT, LWSDHCPOPT_PAD = 0, LWSDHCPOPT_SUBNET_MASK = 1, LWSDHCPOPT_TIME_OFFSET = 2, LWSDHCPOPT_ROUTER = 3, LWSDHCPOPT_TIME_SERVER = 4, LWSDHCPOPT_NAME_SERVER = 5, LWSDHCPOPT_DNSERVER = 6, LWSDHCPOPT_LOG_SERVER = 7, LWSDHCPOPT_COOKIE_SERVER = 8, LWSDHCPOPT_LPR_SERVER = 9, LWSDHCPOPT_IMPRESS_SERVER = 10, LWSDHCPOPT_RESLOC_SERVER = 11, LWSDHCPOPT_HOST_NAME = 12, LWSDHCPOPT_BOOTFILE_SIZE = 13, LWSDHCPOPT_MERIT_DUMP_FILE = 14, LWSDHCPOPT_DOMAIN_NAME = 15, LWSDHCPOPT_SWAP_SERVER = 16, LWSDHCPOPT_ROOT_PATH = 17, LWSDHCPOPT_EXTENSIONS_PATH = 18, LWSDHCPOPT_BROADCAST_ADS = 28, LWSDHCPOPT_REQUESTED_ADS = 50, LWSDHCPOPT_LEASE_TIME = 51, LWSDHCPOPT_OPTION_OVERLOAD = 52, LWSDHCPOPT_MESSAGE_TYPE = 53, LWSDHCPOPT_SERVER_ID = 54, LWSDHCPOPT_PARAM_REQ_LIST = 55, LWSDHCPOPT_MESSAGE = 56, LWSDHCPOPT_MAX_DHCP_MSG_SIZE = 57, LWSDHCPOPT_RENEWAL_TIME = 58, /* AKA T1 */ LWSDHCPOPT_REBINDING_TIME = 59, /* AKA T2 */ LWSDHCPOPT_VENDOR_CLASS_ID = 60, LWSDHCPOPT_CLIENT_ID = 61, LWSDHCPOPT_END_OPTIONS = 255 }; typedef struct lws_dhcpc_req { lws_dll2_t list; char domain[64]; struct lws_context *context; lws_sorted_usec_list_t sul_conn; lws_sorted_usec_list_t sul_write; dhcpc_cb_t cb; /* cb on completion / failure */ void *opaque; /* ignored by lws, give to cb */ /* these are separated so we can close the bcast one asynchronously */ struct lws *wsi_raw; /* for broadcast */ lws_dhcpc_state_t state; uint32_t ipv4[_IPV4_COUNT]; uint16_t retry_count_conn; uint16_t retry_count_write; uint8_t mac[6]; uint8_t xid[4]; uint8_t af; /* address family */ } lws_dhcpc_req_t; /* interface name is overallocated here */ static const uint32_t botable2[] = { 1500, 1750, 5000 /* in case dog slow */ }; static const lws_retry_bo_t bo2 = { botable2, LWS_ARRAY_SIZE(botable2), LWS_RETRY_CONCEAL_ALWAYS, 0, 0, 20 }; static const uint8_t rawdisc[] = { 0x45, 0x00, 0, 0, 0, 0, 0x40, 0, 0x2e, IPPROTO_UDP, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 68, 0, 67, 0, 0, 0, 0 }; #define LDHC_OP_BOOTREQUEST 1 #define LDHC_OP_BOOTREPLY 2 /* * IPv4... max total 576 * * 0 1 2 3 * 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 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | op (1) | htype (1) | hlen (1) | hops (1) | * +---------------+---------------+---------------+---------------+ * | +04 xid (4) | * +-------------------------------+-------------------------------+ * | +08 secs (2) | +0a flags (2) | * +-------------------------------+-------------------------------+ * | +0C ciaddr (4) client IP | * +---------------------------------------------------------------+ * | +10 yiaddr (4) your IP | * +---------------------------------------------------------------+ * | +14 siaddr (4) server IP | * +---------------------------------------------------------------+ * | +18 giaddr (4) gateway IP | * +---------------------------------------------------------------+ * | | * | +1C chaddr (16) client HWADDR | * +---------------------------------------------------------------+ * | | * | +2C sname (64) | * +---------------------------------------------------------------+ * | | * | +6C file (128) | * +---------------------------------------------------------------+ * | | * | +EC options (variable) | * +---------------------------------------------------------------+ */ static const char *dhcp_entry_names[] = { "proposed ip", "dhcp server", "router", "subnet mask", "broadcast", "time server", "dns1", "dns2", "dns3", "dns4", "lease secs", "rebinding secs", "renewal secs", }; static void lws_dhcpc_retry_conn(struct lws_sorted_usec_list *sul) { lws_dhcpc_req_t *r = lws_container_of(sul, lws_dhcpc_req_t, sul_conn); if (r->wsi_raw || !lws_dll2_is_detached(&r->sul_conn.list)) return; /* create the UDP socket aimed at the server */ r->retry_count_write = 0; r->wsi_raw = lws_create_adopt_udp(r->context->vhost_system, "0.0.0.0", 68, LWS_CAUDP_PF_PACKET | LWS_CAUDP_BROADCAST, "lws-dhcpclient", (const char *)&r[1], NULL, NULL, &bo2); lwsl_debug("%s: created wsi_raw: %p\n", __func__, r->wsi_raw); if (!r->wsi_raw) { lwsl_err("%s: unable to create udp skt\n", __func__); lws_retry_sul_schedule(r->context, 0, &r->sul_conn, &bo2, lws_dhcpc_retry_conn, &r->retry_count_conn); return; } /* force the network if up */ lws_plat_if_up((const char *)&r[1], r->wsi_raw->desc.sockfd, 0); lws_plat_if_up((const char *)&r[1], r->wsi_raw->desc.sockfd, 1); r->wsi_raw->user_space = r; r->wsi_raw->user_space_externally_allocated = 1; lws_get_random(r->wsi_raw->context, r->xid, 4); } static void lws_dhcpc_retry_write(struct lws_sorted_usec_list *sul) { lws_dhcpc_req_t *r = lws_container_of(sul, lws_dhcpc_req_t, sul_write); lwsl_debug("%s\n", __func__); if (r && r->wsi_raw) lws_callback_on_writable(r->wsi_raw); } #if 0 static int lws_sys_dhcpc_notify_cb(lws_state_manager_t *mgr, lws_state_notify_link_t *l, int current, int target) { lws_dhcpc_req_t *r = lws_container_of(l, lws_dhcpc_req_t, notify_link); if (target != LWS_SYSTATE_TIME_VALID || v->set_time) return 0; /* it's trying to do it ever since the protocol / vhost was set up */ return 1; } #endif static int lws_dhcpc_prep(uint8_t *start, int bufsiz, lws_dhcpc_req_t *r, int op) { uint8_t *p = start; memset(start, 0, bufsiz); *p++ = 1; *p++ = 1; *p++ = 6; /* sizeof ethernet MAC */ memcpy(p + 1, r->xid, 4); // p[7] = 0x80; /* broadcast flag */ p += 0x1c - 3; if (lws_plat_ifname_to_hwaddr(r->wsi_raw->desc.sockfd, (const char *)&r[1], r->mac, 6) < 0) return -1; memcpy(p, r->mac, 6); p += 16 + 64 + 128; *p++ = 0x63; /* RFC2132 Magic Cookie indicates start of options */ *p++ = 0x82; *p++ = 0x53; *p++ = 0x63; *p++ = LWSDHCPOPT_MESSAGE_TYPE; *p++ = 1; /* length */ *p++ = op; switch (op) { case LWSDHCPDISCOVER: *p++ = LWSDHCPOPT_PARAM_REQ_LIST; *p++ = 4; /* length */ *p++ = 1; /* subnet mask */ *p++ = 3; /* router */ *p++ = 15; /* domain name */ *p++ = 6; /* DNServer */ break; case LWSDHCPREQUEST: *p++ = LWSDHCPOPT_REQUESTED_ADS; *p++ = 4; /* length */ lws_ser_wu32be(p, r->ipv4[IPV4_PROPOSED]); p += 4; *p++ = LWSDHCPOPT_SERVER_ID; *p++ = 4; /* length */ lws_ser_wu32be(p, r->ipv4[IPV4_SERVER]); p += 4; break; } *p++ = LWSDHCPOPT_END_OPTIONS; return lws_ptr_diff(p, start); } static int callback_dhcpc(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { lws_dhcpc_req_t *r = (lws_dhcpc_req_t *)user; uint8_t pkt[LWS_PRE + 576], *p = pkt + LWS_PRE, *end; int n, m; switch (reason) { case LWS_CALLBACK_RAW_ADOPT: lwsl_debug("%s: LWS_CALLBACK_RAW_ADOPT\n", __func__); lws_callback_on_writable(wsi); break; case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: lwsl_err("%s: udp conn failed\n", __func__); /* fallthru */ case LWS_CALLBACK_RAW_CLOSE: lwsl_debug("%s: LWS_CALLBACK_RAW_CLOSE\n", __func__); if (!r) break; r->wsi_raw = NULL; lws_sul_schedule(r->context, 0, &r->sul_write, NULL, LWS_SET_TIMER_USEC_CANCEL); if (r->state != LDHC_BOUND) { r->state = LDHC_INIT; lws_retry_sul_schedule(r->context, 0, &r->sul_conn, &bo2, lws_dhcpc_retry_conn, &r->retry_count_conn); } break; case LWS_CALLBACK_RAW_RX: switch (r->state) { case LDHC_INIT: /* expect DHCPOFFER */ case LDHC_REQUESTING: /* expect DHCPACK */ /* * We should check carefully if we like what we were * sent... anything can spam us with crafted replies */ if (len < 0x100) break; p = (uint8_t *)in + 28; /* skip to UDP payload */ if (p[0] != 2 || p[1] != 1 || p[2] != 6) break; if (memcmp(&p[4], r->xid, 4)) /* must be our xid */ break; if (memcmp(&p[0x1c], r->mac, 6)) /* our netif mac? */ break; /* the DHCP magic cookie must be in place */ if (lws_ser_ru32be(&p[0xec]) != 0x63825363) break; r->ipv4[IPV4_PROPOSED] = lws_ser_ru32be(&p[0x10]); r->ipv4[IPV4_SERVER] = lws_ser_ru32be(&p[0x14]); /* it looks legit so far... look at the options */ end = (uint8_t *)in + len; p += 0xec + 4; while (p < end) { uint8_t c = *p++; uint8_t l = 0; if (c && c != 0xff) { /* pad 0 and EOT 0xff have no length */ l = *p++; if (!l) { lwsl_err("%s: zero length\n", __func__); goto broken; } if (p + l > end) { /* ...nice try... */ lwsl_err("%s: bad len\n", __func__); goto broken; } } if (c == 0xff) /* end of options */ break; m = 0; switch (c) { case LWSDHCPOPT_SUBNET_MASK: n = IPV4_SUBNET_MASK; goto get_ipv4; case LWSDHCPOPT_ROUTER: n = IPV4_ROUTER; goto get_ipv4; case LWSDHCPOPT_TIME_SERVER: n = IPV4_TIME_SERVER; goto get_ipv4; case LWSDHCPOPT_BROADCAST_ADS: n = IPV4_BROADCAST; goto get_ipv4; case LWSDHCPOPT_LEASE_TIME: n = IPV4_LEASE_SECS; goto get_ipv4; case LWSDHCPOPT_RENEWAL_TIME: /* AKA T1 */ n = IPV4_RENEWAL_SECS; goto get_ipv4; case LWSDHCPOPT_REBINDING_TIME: /* AKA T2 */ n = IPV4_REBINDING_SECS; goto get_ipv4; case LWSDHCPOPT_DNSERVER: if (l & 3) break; m = IPV4_DNS_SRV_1; while (l && m - IPV4_DNS_SRV_1 < 4) { r->ipv4[m++] = lws_ser_ru32be(p); l -= 4; p += 4; } break; case LWSDHCPOPT_DOMAIN_NAME: m = l; if (m > (int)sizeof(r->domain) - 1) m = sizeof(r->domain) - 1; memcpy(r->domain, p, m); r->domain[m] = '\0'; break; case LWSDHCPOPT_MESSAGE_TYPE: /* * Confirm this is the right message * for the state of the negotiation */ if (r->state == LDHC_INIT && *p != LWSDHCPOFFER) goto broken; if (r->state == LDHC_REQUESTING && *p != LWSDHCPACK) goto broken; break; default: break; } p += l; continue; get_ipv4: if (l >= 4) r->ipv4[n] = lws_ser_ru32be(p); p += l; continue; broken: memset(r->ipv4, 0, sizeof(r->ipv4)); break; } #if defined(_DEBUG) /* dump what we have parsed out */ for (n = 0; n < (int)LWS_ARRAY_SIZE(dhcp_entry_names); n++) if (n >= IPV4_LEASE_SECS) lwsl_info("%s: %s: %ds\n", __func__, dhcp_entry_names[n], r->ipv4[n]); else { m = ntohl(r->ipv4[n]); lws_write_numeric_address((uint8_t *)&m, 4,(char *)pkt, 20); lwsl_info("%s: %s: %s\n", __func__, dhcp_entry_names[n], pkt); } #endif /* * Having seen everything in there... do we really feel * we could use it? Everything critical is there? */ if (!r->ipv4[IPV4_PROPOSED] || !r->ipv4[IPV4_SERVER] || !r->ipv4[IPV4_ROUTER] || !r->ipv4[IPV4_SUBNET_MASK] || !r->ipv4[IPV4_LEASE_SECS] || !r->ipv4[IPV4_DNS_SRV_1]) { memset(r->ipv4, 0, sizeof(r->ipv4)); break; } /* * Network layout has to be internally consistent... * DHCP server has to be reachable by broadcast and * default route has to be on same subnet */ if ((r->ipv4[IPV4_PROPOSED] & r->ipv4[IPV4_SUBNET_MASK]) != (r->ipv4[IPV4_SERVER] & r->ipv4[IPV4_SUBNET_MASK])) break; if ((r->ipv4[IPV4_PROPOSED] & r->ipv4[IPV4_SUBNET_MASK]) != (r->ipv4[IPV4_ROUTER] & r->ipv4[IPV4_SUBNET_MASK])) break; if (r->state == LDHC_INIT) { lwsl_info("%s: moving to REQ\n", __func__); r->state = LDHC_REQUESTING; lws_callback_on_writable(r->wsi_raw); break; } /* * that's it... commit to the configuration */ /* set up our network interface as offered */ if (lws_plat_ifconfig_ip((const char *)&r[1], r->wsi_raw->desc.sockfd, (uint8_t *)&r->ipv4[IPV4_PROPOSED], (uint8_t *)&r->ipv4[IPV4_SUBNET_MASK], (uint8_t *)&r->ipv4[IPV4_ROUTER])) { /* * Problem setting the IP... maybe something * transient like racing with NetworkManager? * Since the sul retries are still around it * will retry */ return -1; } /* clear timeouts related to the broadcast socket */ lws_sul_schedule(r->context, 0, &r->sul_write, NULL, LWS_SET_TIMER_USEC_CANCEL); lws_sul_schedule(r->context, 0, &r->sul_conn, NULL, LWS_SET_TIMER_USEC_CANCEL); lwsl_notice("%s: DHCP configured %s\n", __func__, (const char *)&r[1]); r->state = LDHC_BOUND; lws_state_transition_steps(&wsi->context->mgr_system, LWS_SYSTATE_OPERATIONAL); r->cb(r->opaque, r->af, (uint8_t *)&r->ipv4[IPV4_PROPOSED], 4); r->wsi_raw = NULL; return -1; /* close the broadcast wsi */ default: break; } break; case LWS_CALLBACK_RAW_WRITEABLE: if (!r) break; /* * UDP is not reliable, it can be locally dropped, or dropped * by any intermediary or the remote peer. So even though we * will do the write in a moment, we schedule another request * for rewrite according to the wsi retry policy. * * If the result came before, we'll cancel it in the close flow. * * If we have already reached the end of our concealed retries * in the policy, just close without another write. */ if (lws_dll2_is_detached(&r->sul_write.list) && lws_retry_sul_schedule_retry_wsi(wsi, &r->sul_write, lws_dhcpc_retry_write, &r->retry_count_write)) { /* we have reached the end of our concealed retries */ lwsl_warn("%s: concealed retries done, failing\n", __func__); goto retry_conn; } switch (r->state) { case LDHC_INIT: n = LWSDHCPDISCOVER; goto bcast; case LDHC_REQUESTING: n = LWSDHCPREQUEST; /* fallthru */ bcast: n = lws_dhcpc_prep(p + 28, sizeof(pkt) - LWS_PRE - 28, r, n); if (n < 0) { lwsl_err("%s: failed to prep\n", __func__); break; } m = lws_plat_rawudp_broadcast(p, rawdisc, LWS_ARRAY_SIZE(rawdisc), n + 28, r->wsi_raw->desc.sockfd, (const char *)&r[1]); if (m < 0) lwsl_err("%s: Failed to write dhcp client req: " "%d %d, errno %d\n", __func__, n, m, LWS_ERRNO); break; default: break; } return 0; retry_conn: lws_retry_sul_schedule(wsi->context, 0, &r->sul_conn, &bo2, lws_dhcpc_retry_conn, &r->retry_count_conn); return -1; default: break; } return 0; #if 0 cancel_conn_timer: lws_sul_schedule(r->context, 0, &r->sul_conn, NULL, LWS_SET_TIMER_USEC_CANCEL); return 0; #endif } struct lws_protocols lws_system_protocol_dhcpc = { "lws-dhcpclient", callback_dhcpc, 0, 128, }; static void lws_dhcpc_destroy(lws_dhcpc_req_t **pr) { lws_dhcpc_req_t *r = *pr; lws_sul_schedule(r->context, 0, &r->sul_conn, NULL, LWS_SET_TIMER_USEC_CANCEL); lws_sul_schedule(r->context, 0, &r->sul_write, NULL, LWS_SET_TIMER_USEC_CANCEL); if (r->wsi_raw) lws_set_timeout(r->wsi_raw, 1, LWS_TO_KILL_ASYNC); lws_dll2_remove(&r->list); lws_free_set_NULL(r); } int lws_dhcpc_status(struct lws_context *context, lws_sockaddr46 *sa46) { lws_dhcpc_req_t *r; lws_start_foreach_dll(struct lws_dll2 *, p, context->dhcpc_owner.head) { r = (lws_dhcpc_req_t *)p; if (r->state == LDHC_BOUND) { if (sa46) { memset(sa46, 0, sizeof(*sa46)); sa46->sa4.sin_family = AF_INET; sa46->sa4.sin_addr.s_addr = r->ipv4[IPV4_DNS_SRV_1]; } return 1; } } lws_end_foreach_dll(p); return 0; } static lws_dhcpc_req_t * lws_dhcpc_find(struct lws_context *context, const char *iface, int af) { lws_dhcpc_req_t *r; /* see if we are already looking after this af / iface combination */ lws_start_foreach_dll(struct lws_dll2 *, p, context->dhcpc_owner.head) { r = (lws_dhcpc_req_t *)p; if (!strcmp((const char *)&r[1], iface) && af == r->af) return r; /* yes... */ } lws_end_foreach_dll(p); return NULL; } /* * Create a persistent dhcp client entry for network interface "iface" and AF * type "af" */ int lws_dhcpc_request(struct lws_context *context, const char *iface, int af, dhcpc_cb_t cb, void *opaque) { lws_dhcpc_req_t *r = lws_dhcpc_find(context, iface, af); int n; /* see if we are already looking after this af / iface combination */ if (r) return 0; /* nope... let's create a request object as he asks */ n = strlen(iface); r = lws_zalloc(sizeof(*r) + n + 1, __func__); if (!r) return 1; memcpy(&r[1], iface, n + 1); r->af = af; r->cb = cb; r->opaque = opaque; r->context = context; r->state = LDHC_INIT; lws_dll2_add_head(&r->list, &context->dhcpc_owner); /* add him to list */ lws_dhcpc_retry_conn(&r->sul_conn); return 0; } /* * Destroy every DHCP client object related to interface "iface" */ static int _remove_if(struct lws_dll2 *d, void *opaque) { lws_dhcpc_req_t *r = lws_container_of(d, lws_dhcpc_req_t, list); if (!opaque || !strcmp((const char *)&r[1], (const char *)opaque)) lws_dhcpc_destroy(&r); return 0; } int lws_dhcpc_remove(struct lws_context *context, const char *iface) { lws_dll2_foreach_safe(&context->dhcpc_owner, (void *)iface, _remove_if); return 0; }