diff options
Diffstat (limited to 'libdhcp')
-rw-r--r-- | libdhcp/CMakeLists.txt | 2 | ||||
-rw-r--r-- | libdhcp/dhcp.c | 710 | ||||
-rw-r--r-- | libdhcp/dhcp_client.c | 913 | ||||
-rw-r--r-- | libdhcp/dhcp_common.h | 305 | ||||
-rw-r--r-- | libdhcp/include/libdhcp/client.h (renamed from libdhcp/include/libdhcp/dhcp.h) | 95 | ||||
-rw-r--r-- | libdhcp/notes.md | 32 |
6 files changed, 1257 insertions, 800 deletions
diff --git a/libdhcp/CMakeLists.txt b/libdhcp/CMakeLists.txt index a14fd50..2ded1f4 100644 --- a/libdhcp/CMakeLists.txt +++ b/libdhcp/CMakeLists.txt @@ -6,7 +6,7 @@ add_library(libdhcp INTERFACE) target_include_directories(libdhcp SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) target_sources(libdhcp INTERFACE - dhcp.c + dhcp_client.c ) target_link_libraries(libdhcp INTERFACE libmisc diff --git a/libdhcp/dhcp.c b/libdhcp/dhcp.c deleted file mode 100644 index 9786018..0000000 --- a/libdhcp/dhcp.c +++ /dev/null @@ -1,710 +0,0 @@ -/* libdhcp/dhcp.c - A DHCP client - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - * - * ----------------------------------------------------------------------------- - * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Internet/DHCP/dhcp.c - * - * Copyright (c) 2013, WIZnet Co., LTD. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the <ORGANIZATION> nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - * - * SPDX-License-Identifier: BSD-3-Clause - * - * ----------------------------------------------------------------------------- - * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/license.txt - * - * Copyright (c) 2014 WIZnet Co.,Ltd. - * Copyright (c) WIZnet ioLibrary Project. - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - * SPDX-License-Identifier: MIT - */ - -#include <string.h> /* for strlen(), memcpy(), memset() */ - -#include <libmisc/endian.h> -#include <libmisc/vcall.h> -#include <libdhcp/dhcp.h> - -/* Config *********************************************************************/ - -#include "config.h" - -#ifndef CONFIG_DHCP_DEBUG - #define CONFIG_DHCP_DEBUG 1 -#endif -#ifndef CONFIG_DHCP_HOPS - #define CONFIG_DHCP_HOPS 0 -#endif -#ifndef CONFIG_DHCP_SECS - #define CONFIG_DHCP_SECS 0 -#endif -#ifndef CONFIG_DHCP_OPT_SIZE - #define CONFIG_DHCP_OPT_SIZE 312 -#endif - -/* RFC 2131 definitions *******************************************************/ - -/** https://datatracker.ietf.org/doc/html/rfc2131#page-9 */ -struct dhcp_msg { - uint8_t op; /* DHCP_OP_{x} */ - uint8_t htype; /* DHCP_HTYPE_{x} */ - uint8_t hlen; /* length of ->chaddr (`sizeof(struct net_eth_addr)`) */ - uint8_t hops; - - uint32be_t xid; /* transaction ID */ - - uint16be_t secs; - uint16be_t flags; /* DHCP_FLAG_{x} */ - - 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) */ - 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) */ - - 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) - -/** https://datatracker.ietf.org/doc/html/rfc2131#page-10 */ -#define DHCP_OP_BOOTREQUEST 1 -#define DHCP_OP_BOOTREPLY 2 - -/** https://datatracker.ietf.org/doc/html/rfc2131#page-11 */ -#define DHCP_FLAG_BROADCAST 0x8000 - -/** 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 - -/* IANA assignments ***********************************************************/ - -/** - * Port Numbers - * https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml - */ -#define DHCP_PORT_SERVER ((uint16_t)67) -#define DHCP_PORT_CLIENT ((uint16_t)68) - -/** - * Hardware Types - * https://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml#arp-parameters-2 - */ -#define DHCP_HTYPE_ETHERNET ((uint8_t) 1) - -/** - * DHCP Options - * https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#options - */ -#define DHCP_OPT_PAD ((uint8_t) 0) /* RFC2132: length: 0; meaning: None */ -#define DHCP_OPT_SUBNET_MASK ((uint8_t) 1) /* RFC2132: length: 4; meaning: Subnet Mask Value */ -#define DHCP_OPT_TIME_OFFSET ((uint8_t) 2) /* RFC2132: length: 4; meaning: Time Offset in Seconds from UTC (note: deprecated by 100 and 101 */ -#define DHCP_OPT_ROUTER ((uint8_t) 3) /* RFC2132: length: N; meaning: N/4 Router addresses */ -#define DHCP_OPT_TIME_SERVER ((uint8_t) 4) /* RFC2132: length: N; meaning: N/4 Timeserver addresses */ -#define DHCP_OPT_NAME_SERVER ((uint8_t) 5) /* RFC2132: length: N; meaning: N/4 IEN-116 Server addresses */ -#define DHCP_OPT_DOMAIN_SERVER ((uint8_t) 6) /* RFC2132: length: N; meaning: N/4 DNS Server addresses */ -#define DHCP_OPT_LOG_SERVER ((uint8_t) 7) /* RFC2132: length: N; meaning: N/4 Logging Server addresses */ -#define DHCP_OPT_QUOTES_SERVER ((uint8_t) 8) /* RFC2132: length: N; meaning: N/4 Quotes Server addresses */ -#define DHCP_OPT_LPR_SERVER ((uint8_t) 9) /* RFC2132: length: N; meaning: N/4 Printer Server addresses */ -#define DHCP_OPT_IMPRESS_SERVER ((uint8_t) 10) /* RFC2132: length: N; meaning: N/4 Impress Server addresses */ -#define DHCP_OPT_RLP_SERVER ((uint8_t) 11) /* RFC2132: length: N; meaning: N/4 RLP Server addresses */ -#define DHCP_OPT_HOSTNAME ((uint8_t) 12) /* RFC2132: length: N; meaning: Hostname string */ -#define DHCP_OPT_BOOT_FILE_SIZE ((uint8_t) 13) /* RFC2132: length: 2; meaning: Size of boot file in 512 byte chunks */ -#define DHCP_OPT_MERIT_DUMP_FILE ((uint8_t) 14) /* RFC2132: length: N; meaning: Client to dump and name the file to dump it to */ -#define DHCP_OPT_DOMAIN_NAME ((uint8_t) 15) /* RFC2132: length: N; meaning: The DNS domain name of the client */ -#define DHCP_OPT_SWAP_SERVER ((uint8_t) 16) /* RFC2132: length: N; meaning: Swap Server address */ -#define DHCP_OPT_ROOT_PATH ((uint8_t) 17) /* RFC2132: length: N; meaning: Path name for root disk */ -#define DHCP_OPT_EXTENSION_FILE ((uint8_t) 18) /* RFC2132: length: N; meaning: Path name for more BOOTP info */ -#define DHCP_OPT_FORWARD_ONOFF ((uint8_t) 19) /* RFC2132: length: 1; meaning: Enable/Disable IP Forwarding */ -#define DHCP_OPT_SRCRTE_ONOFF ((uint8_t) 20) /* RFC2132: length: 1; meaning: Enable/Disable Source Routing */ -#define DHCP_OPT_POLICY_FILTER ((uint8_t) 21) /* RFC2132: length: N; meaning: Routing Policy Filters */ -#define DHCP_OPT_MAX_DG_ASSEMBLY ((uint8_t) 22) /* RFC2132: length: 2; meaning: Max Datagram Reassembly Size */ -#define DHCP_OPT_DEFAULT_IP_TTL ((uint8_t) 23) /* RFC2132: length: 1; meaning: Default IP Time to Live */ -#define DHCP_OPT_MTU_TIMEOUT ((uint8_t) 24) /* RFC2132: length: 4; meaning: Path MTU Aging Timeout */ -#define DHCP_OPT_MTU_PLATEAU ((uint8_t) 25) /* RFC2132: length: N; meaning: Path MTU Plateau Table */ -#define DHCP_OPT_MTU_INTERFACE ((uint8_t) 26) /* RFC2132: length: 2; meaning: Interface MTU Size */ -#define DHCP_OPT_MTU_SUBNET ((uint8_t) 27) /* RFC2132: length: 1; meaning: All Subnets are Local */ -#define DHCP_OPT_BROADCAST_ADDRESS ((uint8_t) 28) /* RFC2132: length: 4; meaning: Broadcast Address */ -#define DHCP_OPT_MASK_DISCOVERY ((uint8_t) 29) /* RFC2132: length: 1; meaning: Perform Mask Discovery */ -#define DHCP_OPT_MASK_SUPPLIER ((uint8_t) 30) /* RFC2132: length: 1; meaning: Provide Mask to Others */ -#define DHCP_OPT_ROUTER_DISCOVERY ((uint8_t) 31) /* RFC2132: length: 1; meaning: Perform Router Discovery */ -#define DHCP_OPT_ROUTER_REQUEST ((uint8_t) 32) /* RFC2132: length: 4; meaning: Router Solicitation Address */ -#define DHCP_OPT_STATIC_ROUTE ((uint8_t) 33) /* RFC2132: length: N; meaning: Static Routing Table */ -#define DHCP_OPT_TRAILERS ((uint8_t) 34) /* RFC2132: length: 1; meaning: Trailer Encapsulation */ -#define DHCP_OPT_ARP_TIMEOUT ((uint8_t) 35) /* RFC2132: length: 4; meaning: ARP Cache Timeout */ -#define DHCP_OPT_ETHERNET ((uint8_t) 36) /* RFC2132: length: 1; meaning: Ethernet Encapsulation */ -#define DHCP_OPT_DEFAULT_TCP_TTL ((uint8_t) 37) /* RFC2132: length: 1; meaning: Default TCP Time to Live */ -#define DHCP_OPT_KEEPALIVE_TIME ((uint8_t) 38) /* RFC2132: length: 4; meaning: TCP Keepalive Interval */ -#define DHCP_OPT_KEEPALIVE_DATA ((uint8_t) 39) /* RFC2132: length: 1; meaning: TCP Keepalive Garbage */ -#define DHCP_OPT_NIS_DOMAIN ((uint8_t) 40) /* RFC2132: length: N; meaning: NIS Domain Name */ -#define DHCP_OPT_NIS_SERVERS ((uint8_t) 41) /* RFC2132: length: N; meaning: NIS Server Addresses */ -#define DHCP_OPT_NTP_SERVERS ((uint8_t) 42) /* RFC2132: length: N; meaning: NTP Server Addresses */ -#define DHCP_OPT_VENDOR_SPECIFIC ((uint8_t) 43) /* RFC2132: length: N; meaning: Vendor Specific Information */ -#define DHCP_OPT_NETBIOS_NAME_SRV ((uint8_t) 44) /* RFC2132: length: N; meaning: NETBIOS Name Servers */ -#define DHCP_OPT_NETBIOS_DIST_SRV ((uint8_t) 45) /* RFC2132: length: N; meaning: NETBIOS Datagram Distribution */ -#define DHCP_OPT_NETBIOS_NODE_TYPE ((uint8_t) 46) /* RFC2132: length: 1; meaning: NETBIOS Node Type */ -#define DHCP_OPT_NETBIOS_SCOPE ((uint8_t) 47) /* RFC2132: length: N; meaning: NETBIOS Scope */ -#define DHCP_OPT_X_WINDOW_FONT ((uint8_t) 48) /* RFC2132: length: N; meaning: X Window Font Server */ -#define DHCP_OPT_X_WINDOW_MANAGER ((uint8_t) 49) /* RFC2132: length: N; meaning: X Window Display Manager */ -#define DHCP_OPT_ADDRESS_REQUEST ((uint8_t) 50) /* RFC2132: length: 4; meaning: Requested IP Address */ -#define DHCP_OPT_ADDRESS_TIME ((uint8_t) 51) /* RFC2132: length: 4; meaning: IP Address Lease Time */ -#define DHCP_OPT_OVERLOAD ((uint8_t) 52) /* RFC2132: length: 1; meaning: Overload "sname" or "file */ -#define DHCP_OPT_DHCP_MSG_TYPE ((uint8_t) 53) /* RFC2132: length: 1; meaning: DHCP Message Type */ -#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_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 */ - -/** - * 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 */ - -/* Implementation *************************************************************/ - -#if CONFIG_DHCP_DEBUG - #include <stdio.h> - #define debugf(fmt, ...) printf(fmt "\n" __VA_OPT__(,) __VA_ARGS__) -#else - #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); - 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); - - switch (dhcp_state) { - case STATE_DHCP_LEASED: - case STATE_DHCP_REREQUEST: - msg->ciaddr = global_lease.addr; - ip = DHCP_SIP; - 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); - } - - /* 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); - - /* End. */ - pDHCPMSG->options[k++] = DHCP_OPT_END; - - 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); -} - -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; - } - } - - 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++]; - switch (opt_typ) { - case DHCP_OPT_END: - opt_len = k_max - k; - break; - case DHCP_OPT_PAD: - opt_len = 0; - break; - default: - opt_len = pDHCPMSG->options[k++]; - xhandle(opt_typ, opt_len, &pDHCPMSG->options[k]); - } - switch (opt_typ) { - case DHCP_OPT_DHCP_MSG_TYPE: - if (opt_len != 1) - continue; - msg_type = pDHCPMSG->options[k]; - 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 - 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; - break; - } - } - return msg_type; -} - -static void dhcp_reset_timeout(void) { - dhcp_tick_1s = 0; - dhcp_tick_next = DHCP_WAIT_TIME; - dhcp_retry_count = 0; -} - -static uint8_t dhcp_check_timeout(implements_net_packet_conn *sock) { - uint8_t ret = DHCP_RET_RUNNING; - - if (dhcp_retry_count < MAX_DHCP_RETRY) { - if (dhcp_tick_next < dhcp_tick_1s) { - - switch ( dhcp_state ) { - case STATE_DHCP_DISCOVER : - /*debugf("<<timeout>> state : STATE_DHCP_DISCOVER");*/ - dhcp_send_DISCOVER(sock); - break; - - case STATE_DHCP_REQUEST : - /*debugf("<<timeout>> state : STATE_DHCP_REQUEST");*/ - dhcp_send_REQUEST(sock); - break; - - case STATE_DHCP_REREQUEST : - /*debugf("<<timeout>> state : STATE_DHCP_REREQUEST");*/ - dhcp_send_REQUEST(sock); - break; - - default : - break; - } - - dhcp_tick_1s = 0; - dhcp_tick_next = dhcp_tick_1s + DHCP_WAIT_TIME; - dhcp_retry_count++; - } - } else { /* timeout occurred */ - - switch(dhcp_state) { - case STATE_DHCP_DISCOVER: - dhcp_state = STATE_DHCP_INIT; - ret = DHCP_RET_FAILED; - break; - case STATE_DHCP_REQUEST: - case STATE_DHCP_REREQUEST: - dhcp_send_DISCOVER(sock); - dhcp_state = STATE_DHCP_DISCOVER; - break; - default : - 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; - } - - /* 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; - - xid++; - - dhcp_send_REQUEST(sock); - - 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; - } - - return ret; -} diff --git a/libdhcp/dhcp_client.c b/libdhcp/dhcp_client.c new file mode 100644 index 0000000..c5da59b --- /dev/null +++ b/libdhcp/dhcp_client.c @@ -0,0 +1,913 @@ +/* libdhcp/dhcp_client.c - A DHCP client + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * ----------------------------------------------------------------------------- + * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Internet/DHCP/dhcp.c + * + * Copyright (c) 2013, WIZnet Co., LTD. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the <ORGANIZATION> nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * ----------------------------------------------------------------------------- + * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/license.txt + * + * Copyright (c) 2014 WIZnet Co.,Ltd. + * Copyright (c) WIZnet ioLibrary Project. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * SPDX-License-Identifier: MIT + */ + +/* Implemented: + * - DHCPv4: https://datatracker.ietf.org/doc/html/rfc2131 + * - DHCPv4 long options: https://datatracker.ietf.org/doc/html/rfc3396 + * - DHCPv4 client identifiers in replies: https://datatracker.ietf.org/doc/html/rfc6842 + * + * TODO: + * - AutoIPv4: https://datatracker.ietf.org/doc/html/rfc3927 + * - mDNS responder: https://datatracker.ietf.org/doc/html/rfc6762 + * + * Not implemented: + * - DHCPv4 node-specific client identifiers: https://datatracker.ietf.org/doc/html/rfc4361 + * - DNAv4: https://datatracker.ietf.org/doc/html/rfc4436 + * - LLMNR responder: https://datatracker.ietf.org/doc/html/rfc4795 + * + * Not implemented, because the W5500 doesn't support IPv6: + * - SLAAC: https://datatracker.ietf.org/doc/html/rfc2462 + * - DNAv6: https://datatracker.ietf.org/doc/html/rfc6059 + * - DHCPv6: https://datatracker.ietf.org/doc/html/rfc8415 + */ + +#include <string.h> /* for strlen(), memcpy(), memset() */ + +#include <libmisc/rand.h> +#include <libmisc/vcall.h> +#include <libhw/generic/alarmclock.h> + +#define LOG_NAME DHCP +#include <libmisc/log.h> + +#include <libdhcp/client.h> + +#include "dhcp_common.h" + +/* Config *********************************************************************/ + +#include "config.h" + +#ifndef CONFIG_DHCP_DEBUG + #error config.h must define CONFIG_DHCP_DEBUG +#endif +#ifndef CONFIG_DHCP_SELECTING_NS + #error config.h must define CONFIG_DHCP_SELECTING_NS +#endif +#ifndef CONFIG_DHCP_CAN_RECV_UNICAST_IP_WITHOUT_IP + #error config.h must define CONFIG_DHCP_CAN_RECV_UNICAST_IP_WITHOUT_IP +#endif + +/* Implementation *************************************************************/ + +enum requirement { + MUST, + MUST_NOT, + SHOULD, + SHOULD_NOT, + MAY, + + _SHOULD_NOT_HAPPEN, +}; + +struct dhcp_client { + /* Static. */ + implements_net_iface *iface; + implements_net_packet_conn *sock; + struct net_eth_addr self_eth_addr; + char *self_hostname; + size_t self_id_len; + void *self_id_dat; + + /* Mutable. */ + enum { + STATE_INIT, + STATE_SELECTING, + STATE_REQUESTING, + STATE_BOUND, + STATE_RENEWING, + STATE_REBINDING, + + STATE_INIT_REBOOT, + STATE_REBOOTING, + } state; + uint8_t last_sent_msgtyp; + uint16_t last_discover_secs; + /* 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; + +}; + +/** + * For convenience in switch blocks, a list of the states that, when + * msgtyp==DHCP_MSGTYP_REQUEST, dhcp_client_send() has assert()ed the state is + * not. + */ +#define IMPOSSIBLE_REQUEST_STATES \ + STATE_INIT: \ + case STATE_REQUESTING: \ + case STATE_REBINDING: \ + case STATE_REBOOTING + +static inline enum requirement dhcp_table5(typeof((struct dhcp_client){}.state) state, + uint8_t msgtyp, + uint8_t opt) { + /* Encode "Table 5: Fields and options used by DHCP clients" + * from + * https://datatracker.ietf.org/doc/html/rfc2131#page-38. */ +#define INC_ADDR ({ \ + enum requirement req; \ + switch (state) { \ + case STATE_SELECTING: case STATE_INIT_REBOOT: req = MUST; break; \ + case STATE_BOUND: case STATE_RENEWING: req = MUST_NOT; break; \ + case IMPOSSIBLE_REQUEST_STATES: default: req = _SHOULD_NOT_HAPPEN; \ + } \ + req; \ + }) +#define INC_SERVER ({ \ + enum requirement req; \ + switch (state) { \ + case STATE_SELECTING: req = MUST; break; \ + case STATE_INIT_REBOOT: case STATE_BOUND: case STATE_RENEWING: req = MUST_NOT; break; \ + case IMPOSSIBLE_REQUEST_STATES: default: req = _SHOULD_NOT_HAPPEN; \ + } \ + req; \ + }) + struct { enum requirement cols[5]; } row; +#define ROW(...) row = (typeof(row)){{ __VA_ARGS__ }} + switch (opt) { + /* Option DISCOVER INFORM REQUEST DECLINE RELEASE */ + /* ------ ---------- ---------- ---------- -------- -------- */ + case DHCP_OPT_ADDRESS_REQUEST: ROW(MAY, MUST_NOT, INC_ADDR, MUST, MUST_NOT); break; + case DHCP_OPT_ADDRESS_TIME: ROW(MAY, MUST_NOT, MAY, MUST_NOT, MUST_NOT); break; + case DHCP_OPT_OVERLOAD: ROW(MAY, MAY, MAY, MAY, MAY ); break; + case DHCP_OPT_DHCP_MSG_TYPE: ROW(MUST, MUST, MUST, MUST, MUST ); break; + case DHCP_OPT_CLIENT_ID: ROW(MAY, MAY, MAY, MAY, MAY ); break; + case DHCP_OPT_CLASS_ID: ROW(MAY, MAY, MAY, MUST_NOT, MUST_NOT); break; + case DHCP_OPT_DHCP_SERVER_ID: ROW(MUST_NOT, MUST_NOT, INC_SERVER, MUST, MUST ); break; + case DHCP_OPT_PARAMETER_LIST: ROW(MAY, MAY, MAY, MUST_NOT, MUST_NOT); break; + case DHCP_OPT_DHCP_MAX_MSG_SIZE: ROW(MAY, MAY, MAY, MUST_NOT, MUST_NOT); break; + case DHCP_OPT_DHCP_MESSAGE: ROW(SHOULD_NOT, SHOULD_NOT, SHOULD_NOT, SHOULD, SHOULD ); break; + default: ROW(MAY, MAY, MAY, MUST_NOT, MUST_NOT); + } +#undef ROW +#undef INC_SERVER +#undef INC_ADDR + switch (msgtyp) { + case DHCP_MSGTYP_DISCOVER: return row.cols[0]; + case DHCP_MSGTYP_INFORM: return row.cols[1]; + case DHCP_MSGTYP_REQUEST: return row.cols[2]; + case DHCP_MSGTYP_DECLINE: return row.cols[3]; + case DHCP_MSGTYP_RELEASE: return row.cols[4]; + default: return _SHOULD_NOT_HAPPEN; + } +} + +/** + * @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 client->last_sent_msgtyp + * @return whether there was an error + */ +static bool dhcp_client_send(struct dhcp_client *client, uint8_t msgtyp, const char *errstr, struct dhcp_msg *scratch_msg) { + /**********************************************************************\ + * 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 */ + + /**********************************************************************\ + * Setup * + \**********************************************************************/ + + bool server_broadcasts, client_broadcasts; + + 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"); + } + + /**********************************************************************\ + * Build the message * + \**********************************************************************/ + + *scratch_msg = (struct dhcp_msg){0}; + size_t optlen = 0; + + /* Base structure. + * https://datatracker.ietf.org/doc/html/rfc2131#page-37 */ + scratch_msg->op = DHCP_OP_BOOTREQUEST; + scratch_msg->htype = DHCP_HTYPE_ETHERNET; + scratch_msg->hlen = sizeof(client->self_eth_addr); + scratch_msg->hops = 0; /* relays increment this when they forward it along */ + scratch_msg->xid = uint32be_marshal(client->xid); + scratch_msg->secs = uint16be_marshal( ({ + uint16_t secs; + switch (msgtyp) { + case DHCP_MSGTYP_DISCOVER: case DHCP_MSGTYP_INFORM: + case DHCP_MSGTYP_REQUEST: + secs = (VCALL(bootclock, get_time_ns) - client->time_ns_init)/NS_PER_S; + if (!secs) + /* systemd's sd-dhcp-client.c asserts that some + * servers are broken and require .secs to be + * non-zero, even though RFC 2131 explicitly + * says that zero is valid. */ + secs = 1; + if (msgtyp == DHCP_MSGTYP_REQUEST && + client->state == STATE_SELECTING) + /* "The DHCPREQUEST message MUST use the same + * value in the DHCP message header's 'secs' + * field ... as the original DHCPDISCOVER + * message" -- RFC 2131 + */ + secs = client->last_discover_secs; + if (msgtyp == DHCP_MSGTYP_DISCOVER) + /* Record the value to make the above + * possible. */ + client->last_discover_secs = secs; + break; + case DHCP_MSGTYP_DECLINE: case DHCP_MSGTYP_RELEASE: + secs = 0; + break; + default: + assert_notreached("invalid message type for client to send"); + } + secs; + }) ); + switch (msgtyp) { + case DHCP_MSGTYP_DISCOVER: case DHCP_MSGTYP_INFORM: case DHCP_MSGTYP_REQUEST: + scratch_msg->flags = uint16be_marshal(server_broadcasts ? DHCP_FLAG_BROADCAST : 0); + break; + case DHCP_MSGTYP_DECLINE: case DHCP_MSGTYP_RELEASE: + scratch_msg->flags = uint16be_marshal(0); + break; + } + switch (msgtyp) { + case DHCP_MSGTYP_DISCOVER: scratch_msg->ciaddr = net_ip4_addr_zero; break; + case DHCP_MSGTYP_INFORM: scratch_msg->ciaddr = client->lease_client_addr; break; + case DHCP_MSGTYP_REQUEST: switch (client->state) { + case STATE_SELECTING: case STATE_INIT_REBOOT: + scratch_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. */ + scratch_msg->ciaddr = client->lease_client_addr; break; + case IMPOSSIBLE_REQUEST_STATES: + assert_notreached("invalid client state for sending DHCPREQUEST"); + } break; + case DHCP_MSGTYP_DECLINE: scratch_msg->ciaddr = net_ip4_addr_zero; break; + case DHCP_MSGTYP_RELEASE: scratch_msg->ciaddr = client->lease_client_addr; break; + } + scratch_msg->yiaddr = net_ip4_addr_zero; /* only set by servers */ + scratch_msg->siaddr = net_ip4_addr_zero; /* only set by servers */ + scratch_msg->giaddr = net_ip4_addr_zero; /* only set by relays */ + memcpy(scratch_msg->chaddr, client->self_eth_addr.octets, sizeof(client->self_eth_addr)); + /* scratch_msg->sname = "options, if indicated in 'sname/file' option'; otherwise unused"; */ + /* scratch_msg->file = "options, if indicated in 'sname/file' option'; otherwise unused"; */ + + /* Magic cookie. + * https://datatracker.ietf.org/doc/html/rfc2131#section-4.1 */ + scratch_msg->options[optlen++] = dhcp_magic_cookie[0]; + scratch_msg->options[optlen++] = dhcp_magic_cookie[1]; + scratch_msg->options[optlen++] = dhcp_magic_cookie[2]; + scratch_msg->options[optlen++] = dhcp_magic_cookie[3]; + + /* Options. */ + for (uint8_t opt = 1; opt < 255; opt++) { + enum requirement req = dhcp_table5(client->state, msgtyp, opt); + switch (req) { + case MUST_NOT: + /* Do nothing. */ + break; + case MUST: + case SHOULD: + case SHOULD_NOT: + case MAY: + struct { size_t len; const void *ptr; } val; + uint8_t _val_prl[] = { + DHCP_OPT_SUBNET_MASK, + DHCP_OPT_ROUTER, + DHCP_OPT_RENEWAL_TIME, + DHCP_OPT_REBINDING_TIME, + }; + uint16be_t _val16 ; +#define V_RAW(len, ptr) val = (typeof(val)){ len, ptr } +#define V_OBJ(x) V_RAW(sizeof(x), &x) +#define V_STR(x) V_RAW(x ? strlen(x) : 0, x) +#define V_NONE() V_RAW(0, NULL) + switch (opt) { + /* For clarity, list all options mentioned in "Table 5: + * Fields and options used by DHCP clients" from + * https://datatracker.ietf.org/doc/html/rfc2131#page-38, + * even if we don't have a value for them. */ + case DHCP_OPT_ADDRESS_REQUEST: V_OBJ(client->lease_client_addr); break; + case DHCP_OPT_ADDRESS_TIME: V_NONE(); break; + case DHCP_OPT_OVERLOAD: V_NONE(); break; + case DHCP_OPT_DHCP_MSG_TYPE: V_OBJ(msgtyp); break; + case DHCP_OPT_CLIENT_ID: V_RAW(client->self_id_len, client->self_id_dat); break; + case DHCP_OPT_CLASS_ID: V_NONE(); break; + case DHCP_OPT_DHCP_SERVER_ID: V_OBJ(client->lease_server_id); break; + case DHCP_OPT_PARAMETER_LIST: V_OBJ(_val_prl); break; + case DHCP_OPT_DHCP_MAX_MSG_SIZE: + if (CONFIG_DHCP_OPT_SIZE <= DHCP_MSG_MIN_MAX_OPT_SIZE) + V_NONE(); + else { + _val16 = uint16be_marshal(20 /* IP header */ + + 8 /* UDP header */ + + sizeof(*scratch_msg)); + V_OBJ(_val16); + } + break; + case DHCP_OPT_DHCP_MESSAGE: V_STR(errstr); break; + /* "Site-specific" and "All others". */ + case DHCP_OPT_HOSTNAME: V_STR(client->self_hostname); break; + default: V_NONE(); + }; +#undef V_NONE +#undef V_STR +#undef V_OBJ +#undef V_RAW + if (req == MUST) + assert(val.len); + if (val.len) { + assert(val.len <= UINT16_MAX); + for (size_t done = 0; done < val.len;) { + uint8_t len = val.len - done > UINT8_MAX + ? UINT8_MAX + : val.len - done; + scratch_msg->options[optlen++] = opt; + scratch_msg->options[optlen++] = len; + memcpy(&scratch_msg->options[optlen], &((char*)val.ptr)[done], len); + optlen += len; + done += len; + } + } + break; + case _SHOULD_NOT_HAPPEN: + assert_notreached("bad table"); + } + } + scratch_msg->options[optlen++] = DHCP_OPT_END; + assert(optlen <= CONFIG_DHCP_OPT_SIZE); + + /**********************************************************************\ + * Send * + \**********************************************************************/ + ssize_t r = VCALL(client->sock, sendto, scratch_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; + } + client->last_sent_msgtyp = msgtyp; + return false; +} + +struct dhcp_recv_msg { + struct dhcp_msg raw; + struct { + uint16_t off; + uint16_t len; + } options[0xff]; + uint8_t option_dat[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; +} + +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: + return; + case DHCP_OPT_PAD: + opt_len = 0; + break; + default: + opt_len = dat[pos++]; + memcpy(&ret->option_dat[ret->options[opt_typ].off+ret->options[opt_typ].len], &dat[pos], opt_len); + ret->options[opt_typ].len += opt_len; + } + } +} + +static inline enum requirement dhcp_table3(uint8_t req_msgtyp, uint8_t resp_msgtyp, uint8_t resp_opt) { + /* Encode "Table 3: Fields and options used by DHCP servers" + * from https://datatracker.ietf.org/doc/html/rfc2131#page-29, + * with modifications from + * https://datatracker.ietf.org/doc/html/rfc6842#page-4. */ + struct { enum requirement cols[3]; } row; +#define ROW(...) row = (typeof(row)){{ __VA_ARGS__ }} + switch (resp_opt) { + /* Option DHCPOFFER DHCPACK DHCPNAK */ + /* -------------------------- -------- ---------- -------- */ + case DHCP_OPT_ADDRESS_REQUEST: ROW(MUST_NOT, MUST_NOT, MUST_NOT); break; + case DHCP_OPT_ADDRESS_TIME: ROW(MUST, req_msgtyp == DHCP_MSGTYP_REQUEST ? MUST : MUST_NOT, + MUST_NOT); break; + case DHCP_OPT_OVERLOAD: ROW(MAY, MAY, MUST_NOT); break; + case DHCP_OPT_DHCP_MSG_TYPE: ROW(MUST, MUST, MUST ); break; + case DHCP_OPT_PARAMETER_LIST: ROW(MUST_NOT, MUST_NOT, MUST_NOT); break; + case DHCP_OPT_DHCP_MESSAGE: ROW(SHOULD, SHOULD, SHOULD ); break; + case DHCP_OPT_CLIENT_ID: ROW(MAY, MAY, MAY ); /* MUST_NOTs changed to MAY, per RFC 6842 */ break; + case DHCP_OPT_CLASS_ID: ROW(MAY, MAY, MAY ); break; + case DHCP_OPT_DHCP_SERVER_ID: ROW(MUST, MUST, MUST ); break; + case DHCP_OPT_DHCP_MAX_MSG_SIZE: ROW(MUST_NOT, MUST_NOT, MUST_NOT); break; + default: ROW(MAY, MAY, MUST_NOT); + } +#undef ROW + switch (resp_msgtyp) { + case DHCP_MSGTYP_OFFER: return row.cols[0]; + case DHCP_MSGTYP_ACK: return row.cols[1]; + case DHCP_MSGTYP_NAK: return row.cols[2]; + default: return _SHOULD_NOT_HAPPEN; + } +} + +/** + * @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].off = 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. */ + if (!ret->options[DHCP_OPT_DHCP_MSG_TYPE].len) + goto ignore; + for (uint8_t opt = 1; opt < 255; opt++) { + enum requirement req = dhcp_table3(client->last_sent_msgtyp, + ret->option_dat[ret->options[DHCP_OPT_DHCP_MSG_TYPE].off], + opt); + switch (req) { + case MUST: + if (!ret->options[opt].len) + goto ignore; + break; + case MUST_NOT: + if (ret->options[opt].len) + goto ignore; + break; + case SHOULD: + case SHOULD_NOT: + case MAY: + /* Do nothing. */ + break; + case _SHOULD_NOT_HAPPEN: + goto ignore; + } + } + /* Validate values of options. */ + if (ret->options[DHCP_OPT_CLASS_ID].len) { + /* https://datatracker.ietf.org/doc/html/rfc6842#page-4 */ + if (ret->options[DHCP_OPT_CLASS_ID].len != client->self_id_len || + memcmp(&ret->option_dat[ret->options[DHCP_OPT_CLASS_ID].off], client->self_id_dat, client->self_id_len)) + /* ignore messages that aren't to us */ + goto ignore; + } + + return 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); + ssize_t v = VCALL(sock, sendto, "CHECK_IP_CONFLICT", 17, addr, 5000); + return v != NET_EARP_TIMEOUT; +} + +static void dhcp_client_take_lease(struct dhcp_client *client, struct dhcp_recv_msg *msg, bool ifup) { + assert(client); + assert(msg); + + client->lease_client_addr = msg->raw.yiaddr; + client->lease_auxdata.subnet_mask = msg->options[DHCP_OPT_SUBNET_MASK].len + ? *((struct net_ip4_addr *)&msg->option_dat[msg->options[DHCP_OPT_SUBNET_MASK].off]) + : net_ip4_addr_zero; + client->lease_auxdata.gateway_addr = msg->options[DHCP_OPT_ROUTER].len + ? *((struct net_ip4_addr *)&msg->option_dat[msg->options[DHCP_OPT_SUBNET_MASK].off]) + : net_ip4_addr_zero; + client->lease_server_id = + *((struct net_ip4_addr *)&msg->option_dat[msg->options[DHCP_OPT_DHCP_SERVER_ID].off]); + + uint64_t dur_ns_end = + ((uint64_t)uint32be_decode(&msg->option_dat[msg->options[DHCP_OPT_ADDRESS_TIME].off]))*NS_PER_S; + uint64_t dur_ns_t1 = msg->options[DHCP_OPT_RENEWAL_TIME].len + ? ((uint64_t)uint32be_decode(&msg->option_dat[msg->options[DHCP_OPT_RENEWAL_TIME].off]))*NS_PER_S + : (dur_ns_end == DHCP_INFINITY * NS_PER_S) + ? DHCP_INFINITY * NS_PER_S + : dur_ns_end/2; + uint64_t dur_ns_t2 = msg->options[DHCP_OPT_REBINDING_TIME].len + ? ((uint64_t)uint32be_decode(&msg->option_dat[msg->options[DHCP_OPT_RENEWAL_TIME].off]))*NS_PER_S + : (dur_ns_end == DHCP_INFINITY * NS_PER_S) + ? DHCP_INFINITY * NS_PER_S + : (dur_ns_end*7)/8; /* 0.875 = 7/8 */ + + client->lease_time_ns_t1 = (dur_ns_t1 == DHCP_INFINITY * NS_PER_S) ? 0 : client->time_ns_init + dur_ns_t1; + client->lease_time_ns_t2 = (dur_ns_t2 == DHCP_INFINITY * NS_PER_S) ? 0 : client->time_ns_init + dur_ns_t2; + client->lease_time_ns_end = (dur_ns_end == DHCP_INFINITY * NS_PER_S) ? 0 : client->time_ns_init + dur_ns_end; + + if (ifup) + 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, + }); +} + +static void dhcp_client_setstate(struct dhcp_client *client, + typeof((struct dhcp_client){}.state) newstate, + uint8_t send_msgtyp, const char *errstr, struct dhcp_recv_msg *scratch_msg) { + if (send_msgtyp) + (void)dhcp_client_send(client, send_msgtyp, errstr, &scratch_msg->raw); + client->state = newstate; +} + +[[noreturn]] static void dhcp_client_run(struct dhcp_client *client, struct dhcp_recv_msg *scratch_msg) { + assert(client); + + ssize_t r; + + /* State transition diagram: https://datatracker.ietf.org/doc/html/rfc2131#page-35 */ + for (;;) { + switch (client->state) { + case STATE_INIT: + client->xid = rand_uint63n(UINT32_MAX); + client->time_ns_init = VCALL(bootclock, get_time_ns); + dhcp_client_setstate(client, STATE_SELECTING, DHCP_MSGTYP_DISCOVER, NULL, scratch_msg); + break; + case STATE_SELECTING: + VCALL(client->sock, set_read_deadline, client->time_ns_init+CONFIG_DHCP_SELECTING_NS); + switch ((r = dhcp_client_recv(client, scratch_msg))) { + case 0: + switch (scratch_msg->option_dat[scratch_msg->options[DHCP_OPT_DHCP_MSG_TYPE].off]) { + case DHCP_MSGTYP_OFFER: + /* Accept the first offer. */ + dhcp_client_take_lease(client, scratch_msg, false); + dhcp_client_setstate(client, STATE_REQUESTING, DHCP_MSGTYP_REQUEST, NULL, scratch_msg); + break; + default: + /* ignore */ + } + break; + case -NET_ERECV_TIMEOUT: + dhcp_client_setstate(client, STATE_INIT, 0, NULL, scratch_msg); + break; + default: + assert(r < 0); + debugf("error: recvfrom: %d", r); + } + break; + case STATE_REQUESTING: + VCALL(client->sock, set_read_deadline, 0); + switch ((r = dhcp_client_recv(client, scratch_msg))) { + case 0: + switch (scratch_msg->option_dat[scratch_msg->options[DHCP_OPT_DHCP_MSG_TYPE].off]) { + case DHCP_MSGTYP_NAK: + dhcp_client_setstate(client, STATE_INIT, 0, NULL, scratch_msg); + break; + case DHCP_MSGTYP_ACK: + if (dhcp_check_conflict(client->sock, client->lease_client_addr)) { + dhcp_client_setstate(client, STATE_INIT, DHCP_MSGTYP_DECLINE, "IP is already in use", scratch_msg); + } else { + dhcp_client_take_lease(client, scratch_msg, true); + dhcp_client_setstate(client, STATE_BOUND, 0, NULL, scratch_msg); + } + break; + default: + /* ignore */ + } + break; + default: + assert(r < 0); + debugf("error: recvfrom: %d", r); + } + break; + case STATE_BOUND: + VCALL(client->sock, set_read_deadline, client->lease_time_ns_t1); + switch ((r = dhcp_client_recv(client, scratch_msg))) { + case 0: + /* discard */ + break; + case -NET_ERECV_TIMEOUT: + dhcp_client_setstate(client, STATE_RENEWING, DHCP_MSGTYP_REQUEST, NULL, scratch_msg); + break; + default: + assert(r < 0); + debugf("error: recvfrom: %d", r); + } + break; + case STATE_RENEWING: + client->xid = rand_uint63n(UINT32_MAX); + client->time_ns_init = VCALL(bootclock, get_time_ns); + + VCALL(client->sock, set_read_deadline, client->lease_time_ns_t2); + switch ((r = dhcp_client_recv(client, scratch_msg))) { + case 0: + switch (scratch_msg->option_dat[scratch_msg->options[DHCP_OPT_DHCP_MSG_TYPE].off]) { + case DHCP_MSGTYP_NAK: + VCALL(client->iface, ifdown); + dhcp_client_setstate(client, STATE_INIT, 0, NULL, scratch_msg); + break; + case DHCP_MSGTYP_ACK: + dhcp_client_take_lease(client, scratch_msg, true); + dhcp_client_setstate(client, STATE_BOUND, 0, NULL, scratch_msg); + break; + default: + /* ignore */ + } + break; + case -NET_ERECV_TIMEOUT: + client->lease_server_id = net_ip4_addr_zero; + dhcp_client_setstate(client, STATE_REBINDING, DHCP_MSGTYP_REQUEST, NULL, scratch_msg); + break; + default: + assert(r < 0); + debugf("error: recvfrom: %d", r); + } + break; + case STATE_REBINDING: + VCALL(client->sock, set_read_deadline, client->lease_time_ns_end); + switch ((r = dhcp_client_recv(client, scratch_msg))) { + case 0: + switch (scratch_msg->option_dat[scratch_msg->options[DHCP_OPT_DHCP_MSG_TYPE].off]) { + case DHCP_MSGTYP_NAK: + VCALL(client->iface, ifdown); + dhcp_client_setstate(client, STATE_BOUND, 0, NULL, scratch_msg); + break; + case DHCP_MSGTYP_ACK: + dhcp_client_take_lease(client, scratch_msg, true); + dhcp_client_setstate(client, STATE_BOUND, 0, NULL, scratch_msg); + break; + default: + /* ignore */ + } + break; + case -NET_ERECV_TIMEOUT: + VCALL(client->iface, ifdown); + dhcp_client_setstate(client, STATE_BOUND, 0, NULL, scratch_msg); + 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"); + } + } +} + +[[noreturn]] void dhcp_client_main(implements_net_iface *iface, + char *self_hostname) { + assert(iface); + + /* Even though a client ID is optional and not meaningful for + * us (the best we can do is to duplicate .chaddr), systemd's + * sd-dhcp-client.c asserts that some servers are broken and + * require it to be set. */ + struct {uint8_t typ; struct net_eth_addr dat;} client_id = { + DHCP_HTYPE_ETHERNET, + VCALL(iface, hwaddr), + }; + + struct dhcp_client client = { + /* Static. */ + .iface = iface, + .sock = VCALL(iface, udp_conn, DHCP_PORT_CLIENT), + .self_eth_addr = VCALL(iface, hwaddr), + .self_hostname = self_hostname, + .self_id_len = sizeof(client_id), + .self_id_dat = &client_id, + + /* Mutable. */ + .state = STATE_INIT, + }; + assert(client.sock); + + struct dhcp_recv_msg scratch; + + dhcp_client_run(&client, &scratch); +} diff --git a/libdhcp/dhcp_common.h b/libdhcp/dhcp_common.h new file mode 100644 index 0000000..b265df3 --- /dev/null +++ b/libdhcp/dhcp_common.h @@ -0,0 +1,305 @@ +/* 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 + * + * ----------------------------------------------------------------------------- + * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Internet/DHCP/dhcp.c + * + * Copyright (c) 2013, WIZnet Co., LTD. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the <ORGANIZATION> nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * ----------------------------------------------------------------------------- + * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/license.txt + * + * Copyright (c) 2014 WIZnet Co.,Ltd. + * Copyright (c) WIZnet ioLibrary Project. + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * 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 + #error config.h must define CONFIG_DHCP_OPT_SIZE (minimum 312) +#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} */ + uint8_t htype; /* DHCP_HTYPE_{x} */ + uint8_t hlen; /* length of ->chaddr (`sizeof(struct net_eth_addr)`) */ + uint8_t hops; + + uint32be_t xid; /* transaction ID */ + + uint16be_t secs; + uint16be_t flags; /* DHCP_FLAG_{x} */ + + 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 */ + 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) */ + + 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) + +/** https://datatracker.ietf.org/doc/html/rfc2131#page-10 */ +#define DHCP_OP_BOOTREQUEST 1 +#define DHCP_OP_BOOTREPLY 2 + +/** https://datatracker.ietf.org/doc/html/rfc2131#page-11 */ +#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 + +/* IANA assignments ***********************************************************/ + +/** + * Port Numbers + * https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml + */ +#define DHCP_PORT_SERVER ((uint16_t)67) +#define DHCP_PORT_CLIENT ((uint16_t)68) + +/** + * Hardware Types + * https://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml#arp-parameters-2 + */ +#define DHCP_HTYPE_ETHERNET ((uint8_t) 1) + +/** + * DHCP Options + * https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#options + */ +#define DHCP_OPT_PAD ((uint8_t) 0) /* RFC2132: length: 0; meaning: None */ +#define DHCP_OPT_SUBNET_MASK ((uint8_t) 1) /* RFC2132: length: 4; meaning: Subnet Mask Value */ +#define DHCP_OPT_TIME_OFFSET ((uint8_t) 2) /* RFC2132: length: 4; meaning: Time Offset in Seconds from UTC (note: deprecated by 100 and 101 */ +#define DHCP_OPT_ROUTER ((uint8_t) 3) /* RFC2132: length: N; meaning: N/4 Router addresses */ +#define DHCP_OPT_TIME_SERVER ((uint8_t) 4) /* RFC2132: length: N; meaning: N/4 Timeserver addresses */ +#define DHCP_OPT_NAME_SERVER ((uint8_t) 5) /* RFC2132: length: N; meaning: N/4 IEN-116 Server addresses */ +#define DHCP_OPT_DOMAIN_SERVER ((uint8_t) 6) /* RFC2132: length: N; meaning: N/4 DNS Server addresses */ +#define DHCP_OPT_LOG_SERVER ((uint8_t) 7) /* RFC2132: length: N; meaning: N/4 Logging Server addresses */ +#define DHCP_OPT_QUOTES_SERVER ((uint8_t) 8) /* RFC2132: length: N; meaning: N/4 Quotes Server addresses */ +#define DHCP_OPT_LPR_SERVER ((uint8_t) 9) /* RFC2132: length: N; meaning: N/4 Printer Server addresses */ +#define DHCP_OPT_IMPRESS_SERVER ((uint8_t) 10) /* RFC2132: length: N; meaning: N/4 Impress Server addresses */ +#define DHCP_OPT_RLP_SERVER ((uint8_t) 11) /* RFC2132: length: N; meaning: N/4 RLP Server addresses */ +#define DHCP_OPT_HOSTNAME ((uint8_t) 12) /* RFC2132: length: N; meaning: Hostname string */ +#define DHCP_OPT_BOOT_FILE_SIZE ((uint8_t) 13) /* RFC2132: length: 2; meaning: Size of boot file in 512 byte chunks */ +#define DHCP_OPT_MERIT_DUMP_FILE ((uint8_t) 14) /* RFC2132: length: N; meaning: Client to dump and name the file to dump it to */ +#define DHCP_OPT_DOMAIN_NAME ((uint8_t) 15) /* RFC2132: length: N; meaning: The DNS domain name of the client */ +#define DHCP_OPT_SWAP_SERVER ((uint8_t) 16) /* RFC2132: length: N; meaning: Swap Server address */ +#define DHCP_OPT_ROOT_PATH ((uint8_t) 17) /* RFC2132: length: N; meaning: Path name for root disk */ +#define DHCP_OPT_EXTENSION_FILE ((uint8_t) 18) /* RFC2132: length: N; meaning: Path name for more BOOTP info */ +#define DHCP_OPT_FORWARD_ONOFF ((uint8_t) 19) /* RFC2132: length: 1; meaning: Enable/Disable IP Forwarding */ +#define DHCP_OPT_SRCRTE_ONOFF ((uint8_t) 20) /* RFC2132: length: 1; meaning: Enable/Disable Source Routing */ +#define DHCP_OPT_POLICY_FILTER ((uint8_t) 21) /* RFC2132: length: N; meaning: Routing Policy Filters */ +#define DHCP_OPT_MAX_DG_ASSEMBLY ((uint8_t) 22) /* RFC2132: length: 2; meaning: Max Datagram Reassembly Size */ +#define DHCP_OPT_DEFAULT_IP_TTL ((uint8_t) 23) /* RFC2132: length: 1; meaning: Default IP Time to Live */ +#define DHCP_OPT_MTU_TIMEOUT ((uint8_t) 24) /* RFC2132: length: 4; meaning: Path MTU Aging Timeout */ +#define DHCP_OPT_MTU_PLATEAU ((uint8_t) 25) /* RFC2132: length: N; meaning: Path MTU Plateau Table */ +#define DHCP_OPT_MTU_INTERFACE ((uint8_t) 26) /* RFC2132: length: 2; meaning: Interface MTU Size */ +#define DHCP_OPT_MTU_SUBNET ((uint8_t) 27) /* RFC2132: length: 1; meaning: All Subnets are Local */ +#define DHCP_OPT_BROADCAST_ADDRESS ((uint8_t) 28) /* RFC2132: length: 4; meaning: Broadcast Address */ +#define DHCP_OPT_MASK_DISCOVERY ((uint8_t) 29) /* RFC2132: length: 1; meaning: Perform Mask Discovery */ +#define DHCP_OPT_MASK_SUPPLIER ((uint8_t) 30) /* RFC2132: length: 1; meaning: Provide Mask to Others */ +#define DHCP_OPT_ROUTER_DISCOVERY ((uint8_t) 31) /* RFC2132: length: 1; meaning: Perform Router Discovery */ +#define DHCP_OPT_ROUTER_REQUEST ((uint8_t) 32) /* RFC2132: length: 4; meaning: Router Solicitation Address */ +#define DHCP_OPT_STATIC_ROUTE ((uint8_t) 33) /* RFC2132: length: N; meaning: Static Routing Table */ +#define DHCP_OPT_TRAILERS ((uint8_t) 34) /* RFC2132: length: 1; meaning: Trailer Encapsulation */ +#define DHCP_OPT_ARP_TIMEOUT ((uint8_t) 35) /* RFC2132: length: 4; meaning: ARP Cache Timeout */ +#define DHCP_OPT_ETHERNET ((uint8_t) 36) /* RFC2132: length: 1; meaning: Ethernet Encapsulation */ +#define DHCP_OPT_DEFAULT_TCP_TTL ((uint8_t) 37) /* RFC2132: length: 1; meaning: Default TCP Time to Live */ +#define DHCP_OPT_KEEPALIVE_TIME ((uint8_t) 38) /* RFC2132: length: 4; meaning: TCP Keepalive Interval */ +#define DHCP_OPT_KEEPALIVE_DATA ((uint8_t) 39) /* RFC2132: length: 1; meaning: TCP Keepalive Garbage */ +#define DHCP_OPT_NIS_DOMAIN ((uint8_t) 40) /* RFC2132: length: N; meaning: NIS Domain Name */ +#define DHCP_OPT_NIS_SERVERS ((uint8_t) 41) /* RFC2132: length: N; meaning: NIS Server Addresses */ +#define DHCP_OPT_NTP_SERVERS ((uint8_t) 42) /* RFC2132: length: N; meaning: NTP Server Addresses */ +#define DHCP_OPT_VENDOR_SPECIFIC ((uint8_t) 43) /* RFC2132: length: N; meaning: Vendor Specific Information */ +#define DHCP_OPT_NETBIOS_NAME_SRV ((uint8_t) 44) /* RFC2132: length: N; meaning: NETBIOS Name Servers */ +#define DHCP_OPT_NETBIOS_DIST_SRV ((uint8_t) 45) /* RFC2132: length: N; meaning: NETBIOS Datagram Distribution */ +#define DHCP_OPT_NETBIOS_NODE_TYPE ((uint8_t) 46) /* RFC2132: length: 1; meaning: NETBIOS Node Type */ +#define DHCP_OPT_NETBIOS_SCOPE ((uint8_t) 47) /* RFC2132: length: N; meaning: NETBIOS Scope */ +#define DHCP_OPT_X_WINDOW_FONT ((uint8_t) 48) /* RFC2132: length: N; meaning: X Window Font Server */ +#define DHCP_OPT_X_WINDOW_MANAGER ((uint8_t) 49) /* RFC2132: length: N; meaning: X Window Display Manager */ +#define DHCP_OPT_ADDRESS_REQUEST ((uint8_t) 50) /* RFC2132: length: 4; meaning: Requested IP Address */ +#define DHCP_OPT_ADDRESS_TIME ((uint8_t) 51) /* RFC2132: length: 4; meaning: IP Address Lease Time */ +#define DHCP_OPT_OVERLOAD ((uint8_t) 52) /* RFC2132: length: 1; meaning: Overload "sname" or "file */ +#define DHCP_OPT_DHCP_MSG_TYPE ((uint8_t) 53) /* RFC2132: length: 1; meaning: DHCP Message Type */ +#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 */ /* 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, 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/include/libdhcp/dhcp.h b/libdhcp/include/libdhcp/client.h index 0b4c06f..c3cb76f 100644 --- a/libdhcp/include/libdhcp/dhcp.h +++ b/libdhcp/include/libdhcp/client.h @@ -1,4 +1,4 @@ -/* libdhcp/dhcp.h - A DHCP client +/* libdhcp/client.h - A DHCP client * * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later @@ -64,95 +64,12 @@ * SPDX-License-Identifier: MIT */ -#ifndef _LIBDHCP_DHCP_H_ -#define _LIBDHCP_DHCP_H_ +#ifndef _LIBDHCP_CLIENT_H_ +#define _LIBDHCP_CLIENT_H_ #include <libhw/generic/net.h> -#include "hw/w5500.h" -enum dhcp_event { - DHCP_ASSIGN, - DHCP_UPDATE, - DHCP_CONFLICT, -}; +[[noreturn]] void dhcp_client_main(implements_net_iface *iface, + char *self_hostname); -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; - } -} - -#endif /* _LIBDHCP_DHCP_H_ */ +#endif /* _LIBDHCP_CLIENT_H_ */ diff --git a/libdhcp/notes.md b/libdhcp/notes.md new file mode 100644 index 0000000..ac14ae2 --- /dev/null +++ b/libdhcp/notes.md @@ -0,0 +1,32 @@ +<!-- + libdhcp/notes.md - Description of sbc-harness + + Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + SPDX-License-Identifier: AGPL-3.0-or-later +--> + +| <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. |