diff options
38 files changed, 1726 insertions, 976 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 100214c..c9774c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,15 @@ project(sbc_harness) pico_sdk_init() -add_compile_options(-Wall -Wextra -Werror) +add_compile_options(-Wall -Wextra -Wswitch-enum -Werror) + +function(_suppress_tinyusb_warnings) + __suppress_tinyusb_warnings() + set_source_files_properties( + ${PICO_TINYUSB_PATH}/src/device/usbd.c + PROPERTIES + COMPILE_OPTIONS "-Wno-switch-enum") +endfunction() function(target_embed_sources arg_target arg_hdrname) set(embed_objs) diff --git a/cmd/sbc_harness/CMakeLists.txt b/cmd/sbc_harness/CMakeLists.txt index 2018cf7..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) @@ -27,6 +27,8 @@ pico_enable_stdio_uart(sbc_harness_objs 1) pico_enable_stdio_semihosting(sbc_harness_objs 0) pico_enable_stdio_rtt(sbc_harness_objs 0) +suppress_tinyusb_warnings() + # Analyze the stack ############################################################ add_stack_analysis(sbc_harness_stack.c sbc_harness_objs) 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/cmd/srv9p/static9p.c b/cmd/srv9p/static9p.c index 3632c26..90f3856 100644 --- a/cmd/srv9p/static9p.c +++ b/cmd/srv9p/static9p.c @@ -4,8 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include <assert.h> - +#include <libmisc/assert.h> #include <libmisc/vcall.h> #include "static9p.h" diff --git a/lib9p/9p.generated.c b/lib9p/9p.generated.c index 6136433..1a00f10 100644 --- a/lib9p/9p.generated.c +++ b/lib9p/9p.generated.c @@ -1,11 +1,12 @@ -/* Generated by `./lib9p/idl.gen lib9p/idl/2002-9P2000.9p lib9p/idl/2005-9P2000.u.9p lib9p/idl/2012-9P2000.e.9p`. DO NOT EDIT! */ +/* Generated by `lib9p/idl.gen lib9p/idl/2002-9P2000.9p lib9p/idl/2005-9P2000.u.9p lib9p/idl/2012-9P2000.e.9p`. DO NOT EDIT! */ -#include <assert.h> #include <stdbool.h> #include <stddef.h> /* for size_t */ #include <inttypes.h> /* for PRI* macros */ #include <string.h> /* for memset() */ +#include <libmisc/assert.h> + #include <lib9p/9p.h> #include "internal.h" diff --git a/lib9p/idl.gen b/lib9p/idl.gen index ec42cfd..3be1d7e 100755 --- a/lib9p/idl.gen +++ b/lib9p/idl.gen @@ -681,12 +681,13 @@ def gen_c(versions: set[str], typs: list[Type]) -> str: ret = f"""/* Generated by `{' '.join(sys.argv)}`. DO NOT EDIT! */ -#include <assert.h> #include <stdbool.h> #include <stddef.h> /* for size_t */ #include <inttypes.h> /* for PRI* macros */ #include <string.h> /* for memset() */ +#include <libmisc/assert.h> + #include <lib9p/9p.h> #include "internal.h" diff --git a/lib9p/include/lib9p/9p.generated.h b/lib9p/include/lib9p/9p.generated.h index d5e94d1..feca8c9 100644 --- a/lib9p/include/lib9p/9p.generated.h +++ b/lib9p/include/lib9p/9p.generated.h @@ -1,4 +1,4 @@ -/* Generated by `./lib9p/idl.gen lib9p/idl/2002-9P2000.9p lib9p/idl/2005-9P2000.u.9p lib9p/idl/2012-9P2000.e.9p`. DO NOT EDIT! */ +/* Generated by `lib9p/idl.gen lib9p/idl/2002-9P2000.9p lib9p/idl/2005-9P2000.u.9p lib9p/idl/2012-9P2000.e.9p`. DO NOT EDIT! */ #ifndef _LIB9P_9P_H_ #error Do not include <lib9p/9p.generated.h> directly; include <lib9p/9p.h> instead diff --git a/lib9p/include/lib9p/9p.h b/lib9p/include/lib9p/9p.h index 171ad3b..9d22354 100644 --- a/lib9p/include/lib9p/9p.h +++ b/lib9p/include/lib9p/9p.h @@ -7,10 +7,11 @@ #ifndef _LIB9P_9P_H_ #define _LIB9P_9P_H_ -#include <assert.h> #include <stdbool.h> #include <sys/types.h> /* for ssize_t */ +#include <libmisc/assert.h> + #include <lib9p/linux-errno.h> /* configuration **************************************************************/ diff --git a/lib9p/map.h b/lib9p/map.h index c816bde..f42bb2c 100644 --- a/lib9p/map.h +++ b/lib9p/map.h @@ -84,7 +84,7 @@ static VAL_T *MAP_METHOD(NAME,store)(struct NAME *m, KEY_T k, VAL_T v) { m->items[i].val = v; return &(m->items[i].val); } - __builtin_unreachable(); + assert_notreached("should have returned from inside for() loop"); } /** diff --git a/lib9p/srv.c b/lib9p/srv.c index 9192794..e4212e5 100644 --- a/lib9p/srv.c +++ b/lib9p/srv.c @@ -4,7 +4,6 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include <assert.h> #include <alloca.h> #include <inttypes.h> /* for PRI* */ #include <stdio.h> /* for fprintf(), stderr */ @@ -14,6 +13,7 @@ #include <libcr_ipc/chan.h> #include <libcr_ipc/mutex.h> #include <libcr_ipc/select.h> +#include <libmisc/assert.h> #include <libmisc/vcall.h> #define IMPLEMENTATION_FOR_LIB9P_SRV_H YES diff --git a/libcr/CMakeLists.txt b/libcr/CMakeLists.txt index ae7c8fe..fbc7618 100644 --- a/libcr/CMakeLists.txt +++ b/libcr/CMakeLists.txt @@ -8,6 +8,9 @@ target_include_directories(libcr SYSTEM INTERFACE ${CMAKE_CURRENT_LIST_DIR}/incl target_sources(libcr INTERFACE coroutine.c ) +target_link_libraries(libcr INTERFACE + libmisc +) target_compile_options(libcr INTERFACE -fno-split-stack ) diff --git a/libcr/coroutine.c b/libcr/coroutine.c index 41d987e..ed1c8e0 100644 --- a/libcr/coroutine.c +++ b/libcr/coroutine.c @@ -4,12 +4,13 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include <assert.h> #include <setjmp.h> /* for setjmp(), longjmp(), jmp_buf */ #include <stdint.h> /* for uint8_t */ #include <stdio.h> /* for printf(), fprintf(), stderr */ #include <stdlib.h> /* for aligned_alloc(), free() */ +#include <libmisc/assert.h> + #include <libcr/coroutine.h> /* Configuration **************************************************************/ @@ -363,17 +364,6 @@ static cid_t coroutine_running = 0; #define debugf(...) #endif -#ifdef __GLIBC__ - #define assertf(expr, ...) ({ \ - if (!(expr)) { \ - errorf("assertion: " __VA_ARGS__); \ - __assert_fail(#expr, __FILE__, __LINE__, __func__); \ - } \ - }) -#else - #define assertf(expr, ...) assert(expr) -#endif - static inline const char* coroutine_state_str(enum coroutine_state state) { assert(state < ARRAY_LEN(coroutine_state_strs)); return coroutine_state_strs[state]; @@ -470,7 +460,7 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, cr_fn_t fn, void *args) { debugf("...stack_base=%p\n", stack_base); /* run until cr_begin() */ cr_plat_call_with_stack(stack_base, fn, args); - __builtin_unreachable(); /* should cr_begin() instead of returning */ + assert_notreached("should cr_begin() instead of returning"); } assert_cid_state(child, state == CR_RUNNABLE); if (parent) diff --git a/libcr_ipc/include/libcr_ipc/_linkedlist.h b/libcr_ipc/include/libcr_ipc/_linkedlist.h index 43b7286..e5aa52a 100644 --- a/libcr_ipc/include/libcr_ipc/_linkedlist.h +++ b/libcr_ipc/include/libcr_ipc/_linkedlist.h @@ -7,7 +7,7 @@ #ifndef _LIBCR_IPC__LINKEDLIST_H_ #define _LIBCR_IPC__LINKEDLIST_H_ -#include <assert.h> +#include <libmisc/assert.h> /* singly linked list *********************************************************/ diff --git a/libcr_ipc/include/libcr_ipc/select.h b/libcr_ipc/include/libcr_ipc/select.h index f094662..788bf53 100644 --- a/libcr_ipc/include/libcr_ipc/select.h +++ b/libcr_ipc/include/libcr_ipc/select.h @@ -5,10 +5,10 @@ */ #include <alloca.h> /* for alloca() */ -#include <assert.h> /* for assert() */ -#include <stdarg.h> /* for va_* */ #include <stddef.h> /* for size_t */ -#include <stdlib.h> /* for random() */ + +#include <libmisc/assert.h> +#include <libmisc/rand.h> #include <libcr_ipc/chan.h> @@ -23,7 +23,6 @@ */ struct cr_select_arg { enum { - _CR_SELECT_OP_NONE, _CR_SELECT_OP_RECV, _CR_SELECT_OP_SEND, _CR_SELECT_OP_DEFAULT, @@ -61,22 +60,9 @@ struct cr_select_arg { ((struct cr_select_arg){ \ .op = _CR_SELECT_OP_DEFAULT, \ }) -#define _CR_SELECT_END \ - ((struct cr_select_arg){ \ - .op = _CR_SELECT_OP_NONE, \ - }) /* cr_select_v(arg_cnt, arg_vec) **********************************************/ -static inline size_t _cr_select_pickone(size_t cnt) { - long fair_cnt = (0x80000000L / cnt) * cnt; - long rnd; - do { - rnd = random(); - } while (rnd >= fair_cnt); - return rnd % cnt; -} - enum _cr_select_class { _CR_SELECT_CLASS_DEFAULT, _CR_SELECT_CLASS_BLOCKING, @@ -98,7 +84,7 @@ static inline enum _cr_select_class _cr_select_getclass(struct cr_select_arg arg case _CR_SELECT_OP_DEFAULT: return _CR_SELECT_CLASS_DEFAULT; default: - __builtin_unreachable(); + assert_notreached("invalid arg.op"); } } @@ -139,7 +125,7 @@ static size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) { } if (cnt_nonblock) { - size_t choice = _cr_select_pickone(cnt_nonblock); + size_t choice = rand_uint63n(cnt_nonblock); for (size_t i = 0, seen = 0; i < arg_cnt; i++) { if (_cr_select_getclass(arg_vec[i]) == _CR_SELECT_CLASS_NONBLOCK) { if (seen == choice) { @@ -154,14 +140,14 @@ static size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) { seen++; } } - __builtin_unreachable(); + assert_notreached("should have returned from inside for() loop"); } if (cnt_default) { for (size_t i = 0; i < arg_cnt; i++) if (_cr_select_getclass(arg_vec[i]) == _CR_SELECT_CLASS_DEFAULT) return i; - __builtin_unreachable(); + assert_notreached("should have returned from inside for() loop"); } struct _cr_select_waiters waiters = { @@ -185,40 +171,9 @@ static size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) { /* cr_select_l(arg1, arg2, arg3, ...) ******************************************/ -#define cr_select_l(...) _cr_selectl(__VA_ARGS__ __VA_OPT__(,) _CR_SELECT_END) -static inline size_t _cr_select_l(struct cr_select_arg first_arg, ...) { - va_list list; - - /* Pass 1: Count how many args we have. */ - size_t arg_cnt = 0; - va_start(list, first_arg); - for (;;) { - struct cr_select_arg arg; - if (arg_cnt == 0) - arg = first_arg; - else - arg = va_arg(list, struct cr_select_arg); - if (arg.op == _CR_SELECT_OP_NONE) - break; - arg_cnt++; - } - va_end(list); - - /* Pass 2: Copy the args to a simple vector on the stack. */ - struct cr_select_arg *arg_vec = alloca(sizeof(struct cr_select_arg) * arg_cnt); - va_start(list, first_arg); - for (size_t i = 0; i < arg_cnt; i++) { - struct cr_select_arg arg; - if (i == 0) - arg = first_arg; - else - arg = va_arg(list, struct cr_select_arg); - arg_vec[i] = arg; - } - va_end(list); - - /* Now pass that to cr_select_v() for the real work. */ - return cr_select_v(arg_cnt, arg_vec); -} +#define cr_select_l(...) ({ \ + struct cr_select_arg _cr_select_args[] = { __VA_ARGS__ }; \ + cr_select_v(sizeof(_cr_select_args)/sizeof(_cr_select_args[0])); \ +}) #endif /* _LIBCR_IPC_SELECT_H_ */ 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..94806a6 --- /dev/null +++ b/libdhcp/dhcp_client.c @@ -0,0 +1,969 @@ +/* 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> + +#include <libdhcp/client.h> + +#include "dhcp_common.h" + +/* Config *********************************************************************/ + +#include "config.h" + +#ifndef CONFIG_DHCP_DEBUG + #define CONFIG_DHCP_DEBUG 1 +#endif +#ifndef CONFIG_DHCP_SELECTING_NS + #define CONFIG_DHCP_SELECTING_NS (5*NS_PER_S) +#endif +#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 + #include <stdio.h> + #define debugf(fmt, ...) printf(fmt "\n" __VA_OPT__(,) __VA_ARGS__) +#else + #define debugf(fmt, ...) ((void)0) +#endif + + +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[63]; + 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; + uint64_t transmit_time_ns; + uint64_t transmit_delay_ns; + uint64_t transmit_deadline_ns; + 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 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) { + /**********************************************************************\ + * 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 */ + + /* For convenience in switch blocks, a list of the states that + * are not in the above assert(). */ +#define IMPOSSIBLE_REQUEST_STATES \ + STATE_INIT: \ + case STATE_REQUESTING: \ + case STATE_REBINDING: \ + case STATE_REBOOTING + + /**********************************************************************\ + * 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 * + \**********************************************************************/ + + struct dhcp_msg msg = {0}; + size_t optlen = 0; + + /* Base structure. + * 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( ({ + 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: + 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; + } + 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; + case IMPOSSIBLE_REQUEST_STATES: + assert_notreached("invalid client state for sending DHCPREQUEST"); + } 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 */ + memcpy(msg.chaddr, client->self_eth_addr.octets, sizeof(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"; */ + + /* Magic cookie. + * 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]; + + /* Options. + * https://datatracker.ietf.org/doc/html/rfc2131#page-38 */ + 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; + const void *val_ptr; + } options[0x100] = { +#define V(x) sizeof(x), &(x) +#define NONE 0, NULL + /* Encode the table from + * https://datatracker.ietf.org/doc/html/rfc2131#page-38 */ +#define INC_ADDR ({ \ + enum requirement req; \ + switch (client->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 (client->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; \ + }) + /* Option DISCOVER INFORM REQUEST DECLINE RELEASE */ + /* ------ ---------- ---------- ---------- -------- -------- */ + [DHCP_OPT_ADDRESS_REQUEST] = { 1, { MAY, MUST_NOT, INC_ADDR, 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 }, client->self_id_len, client->self_id_dat }, + [DHCP_OPT_CLASS_ID] = { 1, { MAY, MAY, MAY, MUST_NOT, MUST_NOT }, NONE }, + [DHCP_OPT_DHCP_SERVER_ID] = { 1, { MUST_NOT, MUST_NOT, INC_SERVER, 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]; + 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 <= UINT16_MAX); + for (size_t done = 0; done < options[opt].val_len;) { + uint8_t len = options[opt].val_len - done > UINT8_MAX + ? UINT8_MAX + : options[opt].val_len - done; + msg.options[optlen++] = opt; + msg.options[optlen++] = len; + memcpy(&msg.options[optlen], &((char*)options[opt].val_ptr)[done], len); + optlen += len; + done += len; + } + } + break; + case _SHOULD_NOT_HAPPEN: + assert_notreached("bad table"); + } + } + msg.options[optlen++] = DHCP_OPT_END; + assert(optlen <= CONFIG_DHCP_OPT_SIZE); + + /**********************************************************************\ + * 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; + } + client->last_sent_msgtyp = msgtyp; + return false; +} + +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; +} + +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->options[opt_typ].dat[ret->options[opt_typ].len], &dat[pos], opt_len); + ret->options[opt_typ].len += opt_len; + } + } +} + +/** + * @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. */ + const struct { + bool have_cols; + enum requirement cols[3]; + } option_req[0x100] = { + /* Enocde the table from + * https://datatracker.ietf.org/doc/html/rfc2131#page-29, with modifications from + * https://datatracker.ietf.org/doc/html/rfc6842#page-4 */ +#define REQ_TIME client->last_sent_msgtyp == DHCP_MSGTYP_REQUEST ? MUST : MUST_NOT + /* Option DHCPOFFER DHCPACK DHCPNAK */ + /* ------ -------- ---------- -------- */ + [DHCP_OPT_ADDRESS_REQUEST] = { 1, { MUST_NOT, MUST_NOT, MUST_NOT } }, + [DHCP_OPT_ADDRESS_TIME] = { 1, { MUST, REQ_TIME, 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, { MAY, MAY, MAY } }, /* MUST_NOTs changed to MAY, per RFC 6842 */ + [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]; + 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: + assert_notreached("bad table"); + } + } + /* 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->options[DHCP_OPT_CLASS_ID].dat, 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); + return VCALL(sock, sendto, "CHECK_IP_CONFLICT", 17, addr, 5000) != NET_ETIMEDOUT; +} + +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->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 dur_ns_end = + ((uint64_t)uint32be_decode(msg->options[DHCP_OPT_ADDRESS_TIME].dat))*NS_PER_S; + uint64_t dur_ns_t1 = msg->options[DHCP_OPT_RENEWAL_TIME].len + ? ((uint64_t)uint32be_decode(msg->options[DHCP_OPT_RENEWAL_TIME].dat))*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->options[DHCP_OPT_RENEWAL_TIME].dat))*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, + }); +} + +/* "in a 10Mb/sec Ethernet internetwork, the delay before the first + * retransmission SHOULD be 4 seconds" -- RFC 2131 */ +#define CONFIG_DHCP_RETRANSMIT_INIT_NS (4*NS_PER_S) +/* "... randomized by the value of a uniform random number chosen from + * the range -1 to +1 [seconds]" -- RFC 2131 */ +#define CONFIG_DHCP_RETRANSMIT_RAND_NS (2*NS_PER_S) +/* "... up to a maximum of 64 seconds." -- RFC 2131 */ +#define CONFIG_DHCP_RETRANSMIT_MAX_NS (64*NS_PER_S) + +#define CONFIG_DHCP_TIMEOUT_SELECTING_NS TODO +#define CONFIG_DHCP_TIMEOUT_REQUESTING_NS TODO +#define CONFIG_DHCP_TIMEOUT_PROBING_NS TODO + +/* Given `init`, the nth transmission is sent at + * + * n-1 + * t = ∑ init*2^(i-1) = init*((2^n)-1) + * i=0 + * + * This means that the at time t=x, the number of transmissions is + * + * n = floor(log_2(ceil(x/init)+1)) + */ +#define _ceildiv(n, d) (( (n)+(d)-1 ) / (d)) +#define _floorlog2(x) (64-__builtin_clzll(x)-1) +#define _transmissions_at(init, x) _floorlog2(_ceildiv(x, init)+1) +#define MAX_TRANSMISSIONS(stage) _transmissions_at(CONFIG_DHCP_RETRANSMIT_INIT_NS, CONFIG_DHCP_TIMEOUT_##stage##_NS) + +/** "the retransmission algorithm in section 4.1" */ +static x retransmit_4_1(struct dhcp_client *client, uint64_t timeout_ns) { + /* "The client MUST adopt a retransmission strategy that + * incorporates a randomized exponential backoff algorithm to + * determine the delay between retransmissions." + * + * However, the exact algorithm is implementation-defined, + * section 4.1 only provides a suggested ("SHOULD") algorithm + * that meets these requirements. I'm not sure why I point + * this out; this implements the section 4.1 algorithm + * faithfully. */ + + if (!client->transmit_delay_ns) + + uint64_t deadline = timeout_ns + + rand_uint63n(CONFIG_DHCP_RETRANSMIT_RAND_NS) - CONFIG_DHCP_RETRANSMIT_RAND_NS/2; + + /* "Selecting a new 'xid' for each retransmission is an + * implementation decision. A client may choose to reuse the + * same 'xid' or select a new 'xid' for each retransmitted + * message." -- RFC 2131 */ + +} + +static inline uint64_t dhcp_renew_rebind_deadline(struct dhcp_client *client) { + /* "In both RENEWING and REBINDING states, if the client + * receives no response to its DHCPREQUEST message, the + * client SHOULD wait one-half of the remaining time + * until T2 (in RENEWING state) and one-half of the + * remaining lease time (in REBINDING state), down to a + * minimum of 60 seconds, before retransmitting the + * DHCPREQUEST message." -- RFC 2131 */ + + uint64_t final_deadline = client->state == STATE_RENEWING + ? client->lease_time_ns_t2 + : client->lease_time_ns_end; + if (!final_deadline) + return 0; + + uint64_t now = VCALL(bootclock, get_time_ns); + if (final_deadline < now + 60*NS_PER_S) + return final_deadline; + uint64_t half_remaining = (final_deadline - now) / 2; + if (half_remaining < 60*NS_PER_S) + return 60*NS_PER_S; + return half_remaining; +} + +static void dhcp_client_setstate(struct dhcp_client *client, + typeof((struct dhcp_client){}.state) newstate, + uint8_t send_msgtyp, const char *errstr) { + if (send_msgtyp) + (void)dhcp_client_send(client, send_msgtyp, errstr); + client->state = newstate; +} + +static __attribute__((noreturn)) void dhcp_client_run(struct dhcp_client *client) { + assert(client); + + struct dhcp_recv_msg msg; + 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); + break; + case STATE_SELECTING: + /* TODO: "The client times out and retransmits + * the DHCPDISCOVER message if the client + * receives no DHCPOFFER messages." */ + VCALL(client->sock, set_read_deadline, client->time_ns_init+CONFIG_DHCP_SELECTING_NS); + switch ((r = dhcp_client_recv(client, &msg))) { + 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, false); + dhcp_client_setstate(client, STATE_REQUESTING, DHCP_MSGTYP_REQUEST, NULL); + break; + default: + /* ignore */ + } + break; + case -NET_ETIMEDOUT: + dhcp_client_setstate(client, STATE_INIT, 0, NULL); + break; + default: + assert(r < 0); + debugf("error: recvfrom: %d", r); + } + break; + case STATE_REQUESTING: + /* TODO: "The client retransmits the + * DHCPREQUEST according to the retransmission + * algorithm in section 4.1." */ + VCALL(client->sock, set_read_deadline, 0); + switch ((r = dhcp_client_recv(client, &msg))) { + case 0: + switch (msg.options[DHCP_OPT_DHCP_MSG_TYPE].dat[0]) { + case DHCP_MSGTYP_NAK: + dhcp_client_setstate(client, STATE_INIT, 0, NULL); + 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"); + } else { + dhcp_client_take_lease(client, &msg, true); + dhcp_client_setstate(client, STATE_BOUND, 0, NULL); + } + 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, &msg))) { + case 0: + /* discard */ + break; + case -NET_ETIMEDOUT: + dhcp_client_setstate(client, STATE_RENEWING, DHCP_MSGTYP_REQUEST, NULL); + 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, dhcp_renew_rebind_deadline(client)); + switch ((r = dhcp_client_recv(client, &msg))) { + case 0: + switch (msg.options[DHCP_OPT_DHCP_MSG_TYPE].dat[0]) { + case DHCP_MSGTYP_NAK: + VCALL(client->iface, ifdown); + dhcp_client_setstate(client, STATE_INIT, 0, NULL); + break; + case DHCP_MSGTYP_ACK: + dhcp_client_take_lease(client, &msg, true); + dhcp_client_setstate(client, STATE_BOUND, 0, NULL); + break; + default: + /* ignore */ + } + break; + case -NET_ETIMEDOUT: + if (VCALL(bootclock, get_time_ns) < client->lease_time_ns_t2) + break; + client->lease_server_id = net_ip4_addr_zero; + dhcp_client_setstate(client, STATE_REBINDING, DHCP_MSGTYP_REQUEST, NULL); + break; + default: + assert(r < 0); + debugf("error: recvfrom: %d", r); + } + break; + case STATE_REBINDING: + VCALL(client->sock, set_read_deadline, dhcp_renew_rebind_deadline(client)); + switch ((r = dhcp_client_recv(client, &msg))) { + case 0: + switch (msg.options[DHCP_OPT_DHCP_MSG_TYPE].dat[0]) { + case DHCP_MSGTYP_NAK: + VCALL(client->iface, ifdown); + dhcp_client_setstate(client, STATE_BOUND, 0, NULL); + break; + case DHCP_MSGTYP_ACK: + dhcp_client_take_lease(client, &msg, true); + dhcp_client_setstate(client, STATE_BOUND, 0, NULL); + break; + default: + /* ignore */ + } + break; + case -NET_ETIMEDOUT: + if (VCALL(bootclock, get_time_ns) < client->lease_time_ns_end) + break; + VCALL(client->iface, ifdown); + dhcp_client_setstate(client, STATE_BOUND, 0, NULL); + 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"); + } + } +} + +__attribute__((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_id_len = sizeof(client_id), + .self_id_dat = &client_id, + + /* Mutable. */ + .state = STATE_INIT, + }; + if (self_hostname) + strncpy(client.self_hostname, self_hostname, sizeof(client.self_hostname)); + + assert(client.sock); + + dhcp_client_run(&client); +} diff --git a/libdhcp/dhcp_common.h b/libdhcp/dhcp_common.h new file mode 100644 index 0000000..0be3a27 --- /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 + #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} */ + 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/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/dhcp.h b/libdhcp/include/libdhcp/client.h index 0b4c06f..b0eaa52 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 @@ -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_ */ diff --git a/libhw/common_include/libhw/generic/net.h b/libhw/common_include/libhw/generic/net.h index 8dedaea..def533c 100644 --- a/libhw/common_include/libhw/generic/net.h +++ b/libhw/common_include/libhw/generic/net.h @@ -52,6 +52,14 @@ struct net_stream_listener_vtable { * connection is still open. */ implements_net_stream_conn *(*accept)(implements_net_stream_listener *self); + + /** + * The net_stream_conn returned from accept() may still be + * valid after the listener is closed. + * + * Return 0 on success, -errno on error. + */ + int (*close)(implements_net_stream_listener *self); }; struct net_stream_conn_vtable { @@ -106,6 +114,11 @@ struct net_packet_conn_vtable { ssize_t (*sendto )(implements_net_packet_conn *self, void *buf, size_t len, struct net_ip4_addr node, uint16_t port); + /** + * @return The full length of the message, which may be more + * than the given `len` (as if the Linux MSG_TRUNC flag were + * given). + */ ssize_t (*recvfrom)(implements_net_packet_conn *self, void *buf, size_t len, struct net_ip4_addr *ret_node, uint16_t *ret_port); @@ -114,4 +127,28 @@ struct net_packet_conn_vtable { int (*close )(implements_net_packet_conn *self); }; +/* Interfaces *****************************************************************/ + +struct net_iface_config { + struct net_ip4_addr addr; + struct net_ip4_addr gateway_addr; + struct net_ip4_addr subnet_mask; +}; + +struct net_iface_vtable; + +typedef struct { + struct net_iface_vtable *vtable; +} implements_net_iface; + +struct net_iface_vtable { + struct net_eth_addr (*hwaddr )(implements_net_iface *); + void (*ifup )(implements_net_iface *, struct net_iface_config); + void (*ifdown )(implements_net_iface *); + + implements_net_stream_listener *(*tcp_listen)(implements_net_iface *, uint16_t local_port); + implements_net_stream_conn *(*tcp_dial )(implements_net_iface *, struct net_ip4_addr remote_node, uint16_t remote_port); + implements_net_packet_conn *(*udp_conn )(implements_net_iface *, uint16_t local_port); +}; + #endif /* _LIBHW_GENERIC_NET_H_ */ diff --git a/libhw/host_alarmclock.c b/libhw/host_alarmclock.c index 141927c..4f43fc7 100644 --- a/libhw/host_alarmclock.c +++ b/libhw/host_alarmclock.c @@ -4,13 +4,13 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include <assert.h> #include <errno.h> #include <error.h> #include <signal.h> #include <time.h> #include <libcr/coroutine.h> +#include <libmisc/assert.h> #include <libmisc/vcall.h> #define IMPLEMENTATION_FOR_LIBHW_GENERIC_ALARMCLOCK_H YES diff --git a/libhw/host_net.c b/libhw/host_net.c index 27df2e2..db31522 100644 --- a/libhw/host_net.c +++ b/libhw/host_net.c @@ -6,7 +6,6 @@ #define _GNU_SOURCE /* for pthread_sigqueue(3gnu) */ /* misc */ -#include <assert.h> /* for assert() */ #include <errno.h> /* for errno, EAGAIN, EINVAL */ #include <error.h> /* for error(3gnu) */ #include <stdlib.h> /* for abs(), shutdown(), SHUT_RD, SHUT_WR, SHUT_RDWR */ @@ -20,6 +19,7 @@ #include <signal.h> /* for siginfo_t, struct sigaction, enum sigval, sigaction(), SA_SIGINFO */ #include <libcr/coroutine.h> +#include <libmisc/assert.h> #include <libmisc/vcall.h> #include <libhw/generic/alarmclock.h> @@ -92,14 +92,16 @@ static inline ssize_t hostnet_map_negerrno(ssize_t v) { /* TCP init() ( AKA socket(3) + listen(3) )************************************/ -static implements_net_stream_conn *hostnet_tcp_accept(implements_net_stream_listener *_listener); +static implements_net_stream_conn *hostnet_tcplist_accept(implements_net_stream_listener *); +static int hostnet_tcplist_close(implements_net_stream_listener *); static void hostnet_tcp_set_read_deadline(implements_net_stream_conn *conn, uint64_t ts_ns); static ssize_t hostnet_tcp_read(implements_net_stream_conn *conn, void *buf, size_t count); static ssize_t hostnet_tcp_write(implements_net_stream_conn *conn, void *buf, size_t count); static int hostnet_tcp_close(implements_net_stream_conn *conn, bool rd, bool wr); static struct net_stream_listener_vtable hostnet_tcp_listener_vtable = { - .accept = hostnet_tcp_accept, + .accept = hostnet_tcplist_accept, + .close = hostnet_tcplist_close, }; static struct net_stream_conn_vtable hostnet_tcp_conn_vtable = { @@ -136,7 +138,7 @@ void hostnet_tcp_listener_init(struct hostnet_tcp_listener *self, uint16_t port) self->fd = listenerfd; } -/* TCP accept() ***************************************************************/ +/* TCP listener accept() ******************************************************/ struct hostnet_pthread_accept_args { pthread_t cr_thread; @@ -156,7 +158,7 @@ static void *hostnet_pthread_accept(void *_args) { return NULL; } -static implements_net_stream_conn *hostnet_tcp_accept(implements_net_stream_listener *_listener) { +static implements_net_stream_conn *hostnet_tcplist_accept(implements_net_stream_listener *_listener) { struct hostnet_tcp_listener *listener = VCALL_SELF(struct hostnet_tcp_listener, implements_net_stream_listener, _listener); assert(listener); @@ -176,6 +178,16 @@ static implements_net_stream_conn *hostnet_tcp_accept(implements_net_stream_list return &listener->active_conn; } +/* TCP listener close() *******************************************************/ + +static int hostnet_tcplist_close(implements_net_stream_listener *_listener) { + struct hostnet_tcp_listener *listener = + VCALL_SELF(struct hostnet_tcp_listener, implements_net_stream_listener, _listener); + assert(listener); + + return hostnet_map_negerrno(close(listener->fd) ? -errno : 0); +} + /* TCP read() *****************************************************************/ static void hostnet_tcp_set_read_deadline(implements_net_stream_conn *_conn, uint64_t ts_ns) { @@ -313,7 +325,7 @@ static int hostnet_tcp_close(implements_net_stream_conn *_conn, bool rd, bool wr else if (!rd && wr) how = SHUT_WR; else - assert(false); + assert_notreached("invalid arguments to stream_conn.close()"); return hostnet_map_negerrno(shutdown(conn->fd, how) ? -errno : 0); } @@ -457,7 +469,8 @@ static void *hostnet_pthread_recvfrom(void *_args) { if (*(args->ret_size) < 0) goto end; - *(args->ret_size) = recvfrom(args->connfd, args->buf, args->count, 0, &addr.gen, &addr_size); + *(args->ret_size) = recvfrom(args->connfd, args->buf, args->count, + MSG_TRUNC, &addr.gen, &addr_size); if (*(args->ret_size) < 0) goto end; diff --git a/libhw/rp2040_hwspi.c b/libhw/rp2040_hwspi.c index 4edcdf7..da7ee78 100644 --- a/libhw/rp2040_hwspi.c +++ b/libhw/rp2040_hwspi.c @@ -4,11 +4,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include <assert.h> - #include <hardware/spi.h> /* pico-sdk:hardware_spi */ #include <hardware/gpio.h> /* pico-sdk:hardware_gpio */ +#include <libmisc/assert.h> #include <libmisc/vcall.h> #define IMPLEMENTATION_FOR_LIBHW_RP2040_HWSPI_H YES @@ -67,7 +66,7 @@ void _rp2040_hwspi_init(struct rp2040_hwspi *self, assert(pin_mosi == 11 || pin_mosi == 15 || pin_mosi == 27); break; default: - assert(false); + assert_notreached("invalid hwspi instance number"); } spi_init(inst, baudrate_hz); @@ -117,7 +116,7 @@ static void rp2040_hwspi_readwritev(implements_spi *_self, const struct bidi_iov else if (iov[i].iov_read_dst) spi_read_blocking(inst, 0, iov[i].iov_read_dst, iov[i].iov_len); else - assert(false); + assert_notreached("bidi_iovec is neither read nor write"); } gpio_put(self->pin_cs, 1); } diff --git a/libhw/rp2040_hwtimer.c b/libhw/rp2040_hwtimer.c index 54bdab3..69c2a50 100644 --- a/libhw/rp2040_hwtimer.c +++ b/libhw/rp2040_hwtimer.c @@ -4,12 +4,11 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -#include <assert.h> - #include <hardware/irq.h> /* pico-sdk:hardware_irq */ #include <hardware/timer.h> /* pico-sdk:hardware_timer */ #include <libcr/coroutine.h> +#include <libmisc/assert.h> #include <libmisc/vcall.h> #define IMPLEMENTATION_FOR_LIBHW_GENERIC_ALARMCLOCK_H YES diff --git a/libhw/rp2040_include/libhw/w5500.h b/libhw/rp2040_include/libhw/w5500.h index 16f88cc..7538bbc 100644 --- a/libhw/rp2040_include/libhw/w5500.h +++ b/libhw/rp2040_include/libhw/w5500.h @@ -25,16 +25,17 @@ struct _w5500_socket { uint8_t socknum; /* mutable */ + struct _w5500_socket *next_free; enum { W5500_MODE_NONE = 0, W5500_MODE_TCP, W5500_MODE_UDP, } mode; - uint16_t port; /* MODE_{TCP,UDP} */ - uint64_t read_deadline_ns; /* MODE_{TCP,UDP} */ - cr_sema_t listen_sema; /* MODE_TCP */ - cr_sema_t read_sema; /* MODE_{TCP,UDP} */ - bool read_open, write_open; /* MODE_TCP */ + uint16_t port; /* MODE_{TCP,UDP} */ + uint64_t read_deadline_ns; /* MODE_{TCP,UDP} */ + cr_sema_t listen_sema; /* MODE_TCP */ + cr_sema_t read_sema; /* MODE_{TCP,UDP} */ + bool list_open, read_open, write_open; /* MODE_TCP */ cr_mutex_t cmd_mu; END_PRIVATE(LIBHW_W5500_H) @@ -42,8 +43,9 @@ struct _w5500_socket { struct w5500 { /* const-after-init */ - implements_spi *spidev; + implements_net_iface; BEGIN_PRIVATE(LIBHW_W5500_H) + implements_spi *spidev; uint pin_intr; uint pin_reset; struct net_eth_addr hwaddr; @@ -51,6 +53,7 @@ struct w5500 { /* mutable */ uint16_t next_local_port; struct _w5500_socket sockets[8]; + struct _w5500_socket *free; cr_sema_t intr; END_PRIVATE(LIBHW_W5500_H) }; @@ -88,24 +91,4 @@ void w5500_hard_reset(struct w5500 *self); */ void w5500_soft_reset(struct w5500 *self); -struct w5500_netcfg { - struct net_ip4_addr gateway_addr; - struct net_ip4_addr subnet_mask; - struct net_ip4_addr addr; -}; - -/** - * TODO. - */ -void w5500_netcfg(struct w5500 *self, struct w5500_netcfg cfg); - -implements_net_stream_listener *w5500_tcp_listen(struct w5500 *self, uint8_t socknum, - uint16_t port); - -/** - * TODO. - */ -implements_net_packet_conn *w5500_udp_conn(struct w5500 *self, uint8_t socknum, - uint16_t port); - #endif /* _LIBHW_W5500_H_ */ diff --git a/libhw/w5500.c b/libhw/w5500.c index 1923a17..ae48a61 100644 --- a/libhw/w5500.c +++ b/libhw/w5500.c @@ -107,22 +107,44 @@ /* vtables ********************************************************************/ -static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listener *); - -static void w5500_tcp_set_read_deadline(implements_net_stream_conn *, uint64_t ns); -static ssize_t w5500_tcp_read( implements_net_stream_conn *, void *, size_t); -static ssize_t w5500_tcp_write( implements_net_stream_conn *, void *, size_t); -static int w5500_tcp_close( implements_net_stream_conn *, bool rd, bool wr); - -static void w5500_udp_set_read_deadline(implements_net_packet_conn *, uint64_t ns); -static ssize_t w5500_udp_recvfrom( implements_net_packet_conn *, void *, size_t, - struct net_ip4_addr *, uint16_t *); -static ssize_t w5500_udp_sendto( implements_net_packet_conn *, void *, size_t, - struct net_ip4_addr, uint16_t); -static int w5500_udp_close( implements_net_packet_conn *); +/* iface */ +static struct net_eth_addr w5500_if_hwaddr (implements_net_iface *); +static void w5500_if_up (implements_net_iface *, struct net_iface_config); +static void w5500_if_down (implements_net_iface *); +static implements_net_stream_listener *w5500_if_tcp_listen (implements_net_iface *, uint16_t local_port); +static implements_net_stream_conn *w5500_if_tcp_dial (implements_net_iface *, struct net_ip4_addr, uint16_t remote_port); +static implements_net_packet_conn *w5500_if_udp_conn (implements_net_iface *, uint16_t local_port); + +/* stream_listener */ +static implements_net_stream_conn *w5500_tcplist_accept(implements_net_stream_listener *); +static int w5500_tcplist_close (implements_net_stream_listener *); + +/* stream_conn */ +static void w5500_tcp_set_read_deadline (implements_net_stream_conn *, uint64_t ns); +static ssize_t w5500_tcp_read (implements_net_stream_conn *, void *, size_t); +static ssize_t w5500_tcp_write (implements_net_stream_conn *, void *, size_t); +static int w5500_tcp_close (implements_net_stream_conn *, bool rd, bool wr); + +/* packet_conn */ +static void w5500_udp_set_read_deadline (implements_net_packet_conn *, uint64_t ns); +static ssize_t w5500_udp_recvfrom (implements_net_packet_conn *, void *, size_t, struct net_ip4_addr *, uint16_t *); +static ssize_t w5500_udp_sendto (implements_net_packet_conn *, void *, size_t, struct net_ip4_addr, uint16_t); +static int w5500_udp_close (implements_net_packet_conn *); + +/* tables */ + +static struct net_iface_vtable w5500_iface_vtable = { + .hwaddr = w5500_if_hwaddr, + .ifup = w5500_if_up, + .ifdown = w5500_if_down, + .tcp_listen = w5500_if_tcp_listen, + .tcp_dial = w5500_if_tcp_dial, + .udp_conn = w5500_if_udp_conn, +}; static struct net_stream_listener_vtable w5500_tcp_listener_vtable = { - .accept = w5500_tcp_accept, + .accept = w5500_tcplist_accept, + .close = w5500_tcplist_close, }; static struct net_stream_conn_vtable w5500_tcp_conn_vtable = { @@ -141,14 +163,40 @@ static struct net_packet_conn_vtable w5500_udp_conn_vtable = { /* mid-level utilities ********************************************************/ -#if 0 -static uint16_t w5500_get_local_port(struct w5500 *self) { +static uint16_t w5500_alloc_local_port(struct w5500 *self) { + assert(self); uint16_t ret = self->next_local_port++; if (self->next_local_port > CONFIG_W5500_LOCAL_PORT_MAX) self->next_local_port = CONFIG_W5500_LOCAL_PORT_MIN; return ret; } -#endif + +static struct _w5500_socket *w5500_alloc_socket(struct w5500 *self) { + assert(self); + struct _w5500_socket *sock = self->free; + if (!sock) + return NULL; + self->free = sock->next_free; + sock->next_free = NULL; + assert(sock->mode == W5500_MODE_NONE); + return sock; +} + +static void w5500_free_socket(struct w5500 *self, struct _w5500_socket *sock) { + assert(self); + assert(sock); + sock->mode = W5500_MODE_NONE; + sock->next_free = self->free; + self->free = sock; +} + +static void w5500_tcp_maybe_free(struct w5500 *chip, struct _w5500_socket *sock) { + assert(chip); + assert(sock); + assert(sock->mode == W5500_MODE_TCP); + if (!sock->list_open && !sock->read_open && !sock->write_open) + w5500_free_socket(chip, sock); +} static COROUTINE w5500_irq_cr(void *_chip) { struct w5500 *chip = _chip; @@ -263,6 +311,7 @@ void _w5500_init(struct w5500 *chip, /* Initialize the data structures. */ *chip = (struct w5500){ /* const-after-init */ + .implements_net_iface = { .vtable = &w5500_iface_vtable }, .spidev = spi, .pin_intr = pin_intr, .pin_reset = pin_reset, @@ -270,6 +319,7 @@ void _w5500_init(struct w5500 *chip, /* mutable */ .next_local_port = CONFIG_W5500_LOCAL_PORT_MIN, }; + chip->free = &chip->sockets[0]; for (uint8_t i = 0; i < 8; i++) { chip->sockets[i] = (struct _w5500_socket){ /* const-after-init */ @@ -278,7 +328,9 @@ void _w5500_init(struct w5500 *chip, .implements_net_packet_conn = { .vtable = &w5500_udp_conn_vtable }, .socknum = i, /* mutable */ - /* these all get initialized to the zero values */ + .next_free = (i + 1 < 8) ? &chip->sockets[i+1] : NULL, + /* the rest of the mutable members get + * initialized to the zero values */ }; } @@ -359,46 +411,122 @@ void w5500_soft_reset(struct w5500 *chip) { w5500_post_reset(chip); } -void w5500_netcfg(struct w5500 *chip, struct w5500_netcfg cfg) { +static struct net_eth_addr w5500_if_hwaddr(implements_net_iface *_chip) { + struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip); + assert(chip); + + return chip->hwaddr; +} + +static void w5500_if_up(implements_net_iface *_chip, struct net_iface_config cfg) { + struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip); + assert(chip); + w5500ll_write_common_reg(chip->spidev, ip_gateway_addr, cfg.gateway_addr); w5500ll_write_common_reg(chip->spidev, ip_subnet_mask, cfg.subnet_mask); w5500ll_write_common_reg(chip->spidev, ip_addr, cfg.addr); } -implements_net_stream_listener *w5500_tcp_listen(struct w5500 *chip, uint8_t socknum, - uint16_t port) { +static void w5500_if_down(implements_net_iface *_chip) { + w5500_if_up(_chip, (struct net_iface_config){0}); +} + +static implements_net_stream_listener *w5500_if_tcp_listen(implements_net_iface *_chip, uint16_t local_port) { + struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip); assert(chip); - assert(socknum < 8); - assert(port); - assert(chip->sockets[socknum].mode == W5500_MODE_NONE); - chip->sockets[socknum].mode = W5500_MODE_TCP; - chip->sockets[socknum].port = port; - chip->sockets[socknum].read_deadline_ns = 0; + struct _w5500_socket *sock = w5500_alloc_socket(chip); + if (!sock) + return NULL; - return &chip->sockets[socknum].implements_net_stream_listener; + if (!local_port) + local_port = w5500_alloc_local_port(chip); + + assert(sock->mode == W5500_MODE_NONE); + sock->mode = W5500_MODE_TCP; + sock->port = local_port; + sock->read_deadline_ns = 0; + sock->list_open = true; + + return &sock->implements_net_stream_listener; } -implements_net_packet_conn *w5500_udp_conn(struct w5500 *chip, uint8_t socknum, - uint16_t port) { +static implements_net_stream_conn *w5500_if_tcp_dial(implements_net_iface *_chip, + struct net_ip4_addr node, uint16_t port) { + struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip); assert(chip); - assert(socknum < 8); + assert(memcmp(node.octets, net_ip4_addr_zero.octets, 4)); + assert(memcmp(node.octets, net_ip4_addr_broadcast.octets, 4)); assert(port); - assert(chip->sockets[socknum].mode == W5500_MODE_NONE); - chip->sockets[socknum].mode = W5500_MODE_UDP; - chip->sockets[socknum].port = port; - chip->sockets[socknum].read_deadline_ns = 0; + struct _w5500_socket *socket = w5500_alloc_socket(chip); + if (!socket) + return NULL; + uint8_t socknum = socket->socknum; + + uint16_t local_port = w5500_alloc_local_port(chip); + + assert(socket->mode == W5500_MODE_NONE); + socket->mode = W5500_MODE_TCP; + socket->port = local_port; + socket->read_deadline_ns = 0; + socket->read_open = socket->write_open = true; + + restart: + /* Mimics socket.c:socket(). */ + w5500_socket_close(socket); + w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_TCP); + w5500ll_write_sock_reg(chip->spidev, socknum, local_port, uint16be_marshal(socket->port)); + w5500_socket_cmd(socket, CMD_OPEN); + while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_TCP_INIT) + cr_yield(); + + /* Mimics socket.c:connect(). */ + w5500ll_write_sock_reg(chip->spidev, socknum, remote_ip_addr, node); + w5500ll_write_sock_reg(chip->spidev, socknum, remote_port, uint16be_marshal(port)); + w5500_socket_cmd(socket, CMD_CONNECT); + for (;;) { + uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state); + switch (state) { + case STATE_TCP_SYNSENT: + cr_yield(); + break; + case STATE_TCP_ESTABLISHED: + return &socket->implements_net_stream_conn; + default: + goto restart; + } + } +} + +implements_net_packet_conn *w5500_if_udp_conn(implements_net_iface *_chip, uint16_t local_port) { + struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip); + assert(chip); + + struct _w5500_socket *socket = w5500_alloc_socket(chip); + if (!socket) + return NULL; + + if (!local_port) + local_port = w5500_alloc_local_port(chip); + + assert(socket->mode == W5500_MODE_NONE); + socket->mode = W5500_MODE_UDP; + socket->port = local_port; + socket->read_deadline_ns = 0; - return &chip->sockets[socknum].implements_net_packet_conn; + return &socket->implements_net_packet_conn; } /* tcp_listener methods *******************************************************/ -static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listener *_socket) { +static implements_net_stream_conn *w5500_tcplist_accept(implements_net_stream_listener *_socket) { ASSERT_SELF(stream_listener, TCP); restart: + if (!socket->list_open) + return NULL; + /* Mimics socket.c:socket(). */ w5500_socket_close(socket); w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_TCP); @@ -428,6 +556,14 @@ static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listen } } +static int w5500_tcplist_close(implements_net_stream_listener *_socket) { + ASSERT_SELF(stream_listener, TCP); + + socket->list_open = false; + w5500_tcp_maybe_free(chip, socket); + return 0; +} + /* tcp_conn methods ***********************************************************/ static ssize_t w5500_tcp_write(implements_net_stream_conn *_socket, void *buf, size_t count) { @@ -577,6 +713,7 @@ static int w5500_tcp_close(implements_net_stream_conn *_socket, bool rd, bool wr } } + w5500_tcp_maybe_free(chip, socket); return 0; } @@ -630,7 +767,7 @@ static void w5500_udp_alarm_handler(void *_arg) { } static ssize_t w5500_udp_recvfrom(implements_net_packet_conn *_socket, void *buf, size_t count, - struct net_ip4_addr *node, uint16_t *port) { + struct net_ip4_addr *ret_node, uint16_t *ret_port) { ASSERT_SELF(packet_conn, UDP); assert(buf); assert(count); @@ -669,11 +806,14 @@ static ssize_t w5500_udp_recvfrom(implements_net_packet_conn *_socket, void *buf * this; this is based off of socket.c:recvfrom(). */ uint8_t hdr[8]; w5500ll_read(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), hdr, sizeof(hdr)); - node->octets[0] = hdr[0]; - node->octets[1] = hdr[1]; - node->octets[2] = hdr[2]; - node->octets[3] = hdr[3]; - *port = uint16be_decode(&hdr[4]); + if (ret_node) { + ret_node->octets[0] = hdr[0]; + ret_node->octets[1] = hdr[1]; + ret_node->octets[2] = hdr[2]; + ret_node->octets[3] = hdr[3]; + } + if (ret_port) + *ret_port = uint16be_decode(&hdr[4]); uint16_t len = uint16be_decode(&hdr[6]); /* Now read the actual data. */ if (count > len) @@ -691,6 +831,6 @@ static int w5500_udp_close(implements_net_packet_conn *_socket) { ASSERT_SELF(packet_conn, UDP); w5500_socket_close(socket); - + w5500_free_socket(chip, socket); return 0; } diff --git a/libhw/w5500_ll.h b/libhw/w5500_ll.h index db66f01..1f578a9 100644 --- a/libhw/w5500_ll.h +++ b/libhw/w5500_ll.h @@ -10,12 +10,12 @@ #ifndef _LIBHW_W5500_LL_H_ #define _LIBHW_W5500_LL_H_ -#include <assert.h> /* for assert(), static_assert() */ #include <stdint.h> /* for uint{n}_t */ #include <string.h> /* for memcmp() */ -#include <libmisc/vcall.h> /* for VCALL() */ +#include <libmisc/assert.h> /* for assert(), static_assert() */ #include <libmisc/endian.h> /* for uint16be_t */ +#include <libmisc/vcall.h> /* for VCALL() */ #include <libhw/generic/net.h> /* for struct net_eth_addr, struct net_ip4_addr */ #include <libhw/generic/spi.h> /* for implements_spi */ diff --git a/libmisc/CMakeLists.txt b/libmisc/CMakeLists.txt index 0c5c019..1f30906 100644 --- a/libmisc/CMakeLists.txt +++ b/libmisc/CMakeLists.txt @@ -5,4 +5,7 @@ add_library(libmisc INTERFACE) target_include_directories(libmisc SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_sources(libmisc INTERFACE + assert.c +) target_compile_options(libmisc INTERFACE "$<$<COMPILE_LANGUAGE:C>:-fplan9-extensions>") diff --git a/libmisc/assert.c b/libmisc/assert.c new file mode 100644 index 0000000..7817ba3 --- /dev/null +++ b/libmisc/assert.c @@ -0,0 +1,23 @@ +/* libmisc/assert.h - More assertions + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <stdio.h> /* for fprintf(), stderr */ +#include <stdlib.h> /* for abort() */ + +#include <libmisc/assert.h> + +#ifndef NDEBUG +__attribute__((__noreturn__)) void +__assert_msg_fail(const char *expr, + const char *file, unsigned int line, const char *func, + const char *msg) { + fprintf(stderr, "error: %s:%u:%s(): assertion \"%s\" failed%s%s\n", + file, line, func, + expr, + msg ? ": " : "", msg); + abort(); +} +#endif diff --git a/libmisc/include/libmisc/assert.h b/libmisc/include/libmisc/assert.h new file mode 100644 index 0000000..c2c4697 --- /dev/null +++ b/libmisc/include/libmisc/assert.h @@ -0,0 +1,24 @@ +/* libmisc/assert.h - More assertions + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC_ASSERT_H_ +#define _LIBMISC_ASSERT_H_ + +#ifdef NDEBUG +# define assert_msg(expr, msg) ((void)0) +#else +# define assert_msg(expr, msg) do { if (!(expr)) __assert_msg_fail(#expr, __FILE__, __LINE__, __func__, msg); } while (0) +__attribute__((__noreturn__)) void +__assert_msg_fail(const char *expr, + const char *file, unsigned int line, const char *func, + const char *msg); +#endif + +#define assert(expr) assert_msg(expr, NULL) /* C89, POSIX-2001 */ +#define assert_notreached(msg) do { assert_msg(0, msg); __builtin_unreachable(); } while (0) +#define static_assert _Static_assert /* C11 */ + +#endif /* _LIBMISC_ASSERT_H_ */ diff --git a/libmisc/include/libmisc/endian.h b/libmisc/include/libmisc/endian.h index 24d7d42..b1bc55c 100644 --- a/libmisc/include/libmisc/endian.h +++ b/libmisc/include/libmisc/endian.h @@ -7,9 +7,10 @@ #ifndef _LIBMISC_ENDIAN_H_ #define _LIBMISC_ENDIAN_H_ -#include <assert.h> #include <stdint.h> /* for uint{n}_t */ +#include <libmisc/assert.h> + /* Big endian *****************************************************************/ typedef struct { diff --git a/libmisc/include/libmisc/rand.h b/libmisc/include/libmisc/rand.h new file mode 100644 index 0000000..bdb0db9 --- /dev/null +++ b/libmisc/include/libmisc/rand.h @@ -0,0 +1,46 @@ +/* libmisc/rand.h - Non-crytpographic random-number utilities + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC_RAND_H_ +#define _LIBMISC_RAND_H_ + +#include <stdint.h> /* for uint{n}_t, UINT{n}_C() */ +#include <stdlib.h> /* for random() */ + +#include <libmisc/assert.h> + +/** + * Return a psuedo-random number in the half-open interval [0,cnt). + * `cnt` must not be greater than 1<<63. + */ +static inline uint64_t rand_uint63n(uint64_t cnt) { + if (cnt <= UINT64_C(1)<<31) { + uint32_t fair_cnt = ((UINT32_C(1)<<31) / cnt) * cnt; + uint32_t rnd; + do { + rnd = random(); + } while (rnd >= fair_cnt); + return rnd % cnt; + } else if (cnt <= UINT64_C(1)<<62) { + uint64_t fair_cnt = ((UINT64_C(1)<<62) / cnt) * cnt; + uint64_t rnd; + do { + rnd = (random() << 31) | random(); + } while (rnd >= fair_cnt); + return rnd % cnt; + } else if (cnt <= UINT64_C(1)<<63) { + uint64_t fair_cnt = ((UINT64_C(1)<<63) / cnt) * cnt; + uint64_t rnd; + do { + rnd = (random() << 62) | (random() << 31) | random(); + } while (rnd >= fair_cnt); + return rnd % cnt; + } else { + assert_notreached("cnt is out of bounds"); + } +} + +#endif /* _LIBMISC_RAND_H_ */ diff --git a/libmisc/include/libmisc/vcall.h b/libmisc/include/libmisc/vcall.h index ea9402e..9b54c06 100644 --- a/libmisc/include/libmisc/vcall.h +++ b/libmisc/include/libmisc/vcall.h @@ -7,9 +7,10 @@ #ifndef _LIBMISC_VCALL_H_ #define _LIBMISC_VCALL_H_ -#include <assert.h> /* for assert() and static_assert() */ #include <stddef.h> /* for offsetof() */ +#include <libmisc/assert.h> + #define VCALL(o, m, ...) \ ({ \ assert(o); \ diff --git a/libusb/CMakeLists.txt b/libusb/CMakeLists.txt index f5d6ae2..c23f30e 100644 --- a/libusb/CMakeLists.txt +++ b/libusb/CMakeLists.txt @@ -14,4 +14,5 @@ target_link_libraries(libusb INTERFACE tinyusb_board libcr_ipc + libmisc ) diff --git a/libusb/include/libusb/tusb_helpers.h b/libusb/include/libusb/tusb_helpers.h index a250ca7..1b4e48e 100644 --- a/libusb/include/libusb/tusb_helpers.h +++ b/libusb/include/libusb/tusb_helpers.h @@ -1,4 +1,4 @@ -/* Generated by `./libusb/include/libusb/tusb_helpers.h.gen `. DO NOT EDIT! */ +/* Generated by `libusb/include/libusb/tusb_helpers.h.gen `. DO NOT EDIT! */ /* libusb/tusb_helpers.h - Preprocessor macros that I think should be included in TinyUSB * * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> @@ -36,16 +36,16 @@ * dated 2000-03-29. There is no longer any mention of this on usb.org, but I found a copy at * http://www.baiheee.com/Documents/090518/090518112619/USB_LANGIDs.pdf * - * So how does the USB-IF defined LANGIDs these days? + * So if they don't publish that anymore, how does the USB-IF define LANGIDs these days? * * https://www.usb.org/deprecated-links-and-tools says "To get the latest LANGID definitions go to * https://docs.microsoft.com/en-us/windows/desktop/intl/language-identifier-constants-and-strings. This * page will change as new LANGIDs are added." That page has no list of LANGIDs, but says "For the * predefined primary language identifiers with their valid sublanguage identifiers, see * [\[MS-LCID\]: Windows Language Code Identifier (LCID) - * Reference](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-565326e84c37f)." + * Reference](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f)." * That page at the time of this writing as a PDF marked as version 16.0, dated 2024-04-24: - * https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-LCID/%5bMS-LCID%5d.pdf + * https://winprotocoldocs-bhdugrdyduf5h2e4.b02.azurefd.net/MS-LCID/%5bMS-LCID%5d.pdf * [MS-LCID] defines an LCID as a 32-bit value consisting of a 16-bit a "Language ID", a 4-bit "Sort * ID", and 12 reserved bits. * diff --git a/libusb/include/libusb/tusb_helpers.h.gen b/libusb/include/libusb/tusb_helpers.h.gen index 326ca22..5a5d1ac 100755 --- a/libusb/include/libusb/tusb_helpers.h.gen +++ b/libusb/include/libusb/tusb_helpers.h.gen @@ -42,16 +42,16 @@ cat <<'EOT' * dated 2000-03-29. There is no longer any mention of this on usb.org, but I found a copy at * http://www.baiheee.com/Documents/090518/090518112619/USB_LANGIDs.pdf * - * So how does the USB-IF defined LANGIDs these days? + * So if they don't publish that anymore, how does the USB-IF define LANGIDs these days? * * https://www.usb.org/deprecated-links-and-tools says "To get the latest LANGID definitions go to * https://docs.microsoft.com/en-us/windows/desktop/intl/language-identifier-constants-and-strings. This * page will change as new LANGIDs are added." That page has no list of LANGIDs, but says "For the * predefined primary language identifiers with their valid sublanguage identifiers, see * [\[MS-LCID\]: Windows Language Code Identifier (LCID) - * Reference](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-565326e84c37f)." + * Reference](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f)." * That page at the time of this writing as a PDF marked as version 16.0, dated 2024-04-24: - * https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-LCID/%5bMS-LCID%5d.pdf + * https://winprotocoldocs-bhdugrdyduf5h2e4.b02.azurefd.net/MS-LCID/%5bMS-LCID%5d.pdf * [MS-LCID] defines an LCID as a 32-bit value consisting of a 16-bit a "Language ID", a 4-bit "Sort * ID", and 12 reserved bits. * @@ -65,7 +65,7 @@ cat <<'EOT' */ EOT if [[ ! -f 3rd-party/MS-LCID.pdf || 3rd-party/MS-LCID.pdf -ot $0 ]]; then - wget --no-use-server-timestamps -O 3rd-party/MS-LCID.pdf 'https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-LCID/%5bMS-LCID%5d.pdf' + wget --no-use-server-timestamps -O 3rd-party/MS-LCID.pdf 'https://winprotocoldocs-bhdugrdyduf5h2e4.b02.azurefd.net/MS-LCID/%5bMS-LCID%5d.pdf' fi if [[ ! -f 3rd-party/MS-LCID.txt || 3rd-party/MS-LCID.txt -ot 3rd-party/MS-LCID.pdf ]]; then pdftotext -layout 3rd-party/MS-LCID.pdf diff --git a/libusb/usb_common.c b/libusb/usb_common.c index ceabad3..17dba0a 100644 --- a/libusb/usb_common.c +++ b/libusb/usb_common.c @@ -7,11 +7,12 @@ #include <stdint.h> /* for uint{n}_t types */ #include <stddef.h> /* for size_t */ #include <string.h> /* memcpy(newlib) */ -#include <assert.h> /* for assert(newlib) */ #include <stdlib.h> /* for malloc(pico_malloc), realloc(pico_malloc), reallocarray(pico_malloc) */ #include "bsp/board_api.h" /* for board_init(), board_init_after_usb(), board_usb_get_serial(TinyUSB) */ #include "tusb.h" /* for various tusb_*_t types */ +#include <libmisc/assert.h> + #include <libusb/tusb_helpers.h> /* for LANGID_*, TU_UTF16() */ #include <libusb/usb_common.h> |