diff --git a/CMakeLists.txt b/CMakeLists.txt index a10178c20..660b3dfed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,6 +49,7 @@ option(LWS_WITH_HUBBUB "Enable libhubbub rewriting support" OFF) option(LWS_WITH_FTS "Full Text Search support" OFF) option(LWS_WITH_SYS_ASYNC_DNS "Nonblocking internal IPv4 + IPv6 DNS resolver" OFF) option(LWS_WITH_SYS_NTPCLIENT "Build in tiny ntpclient good for tls date validation and run via lws_system" OFF) +option(LWS_WITH_SYS_DHCP_CLIENT "Build in tiny DHCP client" OFF) # # TLS library options... all except mbedTLS are basically OpenSSL variants. # @@ -1040,6 +1041,12 @@ if (LWS_WITH_NETWORK) lib/system/ntpclient/ntpclient.c) endif() + if (LWS_WITH_SYS_DHCP_CLIENT) + list(APPEND SOURCES + lib/system/dhcpclient/dhcpclient.c) + endif() + + if (LWS_WITH_DETAILED_LATENCY) list(APPEND SOURCES lib/core-net/detailed-latency.c) diff --git a/README.md b/README.md index ab3b3d177..94e1c2fcb 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,16 @@ various scenarios, CC0-licensed (public domain) for cut-and-paste, allow you to News ---- +## `lws_system`: DHCP client + +DHCP client is now another network service that can be integrated into lws, with +`LWS_WITH_SYS_DHCP_CLIENT` at CMake. When enabled, the `lws_system` state +is held at `DHCP` until at least one registered network interface acquires a +usable set of DHCP information including ip, subnet mask, router / gateway +address and at least one DNS server. + +See the [api-test-dhcp](https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/api-tests/api-test-dhcpc) Minimal Example for how to use. + ## UDP integration with `lws_retry` UDP support in lws has new helper that allow `lws_retry` to be applied for retry, @@ -43,7 +53,9 @@ completes. By default just after context creation, lws attempts to move straight to OPERATIONAL. If no notifier interecepts it, it will succeed to do that and operate in a -backwards-compatible way. +backwards-compatible way. Enabling various features like lws ntpclient also enable +notifiers that hold progress at the related state until their operation completes +successfully, eg, not able to enter `TIME_VALID` until ntpclient has the time. See `READMEs/README.lws_system.md` for details. diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index 160eee10a..b1441ab7f 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -141,6 +141,7 @@ #cmakedefine LWS_WITH_STRUCT_JSON #cmakedefine LWS_WITH_SQLITE3 #cmakedefine LWS_WITH_SYS_NTPCLIENT +#cmakedefine LWS_WITH_SYS_DHCP_CLIENT #cmakedefine LWS_WITH_THREADPOOL #cmakedefine LWS_WITH_TLS #cmakedefine LWS_WITH_UDP diff --git a/include/libwebsockets.h b/include/libwebsockets.h index ff087a53e..4bd6c0374 100644 --- a/include/libwebsockets.h +++ b/include/libwebsockets.h @@ -534,6 +534,8 @@ struct lws; #include #include #include +#include +#include #include #include #include @@ -552,8 +554,6 @@ struct lws; #include #include #include -#include -#include #include #include #include diff --git a/include/libwebsockets/lws-system.h b/include/libwebsockets/lws-system.h index 0c4ad74a8..28b26af7c 100644 --- a/include/libwebsockets/lws-system.h +++ b/include/libwebsockets/lws-system.h @@ -157,3 +157,44 @@ lws_system_get_auth(struct lws_context *context, int idx, uint8_t *buf, size_t b */ LWS_EXTERN LWS_VISIBLE const lws_system_ops_t * lws_system_get_ops(struct lws_context *context); + +typedef int (*dhcpc_cb_t)(void *opaque, int af, uint8_t *ip, int ip_len); + +/** + * lws_dhcpc_request() - add a network interface to dhcpc management + * + * \param c: the lws_context + * \param i: the interface name, like "eth0" + * \param af: address family + * \param cb: the change callback + * \param opaque: opaque pointer given to the callback + * + * Register a network interface as being managed by DHCP. lws will proceed to + * try to acquire an IP. Requires LWS_WITH_SYS_DHCP_CLIENT at cmake. + */ +int +lws_dhcpc_request(struct lws_context *c, const char *i, int af, dhcpc_cb_t cb, + void *opaque); + +/** + * lws_dhcpc_remove() - remove a network interface to dhcpc management + * + * \param context: the lws_context + * \param iface: the interface name, like "eth0" + * + * Remove handling of the network interface from dhcp. + */ +int +lws_dhcpc_remove(struct lws_context *context, const char *iface); + +/** + * lws_dhcpc_status() - has any interface reached BOUND state + * + * \param context: the lws_context + * \param sa46: set to a DNS server from a bound interface, or NULL + * + * Returns 1 if any network interface managed by dhcpc has reached the BOUND + * state (has acquired an IP, gateway and DNS server), otherwise 0. + */ +int +lws_dhcpc_status(struct lws_context *context, lws_sockaddr46 *sa46); diff --git a/lib/core/context.c b/lib/core/context.c index e6f66880b..c712263c8 100644 --- a/lib/core/context.c +++ b/lib/core/context.c @@ -104,6 +104,20 @@ lws_state_notify_protocol_init(struct lws_state_manager *mgr, struct lws_context *context = lws_container_of(mgr, struct lws_context, mgr_system); +#if defined(LWS_WITH_SYS_DHCP_CLIENT) + if (current == LWS_SYSTATE_DHCP) { + /* + * Don't let it past here until at least one iface has been + * configured for operation with DHCP + */ + + if (!lws_dhcpc_status(context, NULL)) + return 1; + } +#endif + + /* protocol part */ + if (context->protocol_init_done) return 0; @@ -155,6 +169,9 @@ lws_create_context(const struct lws_context_creation_info *info) #endif #if defined(LWS_WITH_SYS_NTPCLIENT) lpf++; +#endif +#if defined(LWS_WITH_SYS_DHCP_CLIENT) + lpf++; #endif } @@ -615,7 +632,8 @@ lws_create_context(const struct lws_context_creation_info *info) #if defined(LWS_WITH_NETWORK) -#if defined(LWS_WITH_SYS_ASYNC_DNS) || defined(LWS_WITH_SYS_NTPCLIENT) +#if defined(LWS_WITH_SYS_ASYNC_DNS) || defined(LWS_WITH_SYS_NTPCLIENT) || \ + defined(LWS_WITH_SYS_DHCP_CLIENT) { /* * system vhost @@ -630,6 +648,9 @@ lws_create_context(const struct lws_context_creation_info *info) #if defined(LWS_WITH_SYS_NTPCLIENT) extern const struct lws_protocols lws_system_protocol_ntpc; #endif +#if defined(LWS_WITH_SYS_DHCP_CLIENT) + extern const struct lws_protocols lws_system_protocol_dhcpc; +#endif n = 0; #if defined(LWS_WITH_SYS_ASYNC_DNS) @@ -637,6 +658,9 @@ lws_create_context(const struct lws_context_creation_info *info) #endif #if defined(LWS_WITH_SYS_NTPCLIENT) pp[n++] = &lws_system_protocol_ntpc; +#endif +#if defined(LWS_WITH_SYS_DHCP_CLIENT) + pp[n++] = &lws_system_protocol_dhcpc; #endif pp[n] = NULL; @@ -825,6 +849,9 @@ lws_context_destroy3(struct lws_context *context) #if defined(LWS_WITH_SYS_ASYNC_DNS) lws_async_dns_deinit(&context->async_dns); #endif +#if defined(LWS_WITH_SYS_DHCP_CLIENT) + lws_dhcpc_remove(context, NULL); +#endif if (context->pt[0].fds) lws_free_set_NULL(context->pt[0].fds); diff --git a/lib/core/private-lib-core.h b/lib/core/private-lib-core.h index 3cc257328..0e798ca34 100644 --- a/lib/core/private-lib-core.h +++ b/lib/core/private-lib-core.h @@ -312,6 +312,10 @@ struct lws_context { #if defined(LWS_WITH_NETWORK) lws_state_manager_t mgr_system; lws_state_notify_link_t protocols_notify; +#if defined (LWS_WITH_SYS_DHCP_CLIENT) + lws_dll2_owner_t dhcpc_owner; + /**< list of ifaces with dhcpc */ +#endif #endif /* pointers */ @@ -627,6 +631,9 @@ int lws_plat_user_colon_group_to_ids(const char *u_colon_g, uid_t *puid, gid_t *pgid); #endif +int +lws_plat_ifname_to_hwaddr(int fd, const char *ifname, uint8_t *hwaddr, int len); + LWS_EXTERN int lws_check_byte_utf8(unsigned char state, unsigned char c); LWS_EXTERN int LWS_WARN_UNUSED_RESULT diff --git a/lib/plat/unix/unix-sockets.c b/lib/plat/unix/unix-sockets.c index 91dbd5d24..5894e2fa2 100644 --- a/lib/plat/unix/unix-sockets.c +++ b/lib/plat/unix/unix-sockets.c @@ -438,9 +438,11 @@ lws_plat_ifconfig_ip(const char *ifname, int fd, uint8_t *ip, uint8_t *mask_ip, route.rt_flags = RTF_UP | RTF_GATEWAY; route.rt_metric = 100; + route.rt_dev = (char *)ifname; if (ioctl(fd, SIOCADDRT, &route) < 0) { - lwsl_err("%s: SIOCADDRT fail: %d\n", __func__, LWS_ERRNO); + lwsl_err("%s: SIOCADDRT 0x%x fail: %d\n", __func__, + (unsigned int)htonl(*(uint32_t *)gateway_ip), LWS_ERRNO); return 1; } diff --git a/lib/system/async-dns/async-dns.c b/lib/system/async-dns/async-dns.c index 0fe559ec4..30c995d42 100644 --- a/lib/system/async-dns/async-dns.c +++ b/lib/system/async-dns/async-dns.c @@ -296,6 +296,11 @@ lws_async_dns_init(struct lws_context *context) memset(&dns->sa46, 0, sizeof(dns->sa46)); +#if defined(LWS_WITH_SYS_DHCP_CLIENT) + if (lws_dhcpc_status(context, &dns->sa46)) + goto ok; +#endif + n = lws_plat_asyncdns_init(context, &dns->sa46); if (n < 0) { lwsl_warn("%s: no valid dns server, retry\n", __func__); @@ -306,6 +311,9 @@ lws_async_dns_init(struct lws_context *context) if (n != LADNS_CONF_SERVER_CHANGED) return 0; +#if defined(LWS_WITH_SYS_DHCP_CLIENT) +ok: +#endif dns->sa46.sa4.sin_port = htons(53); lws_write_numeric_address((uint8_t *)&dns->sa46.sa4.sin_addr.s_addr, 4, ads, sizeof(ads)); diff --git a/lib/system/dhcpclient/dhcpclient.c b/lib/system/dhcpclient/dhcpclient.c new file mode 100644 index 000000000..38dceffb2 --- /dev/null +++ b/lib/system/dhcpclient/dhcpclient.c @@ -0,0 +1,777 @@ + /* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2019 Andy Green + * + * 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, &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; + + 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; +} diff --git a/lib/system/dhcpclient/private-lib-system-dhcpclient.h b/lib/system/dhcpclient/private-lib-system-dhcpclient.h new file mode 100644 index 000000000..ec03aae01 --- /dev/null +++ b/lib/system/dhcpclient/private-lib-system-dhcpclient.h @@ -0,0 +1,25 @@ + /* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2019 Andy Green + * + * 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. + */ + + diff --git a/minimal-examples/api-tests/api-test-dhcpc/CMakeLists.txt b/minimal-examples/api-tests/api-test-dhcpc/CMakeLists.txt new file mode 100644 index 000000000..422cf4955 --- /dev/null +++ b/minimal-examples/api-tests/api-test-dhcpc/CMakeLists.txt @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 2.8) +include(CheckCSourceCompiles) + +set(SAMP lws-api-test-dhcpc) +set(SRCS main.c) + +# If we are being built as part of lws, confirm current build config supports +# reqconfig, else skip building ourselves. +# +# If we are being built externally, confirm installed lws was configured to +# support reqconfig, else error out with a helpful message about the problem. +# +MACRO(require_lws_config reqconfig _val result) + + if (DEFINED ${reqconfig}) + if (${reqconfig}) + set (rq 1) + else() + set (rq 0) + endif() + else() + set(rq 0) + endif() + + if (${_val} EQUAL ${rq}) + set(SAME 1) + else() + set(SAME 0) + endif() + + if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME}) + if (${_val}) + message("${SAMP}: skipping as lws being built without ${reqconfig}") + else() + message("${SAMP}: skipping as lws built with ${reqconfig}") + endif() + set(${result} 0) + else() + if (LWS_WITH_MINIMAL_EXAMPLES) + set(MET ${SAME}) + else() + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig}) + if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig}) + set(HAS_${reqconfig} 0) + else() + set(HAS_${reqconfig} 1) + endif() + if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val})) + set(MET 1) + else() + set(MET 0) + endif() + endif() + if (NOT MET) + if (${_val}) + message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}") + else() + message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project") + endif() + endif() + endif() +ENDMACRO() + +set(requirements 1) +require_lws_config(LWS_WITH_SYS_DHCP_CLIENT 1 requirements) + +if (requirements) + add_executable(${SAMP} ${SRCS}) + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets) + endif() +endif() diff --git a/minimal-examples/api-tests/api-test-dhcpc/README.md b/minimal-examples/api-tests/api-test-dhcpc/README.md new file mode 100644 index 000000000..74f79a0dd --- /dev/null +++ b/minimal-examples/api-tests/api-test-dhcpc/README.md @@ -0,0 +1,27 @@ +# api test dhcpc + +The application confirms it can set DHCP on the given interface + +## build + +``` + $ cmake . && make +``` + +## usage + +Commandline option|Meaning +---|--- +-d |Debug verbosity in decimal, eg, -d15 +-i |Network interface name to set by DHCP, eg, eth0 or wlo1 + +``` + $ ./lws-api-test-dhcpc -i wlo1 +[2019/10/06 14:56:41:7683] U: LWS API selftest: Async DNS +[2019/10/06 14:56:42:4461] U: main: requesting DHCP for wlo1 +[2019/10/06 14:56:42:5207] N: callback_dhcpc: DHCP configured wlo1 +[2019/10/06 14:56:42:5246] U: lws_dhcpc_cb: dhcp set OK +[2019/10/06 14:56:42:5999] U: Completed: ALL PASS: 1 / 1 +``` + + diff --git a/minimal-examples/api-tests/api-test-dhcpc/main.c b/minimal-examples/api-tests/api-test-dhcpc/main.c new file mode 100644 index 000000000..793c1fb43 --- /dev/null +++ b/minimal-examples/api-tests/api-test-dhcpc/main.c @@ -0,0 +1,79 @@ +/* + * lws-api-test-dhcpc + * + * Written in 2019 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + */ + +#include +#include + +static int interrupted, ok, fail, exp = 1; +struct lws_context *context; +const char *nif; + +static int +lws_dhcpc_cb(void *opaque, int af, uint8_t *ip, int ip_len) +{ + lwsl_user("%s: dhcp set OK\n", __func__); + ok = 1; + interrupted = 1; + return 0; +} + +void sigint_handler(int sig) +{ + interrupted = 1; +} + +int +main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + const char *p; + int n = 1; + + signal(SIGINT, sigint_handler); + + memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ + lws_cmdline_option_handle_builtin(argc, argv, &info); + lwsl_user("LWS API selftest: DHCP Client\n"); + + info.port = CONTEXT_PORT_NO_LISTEN; + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + + if ((p = lws_cmdline_option(argc, argv, "-i"))) + nif = p; + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + if (nif) { + lwsl_user("%s: requesting DHCP for %s\n", __func__, nif); + lws_dhcpc_request(context, nif, AF_INET, lws_dhcpc_cb, NULL); + } else { + lwsl_err("%s: use -i to select if\n", __func__); + interrupted = 1; + } + + /* the usual lws event loop */ + + n = 1; + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + + lws_context_destroy(context); + + if (fail || ok != exp) + lwsl_user("Completed: PASS: %d / %d, FAIL: %d\n", ok, exp, + fail); + else + lwsl_user("Completed: ALL PASS: %d / %d\n", ok, exp); + + return !(ok == exp && !fail); +}