1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-09 00:00:04 +01:00

lws_system: dhcpclient

Generic lws_system IPv4 DHCP client

 - netif and route control via lib/plat apis
 - linux plat pieces implemented
 - Uses raw ip socket for UDP broadcast and rx
 - security-aware
 - usual stuff plus up to 4 x dns server

If it's enabled for build, it holds the system
state at DHCP until at least one registered interface
has acquired a set of IP / mask / router / DNS server

It uses PF_PACKET which is Linux-only atm.  But those
areas are isolated into plat code.

TODOs

 - lease timing and reacquire
 - plat pieces for other than Linux
This commit is contained in:
Andy Green 2019-09-27 14:32:59 -07:00
parent 127e53cf98
commit d0fa39af7f
14 changed files with 1094 additions and 5 deletions

View file

@ -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)

View file

@ -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.

View file

@ -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

View file

@ -534,6 +534,8 @@ struct lws;
#include <libwebsockets/lws-timeout-timer.h>
#include <libwebsockets/lws-state.h>
#include <libwebsockets/lws-retry.h>
#include <libwebsockets/lws-adopt.h>
#include <libwebsockets/lws-network-helper.h>
#include <libwebsockets/lws-system.h>
#include <libwebsockets/lws-detailed-latency.h>
#include <libwebsockets/lws-ws-close.h>
@ -552,8 +554,6 @@ struct lws;
#include <libwebsockets/lws-service.h>
#include <libwebsockets/lws-write.h>
#include <libwebsockets/lws-writeable.h>
#include <libwebsockets/lws-adopt.h>
#include <libwebsockets/lws-network-helper.h>
#include <libwebsockets/lws-ring.h>
#include <libwebsockets/lws-sha1-base64.h>
#include <libwebsockets/lws-x509.h>

View file

@ -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);

View file

@ -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);

View file

@ -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

View file

@ -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;
}

View file

@ -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));

View file

@ -0,0 +1,777 @@
/*
* 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, &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;
}

View file

@ -0,0 +1,25 @@
/*
* 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.
*/

View file

@ -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 <libwebsockets.h>\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()

View file

@ -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 <loglevel>|Debug verbosity in decimal, eg, -d15
-i <netif>|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
```

View file

@ -0,0 +1,79 @@
/*
* lws-api-test-dhcpc
*
* Written in 2019 by Andy Green <andy@warmcat.com>
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
*/
#include <libwebsockets.h>
#include <signal.h>
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 <network-interface> 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);
}