diff options
-rw-r--r-- | cmd/sbc_harness/CMakeLists.txt | 2 | ||||
-rw-r--r-- | cmd/sbc_harness/main.c | 13 | ||||
-rw-r--r-- | libdhcp/dhcp_client.c | 1147 | ||||
-rw-r--r-- | libdhcp/dhcp_common.h | 134 | ||||
-rw-r--r-- | libdhcp/f.md | 27 | ||||
-rw-r--r-- | libdhcp/include/libdhcp/client.h | 87 |
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_ */ |