libwebsockets/lib/system/dhcpclient/dhcpc4.c

536 lines
14 KiB
C

/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2021 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.
*
* The protocol part of dhcp4 client
*/
#include "private-lib-core.h"
#include "private-lib-system-dhcpclient.h"
#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 uint8_t rawdisc4[] = {
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
};
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 int
lws_dhcpc4_prep(uint8_t *start, unsigned 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->is.mac, 6) < 0)
return -1;
memcpy(p, r->is.mac, 6);
p += 16 + 64 + 128;
*p++ = 0x63; /* RFC2132 Magic Cookie indicates start of options */
*p++ = 0x82;
*p++ = 0x53;
*p++ = 0x63;
*p++ = LWSDHC4POPT_MESSAGE_TYPE;
*p++ = 1; /* length */
*p++ = (uint8_t)op;
switch (op) {
case LWSDHC4PDISCOVER:
*p++ = LWSDHC4POPT_PARAM_REQ_LIST;
*p++ = 4; /* length */
*p++ = LWSDHC4POPT_SUBNET_MASK;
*p++ = LWSDHC4POPT_ROUTER;
*p++ = LWSDHC4POPT_DNSERVER;
*p++ = LWSDHC4POPT_DOMAIN_NAME;
break;
case LWSDHC4PREQUEST:
if (r->is.sa46[LWSDH_SA46_IP].sa4.sin_family != AF_INET)
break;
*p++ = LWSDHC4POPT_REQUESTED_ADS;
*p++ = 4; /* length */
lws_ser_wu32be(p, r->is.sa46[LWSDH_SA46_IP].sa4.sin_addr.s_addr);
p += 4;
*p++ = LWSDHC4POPT_SERVER_ID;
*p++ = 4; /* length */
lws_ser_wu32be(p, r->is.sa46[LWSDH_SA46_DHCP_SERVER].sa4.sin_addr.s_addr);
p += 4;
break;
}
*p++ = LWSDHC4POPT_END_OPTIONS;
return lws_ptr_diff(p, start);
}
static int
callback_dhcpc4(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;
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_cancel(&r->sul_write);
if (r->state != LDHC_BOUND) {
r->state = LDHC_INIT;
lws_retry_sul_schedule(r->context, 0, &r->sul_conn,
&bo2, lws_dhcpc4_retry_conn,
&r->retry_count_conn);
}
break;
case LWS_CALLBACK_RAW_RX:
if (lws_dhcpc4_parse(r, in, len))
break;
/*
* that's it... commit to the configuration
*/
/* set up our network interface as offered */
if (lws_plat_ifconfig(r->wsi_raw->desc.sockfd, &r->is))
/*
* 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_cancel(&r->sul_write);
lws_sul_cancel(&r->sul_conn);
lwsl_notice("%s: DHCP configured %s\n", __func__,
(const char *)&r[1]);
r->state = LDHC_BOUND;
lws_state_transition_steps(&wsi->a.context->mgr_system,
LWS_SYSTATE_OPERATIONAL);
r->cb(r->opaque, &r->is);
r->wsi_raw = NULL;
return -1; /* close the broadcast wsi */
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 = LWSDHC4PDISCOVER;
goto bcast;
case LDHC_REQUESTING:
n = LWSDHC4PREQUEST;
/* fallthru */
bcast:
n = lws_dhcpc4_prep(p + 28, (unsigned int)
(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, rawdisc4,
LWS_ARRAY_SIZE(rawdisc4),
(size_t)(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->a.context, 0, &r->sul_conn, &bo2,
lws_dhcpc4_retry_conn,
&r->retry_count_conn);
return -1;
default:
break;
}
return 0;
}
struct lws_protocols lws_system_protocol_dhcpc4 =
{ "lws-dhcp4client", callback_dhcpc4, 0, 128, 0, NULL, 0 };
void
lws_dhcpc4_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-dhcp4client", (const char *)&r[1],
NULL, NULL, &bo2, "dhcpc");
lwsl_debug("%s: created wsi_raw: %s\n", __func__, lws_wsi_tag(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_dhcpc4_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->a.context, r->xid, 4);
}
static void
lws_sa46_set_ipv4(lws_dhcpc_req_t *r, unsigned int which, uint8_t *p)
{
r->is.sa46[which].sa4.sin_family = AF_INET;
r->is.sa46[which].sa4.sin_addr.s_addr = ntohl(lws_ser_ru32be(p));
}
int
lws_dhcpc4_parse(lws_dhcpc_req_t *r, void *in, size_t len)
{
uint8_t pkt[LWS_PRE + 576], *p = pkt + LWS_PRE, *end;
int n, m;
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->is.mac, 6)) /* our netif mac? */
break;
/* the DHCP magic cookie must be in place */
if (lws_ser_ru32be(&p[0xec]) != 0x63825363)
break;
/* "your" client IP address */
lws_sa46_set_ipv4(r, LWSDH_SA46_IP, p + 0x10);
/* IP of next server used in bootstrap */
lws_sa46_set_ipv4(r, LWSDH_SA46_DHCP_SERVER, 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 LWSDHC4POPT_SUBNET_MASK:
n = LWSDH_IPV4_SUBNET_MASK;
goto get_ipv4;
case LWSDHC4POPT_ROUTER:
lws_sa46_set_ipv4(r, LWSDH_SA46_IPV4_ROUTER, p);
break;
case LWSDHC4POPT_TIME_SERVER:
lws_sa46_set_ipv4(r, LWSDH_SA46_NTP_SERVER, p);
break;
case LWSDHC4POPT_BROADCAST_ADS:
n = LWSDH_IPV4_BROADCAST;
goto get_ipv4;
case LWSDHC4POPT_LEASE_TIME:
n = LWSDH_LEASE_SECS;
goto get_ipv4;
case LWSDHC4POPT_RENEWAL_TIME: /* AKA T1 */
n = LWSDH_RENEWAL_SECS;
goto get_ipv4;
case LWSDHC4POPT_REBINDING_TIME: /* AKA T2 */
n = LWSDH_REBINDING_SECS;
goto get_ipv4;
case LWSDHC4POPT_DNSERVER:
if (l & 3)
break;
m = LWSDH_SA46_DNS_SRV_1;
while (l && m - LWSDH_SA46_DNS_SRV_1 < 4) {
lws_sa46_set_ipv4(r, (unsigned int)m, p);
lws_async_dns_server_add(r->context,
&r->is.sa46[m]);
l = (uint8_t)(l - 4);
p += 4;
m++;
}
break;
case LWSDHC4POPT_DOMAIN_NAME:
m = l;
if (m > (int)sizeof(r->is.domain) - 1)
m = sizeof(r->is.domain) - 1;
lws_strnncpy(r->is.domain, (const char *)p,
(unsigned int)m, sizeof(r->is.domain));
break;
case LWSDHC4POPT_MESSAGE_TYPE:
/*
* Confirm this is the right message
* for the state of the negotiation
*/
if (r->state == LDHC_INIT && *p != LWSDHC4POFFER)
goto broken;
if (r->state == LDHC_REQUESTING &&
*p != LWSDHC4PACK)
goto broken;
break;
default:
break;
}
p += l;
continue;
get_ipv4:
if (l >= 4)
r->is.nums[n] = ntohl(lws_ser_ru32be(p));
p += l;
continue;
broken:
memset(r->is.sa46, 0, sizeof(r->is.sa46));
break;
}
#if defined(_DEBUG)
/* dump what we have parsed out */
for (n = 0; n < (int)_LWSDH_NUMS_COUNT; n++) {
m = (int)ntohl(r->is.nums[n]);
lwsl_info("%s: %d: 0x%x\n", __func__, n, m);
}
for (n = 0; n < (int)_LWSDH_SA46_COUNT; n++) {
lws_sa46_write_numeric_address(&r->is.sa46[n],
(char *)pkt, 48);
lwsl_info("%s: %d: %s\n", __func__, n, pkt);
}
#endif
/*
* Having seen everything in there... do we really feel
* we could use it? Everything critical is there?
*/
if (!r->is.sa46[LWSDH_SA46_IP].sa4.sin_family ||
!r->is.sa46[LWSDH_SA46_DHCP_SERVER].sa4.sin_family ||
!r->is.sa46[LWSDH_SA46_IPV4_ROUTER].sa4.sin_family ||
!r->is.nums[LWSDH_IPV4_SUBNET_MASK] ||
!r->is.nums[LWSDH_LEASE_SECS] ||
!r->is.sa46[LWSDH_SA46_DNS_SRV_1].sa4.sin_family) {
lwsl_notice("%s: rejecting on incomplete\n", __func__);
memset(r->is.sa46, 0, sizeof(r->is.sa46));
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->is.sa46[LWSDH_SA46_IP].sa4.sin_addr.s_addr &
r->is.nums[LWSDH_IPV4_SUBNET_MASK]) !=
(r->is.sa46[LWSDH_SA46_DHCP_SERVER].sa4.sin_addr.s_addr &
r->is.nums[LWSDH_IPV4_SUBNET_MASK])) {
lwsl_notice("%s: rejecting on srv %x reachable on mask %x\n",
__func__, r->is.sa46[LWSDH_SA46_IP].sa4.sin_addr.s_addr,
r->is.nums[LWSDH_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;
}
return 0;
default:
break;
}
return 1;
}