summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/sbc_harness/CMakeLists.txt2
-rw-r--r--cmd/sbc_harness/main.c13
-rw-r--r--libdhcp/dhcp_client.c1147
-rw-r--r--libdhcp/dhcp_common.h134
-rw-r--r--libdhcp/f.md27
-rw-r--r--libdhcp/include/libdhcp/client.h87
6 files changed, 880 insertions, 530 deletions
diff --git a/cmd/sbc_harness/CMakeLists.txt b/cmd/sbc_harness/CMakeLists.txt
index fb8fc4b..9a84640 100644
--- a/cmd/sbc_harness/CMakeLists.txt
+++ b/cmd/sbc_harness/CMakeLists.txt
@@ -19,7 +19,7 @@ target_link_libraries(sbc_harness_objs
libmisc
libusb
- #libdhcp
+ libdhcp
libhw
)
pico_enable_stdio_usb(sbc_harness_objs 0)
diff --git a/cmd/sbc_harness/main.c b/cmd/sbc_harness/main.c
index b9c5330..97751a7 100644
--- a/cmd/sbc_harness/main.c
+++ b/cmd/sbc_harness/main.c
@@ -15,6 +15,7 @@
#include <libhw/w5500.h>
#include <libmisc/hash.h>
#include <libusb/usb_common.h>
+#include <libdhcp/client.h>
#include "usb_keyboard.h"
@@ -34,6 +35,15 @@ COROUTINE hello_world_cr(void *_chan) {
cr_end();
}
+COROUTINE dhcp_cr(void *_chip) {
+ struct w5500 *chip = _chip;
+ cr_begin();
+
+ dhcp_client_main(chip, "harness");
+
+ cr_end();
+}
+
int main() {
/* initialization *****************************************************/
stdio_uart_init();
@@ -80,8 +90,9 @@ int main() {
usb_keyboard_rpc_t keyboard_chan = {0};
coroutine_add(usb_keyboard_cr, &keyboard_chan);
//coroutine_add(hello_world_cr, &keyboard_chan);
- //coroutine_add(dhcp_client_cr, NULL);
+ coroutine_add(dhcp_cr, &dev_w5500);
/* event loop *********************************************************/
+ printf("main\n");
coroutine_main();
}
diff --git a/libdhcp/dhcp_client.c b/libdhcp/dhcp_client.c
index a4b015c..ff9a0e9 100644
--- a/libdhcp/dhcp_client.c
+++ b/libdhcp/dhcp_client.c
@@ -64,11 +64,41 @@
* SPDX-License-Identifier: MIT
*/
+/* IPv4:
+ * - DHCPv4: https://datatracker.ietf.org/doc/html/rfc2131
+ * - Auto-IP: https://datatracker.ietf.org/doc/html/rfc3927
+ * - DNAv4: https://datatracker.ietf.org/doc/html/rfc4436
+ *
+ * This implmementation does NOT support RFC 4361 and thus cannot
+ * support RFC 6842.
+ *
+ * - DHCPv4 node-specific client identifiers: https://datatracker.ietf.org/doc/html/rfc4361
+ * - DHCPv4 client identifiers in replies: https://datatracker.ietf.org/doc/html/rfc6842
+ *
+ * This implementation does NOT support split options (originally RFC
+ * 2131, but clarified in RFC 3396).
+ *
+ * - DHCPv4 long options: https://datatracker.ietf.org/doc/html/rfc3396
+ *
+
+ * IPv6:
+ * - SLAAC: https://datatracker.ietf.org/doc/html/rfc2462
+ * - DNAv6: https://datatracker.ietf.org/doc/html/rfc6059
+ *
+ * Common:
+ * - NetBIOS name resolution: https://datatracker.ietf.org/doc/html/rfc1001
+ * https://datatracker.ietf.org/doc/html/rfc1002
+ * - LLMNR: https://datatracker.ietf.org/doc/html/rfc4795
+ * - mDNS: https://datatracker.ietf.org/doc/html/rfc6762
+ */
+
#include <string.h> /* for strlen(), memcpy(), memset() */
+#include <stdlib.h> /* for random() */
-#include <libmisc/endian.h>
#include <libmisc/vcall.h>
-#include <libdhcp/dhcp.h>
+#include <libhw/generic/alarmclock.h>
+
+#include <libdhcp/client.h>
#include "dhcp_common.h"
@@ -77,15 +107,16 @@
#include "config.h"
#ifndef CONFIG_DHCP_DEBUG
- #define CONFIG_DHCP_DEBUG 1
+ #define CONFIG_DHCP_DEBUG 1
#endif
-#ifndef CONFIG_DHCP_HOPS
- #define CONFIG_DHCP_HOPS 0
+#ifndef CONFIG_DHCP_SELECTING_NS
+ #define CONFIG_DHCP_SELECTING_NS (5*NS_PER_S)
#endif
-#ifndef CONFIG_DHCP_SECS
- #define CONFIG_DHCP_SECS 0
+#ifndef CONFIG_DHCP_CAN_RECV_UNICAST_IP_WITHOUT_IP
+ #define CONFIG_DHCP_CAN_RECV_UNICAST_IP_WITHOUT_IP false
#endif
+
/* Implementation *************************************************************/
#if CONFIG_DHCP_DEBUG
@@ -95,479 +126,745 @@
#define debugf(fmt, ...) ((void)0)
#endif
-/* DHCP state machine. */
-#define STATE_DHCP_INIT 0 /* Initialize */
-#define STATE_DHCP_DISCOVER 1 /* send DISCOVER and wait OFFER */
-#define STATE_DHCP_REQUEST 2 /* send REQEUST and wait ACK or NACK */
-#define STATE_DHCP_LEASED 3 /* ReceiveD ACK and IP leased */
-#define STATE_DHCP_REREQUEST 4 /* send REQUEST for maintaining leased IP */
-#define STATE_DHCP_RELEASE 5 /* No use */
-#define STATE_DHCP_STOP 6 /* Stop processing DHCP */
-
-/* Global state. */
-struct net_ip4_addr DHCP_SIP; /* DHCP Server IP address */
-struct net_ip4_addr DHCP_REAL_SIP; /* For extract my DHCP server in a few DHCP server */
-struct net_ip4_addr OLD_allocated_ip = {0}; /* Previous IP address */
-struct dhcp_lease global_lease = {.lifetime = DHCP_INFINITY};
-int8_t dhcp_state = STATE_DHCP_INIT; /* DHCP state */
-int8_t dhcp_retry_count = 0;
-volatile uint32_t dhcp_tick_1s = 0; /* unit 1 second */
-uint32_t dhcp_tick_next = DHCP_WAIT_TIME ;
-char global_hostname[64];
-struct net_eth_addr global_eth_addr; /* DHCP Client MAC address. */
-
#define mem_encode(dst, obj) ({ \
typeof(obj) _obj = obj; \
memcpy(dst, &_obj, sizeof(_obj)); \
sizeof(_obj); \
})
-static inline size_t str_encode(void *dst, char *str) {
- size_t len = strlen(str);
+static inline size_t strn_encode(void *dst, char *str, size_t n) {
+ size_t len = strnlen(str, n);
memcpy(dst, str, len);
return len;
}
-/* make the common DHCP message */
-static inline void dhcp_msg_init(struct dhcp_msg *msg, size_t *optlen,
- struct net_eth_addr self_eth_addr,
- uint16_t flags, uint32_t xid) {
- size_t tmp;
-
- assert(msg);
- assert(optlen);
-
- *msg = (struct dhcp_msg){0};
-
- msg->op = DHCP_OP_BOOTREQUEST;
- msg->htype = DHCP_HTYPE_ETHERNET;
- msg->hlen = sizeof(self_eth_addr);
- msg->hops = CONFIG_DHCP_HOPS;
- msg->xid = uint32be_marshal(xid);
- msg->secs = uint16be_marshal(CONFIG_DHCP_SECS);
- msg->flags = uint16be_marshal(flags);
- msg->ciaddr = (struct net_ip4_addr){0};
- msg->yiaddr = (struct net_ip4_addr){0};
- msg->siaddr = (struct net_ip4_addr){0};
- msg->siaddr = (struct net_ip4_addr){0};
- msg->giaddr = (struct net_ip4_addr){0};
-
- tmp = mem_encode(msg->chaddr, self_eth_addr);
- memset(&msg->chaddr[tmp], 0, sizeof(msg->chaddr)-tmp);
-
- memset(msg->sname, 0, sizeof(msg->sname));
- memset(msg->file, 0, sizeof(msg->file));
-
- *optlen = 0;
- msg->options[*optlen++] = dhcp_magic_cookie[0];
- msg->options[*optlen++] = dhcp_magic_cookie[1];
- msg->options[*optlen++] = dhcp_magic_cookie[2];
- msg->options[*optlen++] = dhcp_magic_cookie[3];
-}
-
-static void dhcp_send_DISCOVER(implements_net_packet_conn *sock) {
- struct dhcp_msg msg;
- size_t k;
-
- dhcp_msg_init(&msg, &k, DHCP_FLAG_BROADCAST);
- DHCP_SIP = (struct net_ip4_addr){0};
- DHCP_REAL_SIP = (struct net_ip4_addr){0};
-
- /* Message type. */
- msg->options[k++] = DHCP_OPT_DHCP_MSG_TYPE;
- msg->options[k++] = 1;
- msg->options[k++] = DHCP_MSGTYP_DISCOVER;
-
- /* Our Client ID. */
- msg->options[k++] = DHCP_OPT_CLIENT_ID;
- msg->options[k++] = 1+sizeof(self_eth_addr);
- msg->options[k++] = DHCP_HTYPE_ETHERNET;
- k += mem_encode(&msg->options[k], self_eth_addr);
-
- /* Our requested hostname. */
- msg->options[k++] = DHCP_OPT_HOSTNAME;
- msg->options[k++] = strlen(global_hostname);
- k += str_encode(&msg->options[k], global_hostname);
-
- /* Which parameters we want back. */
- msg->options[k++] = DHCP_OPT_PARAMETER_LIST;
- msg->options[k++] = 6;
- msg->options[k++] = DHCP_OPT_SUBNET_MASK; /* 1 */
- msg->options[k++] = DHCP_OPT_ROUTER; /* 2 */
- msg->options[k++] = DHCP_OPT_DOMAIN_SERVER; /* 3 */
- msg->options[k++] = DHCP_OPT_DOMAIN_NAME; /* 4 */
- msg->options[k++] = DHCP_OPT_RENEWAL_TIME; /* 5 */
- msg->options[k++] = DHCP_OPT_REBINDING_TIME; /* 6 */
-
- /* End. */
- msg->options[k++] = DHCP_OPT_END;
- assert(k <= CONFIG_DHCP_OPT_SIZE);
-
- /* Send. */
- debugf("> Send DHCP_DISCOVER");
- VCALL(sock, sendto, msg, DHCP_MSG_BASE_SIZE + k, net_ip4_addr_broadcast, DHCP_PORT_SERVER);
-}
-
-static void dhcp_send_REQUEST(implements_net_packet_conn *sock) {
- struct net_ip4_addr ip;
- struct dhcp_msg msg;
- size_t k;
-
- dhcp_msg_init(msg, &k,
- (dhcp_state != STATE_DHCP_LEASED && dhcp_state != STATE_DHCP_REREQUEST) ? DHCP_FLAG_BROADCAST : 0);
+enum requirement {
+ MUST,
+ MUST_NOT,
+ SHOULD,
+ SHOULD_NOT,
+ MAY,
+
+ IT_DEPENDS,
+};
+
+struct dhcp_client {
+ /* Static. */
+ implements_net_iface *iface;
+ implements_net_packet_conn *sock;
+ struct net_eth_addr self_eth_addr;
+ char self_hostname[63];
+
+ /* Lease. */
+ enum {
+ STATE_INIT,
+ STATE_SELECTING,
+ STATE_REQUESTING,
+ STATE_BOUND,
+ STATE_RENEWING,
+ STATE_REBINDING,
+
+ STATE_INIT_REBOOT,
+ STATE_REBOOTING,
+ } state;
+ /* Lifetime of xid:
+ * 1. initial : [INIT]->DHCPDISCOVER->DHCPOFFER->DHCPREQUEST-+->DHCPNAK-------------->[INIT]
+ * |->DHCPACK->DHCPDECLINE->[INIT]
+ * `->DHCPACK-------------->[BOUND]
+ * 2. renew/rebind : [RENEWING/REBINDING]->DHCPREQUEST-+->DHCPNAK->[INIT]
+ * `->DHCPACK->[BOUND]
+ */
+ uint32_t xid;
+ uint64_t time_ns_init;
+ struct net_ip4_addr lease_client_addr; /* .yiaddr */
+ struct net_ip4_addr lease_server_id; /* .options[DHCP_OPT_DHCP_SERVER_ID] */
+ uint64_t lease_time_ns_t1; /* .options[DHCP_OPT_RENEWAL_TIME] + time_ns_init */
+ uint64_t lease_time_ns_t2; /* .options[DHCP_OPT_REBINDING_TIME] + time_ns_init */
+ uint64_t lease_time_ns_end; /* .options[DHCP_OPT_ADDRESS_TIME] + time_ns_init */
+ struct {
+ struct net_ip4_addr subnet_mask; /* .options[DHCP_OPT_SUBNET_MASK] */
+ struct net_ip4_addr gateway_addr; /* .options[DHCP_OPT_ROUTER] */
+ } lease_auxdata;
+
+};
+
+/**
+ * @param client->state
+ * @param client->self_eth_addr
+ * @param client->xid
+ * @param client->time_ns_init
+ * @param client->lease_client_addr (sometimes)
+ * @param client->lease_server_id (sometimes)
+ * @param client->sock
+ *
+ * @return whether there was an error
+ */
+static bool dhcp_client_send(struct dhcp_client *client, uint8_t msgtyp, char *errstr) {
+ struct dhcp_msg msg = {0};
+ size_t optlen = 0;
+ bool server_broadcasts, client_broadcasts;
+
+ /**********************************************************************\
+ * Preconditions *
+ \**********************************************************************/
+
+ assert(client);
+ assert(msgtyp == DHCP_MSGTYP_DISCOVER ||
+ msgtyp == DHCP_MSGTYP_INFORM ||
+ msgtyp == DHCP_MSGTYP_REQUEST ||
+ msgtyp == DHCP_MSGTYP_DECLINE ||
+ msgtyp == DHCP_MSGTYP_RELEASE );
+ if (msgtyp == DHCP_MSGTYP_REQUEST)
+ assert(client->state == STATE_SELECTING || /* initial selection */
+ client->state == STATE_INIT_REBOOT || /* variant initial selection */
+ client->state == STATE_BOUND || /* T1 expired, start renew */
+ client->state == STATE_RENEWING ); /* T2 expired, start rebind */
+#define IMPOSSIBLE_REQUEST_CASES \
+ case STATE_INIT: \
+ case STATE_REQUESTING: \
+ case STATE_REBINDING: \
+ case STATE_REBOOTING: \
+ assert(msgtyp == DHCP_MSGTYP_REQUEST); \
+ assert_notreached("invalid client state for sending DHCPREQUEST"); \
+ default: \
+ assert_notreached("invalid client state");
+
+ /**********************************************************************\
+ * Setup *
+ \**********************************************************************/
+
+ server_broadcasts = !CONFIG_DHCP_CAN_RECV_UNICAST_IP_WITHOUT_IP;
+ if (msgtyp == DHCP_MSGTYP_REQUEST &&
+ (client->state == STATE_BOUND || client->state == STATE_RENEWING))
+ server_broadcasts = false;
+
+ /* https://datatracker.ietf.org/doc/html/rfc2131#section-4.4.4 */
+ switch (msgtyp) {
+ case DHCP_MSGTYP_DISCOVER: client_broadcasts = true; break;
+ case DHCP_MSGTYP_INFORM: client_broadcasts = true; break; /* may unicast if it knows the server */
+ case DHCP_MSGTYP_REQUEST: client_broadcasts = client->state != STATE_BOUND; break;
+ case DHCP_MSGTYP_DECLINE: client_broadcasts = true; break;
+ case DHCP_MSGTYP_RELEASE: client_broadcasts = false; break;
+ default: assert_notreached("invalid message type for client to send");
+ }
- switch (dhcp_state) {
- case STATE_DHCP_LEASED:
- case STATE_DHCP_REREQUEST:
- msg->ciaddr = global_lease.addr;
- ip = DHCP_SIP;
+ /**********************************************************************\
+ * Table at https://datatracker.ietf.org/doc/html/rfc2131#page-37 *
+ \**********************************************************************/
+
+ msg.op = DHCP_OP_BOOTREQUEST;
+ msg.htype = DHCP_HTYPE_ETHERNET;
+ msg.hlen = sizeof(client->self_eth_addr);
+ msg.hops = 0; /* relays increment this when they forward it along */
+ msg.xid = uint32be_marshal(client->xid);
+ msg.secs = uint16be_marshal((VCALL(bootclock, get_time_ns) - client->time_ns_init)/NS_PER_S);
+ switch (msgtyp) {
+ case DHCP_MSGTYP_DISCOVER:
+ case DHCP_MSGTYP_INFORM:
+ case DHCP_MSGTYP_REQUEST:
+ msg.flags = uint16be_marshal(server_broadcasts ? DHCP_FLAG_BROADCAST : 0);
+ break;
+ case DHCP_MSGTYP_DECLINE:
+ case DHCP_MSGTYP_RELEASE:
+ msg.flags = uint16be_marshal(0);
break;
- default:
- ip = net_ip4_addr_broadcast;
}
-
- /* Message type. */
- msg->options[k++] = DHCP_OPT_DHCP_MSG_TYPE;
- msg->options[k++] = 1;
- msg->options[k++] = DHCP_MSGTYP_REQUEST;
-
- /* Our Client ID. */
- msg->options[k++] = DHCP_OPT_CLIENT_ID;
- msg->options[k++] = 1+sizeof(self_eth_addr);
- msg->options[k++] = DHCP_HTYPE_ETHERNET;
- k += mem_encode(&msg->options[k], self_eth_addr);
-
- switch (dhcp_state) {
- case STATE_DHCP_LEASED:
- case STATE_DHCP_REREQUEST:
- /* Our current IP address. */
- msg->options[k++] = DHCP_OPT_ADDRESS_REQUEST;
- msg->options[k++] = sizeof(global_lease.addr);
- k += mem_encode(&msg->options[k], global_lease.addr);
-
- /* The server we got it from. */
- msg->options[k++] = DHCP_OPT_DHCP_SERVER_ID;
- msg->options[k++] = sizeof(DHCP_SIP);
- k += mem_encode(&msg->options[k], DHCP_SIP);
+ switch (msgtyp) {
+ case DHCP_MSGTYP_DISCOVER: msg.ciaddr = net_ip4_addr_zero; break;
+ case DHCP_MSGTYP_INFORM: msg.ciaddr = client->lease_client_addr; break;
+ case DHCP_MSGTYP_REQUEST: switch (client->state) {
+ case STATE_SELECTING: case STATE_INIT_REBOOT:
+ msg.ciaddr = net_ip4_addr_zero; break;
+ case STATE_BOUND: case STATE_RENEWING: /* case STATE_REBINDING: */
+ /* RFC 2131 includes "REBINDING" here, but a
+ * DHCPREQUEST is never sent in the REBINDING
+ * state. */
+ msg.ciaddr = client->lease_client_addr; break;
+ IMPOSSIBLE_REQUEST_CASES
+ } break;
+ case DHCP_MSGTYP_DECLINE: msg.ciaddr = net_ip4_addr_zero; break;
+ case DHCP_MSGTYP_RELEASE: msg.ciaddr = client->lease_client_addr; break;
+ }
+ msg.yiaddr = net_ip4_addr_zero; /* only set by servers */
+ msg.siaddr = net_ip4_addr_zero; /* only set by servers */
+ msg.giaddr = net_ip4_addr_zero; /* only set by relays */
+ mem_encode(msg.chaddr, client->self_eth_addr);
+ /* msg.sname = "options, if indicated in 'sname/file' option'; otherwise unused"; */
+ /* msg.file = "options, if indicated in 'sname/file' option'; otherwise unused"; */
+
+ /**********************************************************************\
+ * https://datatracker.ietf.org/doc/html/rfc2131#section-4.1 *
+ \**********************************************************************/
+
+ msg.options[optlen++] = dhcp_magic_cookie[0];
+ msg.options[optlen++] = dhcp_magic_cookie[1];
+ msg.options[optlen++] = dhcp_magic_cookie[2];
+ msg.options[optlen++] = dhcp_magic_cookie[3];
+
+ /**********************************************************************\
+ * Table at https://datatracker.ietf.org/doc/html/rfc2131#page-38 *
+ \**********************************************************************/
+
+#define V(x) sizeof(x), &(x)
+#define NONE 0, NULL
+
+ static uint8_t parameter_request_list[] = {
+ DHCP_OPT_SUBNET_MASK,
+ DHCP_OPT_ROUTER,
+ DHCP_OPT_RENEWAL_TIME,
+ DHCP_OPT_REBINDING_TIME,
+ };
+
+ const struct {
+ bool have_cols;
+ enum requirement cols[5];
+ size_t val_len;
+ void *val_ptr;
+ } options[0x100] = {
+ /* https://datatracker.ietf.org/doc/html/rfc2131#page-38 */
+ /* Option DISCOVER INFORM REQUEST DECLINE RELEASE */
+ /* ------ ---------- ---------- ---------- -------- -------- */
+ [DHCP_OPT_ADDRESS_REQUEST] = { 1, { MAY, MUST_NOT, IT_DEPENDS, MUST, MUST_NOT }, V(client->lease_client_addr) },
+ [DHCP_OPT_ADDRESS_TIME] = { 1, { MAY, MUST_NOT, MAY, MUST_NOT, MUST_NOT }, NONE },
+ [DHCP_OPT_OVERLOAD] = { 1, { MAY, MAY, MAY, MAY, MAY }, NONE },
+ [DHCP_OPT_DHCP_MSG_TYPE] = { 1, { MUST, MUST, MUST, MUST, MUST }, V(msgtyp) },
+ [DHCP_OPT_CLIENT_ID] = { 1, { MAY, MAY, MAY, MAY, MAY }, NONE },
+ [DHCP_OPT_CLASS_ID] = { 1, { MAY, MAY, MAY, MUST_NOT, MUST_NOT }, NONE },
+ [DHCP_OPT_DHCP_SERVER_ID] = { 1, { MUST_NOT, MUST_NOT, IT_DEPENDS, MUST, MUST }, V(client->lease_server_id) },
+ [DHCP_OPT_PARAMETER_LIST] = { 1, { MAY, MAY, MAY, MUST_NOT, MUST_NOT }, V(parameter_request_list) },
+ [DHCP_OPT_DHCP_MAX_MSG_SIZE] = { 1, { MAY, MAY, MAY, MUST_NOT, MUST_NOT },
+ CONFIG_DHCP_OPT_SIZE > DHCP_MSG_MIN_MAX_OPT_SIZE ? 2 : 0,
+ CONFIG_DHCP_OPT_SIZE > DHCP_MSG_MIN_MAX_OPT_SIZE ? &(struct{uint16be_t;}){uint16be_marshal(/*IP*/20+/*UDP*/8+sizeof(msg))} : NULL },
+ [DHCP_OPT_DHCP_MESSAGE] = { 1, { SHOULD_NOT, SHOULD_NOT, SHOULD_NOT, SHOULD, SHOULD }, errstr ? strlen(errstr) : 0, errstr },
+ [0]/* All others */ = { 0, { MAY, MAY, MAY, MUST_NOT, MUST_NOT }, NONE },
+
+ [DHCP_OPT_HOSTNAME] = { 0, {0},
+ strnlen(client->self_hostname, sizeof(client->self_hostname)),
+ client->self_hostname },
+ };
+ int col;
+ switch (msgtyp) {
+ case DHCP_MSGTYP_DISCOVER: col = 0; break;
+ case DHCP_MSGTYP_INFORM: col = 1; break;
+ case DHCP_MSGTYP_REQUEST: col = 2; break;
+ case DHCP_MSGTYP_DECLINE: col = 3; break;
+ case DHCP_MSGTYP_RELEASE: col = 4; break;
+ default: assert_notreached("invalid message type for client to send");
+ }
+ for (uint8_t opt = 1; opt < 255; opt++) {
+ enum requirement req = options[opt].have_cols
+ ? options[opt].cols[col]
+ : options[0].cols[col];
+ if (req == IT_DEPENDS) {
+ switch (opt) {
+ case DHCP_OPT_ADDRESS_REQUEST:
+ assert(msgtyp == DHCP_MSGTYP_REQUEST);
+ switch (client->state) {
+ case STATE_SELECTING: case STATE_INIT_REBOOT:
+ req = MUST;
+ break;
+ case STATE_BOUND: case STATE_RENEWING:
+ req = MUST_NOT;
+ break;
+ IMPOSSIBLE_REQUEST_CASES
+ }
+ break;
+ case DHCP_OPT_DHCP_SERVER_ID:
+ assert(msgtyp == DHCP_MSGTYP_REQUEST);
+ switch (client->state) {
+ case STATE_SELECTING:
+ req = MUST;
+ break;
+ case STATE_INIT_REBOOT: case STATE_BOUND: case STATE_RENEWING:
+ req = MUST_NOT;
+ break;
+ IMPOSSIBLE_REQUEST_CASES
+ }
+ break;
+ default:
+ assert_notreached("unexpected IT_DEPENDS");
+ }
+ assert(req != IT_DEPENDS);
+ }
+ switch (req) {
+ case MUST_NOT:
+ /* Do nothing. */
+ break;
+ case MUST:
+ assert(options[opt].val_len);
+ /* fall-through */
+ case SHOULD:
+ case SHOULD_NOT:
+ case MAY:
+ if (options[opt].val_len) {
+ assert(options[opt].val_len <= UINT8_MAX);
+ msg.options[optlen++] = opt;
+ msg.options[optlen++] = options[opt].val_len;
+ memcpy(&msg.options[optlen], options[opt].val_ptr, options[opt].val_len);
+ optlen += options[opt].val_len;
+ }
+ break;
+ case IT_DEPENDS:
+ assert_notreached("IT_DEPENDS should have been translated already");
+ }
}
- /* Our requested hostname. */
- msg->options[k++] = DHCP_OPT_HOSTNAME;
- msg->options[k++] = strlen(global_hostname);
- k += str_encode(&msg->options[k], global_hostname);
-
- /* Which parameters we want back. */
- msg->options[k++] = DHCP_OPT_PARAMETER_LIST;
- msg->options[k++] = 8;
- msg->options[k++] = DHCP_OPT_SUBNET_MASK; /* 1 */
- msg->options[k++] = DHCP_OPT_ROUTER; /* 2 */
- msg->options[k++] = DHCP_OPT_DOMAIN_SERVER; /* 3 */
- msg->options[k++] = DHCP_OPT_DOMAIN_NAME; /* 4 */
- msg->options[k++] = DHCP_OPT_RENEWAL_TIME; /* 5 */
- msg->options[k++] = DHCP_OPT_REBINDING_TIME; /* 6 */
- msg->options[k++] = DHCP_OPT_ROUTER_DISCOVERY; /* 7 */
- msg->options[k++] = DHCP_OPT_STATIC_ROUTE; /* 8 */
-
- /* End. */
- msg->options[k++] = DHCP_OPT_END;
- assert(k <= CONFIG_DHCP_OPT_SIZE);
-
- debugf("> Send DHCP_REQUEST");
- VCALL(sock, sendto, msg, DHCP_MSG_BASE_SIZE + k, ip, DHCP_PORT_SERVER);
-}
-
-static void dhcp_send_DECLINE(implements_net_packet_conn *sock) {
- size_t k;
-
- dhcp_msg_init(pDHCPMSG, &k, DHCP_FLAG_BROADCAST);
- pDHCPMSG->flags = uint16be_marshal(0);
-
- /* Option Request Param. */
- pDHCPMSG->options[k++] = DHCP_OPT_DHCP_MSG_TYPE;
- pDHCPMSG->options[k++] = 1;
- pDHCPMSG->options[k++] = DHCP_MSGTYP_DECLINE;
-
- /* Our Client ID. */
- pDHCPMSG->options[k++] = DHCP_OPT_CLIENT_ID;
- pDHCPMSG->options[k++] = 1+sizeof(self_eth_addr);
- pDHCPMSG->options[k++] = DHCP_HTYPE_ETHERNET;
- memcpy(&pDHCPMSG->options[k], self_eth_addr.octets, sizeof(self_eth_addr));
- k += sizeof(self_eth_addr);
-
- /* Our current IP address. */
- pDHCPMSG->options[k++] = DHCP_OPT_ADDRESS_REQUEST;
- pDHCPMSG->options[k++] = sizeof(global_lease.addr);
- k += mem_encode(&pDHCPMSG->options[k], global_lease.addr);
-
- /* The server we got it from. */
- pDHCPMSG->options[k++] = DHCP_OPT_DHCP_SERVER_ID;
- pDHCPMSG->options[k++] = sizeof(DHCP_SIP);
- k += mem_encode(&pDHCPMSG->options[k], DHCP_SIP);
+ /**********************************************************************\
+ * https://datatracker.ietf.org/doc/html/rfc2131#section-4.1 *
+ \**********************************************************************/
- /* End. */
- pDHCPMSG->options[k++] = DHCP_OPT_END;
+ msg.options[optlen++] = DHCP_OPT_END;
+ assert(optlen <= CONFIG_DHCP_OPT_SIZE);
- debugf("> Send DHCP_DECLINE");
- assert(k <= CONFIG_DHCP_OPT_SIZE);
- VCALL(sock, sendto, pDHCPMSG, DHCP_MSG_BASE_SIZE + k, net_ip4_addr_broadcast, DHCP_PORT_SERVER);
+ /* Send. */
+ ssize_t r = VCALL(client->sock, sendto, &msg, DHCP_MSG_BASE_SIZE + optlen,
+ client_broadcasts ? net_ip4_addr_broadcast : client->lease_server_id, DHCP_PORT_SERVER);
+ if (r < 0) {
+ debugf("error: sendto: %zd", r);
+ return true;
+ }
+ return false;
}
-static int8_t dhcp_msg_parse(implements_net_packet_conn *sock) {
- struct net_ip4_addr srv_addr;
- uint16_t srv_port;
- size_t msg_len;
- msg_len = VCALL(sock, recvfrom, pDHCPMSG, sizeof(*pDHCPMSG), &srv_addr, &srv_port);
- debugf("DHCP message : %d.%d.%d.%d(%d) %d received.",
- srv_addr.octets[0], srv_addr.octets[1], srv_addr.octets[2], srv_addr.octets[3], srv_port, msg_len);
- /* Compare server port. */
- if (srv_port != DHCP_PORT_SERVER) {
- return 0;
- }
- /* Compare our MAC address. */
- if (memcmp(pDHCPMSG->chaddr, self_eth_addr.octets, sizeof(self_eth_addr))) {
- debugf("Not my DHCP Message. This message is ignored.");
- return 0;
- }
- /* Compare server IP address. */
- if (memcmp(DHCP_SIP.octets, ((struct net_ip4_addr){0}).octets, sizeof(struct net_ip4_addr))) {
- if ( memcmp(srv_addr.octets, DHCP_SIP.octets, sizeof(srv_addr)) &&
- memcmp(srv_addr.octets, DHCP_REAL_SIP.octets, sizeof(srv_addr)) ) {
- debugf("Another DHCP sever send a response message. This is ignored.");
- return 0;
+struct dhcp_recv_msg {
+ struct dhcp_msg raw;
+ struct {
+ uint16_t len;
+ uint8_t *dat;
+ } options[0x100];
+ uint8_t _option_data[sizeof((struct dhcp_msg){}.options)+
+ sizeof((struct dhcp_msg){}.file)+
+ sizeof((struct dhcp_msg){}.sname)];
+};
+
+/** @return whether there is an error */
+static inline bool _dhcp_client_recv_measure_opts(struct dhcp_recv_msg *ret, uint8_t *optoverload, uint8_t *dat, size_t len, bool require_pad) {
+ for (size_t pos = 0, opt_len; pos < len; pos += opt_len) {
+ uint8_t opt_typ = dat[pos++];
+ switch (opt_typ) {
+ case DHCP_OPT_END:
+ if (require_pad)
+ while (pos < len)
+ if (dat[pos++] != DHCP_OPT_PAD)
+ return true;
+ return false;
+ case DHCP_OPT_PAD:
+ opt_len = 0;
+ break;
+ default:
+ if (pos == len)
+ return true;
+ opt_len = dat[pos++];
+ if (pos+opt_len > len)
+ return true;
+ ret->options[opt_typ].len += opt_len;
+ if (opt_typ == DHCP_OPT_OVERLOAD && opt_len == 1)
+ *optoverload = *optoverload | dat[pos];
}
}
+ return true;
+}
- uint8_t msg_type = 0;
- for (size_t k = 4, k_max = msg_len - DHCP_MSG_BASE_SIZE, opt_len; k < k_max; k += opt_len) {
- uint8_t opt_typ;
- opt_typ = pDHCPMSG->options[k++];
+static inline void _dhcp_client_recv_consolidate_opts(struct dhcp_recv_msg *ret, uint8_t *dat, size_t len) {
+ for (size_t pos = 0, opt_len; pos < len; pos += opt_len) {
+ uint8_t opt_typ = dat[pos++];
switch (opt_typ) {
case DHCP_OPT_END:
- opt_len = k_max - k;
- break;
+ return;
case DHCP_OPT_PAD:
opt_len = 0;
break;
default:
- opt_len = pDHCPMSG->options[k++];
- xhandle(opt_typ, opt_len, &pDHCPMSG->options[k]);
+ opt_len = dat[pos++];
+ memcpy(&ret->options[opt_typ].dat[ret->options[opt_typ].len], &dat[pos], opt_len);
+ ret->options[opt_typ].len += opt_len;
}
- switch (opt_typ) {
- case DHCP_OPT_DHCP_MSG_TYPE:
- if (opt_len != 1)
- continue;
- msg_type = pDHCPMSG->options[k];
+ }
+}
+
+/**
+ * @param client->sock
+ * @param client->self_eth_addr
+ * @param client->xid
+ * @param client->lease_server_id
+ *
+ * @return
+ * - <0: -errno
+ * - 0: success
+ * - >0: should not happen
+ */
+static ssize_t dhcp_client_recv(struct dhcp_client *client, struct dhcp_recv_msg *ret) {
+ struct net_ip4_addr srv_addr;
+ uint16_t srv_port;
+ ssize_t msg_len;
+
+ assert(client);
+
+ ignore:
+ msg_len = VCALL(client->sock, recvfrom, &ret->raw, sizeof(ret->raw), &srv_addr, &srv_port);
+ if (msg_len < 0)
+ /* msg_len is -errno */
+ return msg_len;
+
+ /* Validate L3: IP */
+ /* Don't validate that srv_addr matches client->server_id
+ * because there may be a relay between us and the server, and
+ * don't bother to track the relay IP either, because it may
+ * change. */
+
+ /* Validate L4: UDP. */
+ if (srv_port != DHCP_PORT_SERVER)
+ goto ignore;
+
+ /* Validate L5: DHCP. */
+ if ((size_t)msg_len < DHCP_MSG_BASE_SIZE + sizeof(dhcp_magic_cookie))
+ /* ignore impossibly short message */
+ goto ignore;
+ if ((size_t)msg_len > sizeof(ret->raw))
+ /* ignore message that is larger than the specified
+ * DHCP_OPT_DHCP_MAX_MSG_SIZE */
+ goto ignore;
+ if (ret->raw.op != DHCP_OP_BOOTREPLY)
+ /* ignore non-replies */
+ goto ignore;
+ if (memcmp(ret->raw.chaddr, client->self_eth_addr.octets, sizeof(client->self_eth_addr)))
+ /* ignore messages that aren't to us */
+ goto ignore;
+ if (uint32be_unmarshal(ret->raw.xid) != client->xid)
+ /* ignore messages that aren't in response to what we've said */
+ goto ignore;
+ if (memcmp(client->lease_server_id.octets, net_ip4_addr_zero.octets, 4))
+ if (memcmp(srv_addr.octets, client->lease_server_id.octets, 4))
+ /* ignore messages from the wrong server */
+ goto ignore;
+ if (memcmp(ret->raw.options, dhcp_magic_cookie, sizeof(dhcp_magic_cookie)))
+ /* ignore messages without options */
+ goto ignore;
+
+ /* Consolidate split options into contiguous buffers.
+ *
+ * RFC 2131 and RFC 2132 specify this behavior, but are a bit
+ * ambiguous about it; RFC 3396 provides clarification.
+ *
+ * "The aggregate option buffer is made up of the optional
+ * parameters field, the file field, and the sname field, in
+ * that order." -- RFC 3396
+ */
+ uint8_t optoverload = 0;
+ memset(&ret->options, 0, sizeof(ret->options));
+ /* Size the buffers. */
+ if (_dhcp_client_recv_measure_opts(ret, &optoverload,
+ &ret->raw.options[sizeof(dhcp_magic_cookie)],
+ msg_len - (DHCP_MSG_BASE_SIZE + sizeof(dhcp_magic_cookie)),
+ false))
+ goto ignore;
+ if (optoverload & 1u)
+ if (_dhcp_client_recv_measure_opts(ret, &optoverload,
+ ret->raw.file, sizeof(ret->raw.file),
+ true))
+ goto ignore;
+ if (optoverload & 2u)
+ if (_dhcp_client_recv_measure_opts(ret, &optoverload,
+ ret->raw.sname, sizeof(ret->raw.sname),
+ true))
+ goto ignore;
+ /* Validate sizes, allocate buffers. */
+ for (uint8_t opt = 1, allocated = 0; opt < 255; opt++) {
+ if (!ret->options[opt].len)
+ continue;
+ if (!dhcp_opt_length_is_valid(opt, ret->options[opt].len))
+ goto ignore;
+ ret->options[opt].dat = &ret->_option_data[allocated];
+ allocated += ret->options[opt].len;
+ ret->options[opt].len = 0;
+ }
+ /* Fill the buffers. */
+ _dhcp_client_recv_consolidate_opts(ret,
+ &ret->raw.options[sizeof(dhcp_magic_cookie)],
+ msg_len - (DHCP_MSG_BASE_SIZE + sizeof(dhcp_magic_cookie)));
+ if (optoverload & 1u)
+ _dhcp_client_recv_consolidate_opts(ret, ret->raw.file, sizeof(ret->raw.file));
+ if (optoverload & 2u)
+ _dhcp_client_recv_consolidate_opts(ret, ret->raw.sname, sizeof(ret->raw.sname));
+ /* Validate presence of options. */
+ static const struct {
+ bool have_cols;
+ enum requirement cols[3];
+ } option_req[0x100] = {
+ /* https://datatracker.ietf.org/doc/html/rfc2131#page-29 */
+ /* Option DHCPOFFER DHCPACK DHCPNAK */
+ /* ------ -------- ---------- -------- */
+ [DHCP_OPT_ADDRESS_REQUEST] = { 1, { MUST_NOT, MUST_NOT, MUST_NOT } },
+ [DHCP_OPT_ADDRESS_TIME] = { 1, { MUST, IT_DEPENDS, MUST_NOT } },
+ [DHCP_OPT_OVERLOAD] = { 1, { MAY, MAY, MUST_NOT } },
+ [DHCP_OPT_DHCP_MSG_TYPE] = { 1, { MUST, MUST, MUST } },
+ [DHCP_OPT_PARAMETER_LIST] = { 1, { MUST_NOT, MUST_NOT, MUST_NOT } },
+ [DHCP_OPT_DHCP_MESSAGE] = { 1, { SHOULD, SHOULD, SHOULD } },
+ [DHCP_OPT_CLIENT_ID] = { 1, { MUST_NOT, MUST_NOT, MAY } },
+ [DHCP_OPT_CLASS_ID] = { 1, { MAY, MAY, MAY } },
+ [DHCP_OPT_DHCP_SERVER_ID] = { 1, { MUST, MUST, MUST } },
+ [DHCP_OPT_DHCP_MAX_MSG_SIZE] = { 1, { MUST_NOT, MUST_NOT, MUST_NOT } },
+ [0]/* All others */ = { 0, { MAY, MAY, MUST_NOT } },
+ };
+ if (!ret->options[DHCP_OPT_DHCP_MSG_TYPE].len)
+ goto ignore;
+ int col;
+ switch (ret->options[DHCP_OPT_DHCP_MSG_TYPE].dat[0]) {
+ case DHCP_MSGTYP_OFFER: col = 0; break;
+ case DHCP_MSGTYP_ACK: col = 1; break;
+ case DHCP_MSGTYP_NAK: col = 2; break;
+ default: goto ignore;
+ }
+ for (uint8_t opt = 1; opt < 255; opt++) {
+ enum requirement req = option_req[opt].have_cols
+ ? option_req[opt].cols[col]
+ : option_req[0].cols[col];
+ if (req == IT_DEPENDS) {
+ /* "MUST" when this is a response to a DHCPREQUEST,
+ * "MUST_NOT" when this is a response to a DHCPINFORM.
+ * Because this client implementation never sends
+ * DHCPINFORM, this must be a response to
+ * DHCPREQUEST. */
+ req = MUST;
+ assert(req != IT_DEPENDS);
+ }
+ switch (req) {
+ case MUST:
+ if (!ret->options[opt].len)
+ goto ignore;
break;
- case DHCP_OPT_ADDRESS_TIME:
- if (opt_len != 4)
- continue;
- global_lease.lifetime = uint32be_decode(&pDHCPMSG->options[k]);
-#if CONFIG_DHCP_DEBUG
- global_lease.lifetime = 10;
-#endif
+ case MUST_NOT:
+ if (ret->options[opt].len)
+ goto ignore;
break;
- case DHCP_OPT_DHCP_SERVER_ID:
- if (opt_len != 4)
- continue;
- DHCP_SIP.octets[0] = pDHCPMSG->options[k+0];
- DHCP_SIP.octets[1] = pDHCPMSG->options[k+1];
- DHCP_SIP.octets[2] = pDHCPMSG->options[k+2];
- DHCP_SIP.octets[3] = pDHCPMSG->options[k+3];
- DHCP_REAL_SIP = srv_addr;
+ case SHOULD:
+ case SHOULD_NOT:
+ case MAY:
+ /* Do nothing. */
break;
+ case IT_DEPENDS:
+ assert_notreached("IT_DEPENDS should have been translated already");
}
}
- return msg_type;
+
+ return 0;
}
-static void dhcp_reset_timeout(void) {
- dhcp_tick_1s = 0;
- dhcp_tick_next = DHCP_WAIT_TIME;
- dhcp_retry_count = 0;
+/** @return true if there's a conflict, false if the addr appears to be unused */
+static bool dhcp_check_conflict(implements_net_packet_conn *sock, struct net_ip4_addr addr) {
+ assert(sock);
+ return VCALL(sock, sendto, "CHECK_IP_CONFLICT", 17, addr, 5000) != NET_ETIMEDOUT;
}
-static uint8_t dhcp_check_timeout(implements_net_packet_conn *sock) {
- uint8_t ret = DHCP_RET_RUNNING;
+static void dhcp_client_take_lease(struct dhcp_client *client, struct dhcp_recv_msg *msg) {
+ assert(client);
+ assert(msg);
- if (dhcp_retry_count < MAX_DHCP_RETRY) {
- if (dhcp_tick_next < dhcp_tick_1s) {
+ client->lease_client_addr = msg->raw.yiaddr;
+ client->lease_auxdata.subnet_mask = msg->options[DHCP_OPT_SUBNET_MASK].len
+ ? *((struct net_ip4_addr *)msg->options[DHCP_OPT_SUBNET_MASK].dat)
+ : net_ip4_addr_zero;
+ client->lease_auxdata.gateway_addr = msg->options[DHCP_OPT_ROUTER].len
+ ? *((struct net_ip4_addr *)msg->options[DHCP_OPT_SUBNET_MASK].dat)
+ : net_ip4_addr_zero;
+ client->lease_server_id =
+ *((struct net_ip4_addr *)msg->options[DHCP_OPT_DHCP_SERVER_ID].dat);
+
+ uint64_t duration_of_lease_ns =
+ ((uint64_t)uint32be_decode(msg->options[DHCP_OPT_ADDRESS_TIME].dat))*NS_PER_S;
+ uint64_t t1_ns = msg->options[DHCP_OPT_RENEWAL_TIME].len
+ ? uint32be_decode(msg->options[DHCP_OPT_RENEWAL_TIME].dat)*NS_PER_S
+ : duration_of_lease_ns/2;
+ uint64_t t2_ns = msg->options[DHCP_OPT_REBINDING_TIME].len
+ ? uint32be_decode(msg->options[DHCP_OPT_RENEWAL_TIME].dat)*NS_PER_S
+ : (duration_of_lease_ns*7)/8; /* 0.875 = 7/8 */
+
+ client->lease_time_ns_t1 = client->time_ns_init + t1_ns;
+ client->lease_time_ns_t2 = client->time_ns_init + t2_ns;
+ client->lease_time_ns_end = client->time_ns_init + duration_of_lease_ns;
+}
- switch ( dhcp_state ) {
- case STATE_DHCP_DISCOVER :
- /*debugf("<<timeout>> state : STATE_DHCP_DISCOVER");*/
- dhcp_send_DISCOVER(sock);
- break;
+static __attribute__((noreturn)) void dhcp_client_run(struct dhcp_client *client) {
+ assert(client);
- case STATE_DHCP_REQUEST :
- /*debugf("<<timeout>> state : STATE_DHCP_REQUEST");*/
- dhcp_send_REQUEST(sock);
- break;
+ /* State transition diagram: https://datatracker.ietf.org/doc/html/rfc2131#page-35 */
+ for (;;) {
+ switch (client->state) {
+ case STATE_INIT: {
+ client->xid = (uint32_t)random(); /* random() returns 31 random bits; good enough */
+ client->time_ns_init = VCALL(bootclock, get_time_ns);
- case STATE_DHCP_REREQUEST :
- /*debugf("<<timeout>> state : STATE_DHCP_REREQUEST");*/
- dhcp_send_REQUEST(sock);
+ if (dhcp_client_send(client, DHCP_MSGTYP_DISCOVER, NULL))
break;
-
- default :
+ VCALL(client->sock, set_read_deadline, client->time_ns_init+CONFIG_DHCP_SELECTING_NS);
+ client->state = STATE_SELECTING;
+ break;
+ }
+ case STATE_SELECTING: {
+ struct dhcp_recv_msg msg;
+ ssize_t r = dhcp_client_recv(client, &msg);
+ switch (r) {
+ case 0:
+ switch (msg.options[DHCP_OPT_DHCP_MSG_TYPE].dat[0]) {
+ case DHCP_MSGTYP_OFFER:
+ /* Accept the first offer. */
+ dhcp_client_take_lease(client, &msg);
+ if (dhcp_client_send(client, DHCP_MSGTYP_REQUEST, NULL))
+ break;
+ VCALL(client->sock, set_read_deadline, 0);
+ client->state = STATE_REQUESTING;
+ break;
+ default:
+ /* ignore */
+ }
+ break;
+ case -NET_ETIMEDOUT:
+ client->state = STATE_INIT;
break;
+ default:
+ assert(r < 0);
+ debugf("error: recvfrom: %d", r);
}
-
- dhcp_tick_1s = 0;
- dhcp_tick_next = dhcp_tick_1s + DHCP_WAIT_TIME;
- dhcp_retry_count++;
+ break;
}
- } else { /* timeout occurred */
-
- switch(dhcp_state) {
- case STATE_DHCP_DISCOVER:
- dhcp_state = STATE_DHCP_INIT;
- ret = DHCP_RET_FAILED;
+ case STATE_REQUESTING: {
+ struct dhcp_recv_msg msg;
+ ssize_t r = dhcp_client_recv(client, &msg);
+ switch (r) {
+ case 0:
+ switch (msg.options[DHCP_OPT_DHCP_MSG_TYPE].dat[0]) {
+ case DHCP_MSGTYP_NAK:
+ client->state = STATE_INIT;
+ break;
+ case DHCP_MSGTYP_ACK:
+ if (dhcp_check_conflict(client->sock, client->lease_client_addr)) {
+ (void)dhcp_client_send(client, DHCP_MSGTYP_DECLINE, "IP is already in use");
+ client->state = STATE_INIT;
+ } else {
+ dhcp_client_take_lease(client, &msg);
+ VCALL(client->iface, ifup, (struct net_iface_config){
+ .addr = client->lease_client_addr,
+ .gateway_addr = client->lease_auxdata.gateway_addr,
+ .subnet_mask = client->lease_auxdata.subnet_mask,
+ });
+ }
+ default:
+ /* ignore */
+ }
+ break;
+ default:
+ assert(r < 0);
+ debugf("error: recvfrom: %d", r);
+ }
break;
- case STATE_DHCP_REQUEST:
- case STATE_DHCP_REREQUEST:
- dhcp_send_DISCOVER(sock);
- dhcp_state = STATE_DHCP_DISCOVER;
+ }
+ case STATE_BOUND: {
+ VCALL(client->sock, set_read_deadline, client->lease_time_ns_t1);
+ struct dhcp_recv_msg msg;
+ ssize_t r = dhcp_client_recv(client, &msg);
+ switch (r) {
+ case 0:
+ /* discard */
+ break;
+ case -NET_ETIMEDOUT:
+ if (dhcp_client_send(client, DHCP_MSGTYP_REQUEST, NULL))
+ break;
+ client->state = STATE_RENEWING;
+ break;
+ default:
+ assert(r < 0);
+ debugf("error: recvfrom: %d", r);
+ }
break;
- default :
+ }
+ case STATE_RENEWING: {
+ VCALL(client->sock, set_read_deadline, client->lease_time_ns_t2);
+ struct dhcp_recv_msg msg;
+ ssize_t r = dhcp_client_recv(client, &msg);
+ switch (r) {
+ case 0:
+ switch (msg.options[DHCP_OPT_DHCP_MSG_TYPE].dat[0]) {
+ case DHCP_MSGTYP_NAK:
+ client->state = STATE_INIT;
+ break;
+ case DHCP_MSGTYP_ACK:
+ dhcp_client_take_lease(client, &msg);
+ VCALL(client->iface, ifup, (struct net_iface_config){
+ .addr = client->lease_client_addr,
+ .gateway_addr = client->lease_auxdata.gateway_addr,
+ .subnet_mask = client->lease_auxdata.subnet_mask,
+ });
+ default:
+ /* ignore */
+ }
+ break;
+ case -NET_ETIMEDOUT:
+ if (dhcp_client_send(client, DHCP_MSGTYP_REQUEST, NULL))
+ break;
+ client->state = STATE_REBINDING;
+ break;
+ default:
+ assert(r < 0);
+ debugf("error: recvfrom: %d", r);
+ }
break;
}
- dhcp_reset_timeout();
- }
- return ret;
-}
-
-static int8_t dhcp_check_leasedIP(implements_net_packet_conn *sock) {
- int32_t ret;
-
- /* IP conflict detection : ARP request - ARP reply */
- /* Broadcasting ARP Request for check the IP conflict using UDP sendto() function */
- ret = VCALL(sock, sendto, "CHECK_IP_CONFLICT", 17, global_lease.addr, 5000);
-
- if (ret == NET_ETIMEDOUT) {
- /* UDP send Timeout occurred : allocated IP address is unique, DHCP Success */
- debugf("\r\n> Check leased IP - OK");
- return 1;
+ case STATE_REBINDING: {
+ VCALL(client->sock, set_read_deadline, client->lease_time_ns_end);
+ struct dhcp_recv_msg msg;
+ ssize_t r = dhcp_client_recv(client, &msg);
+ switch (r) {
+ case 0:
+ switch (msg.options[DHCP_OPT_DHCP_MSG_TYPE].dat[0]) {
+ case DHCP_MSGTYP_NAK:
+ client->state = STATE_INIT;
+ break;
+ case DHCP_MSGTYP_ACK:
+ dhcp_client_take_lease(client, &msg);
+ VCALL(client->iface, ifup, (struct net_iface_config){
+ .addr = client->lease_client_addr,
+ .gateway_addr = client->lease_auxdata.gateway_addr,
+ .subnet_mask = client->lease_auxdata.subnet_mask,
+ });
+ default:
+ /* ignore */
+ }
+ break;
+ case -NET_ETIMEDOUT:
+ client->state = STATE_INIT;
+ break;
+ default:
+ assert(r < 0);
+ debugf("error: recvfrom: %d", r);
+ }
+ break;
+ }
+ case STATE_INIT_REBOOT:
+ case STATE_REBOOTING:
+ assert_notreached("initialization with known network address is not supported");
+ default:
+ assert_notreached("invalid client state");
+ }
}
-
- /* Received ARP reply or etc : IP address conflict occur, DHCP Failed */
- dhcp_send_DECLINE(sock);
-
- ret = dhcp_tick_1s;
- while ((dhcp_tick_1s - ret) < 2) {} /* wait for 1s over; wait to complete to send DECLINE message; */
-
- return 0;
}
-COROUTINE DHCP_cr(struct w5500 *chip, uint8_t socknum,
- struct net_eth_addr self_eth_addr,
- char *self_hostname,
- dhcp_callback_t cb) {
- uint32_t xid;
- implements_net_packet_conn *sock;
-
- xid = 0x12345678;
- xid += self_eth_addr.octets[3];
- xid += self_eth_addr.octets[4];
- xid += self_eth_addr.octets[5];
- xid += (self_eth_addr.octets[3] ^
- self_eth_addr.octets[4] ^
- self_eth_addr.octets[5]);
-
- dhcp_tick_1s = 0;
- dhcp_tick_next = DHCP_WAIT_TIME;
- dhcp_retry_count = 0;
- dhcp_state = STATE_DHCP_INIT;
-
- sock = w5500_udp_conn(chip, socknum, DHCP_PORT_CLIENT);
-
- ret = DHCP_RET_RUNNING;
- msg_type = dhcp_msg_parse(sock);
-
- for (;;) {
- /* State transition diagram: https://datatracker.ietf.org/doc/html/rfc2131#page-35 */
- switch (state) {
- case STATE_DHCP_INIT:
- dhcp_send_DISCOVER(sock);
- dhcp_state = STATE_DHCP_DISCOVER;
- break;
- case STATE_DHCP_DISCOVER :
- if (msg_type == DHCP_MSGTYP_OFFER){
- debugf("> Receive DHCP_OFFER");
- global_lease.addr = pDHCPMSG->yiaddr;
-
- dhcp_send_REQUEST(sock);
- dhcp_state = STATE_DHCP_REQUEST;
- } else
- ret = dhcp_check_timeout(sock);
- break;
-
- case STATE_DHCP_REQUEST :
- if (msg_type == DHCP_MSGTYP_ACK) {
- debugf("> Receive DHCP_ACK");
- if (dhcp_check_leasedIP(sock)) {
- /* Network info assignment from DHCP */
- cb(DHCP_ASSIGN, global_lease);
- dhcp_reset_timeout();
-
- dhcp_state = STATE_DHCP_LEASED;
- } else {
- /* IP address conflict occurred */
- dhcp_reset_timeout();
- cb(DHCP_CONFLICT, global_lease);
- dhcp_state = STATE_DHCP_INIT;
- }
- } else if (msg_type == DHCP_MSGTYP_NAK) {
- debugf("> Receive DHCP_NACK");
-
- dhcp_reset_timeout();
-
- dhcp_state = STATE_DHCP_DISCOVER;
- } else
- ret = dhcp_check_timeout(sock);
- break;
-
- case STATE_DHCP_LEASED :
- ret = DHCP_RET_IP_LEASED;
- if ((global_lease.lifetime != DHCP_INFINITY) && ((global_lease.lifetime/2) < dhcp_tick_1s)) {
- debugf("> Maintains the IP address");
-
- msg_type = 0;
- OLD_allocated_ip = global_lease.addr;
+__attribute__((noreturn)) void dhcp_client_main(implements_net_iface *iface,
+ char *self_hostname) {
+ assert(iface);
- xid++;
+ struct dhcp_client client = {
+ /* Static. */
+ .iface = iface,
+ .sock = VCALL(iface, udp_conn, DHCP_PORT_CLIENT),
+ .self_eth_addr = VCALL(iface, hwaddr),
- dhcp_send_REQUEST(sock);
+ /* Lease. */
+ .state = STATE_INIT,
+ };
+ if (self_hostname)
+ strncpy(client.self_hostname, self_hostname, sizeof(client.self_hostname));
- dhcp_reset_timeout();
-
- dhcp_state = STATE_DHCP_REREQUEST;
- }
- break;
-
- case STATE_DHCP_REREQUEST :
- ret = DHCP_RET_IP_LEASED;
- if (msg_type == DHCP_MSGTYP_ACK) {
- dhcp_retry_count = 0;
- if (memcmp(OLD_allocated_ip.octets, global_lease.addr.octets, sizeof(global_lease.addr))) {
- ret = DHCP_RET_IP_CHANGED;
- cb(DHCP_UPDATE, global_lease);
- debugf(">IP changed.");
- } else {
- debugf(">IP is continued.");
- }
- dhcp_reset_timeout();
- dhcp_state = STATE_DHCP_LEASED;
- } else if (msg_type == DHCP_MSGTYP_NAK) {
- debugf("> Receive DHCP_NACK, Failed to maintain ip");
- dhcp_reset_timeout();
-
- dhcp_state = STATE_DHCP_DISCOVER;
- } else
- ret = dhcp_check_timeout(sock);
- break;
- default :
- break;
- }
+ assert(client.sock);
- return ret;
+ dhcp_client_run(&client);
}
diff --git a/libdhcp/dhcp_common.h b/libdhcp/dhcp_common.h
index 2bde7a4..0be3a27 100644
--- a/libdhcp/dhcp_common.h
+++ b/libdhcp/dhcp_common.h
@@ -1,4 +1,4 @@
-/* libdhcp/dhcp_common.c - TODO
+/* libdhcp/dhcp_common.h - Base definitions for the DHCP protocol
*
* Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -64,14 +64,29 @@
* SPDX-License-Identifier: MIT
*/
+#ifndef _LIBDHCP_DHCP_COMMON_H_
+#define _LIBDHCP_DHCP_COMMON_H_
+
+#include <libmisc/endian.h>
+
/* Config *********************************************************************/
+#include "config.h"
+
#ifndef CONFIG_DHCP_OPT_SIZE
- #define CONFIG_DHCP_OPT_SIZE 312
+ #define CONFIG_DHCP_OPT_SIZE DHCP_MSG_MIN_MAX_OPT_SIZE
#endif
/* RFC 2131 definitions *******************************************************/
+/**
+ * "A DHCP client must be prepared to receive DHCP messages with an
+ * 'options' field of at least length 312 octets." --
+ * https://datatracker.ietf.org/doc/html/rfc2131#page-9
+ */
+#define DHCP_MSG_MIN_MAX_OPT_SIZE 312
+static_assert(CONFIG_DHCP_OPT_SIZE >= DHCP_MSG_MIN_MAX_OPT_SIZE);
+
/** https://datatracker.ietf.org/doc/html/rfc2131#page-9 */
struct dhcp_msg {
uint8_t op; /* DHCP_OP_{x} */
@@ -86,8 +101,8 @@ struct dhcp_msg {
struct net_ip4_addr ciaddr; /* Request IP to DHCP sever */
struct net_ip4_addr yiaddr; /* Offered IP from DHCP server */
- struct net_ip4_addr siaddr; /* next-server IP address (not used) */
- struct net_ip4_addr giaddr; /* relay-agent IP address (not used) */
+ struct net_ip4_addr siaddr; /* next-server IP address */
+ struct net_ip4_addr giaddr; /* relay-agent IP address */
uint8_t chaddr[16]; /* client hardware (MAC) address */
uint8_t sname[64]; /* server name (not used) */
uint8_t file[128]; /* boot file name (not used) */
@@ -95,20 +110,25 @@ struct dhcp_msg {
uint8_t options[CONFIG_DHCP_OPT_SIZE];
};
static_assert(offsetof(struct dhcp_msg, options) == 236);
-#define DHCP_MSG_BASE_SIZE offsetof(struct dhcp_msg, options)
+#define DHCP_MSG_BASE_SIZE offsetof(struct dhcp_msg, options)
/** https://datatracker.ietf.org/doc/html/rfc2131#page-10 */
-#define DHCP_OP_BOOTREQUEST 1
-#define DHCP_OP_BOOTREPLY 2
+#define DHCP_OP_BOOTREQUEST 1
+#define DHCP_OP_BOOTREPLY 2
/** https://datatracker.ietf.org/doc/html/rfc2131#page-11 */
-#define DHCP_FLAG_BROADCAST 0x8000
+#define DHCP_FLAG_BROADCAST 0x8000 /* The BROADCAST flag (set by the client)
+ * indicates that the server should use
+ * broadcast-IP replies instead of
+ * unicast-IP because the client is not
+ * yet capable of receiving unicast-IP at
+ * this stage of configuration. */
/** https://datatracker.ietf.org/doc/html/rfc2131#section-3 */
static const uint8_t dhcp_magic_cookie[] = {99, 130, 83, 99};
/** https://datatracker.ietf.org/doc/html/rfc2131#section-3.3 */
-#define DHCP_INFINITY 0xffffffff
+#define DHCP_INFINITY 0xffffffff
/* IANA assignments ***********************************************************/
@@ -186,22 +206,100 @@ static const uint8_t dhcp_magic_cookie[] = {99, 130, 83, 99};
#define DHCP_OPT_DHCP_SERVER_ID ((uint8_t) 54) /* RFC2132: length: 4; meaning: DHCP Server Identification */
#define DHCP_OPT_PARAMETER_LIST ((uint8_t) 55) /* RFC2132: length: N; meaning: Parameter Request List */
#define DHCP_OPT_DHCP_MESSAGE ((uint8_t) 56) /* RFC2132: length: N; meaning: DHCP Error Message */
-#define DHCP_OPT_DHCP_MAX_MSG_SIZE ((uint8_t) 57) /* RFC2132: length: 2; meaning: DHCP Maximum Message Size */
+#define DHCP_OPT_DHCP_MAX_MSG_SIZE ((uint8_t) 57) /* RFC2132: length: 2; meaning: DHCP Maximum Message Size */ /* note: includes IP & UDP headers (20 and 8, respectively) */
#define DHCP_OPT_RENEWAL_TIME ((uint8_t) 58) /* RFC2132: length: 4; meaning: DHCP Renewal (T1) Time */
#define DHCP_OPT_REBINDING_TIME ((uint8_t) 59) /* RFC2132: length: 4; meaning: DHCP Rebinding (T2) Time */
#define DHCP_OPT_CLASS_ID ((uint8_t) 60) /* RFC2132: length: N; meaning: Class Identifier */
#define DHCP_OPT_CLIENT_ID ((uint8_t) 61) /* RFC2132: length: N; meaning: Client Identifier */
+ /* ... */
#define DHCP_OPT_END ((uint8_t)255) /* RFC2132: length: 0; meaning: None */
+static inline bool dhcp_opt_length_is_valid(uint8_t opt, uint16_t len) {
+ switch (opt) {
+ /* RFC 2132 */
+ case DHCP_OPT_PAD: return len == 0;
+ case DHCP_OPT_SUBNET_MASK: return len == 4;
+ case DHCP_OPT_TIME_OFFSET: return len == 4;
+ case DHCP_OPT_ROUTER: return len >= 4 && len % 4 == 0;
+ case DHCP_OPT_TIME_SERVER: return len >= 4 && len % 4 == 0;
+ case DHCP_OPT_NAME_SERVER: return len >= 4 && len % 4 == 0;
+ case DHCP_OPT_DOMAIN_SERVER: return len >= 4 && len % 4 == 0;
+ case DHCP_OPT_LOG_SERVER: return len >= 4 && len % 4 == 0;
+ case DHCP_OPT_QUOTES_SERVER: return len >= 4 && len % 4 == 0;
+ case DHCP_OPT_LPR_SERVER: return len >= 4 && len % 4 == 0;
+ case DHCP_OPT_IMPRESS_SERVER: return len >= 4 && len % 4 == 0;
+ case DHCP_OPT_RLP_SERVER: return len >= 4 && len % 4 == 0;
+ case DHCP_OPT_HOSTNAME: return len >= 1;
+ case DHCP_OPT_BOOT_FILE_SIZE: return len == 2;
+ case DHCP_OPT_MERIT_DUMP_FILE: return len >= 1;
+ case DHCP_OPT_DOMAIN_NAME: return len >= 1;
+ case DHCP_OPT_SWAP_SERVER: return len == 4; /* IANA says length is "N", but RFC 2132 says "length is 4"; likely releated to errata ID 487 */
+ case DHCP_OPT_ROOT_PATH: return len >= 1;
+ case DHCP_OPT_EXTENSION_FILE: return len >= 1;
+ case DHCP_OPT_FORWARD_ONOFF: return len == 1;
+ case DHCP_OPT_SRCRTE_ONOFF: return len == 1;
+ case DHCP_OPT_POLICY_FILTER: return len >= 8 && len % 8 == 0;
+ case DHCP_OPT_MAX_DG_ASSEMBLY: return len == 2;
+ case DHCP_OPT_DEFAULT_IP_TTL: return len == 1;
+ case DHCP_OPT_MTU_TIMEOUT: return len == 4;
+ case DHCP_OPT_MTU_PLATEAU: return len >= 2 && len % 2 == 0;
+ case DHCP_OPT_MTU_INTERFACE: return len == 2;
+ case DHCP_OPT_MTU_SUBNET: return len == 1;
+ case DHCP_OPT_BROADCAST_ADDRESS: return len == 4;
+ case DHCP_OPT_MASK_DISCOVERY: return len == 1;
+ case DHCP_OPT_MASK_SUPPLIER: return len == 1;
+ case DHCP_OPT_ROUTER_DISCOVERY: return len == 1;
+ case DHCP_OPT_ROUTER_REQUEST: return len == 4;
+ case DHCP_OPT_STATIC_ROUTE: return len >= 8 && len % 8 == 0;
+ case DHCP_OPT_TRAILERS: return len == 1;
+ case DHCP_OPT_ARP_TIMEOUT: return len == 4;
+ case DHCP_OPT_ETHERNET: return len == 1;
+ case DHCP_OPT_DEFAULT_TCP_TTL: return len == 1;
+ case DHCP_OPT_KEEPALIVE_TIME: return len == 4;
+ case DHCP_OPT_KEEPALIVE_DATA: return len == 1;
+ case DHCP_OPT_NIS_DOMAIN: return len >= 1;
+ case DHCP_OPT_NIS_SERVERS: return len >= 4 && len % 4 == 0;
+ case DHCP_OPT_NTP_SERVERS: return len >= 4 && len % 4 == 0;
+ case DHCP_OPT_VENDOR_SPECIFIC: return len >= 1;
+ case DHCP_OPT_NETBIOS_NAME_SRV: return len >= 4 && len % 4 == 0;
+ case DHCP_OPT_NETBIOS_DIST_SRV: return len >= 4 && len % 4 == 0;
+ case DHCP_OPT_NETBIOS_NODE_TYPE: return len == 1;
+ case DHCP_OPT_NETBIOS_SCOPE: return len >= 1;
+ case DHCP_OPT_X_WINDOW_FONT: return len >= 4 && len % 4 == 0;
+ case DHCP_OPT_X_WINDOW_MANAGER: return len >= 4 && len % 4 == 0;
+ case DHCP_OPT_ADDRESS_REQUEST: return len == 4;
+ case DHCP_OPT_ADDRESS_TIME: return len == 4;
+ case DHCP_OPT_OVERLOAD: return len == 1;
+ case DHCP_OPT_DHCP_MSG_TYPE: return len == 1;
+ case DHCP_OPT_DHCP_SERVER_ID: return len == 4;
+ case DHCP_OPT_PARAMETER_LIST: return len >= 1;
+ case DHCP_OPT_DHCP_MESSAGE: return len >= 1;
+ case DHCP_OPT_DHCP_MAX_MSG_SIZE: return len == 2;
+ case DHCP_OPT_RENEWAL_TIME: return len == 4;
+ case DHCP_OPT_REBINDING_TIME: return len == 4;
+ case DHCP_OPT_CLASS_ID: return len >= 1;
+ case DHCP_OPT_CLIENT_ID: return len >= 2;
+
+ /* RFC 2132 */
+ case DHCP_OPT_END: return len == 0;
+
+ /* Unrecognized */
+ default:
+ return true;
+ }
+};
+
/**
* DHCP Message Type 53 Values
* https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#message-type-53
*/
-#define DHCP_MSGTYP_DISCOVER ((uint8_t) 1) /* RFC2132 */
-#define DHCP_MSGTYP_OFFER ((uint8_t) 2) /* RFC2132 */
-#define DHCP_MSGTYP_REQUEST ((uint8_t) 3) /* RFC2132 */
-#define DHCP_MSGTYP_DECLINE ((uint8_t) 4) /* RFC2132 */
-#define DHCP_MSGTYP_ACK ((uint8_t) 5) /* RFC2132 */
-#define DHCP_MSGTYP_NAK ((uint8_t) 6) /* RFC2132 */
-#define DHCP_MSGTYP_RELEASE ((uint8_t) 7) /* RFC2132 */
-#define DHCP_MSGTYP_INFORM ((uint8_t) 8) /* RFC2132 */
+#define DHCP_MSGTYP_DISCOVER ((uint8_t) 1) /* RFC2132, client->server */
+#define DHCP_MSGTYP_OFFER ((uint8_t) 2) /* RFC2132, server->client */
+#define DHCP_MSGTYP_REQUEST ((uint8_t) 3) /* RFC2132, client->server */
+#define DHCP_MSGTYP_DECLINE ((uint8_t) 4) /* RFC2132, client->server */
+#define DHCP_MSGTYP_ACK ((uint8_t) 5) /* RFC2132, server->client */
+#define DHCP_MSGTYP_NAK ((uint8_t) 6) /* RFC2132, server->client */
+#define DHCP_MSGTYP_RELEASE ((uint8_t) 7) /* RFC2132, client->server */
+#define DHCP_MSGTYP_INFORM ((uint8_t) 8) /* RFC2132, client->server */
+
+#endif /* _LIBDHCP_DHCP_COMMON_H_ */
diff --git a/libdhcp/f.md b/libdhcp/f.md
new file mode 100644
index 0000000..447ee5b
--- /dev/null
+++ b/libdhcp/f.md
@@ -0,0 +1,27 @@
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
+| <c> | | <c> |
+| In the message from the | | ... then the server ... |
+| client the ... | | |
+| | | |
+| and and | | |
+
+| <c> | <c> | <c> | | <c> | <c> | <c> |
+| giaddr | ciaddr | bcast | | | | |
+| (relay) | (cur ip) | bit | | verbs | message types | to |
+| is | is | is | | | | |
+|---------+----------+-------+---+------------+---------------------+-----------------------------|
+| nonzero | * | * | | sends | (all) | 'giaddr' BOOTP relay agent |
+| | | | | | | |
+|---------+----------+-------+---+------------+---------------------+-----------------------------|
+| zero | nonzero | * | | unicasts | DHCPOFFER & DHCPACK | 'ciaddr' client |
+| | | | | broadcasts | DHCPNACK | 0xffffffff |
+|---------+----------+-------+---+------------+---------------------+-----------------------------|
+| zero | zero | 1 | | broadcasts | DHCPOFFER & DHCPACK | 0xffffffff |
+| | | | | broadcasts | DHCPNACK | 0xffffffff |
+|---------+----------+-------+---+------------+---------------------+-----------------------------|
+| zero | zero | 0 | | unicasts | DHCPOFFER & DHCPACK | client hw addr and 'yiaddr' |
+| | | | | broadcasts | DHCPNACK | 0xffffffff |
+
+In all cases, when 'giaddr' is zero, the server broadcasts any DHCPNAK
+messages to 0xffffffff.
diff --git a/libdhcp/include/libdhcp/client.h b/libdhcp/include/libdhcp/client.h
index 6037a3b..b0eaa52 100644
--- a/libdhcp/include/libdhcp/client.h
+++ b/libdhcp/include/libdhcp/client.h
@@ -68,91 +68,8 @@
#define _LIBDHCP_DHCP_H_
#include <libhw/generic/net.h>
-#include "hw/w5500.h"
-enum dhcp_event {
- DHCP_ASSIGN,
- DHCP_UPDATE,
- DHCP_CONFLICT,
-};
-
-typedef void (*dhcp_callback_t)(enum dhcp_event, struct dhcp_lease);
-
-/* Retry to processing DHCP */
-#define MAX_DHCP_RETRY 2 ///< Maximum retry count
-#define DHCP_WAIT_TIME 10 ///< Wait Time 10s
-
-/*
- * @brief return value of @ref DHCP_run()
- */
-enum {
- DHCP_RET_FAILED = 0, // Processing Fail
- DHCP_RET_RUNNING, // Processing DHCP protocol
- DHCP_RET_IP_ASSIGN, // First Occupy IP from DHPC server (if cbfunc == null, act as default default_ip_assign)
- DHCP_RET_IP_CHANGED, // Change IP address by new ip from DHCP (if cbfunc == null, act as default default_ip_update)
- DHCP_RET_IP_LEASED, // Stand by
- DHCP_RET_STOPPED // Stop processing DHCP protocol
-};
-
-/*
- * @brief DHCP client initialization (outside of the main loop)
- * @param buf - buffer for processing DHCP message
- */
-void DHCP_init(void *buf);
-
-/*
- * @brief DHCP 1s Tick Timer handler
- * @note SHOULD BE register to your system 1s Tick timer handler
- */
-void DHCP_time_handler(void);
-
-/*
- * @brief DHCP client in the main loop
- * @return The value is as the follow \n
- * @ref DHCP_FAILED \n
- * @ref DHCP_RUNNING \n
- * @ref DHCP_IP_ASSIGN \n
- * @ref DHCP_IP_CHANGED \n
- * @ref DHCP_IP_LEASED \n
- * @ref DHCP_STOPPED \n
- *
- * @note This function is always called by you main task.
- */
-uint8_t DHCP_run(struct w5500 *chip, uint8_t socknum, dhcp_callback_t cb);
-//uint8_t DHCP_run(implements_net_packet_conn *sock, dhcp_callback_t cb);
-
-/*
- * @brief Stop DHCP processing
- * @note If you want to restart. call DHCP_init() and DHCP_run()
- */
-void DHCP_stop(implements_net_packet_conn *sock);
-
-void xhandle(uint8_t opt_typ, uint8_t opt_len, uint8_t *opt_dat) {
- switch (opt_typ) {
- case DHCP_OPT_SUBNET_MASK:
- if (opt_len != 4)
- return;
- global_lease.subnet_mask.octets[0] = opt_dat[0];
- global_lease.subnet_mask.octets[1] = opt_dat[1];
- global_lease.subnet_mask.octets[2] = opt_dat[2];
- global_lease.subnet_mask.octets[3] = opt_dat[3];
- case DHCP_OPT_ROUTER :
- if (opt_len < 4)
- return;
- global_lease.gateway.octets[0] = opt_dat[0];
- global_lease.gateway.octets[1] = opt_dat[1];
- global_lease.gateway.octets[2] = opt_dat[2];
- global_lease.gateway.octets[3] = opt_dat[3];
- break;
- case DHCP_OPT_DOMAIN_SERVER :
- if (opt_len < 4)
- return;
- global_lease.dns.octets[0] = opt_dat[0];
- global_lease.dns.octets[1] = opt_dat[1];
- global_lease.dns.octets[2] = opt_dat[2];
- global_lease.dns.octets[3] = opt_dat[3];
- break;
- }
-}
+__attribute__((noreturn)) void dhcp_client_main(implements_net_iface *iface,
+ char *self_hostname);
#endif /* _LIBDHCP_DHCP_H_ */