diff options
Diffstat (limited to 'libdhcp')
-rw-r--r-- | libdhcp/CMakeLists.txt | 13 | ||||
-rw-r--r-- | libdhcp/dhcp_client.c | 176 | ||||
-rw-r--r-- | libdhcp/dhcp_common.c | 104 | ||||
-rw-r--r-- | libdhcp/dhcp_common.h | 83 | ||||
-rw-r--r-- | libdhcp/include/libdhcp/client.h | 4 | ||||
-rw-r--r-- | libdhcp/tests/config.h | 25 | ||||
l--------- | libdhcp/tests/test.h | 1 | ||||
-rw-r--r-- | libdhcp/tests/test_client.c | 123 |
8 files changed, 361 insertions, 168 deletions
diff --git a/libdhcp/CMakeLists.txt b/libdhcp/CMakeLists.txt index 2ded1f4..f14e46d 100644 --- a/libdhcp/CMakeLists.txt +++ b/libdhcp/CMakeLists.txt @@ -1,13 +1,20 @@ -# libdhcp/CMakeLists.txt - TODO +# libdhcp/CMakeLists.txt - A DHCP client # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later add_library(libdhcp INTERFACE) -target_include_directories(libdhcp SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_include_directories(libdhcp PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) target_sources(libdhcp INTERFACE dhcp_client.c + dhcp_common.c ) target_link_libraries(libdhcp INTERFACE libmisc + libhw_generic ) + +if (ENABLE_TESTS) + add_lib_test(libdhcp test_client) + target_link_libraries(test_client libcr libhw_cr) +endif() diff --git a/libdhcp/dhcp_client.c b/libdhcp/dhcp_client.c index c5da59b..1164355 100644 --- a/libdhcp/dhcp_client.c +++ b/libdhcp/dhcp_client.c @@ -1,6 +1,6 @@ /* libdhcp/dhcp_client.c - A DHCP client * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later * * ----------------------------------------------------------------------------- @@ -86,9 +86,8 @@ #include <string.h> /* for strlen(), memcpy(), memset() */ -#include <libmisc/rand.h> -#include <libmisc/vcall.h> #include <libhw/generic/alarmclock.h> +#include <libmisc/rand.h> #define LOG_NAME DHCP #include <libmisc/log.h> @@ -125,8 +124,8 @@ enum requirement { struct dhcp_client { /* Static. */ - implements_net_iface *iface; - implements_net_packet_conn *sock; + lo_interface net_iface iface; + lo_interface net_packet_conn sock; struct net_eth_addr self_eth_addr; char *self_hostname; size_t self_id_len; @@ -167,10 +166,23 @@ struct dhcp_client { }; +static const char *state_strs[] = { + [STATE_INIT] = "INIT", + [STATE_SELECTING] = "SELECTING", + [STATE_REQUESTING] = "REQUESTING", + [STATE_BOUND] = "BOUND", + [STATE_RENEWING] = "RENEWING", + [STATE_REBINDING] = "REBINDING", + + [STATE_INIT_REBOOT] = "INIT_REBOOT", + [STATE_REBOOTING] = "REBOOTING", +}; + /** - * For convenience in switch blocks, a list of the states that, when - * msgtyp==DHCP_MSGTYP_REQUEST, dhcp_client_send() has assert()ed the state is - * not. + * IMPOSSIBLE_REQUEST_STATES is a convenience macro for use in switch + * blocks; it is a list of the states that (when + * msgtyp==DHCP_MSGTYP_REQUEST) dhcp_client_send() has assert()ed the + * state is not. */ #define IMPOSSIBLE_REQUEST_STATES \ STATE_INIT: \ @@ -240,11 +252,8 @@ static inline enum requirement dhcp_table5(typeof((struct dhcp_client){}.state) * @param client->lease_client_addr (sometimes) * @param client->lease_server_id (sometimes) * @param client->sock - * - * @return client->last_sent_msgtyp - * @return whether there was an error */ -static bool dhcp_client_send(struct dhcp_client *client, uint8_t msgtyp, const char *errstr, struct dhcp_msg *scratch_msg) { +static error dhcp_client_send(struct dhcp_client *client, uint8_t msgtyp, const char *errstr, struct dhcp_msg *scratch_msg) { /**********************************************************************\ * Preconditions * \**********************************************************************/ @@ -286,7 +295,7 @@ static bool dhcp_client_send(struct dhcp_client *client, uint8_t msgtyp, const c * Build the message * \**********************************************************************/ - *scratch_msg = (struct dhcp_msg){0}; + *scratch_msg = (struct dhcp_msg){}; size_t optlen = 0; /* Base structure. @@ -301,7 +310,7 @@ static bool dhcp_client_send(struct dhcp_client *client, uint8_t msgtyp, const c 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; + secs = (LO_CALL(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 @@ -449,14 +458,15 @@ static bool dhcp_client_send(struct dhcp_client *client, uint8_t msgtyp, const c /**********************************************************************\ * Send * \**********************************************************************/ - ssize_t r = VCALL(client->sock, sendto, scratch_msg, DHCP_MSG_BASE_SIZE + optlen, - client_broadcasts ? net_ip4_addr_broadcast : client->lease_server_id, DHCP_PORT_SERVER); - if (r < 0) { - debugf("error: sendto: %zd", r); - return true; + log_debugln("state ", state_strs[client->state], ": sending DHCP ", dhcp_msgtyp_str(msgtyp)); + error err = LO_CALL(client->sock, sendto, scratch_msg, DHCP_MSG_BASE_SIZE + optlen, + client_broadcasts ? net_ip4_addr_broadcast : client->lease_server_id, DHCP_PORT_SERVER); + if (!ERROR_IS_NULL(err)) { + log_debugln("error: sendto: ", (error, err)); + return err; } client->last_sent_msgtyp = msgtyp; - return false; + return ERROR_NULL; } struct dhcp_recv_msg { @@ -552,24 +562,20 @@ static inline enum requirement dhcp_table3(uint8_t req_msgtyp, uint8_t resp_msgt * @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) { +static error 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; + size_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) + size_t_or_error r = LO_CALL(client->sock, recvfrom, &ret->raw, sizeof(ret->raw), &srv_addr, &srv_port); + if (r.is_err) /* msg_len is -errno */ - return msg_len; + return r.err; + msg_len = r.size_t; /* Validate L3: IP */ /* Don't validate that srv_addr matches client->server_id @@ -582,10 +588,10 @@ static ssize_t dhcp_client_recv(struct dhcp_client *client, struct dhcp_recv_msg goto ignore; /* Validate L5: DHCP. */ - if ((size_t)msg_len < DHCP_MSG_BASE_SIZE + sizeof(dhcp_magic_cookie)) + if (msg_len < DHCP_MSG_BASE_SIZE + sizeof(dhcp_magic_cookie)) /* ignore impossibly short message */ goto ignore; - if ((size_t)msg_len > sizeof(ret->raw)) + if (msg_len > sizeof(ret->raw)) /* ignore message that is larger than the specified * DHCP_OPT_DHCP_MAX_MSG_SIZE */ goto ignore; @@ -685,14 +691,7 @@ static ssize_t dhcp_client_recv(struct dhcp_client *client, struct dhcp_recv_msg goto ignore; } - return 0; -} - -/** @return true if there's a conflict, false if the addr appears to be unused */ -static bool dhcp_check_conflict(implements_net_packet_conn *sock, struct net_ip4_addr addr) { - assert(sock); - ssize_t v = VCALL(sock, sendto, "CHECK_IP_CONFLICT", 17, addr, 5000); - return v != NET_EARP_TIMEOUT; + return ERROR_NULL; } static void dhcp_client_take_lease(struct dhcp_client *client, struct dhcp_recv_msg *msg, bool ifup) { @@ -726,39 +725,46 @@ static void dhcp_client_take_lease(struct dhcp_client *client, struct dhcp_recv_ 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){ + if (ifup) { + log_infoln("applying configuration to ", (net_eth_addr, client->self_eth_addr)); + log_infoln(":: addr = ", (net_ip4_addr, client->lease_client_addr)); + log_infoln(":: gateway_addr = ", (net_ip4_addr, client->lease_auxdata.gateway_addr)); + log_infoln(":: subnet_mask = ", (net_ip4_addr, client->lease_auxdata.subnet_mask)); + LO_CALL(client->iface, ifup, (struct net_iface_config){ .addr = client->lease_client_addr, .gateway_addr = client->lease_auxdata.gateway_addr, .subnet_mask = client->lease_auxdata.subnet_mask, }); + } } static void dhcp_client_setstate(struct dhcp_client *client, typeof((struct dhcp_client){}.state) newstate, uint8_t send_msgtyp, const char *errstr, struct dhcp_recv_msg *scratch_msg) { - if (send_msgtyp) - (void)dhcp_client_send(client, send_msgtyp, errstr, &scratch_msg->raw); + if (send_msgtyp) { + error err = dhcp_client_send(client, send_msgtyp, errstr, &scratch_msg->raw); + error_cleanup(&err); + } client->state = newstate; } [[noreturn]] static void dhcp_client_run(struct dhcp_client *client, struct dhcp_recv_msg *scratch_msg) { assert(client); - ssize_t r; - /* State transition diagram: https://datatracker.ietf.org/doc/html/rfc2131#page-35 */ for (;;) { + [[gnu::cleanup(error_cleanup)]] error err = {}; + log_debugln("loop: state=", state_strs[client->state]); switch (client->state) { case STATE_INIT: client->xid = rand_uint63n(UINT32_MAX); - client->time_ns_init = VCALL(bootclock, get_time_ns); + client->time_ns_init = LO_CALL(bootclock, get_time_ns); dhcp_client_setstate(client, STATE_SELECTING, DHCP_MSGTYP_DISCOVER, NULL, scratch_msg); break; case STATE_SELECTING: - VCALL(client->sock, set_read_deadline, client->time_ns_init+CONFIG_DHCP_SELECTING_NS); - switch ((r = dhcp_client_recv(client, scratch_msg))) { - case 0: + LO_CALL(client->sock, set_recv_deadline, client->time_ns_init+CONFIG_DHCP_SELECTING_NS); + LM_PARTIAL_SWITCH ((err = dhcp_client_recv(client, scratch_msg)).num) { + case E_NOERROR: switch (scratch_msg->option_dat[scratch_msg->options[DHCP_OPT_DHCP_MSG_TYPE].off]) { case DHCP_MSGTYP_OFFER: /* Accept the first offer. */ @@ -769,24 +775,24 @@ static void dhcp_client_setstate(struct dhcp_client *client, /* ignore */ } break; - case -NET_ERECV_TIMEOUT: + case E_NET_ERECV_TIMEOUT: dhcp_client_setstate(client, STATE_INIT, 0, NULL, scratch_msg); break; default: - assert(r < 0); - debugf("error: recvfrom: %d", r); + log_debugln("error: recvfrom: ", (error, err)); } break; case STATE_REQUESTING: - VCALL(client->sock, set_read_deadline, 0); - switch ((r = dhcp_client_recv(client, scratch_msg))) { - case 0: + LO_CALL(client->sock, set_recv_deadline, 0); + LM_PARTIAL_SWITCH ((err = dhcp_client_recv(client, scratch_msg)).num) { + case E_NOERROR: switch (scratch_msg->option_dat[scratch_msg->options[DHCP_OPT_DHCP_MSG_TYPE].off]) { case DHCP_MSGTYP_NAK: dhcp_client_setstate(client, STATE_INIT, 0, NULL, scratch_msg); break; case DHCP_MSGTYP_ACK: - if (dhcp_check_conflict(client->sock, client->lease_client_addr)) { + if (LO_CALL(client->iface, arp_ping, client->lease_client_addr)) { + log_debugln("IP ", (net_ip4_addr, client->lease_client_addr), " is already in use"); dhcp_client_setstate(client, STATE_INIT, DHCP_MSGTYP_DECLINE, "IP is already in use", scratch_msg); } else { dhcp_client_take_lease(client, scratch_msg, true); @@ -798,34 +804,32 @@ static void dhcp_client_setstate(struct dhcp_client *client, } break; default: - assert(r < 0); - debugf("error: recvfrom: %d", r); + log_debugln("error: recvfrom: ", (error, err)); } break; case STATE_BOUND: - VCALL(client->sock, set_read_deadline, client->lease_time_ns_t1); - switch ((r = dhcp_client_recv(client, scratch_msg))) { - case 0: + LO_CALL(client->sock, set_recv_deadline, client->lease_time_ns_t1); + LM_PARTIAL_SWITCH ((err = dhcp_client_recv(client, scratch_msg)).num) { + case E_NOERROR: /* discard */ break; - case -NET_ERECV_TIMEOUT: + case E_NET_ERECV_TIMEOUT: dhcp_client_setstate(client, STATE_RENEWING, DHCP_MSGTYP_REQUEST, NULL, scratch_msg); break; default: - assert(r < 0); - debugf("error: recvfrom: %d", r); + log_debugln("error: recvfrom: ", (error, err)); } break; case STATE_RENEWING: client->xid = rand_uint63n(UINT32_MAX); - client->time_ns_init = VCALL(bootclock, get_time_ns); + client->time_ns_init = LO_CALL(bootclock, get_time_ns); - VCALL(client->sock, set_read_deadline, client->lease_time_ns_t2); - switch ((r = dhcp_client_recv(client, scratch_msg))) { - case 0: + LO_CALL(client->sock, set_recv_deadline, client->lease_time_ns_t2); + LM_PARTIAL_SWITCH ((err = dhcp_client_recv(client, scratch_msg)).num) { + case E_NOERROR: switch (scratch_msg->option_dat[scratch_msg->options[DHCP_OPT_DHCP_MSG_TYPE].off]) { case DHCP_MSGTYP_NAK: - VCALL(client->iface, ifdown); + LO_CALL(client->iface, ifdown); dhcp_client_setstate(client, STATE_INIT, 0, NULL, scratch_msg); break; case DHCP_MSGTYP_ACK: @@ -836,22 +840,21 @@ static void dhcp_client_setstate(struct dhcp_client *client, /* ignore */ } break; - case -NET_ERECV_TIMEOUT: + case E_NET_ERECV_TIMEOUT: client->lease_server_id = net_ip4_addr_zero; dhcp_client_setstate(client, STATE_REBINDING, DHCP_MSGTYP_REQUEST, NULL, scratch_msg); break; default: - assert(r < 0); - debugf("error: recvfrom: %d", r); + log_debugln("error: recvfrom: ", (error, err)); } break; case STATE_REBINDING: - VCALL(client->sock, set_read_deadline, client->lease_time_ns_end); - switch ((r = dhcp_client_recv(client, scratch_msg))) { - case 0: + LO_CALL(client->sock, set_recv_deadline, client->lease_time_ns_end); + LM_PARTIAL_SWITCH ((err = dhcp_client_recv(client, scratch_msg)).num) { + case E_NOERROR: switch (scratch_msg->option_dat[scratch_msg->options[DHCP_OPT_DHCP_MSG_TYPE].off]) { case DHCP_MSGTYP_NAK: - VCALL(client->iface, ifdown); + LO_CALL(client->iface, ifdown); dhcp_client_setstate(client, STATE_BOUND, 0, NULL, scratch_msg); break; case DHCP_MSGTYP_ACK: @@ -862,13 +865,12 @@ static void dhcp_client_setstate(struct dhcp_client *client, /* ignore */ } break; - case -NET_ERECV_TIMEOUT: - VCALL(client->iface, ifdown); + case E_NET_ERECV_TIMEOUT: + LO_CALL(client->iface, ifdown); dhcp_client_setstate(client, STATE_BOUND, 0, NULL, scratch_msg); break; default: - assert(r < 0); - debugf("error: recvfrom: %d", r); + log_debugln("error: recvfrom: ", (error, err)); } break; case STATE_INIT_REBOOT: @@ -880,9 +882,9 @@ static void dhcp_client_setstate(struct dhcp_client *client, } } -[[noreturn]] void dhcp_client_main(implements_net_iface *iface, +[[noreturn]] void dhcp_client_main(lo_interface net_iface iface, char *self_hostname) { - assert(iface); + assert(!LO_IS_NULL(iface)); /* Even though a client ID is optional and not meaningful for * us (the best we can do is to duplicate .chaddr), systemd's @@ -890,14 +892,14 @@ static void dhcp_client_setstate(struct dhcp_client *client, * require it to be set. */ struct {uint8_t typ; struct net_eth_addr dat;} client_id = { DHCP_HTYPE_ETHERNET, - VCALL(iface, hwaddr), + LO_CALL(iface, hwaddr), }; struct dhcp_client client = { /* Static. */ .iface = iface, - .sock = VCALL(iface, udp_conn, DHCP_PORT_CLIENT), - .self_eth_addr = VCALL(iface, hwaddr), + .sock = LO_CALL(iface, udp_conn, DHCP_PORT_CLIENT), + .self_eth_addr = LO_CALL(iface, hwaddr), .self_hostname = self_hostname, .self_id_len = sizeof(client_id), .self_id_dat = &client_id, @@ -905,7 +907,7 @@ static void dhcp_client_setstate(struct dhcp_client *client, /* Mutable. */ .state = STATE_INIT, }; - assert(client.sock); + assert(!LO_IS_NULL(client.sock)); struct dhcp_recv_msg scratch; diff --git a/libdhcp/dhcp_common.c b/libdhcp/dhcp_common.c new file mode 100644 index 0000000..d691836 --- /dev/null +++ b/libdhcp/dhcp_common.c @@ -0,0 +1,104 @@ +/* libdhcp/dhcp_common.c - Base definitions for the DHCP protocol + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include "dhcp_common.h" + +/** + * DHCP Options + * https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#options + */ +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 + */ +const char *dhcp_msgtyp_str(uint8_t typ) { + switch (typ) { + case DHCP_MSGTYP_DISCOVER: return "DHCP_MSGTYP_DISCOVER"; + case DHCP_MSGTYP_OFFER: return "DHCP_MSGTYP_OFFER"; + case DHCP_MSGTYP_REQUEST: return "DHCP_MSGTYP_REQUEST"; + case DHCP_MSGTYP_DECLINE: return "DHCP_MSGTYP_DECLINE"; + case DHCP_MSGTYP_ACK: return "DHCP_MSGTYP_ACK"; + case DHCP_MSGTYP_NAK: return "DHCP_MSGTYP_NAK"; + case DHCP_MSGTYP_RELEASE: return "DHCP_MSGTYP_RELEASE"; + case DHCP_MSGTYP_INFORM: return "DHCP_MSGTYP_INFORM"; + default: return const_byte_str(typ); + } +} diff --git a/libdhcp/dhcp_common.h b/libdhcp/dhcp_common.h index b265df3..a0cdd3c 100644 --- a/libdhcp/dhcp_common.h +++ b/libdhcp/dhcp_common.h @@ -1,6 +1,6 @@ /* libdhcp/dhcp_common.h - Base definitions for the DHCP protocol * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later * * ----------------------------------------------------------------------------- @@ -67,7 +67,9 @@ #ifndef _LIBDHCP_DHCP_COMMON_H_ #define _LIBDHCP_DHCP_COMMON_H_ -#include <libmisc/endian.h> +#include <libhw/generic/net.h> /* for struct net_ip4_addr */ +#include <libmisc/endian.h> /* for uint{n}be_t */ +#include <libmisc/log.h> /* for const_byte_str() */ /* Config *********************************************************************/ @@ -214,80 +216,7 @@ static const uint8_t dhcp_magic_cookie[] = {99, 130, 83, 99}; /* ... */ #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; - } -}; +bool dhcp_opt_length_is_valid(uint8_t opt, uint16_t len); /** * DHCP Message Type 53 Values @@ -302,4 +231,6 @@ static inline bool dhcp_opt_length_is_valid(uint8_t opt, uint16_t len) { #define DHCP_MSGTYP_RELEASE ((uint8_t) 7) /* RFC2132, client->server */ #define DHCP_MSGTYP_INFORM ((uint8_t) 8) /* RFC2132, client->server */ +const char *dhcp_msgtyp_str(uint8_t typ); + #endif /* _LIBDHCP_DHCP_COMMON_H_ */ diff --git a/libdhcp/include/libdhcp/client.h b/libdhcp/include/libdhcp/client.h index c3cb76f..f81e9b1 100644 --- a/libdhcp/include/libdhcp/client.h +++ b/libdhcp/include/libdhcp/client.h @@ -1,6 +1,6 @@ /* libdhcp/client.h - A DHCP client * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later * * ----------------------------------------------------------------------------- @@ -69,7 +69,7 @@ #include <libhw/generic/net.h> -[[noreturn]] void dhcp_client_main(implements_net_iface *iface, +[[noreturn]] void dhcp_client_main(lo_interface net_iface iface, char *self_hostname); #endif /* _LIBDHCP_CLIENT_H_ */ diff --git a/libdhcp/tests/config.h b/libdhcp/tests/config.h new file mode 100644 index 0000000..53079fe --- /dev/null +++ b/libdhcp/tests/config.h @@ -0,0 +1,25 @@ +/* config.h - Compile-time configuration for the libdhcp tests + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +#define CONFIG_COROUTINE_STACK_SIZE_DEFAULT (16*1024) +#define CONFIG_COROUTINE_NAME_LEN 16 +#define CONFIG_COROUTINE_NUM 16 + +#define CONFIG_COROUTINE_MEASURE_STACK 1 +#define CONFIG_COROUTINE_PROTECT_STACK 1 +#define CONFIG_COROUTINE_DEBUG 1 +#define CONFIG_COROUTINE_VALGRIND 1 +#define CONFIG_COROUTINE_GDB 1 + +#define CONFIG_DHCP_CAN_RECV_UNICAST_IP_WITHOUT_IP 0 /* bool */ +#define CONFIG_DHCP_DEBUG 1 /* bool */ +#define CONFIG_DHCP_OPT_SIZE 312 /* minimum of 312 */ +#define CONFIG_DHCP_SELECTING_NS (5*NS_PER_S) + +#endif /* _CONFIG_H_ */ diff --git a/libdhcp/tests/test.h b/libdhcp/tests/test.h new file mode 120000 index 0000000..2fb1bd5 --- /dev/null +++ b/libdhcp/tests/test.h @@ -0,0 +1 @@ +../../libmisc/tests/test.h
\ No newline at end of file diff --git a/libdhcp/tests/test_client.c b/libdhcp/tests/test_client.c new file mode 100644 index 0000000..cf76653 --- /dev/null +++ b/libdhcp/tests/test_client.c @@ -0,0 +1,123 @@ +/* libdhcp/tests/test_client.c - Tests for libdhcp client + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> /* for memcpy() */ + +#include <libcr/coroutine.h> +#include <libhw/generic/net.h> +#include <libhw/host_alarmclock.h> + +#include <libdhcp/client.h> + +#include "test.h" + +/******************************************************************************/ + +struct test_udp { +}; +LO_IMPLEMENTATION_H(io_closer, struct test_udp, test_udp); +LO_IMPLEMENTATION_C(io_closer, struct test_udp, test_udp, static); +LO_IMPLEMENTATION_H(net_packet_conn, struct test_udp, test_udp); +LO_IMPLEMENTATION_C(net_packet_conn, struct test_udp, test_udp, static); + +static error test_udp_sendto(struct test_udp *LM_UNUSED(self), void *LM_UNUSED(buf), size_t LM_UNUSED(len), struct net_ip4_addr LM_UNUSED(node), uint16_t LM_UNUSED(port)) { + static unsigned cnt = 0; + if (cnt++ % 2 == 0) + return error_new(E_EUNKNOWN); + return ERROR_NULL; +} + +static size_t_or_error test_udp_recvfrom(struct test_udp *LM_UNUSED(self), void *buf, size_t len, struct net_ip4_addr *ret_node, uint16_t *ret_port) { + static const uint8_t resp_offer[] = {0x02,0x01,0x06,0x00,0xE8,0x40,0xC6,0x79,0x00,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0xC0,0xA8,0x0A,0x78,0xC0,0xA8,0x0A,0x01,0x00,0x00,0x00,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x63,0x82,0x53,0x63,0x35,0x01,0x02,0x36,0x04,0xC0,0xA8,0x0A,0x01,0x33,0x04,0x00,0x00,0xA8,0xC0,0x3A,0x04,0x00,0x00,0x54,0x60,0x3B,0x04,0x00,0x00,0x93,0xA8,0x01,0x04,0xFF,0xFF,0xFF,0x00,0x1C,0x04,0xC0,0xA8,0x0A,0xFF,0x03,0x04,0xC0,0xA8,0x0A,0x01,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + static const uint8_t resp_ack[] = {0x02,0x01,0x06,0x00,0xE8,0x40,0xC6,0x79,0x00,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0xC0,0xA8,0x0A,0x78,0xC0,0xA8,0x0A,0x01,0x00,0x00,0x00,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x63,0x82,0x53,0x63,0x35,0x01,0x05,0x36,0x04,0xC0,0xA8,0x0A,0x01,0x33,0x04,0x00,0x00,0xA8,0xC0,0x3A,0x04,0x00,0x00,0x54,0x60,0x3B,0x04,0x00,0x00,0x93,0xA8,0x01,0x04,0xFF,0xFF,0xFF,0x00,0x1C,0x04,0xC0,0xA8,0x0A,0xFF,0x03,0x04,0xC0,0xA8,0x0A,0x01,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + static unsigned cnt = 0; + const void *resp; + size_t resp_len; + switch (cnt++) { + case 0: return ERROR_NEW_ERR(size_t, error_new(E_EUNKNOWN)); + case 1: resp = resp_offer; resp_len = sizeof(resp_offer); break; + case 2: return ERROR_NEW_ERR(size_t, error_new(E_EUNKNOWN)); + case 3: resp = resp_ack; resp_len = sizeof(resp_ack); break; + default: return ERROR_NEW_ERR(size_t, error_new(E_NET_ERECV_TIMEOUT)); + } + test_assert(len >= resp_len); + memcpy(buf, resp, resp_len); + *ret_node = ((struct net_ip4_addr){{192,168,10,1}}); + *ret_port = 67; + return ERROR_NEW_VAL(size_t, resp_len); +} + +static void test_udp_set_recv_deadline(struct test_udp *LM_UNUSED(self), uint64_t LM_UNUSED(ns_since_boot)) { + /* Do nothing? */ +} + +static error test_udp_close(struct test_udp *LM_UNUSED(self)) { + assert_notreached("not implemented"); +} + +/******************************************************************************/ + +struct test_iface { + struct net_iface_config cfg; + + struct test_udp conn; +}; +LO_IMPLEMENTATION_H(net_iface, struct test_iface, test); +LO_IMPLEMENTATION_C(net_iface, struct test_iface, test, static); + +static struct net_eth_addr test_hwaddr(struct test_iface *LM_UNUSED(self)) { + struct net_eth_addr ret = {{1, 2, 3, 4, 5, 6}}; + return ret; +} + +static void test_ifup(struct test_iface *LM_UNUSED(self), struct net_iface_config LM_UNUSED(cfg)) { + cr_exit(); +} + +static void test_ifdown(struct test_iface *LM_UNUSED(self)) { + assert_notreached("not implemented"); +} + +static bool test_arp_ping(struct test_iface *LM_UNUSED(self), struct net_ip4_addr LM_UNUSED(addr)) { + return false; +} + +static lo_interface net_stream_listener test_tcp_listen(struct test_iface *LM_UNUSED(self), uint16_t LM_UNUSED(local_port)) { + assert_notreached("not implemented"); +} + +static net_stream_conn_or_error test_tcp_dial(struct test_iface *LM_UNUSED(self), struct net_ip4_addr LM_UNUSED(remote_node), uint16_t LM_UNUSED(remote_port)) { + assert_notreached("not implemented"); +} + +static lo_interface net_packet_conn test_udp_conn(struct test_iface *self, uint16_t local_port) { + bool once = false; + test_assert(local_port == 68); + test_assert(!once); + once = true; + return LO_BOX(net_packet_conn, &self->conn); +} + +/******************************************************************************/ + +COROUTINE dhcp_cr(void *) { + cr_begin(); + struct test_iface iface = {}; + dhcp_client_main(LO_BOX(net_iface, &iface), "test-client"); + cr_end(); +} + +int main() { + struct hostclock clock_monotonic = { + .clock_id = CLOCK_MONOTONIC, + }; + bootclock = LO_BOX(alarmclock, &clock_monotonic); + + coroutine_add("dhcp", dhcp_cr, NULL); + coroutine_main(); + return 0; +} |