diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 899357a875..022308340d 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -26,6 +26,7 @@ PSEUDOMODULES += evtimer_mbox PSEUDOMODULES += evtimer_on_ztimer PSEUDOMODULES += fmt_% PSEUDOMODULES += gnrc_dhcpv6_% +PSEUDOMODULES += gnrc_dhcpv6_client_mud_url PSEUDOMODULES += gnrc_ipv6_default PSEUDOMODULES += gnrc_ipv6_ext_frag_stats PSEUDOMODULES += gnrc_ipv6_router diff --git a/sys/include/net/dhcpv6.h b/sys/include/net/dhcpv6.h index 0d3e9ffed9..817d9d96e5 100644 --- a/sys/include/net/dhcpv6.h +++ b/sys/include/net/dhcpv6.h @@ -69,6 +69,7 @@ extern "C" { * delegation (IA_PD) option */ #define DHCPV6_OPT_IAPFX (26U) /**< IA prefix option */ #define DHCPV6_OPT_SMR (82U) /**< SOL_MAX_RT option */ +#define DHCPV6_OPT_MUD_URL (112U) /**< MUD URL option (see RFC 8520) */ /** @} */ /** diff --git a/sys/include/net/dhcpv6/client.h b/sys/include/net/dhcpv6/client.h index f61bedcb50..f727fc3ac3 100644 --- a/sys/include/net/dhcpv6/client.h +++ b/sys/include/net/dhcpv6/client.h @@ -45,7 +45,7 @@ extern "C" { * @brief Static length of the DUID */ #define DHCPV6_CLIENT_DUID_LEN (sizeof(dhcpv6_duid_l2_t) + 8U) -#define DHCPV6_CLIENT_BUFLEN (256) /**< length for send and receive buffer */ +#define DHCPV6_CLIENT_BUFLEN (256) /**< default length for send and receive buffer */ /** * @defgroup net_dhcpv6_conf DHCPv6 client compile configurations @@ -58,6 +58,15 @@ extern "C" { #ifndef CONFIG_DHCPV6_CLIENT_PFX_LEASE_MAX #define CONFIG_DHCPV6_CLIENT_PFX_LEASE_MAX (1U) #endif + +/** + * @brief MUD URL (must use the https:// scheme) + * For more info, see the [definitions](@ref net_dhcpv6_mud_url_option) below + */ +#ifndef CONFIG_DHCPV6_CLIENT_MUD_URL +#define CONFIG_DHCPV6_CLIENT_MUD_URL "https://example.org" +#endif + /** @} */ /** @@ -167,6 +176,31 @@ uint32_t dhcpv6_client_prefix_valid_until(unsigned netif, unsigned pfx_len); /** @} */ +/** + * @name DHCPv6 Manufacturer Usage Description (MUD) URL option definitions + * @see [RFC 8520, section 10](https://tools.ietf.org/html/rfc8520#section-10) + * @anchor net_dhcpv6_mud_url_option + * @{ + */ + +/** + * @brief Length for the send buffer if a MUD URL is included in the DHCP client's packets + * + * @note Only (re)defined by the `gnrc_dhcpv6_client_mud_url` pseudo-module. + */ +#if defined(MODULE_GNRC_DHCPV6_CLIENT_MUD_URL) || defined(DOXYGEN) +#define DHCPV6_CLIENT_SEND_BUFLEN (DHCPV6_CLIENT_BUFLEN + 256) +#else +#define DHCPV6_CLIENT_SEND_BUFLEN (DHCPV6_CLIENT_BUFLEN) +#endif + +/** + * @brief Maximal length of a MUD URL + */ +#define MAX_MUD_URL_LENGTH (0xFF - sizeof(dhcpv6_opt_mud_url_t)) + +/** @} */ + #ifdef __cplusplus } #endif diff --git a/sys/net/application_layer/dhcpv6/Kconfig b/sys/net/application_layer/dhcpv6/Kconfig index 460f36ac24..690bf8dd0f 100644 --- a/sys/net/application_layer/dhcpv6/Kconfig +++ b/sys/net/application_layer/dhcpv6/Kconfig @@ -16,4 +16,19 @@ config DHCPV6_CLIENT_PFX_LEASE_MAX int "Maximum number of prefix leases to be stored" default 1 +menuconfig KCONFIG_USEMODULE_GNRC_DHCPV6_CLIENT_MUD_URL + bool "Enable DHCPv6 Client MUD URL" + help + Enable the inclusion of a MUD URL in DHCPv6 packets + as specified in RFC 8520, section 10. This URL + has to point to a MUD file containing YANG-based JSON + with a description of the device and its suggested + network behavior. The URL must use the "https" scheme. + +if KCONFIG_USEMODULE_GNRC_DHCPV6_CLIENT_MUD_URL + +config CONFIG_DHCPV6_CLIENT_MUD_URL + string "URL pointing to a Manufacturer Usage Description file" + +endif # KCONFIG_USEMODULE_GNRC_DHCPV6_CLIENT_MUD_URL endif # KCONFIG_USEMODULE_DHCPv6 diff --git a/sys/net/application_layer/dhcpv6/_dhcpv6.h b/sys/net/application_layer/dhcpv6/_dhcpv6.h index a0b2fa0ddb..6bec53ebde 100644 --- a/sys/net/application_layer/dhcpv6/_dhcpv6.h +++ b/sys/net/application_layer/dhcpv6/_dhcpv6.h @@ -28,7 +28,6 @@ extern "C" { #endif - /** * @name DHCPv6 multicast addresses * @see [RFC 8415, section 7.1] @@ -214,6 +213,17 @@ typedef struct __attribute__((packed)) { network_uint32_t value; /**< overriding value for SOL_MAX_RT (in sec) */ } dhcpv6_opt_smr_t; +/** + * @brief MUD URL DHCPv6 option format + * @see [RFC 8520, section 10] + * (https://tools.ietf.org/html/rfc8520#section-10) + */ +typedef struct __attribute__((packed)) { + network_uint16_t type; /**< @ref DHCPV6_OPT_MUD_URL */ + network_uint16_t len; /**< length of the MUDstring in octets. */ + char mudString[]; /**< MUD URL using the "https" scheme */ +} dhcpv6_opt_mud_url_t; + #ifdef __cplusplus } #endif diff --git a/sys/net/application_layer/dhcpv6/client.c b/sys/net/application_layer/dhcpv6/client.c index 7551491057..cf7fb3f847 100644 --- a/sys/net/application_layer/dhcpv6/client.c +++ b/sys/net/application_layer/dhcpv6/client.c @@ -65,7 +65,7 @@ typedef struct { uint8_t duid_len; } server_t; -static uint8_t send_buf[DHCPV6_CLIENT_BUFLEN]; +static uint8_t send_buf[DHCPV6_CLIENT_SEND_BUFLEN]; static uint8_t recv_buf[DHCPV6_CLIENT_BUFLEN]; static uint8_t best_adv[DHCPV6_CLIENT_BUFLEN]; static uint8_t duid[DHCPV6_CLIENT_DUID_LEN]; @@ -247,6 +247,22 @@ static inline size_t _compose_elapsed_time_opt(dhcpv6_opt_elapsed_time_t *time) return len + sizeof(dhcpv6_opt_t); } +static inline size_t _compose_mud_url_opt(dhcpv6_opt_mud_url_t *mud_url_opt, + const char *mud_url, size_t len_max) +{ + uint16_t len = strlen(mud_url); + + if (len > len_max) { + assert(0); + return 0; + } + + mud_url_opt->type = byteorder_htons(DHCPV6_OPT_MUD_URL); + mud_url_opt->len = byteorder_htons(len); + strncpy(mud_url_opt->mudString, mud_url, len_max); + return len + sizeof(dhcpv6_opt_mud_url_t); +} + static inline size_t _compose_oro_opt(dhcpv6_opt_oro_t *oro, uint16_t *opts, unsigned opts_num) { @@ -273,7 +289,7 @@ static inline size_t _compose_ia_pd_opt(dhcpv6_opt_ia_pd_t *ia_pd, return len + sizeof(dhcpv6_opt_t); } -static inline size_t _add_ia_pd_from_config(uint8_t *buf) +static inline size_t _add_ia_pd_from_config(uint8_t *buf, size_t len_max) { size_t msg_len = 0; @@ -285,6 +301,12 @@ static inline size_t _add_ia_pd_from_config(uint8_t *buf) msg_len += _compose_ia_pd_opt(ia_pd, ia_id, 0U); } } + + if (msg_len > len_max) { + assert(0); + return 0; + } + return msg_len; } @@ -707,7 +729,16 @@ static void _solicit_servers(event_t *event) msg_len += _compose_elapsed_time_opt(time); msg_len += _compose_oro_opt((dhcpv6_opt_oro_t *)&send_buf[msg_len], oro_opts, ARRAY_SIZE(oro_opts)); - msg_len += _add_ia_pd_from_config(&send_buf[msg_len]); + + if (IS_USED(MODULE_GNRC_DHCPV6_CLIENT_MUD_URL)) { + const char mud_url[] = CONFIG_DHCPV6_CLIENT_MUD_URL; + assert(strlen(mud_url) <= MAX_MUD_URL_LENGTH); + assert(strncmp(mud_url, "https://", 8) == 0); + msg_len += _compose_mud_url_opt((dhcpv6_opt_mud_url_t *)&send_buf[msg_len], + mud_url, sizeof(send_buf) - msg_len); + } + + msg_len += _add_ia_pd_from_config(&send_buf[msg_len], sizeof(send_buf) - msg_len); DEBUG("DHCPv6 client: send SOLICIT\n"); _flush_stale_replies(&sock); res = sock_udp_send(&sock, send_buf, msg_len, &remote); @@ -815,7 +846,7 @@ static void _request_renew_rebind(uint8_t type) msg_len += _compose_elapsed_time_opt(time); msg_len += _compose_oro_opt((dhcpv6_opt_oro_t *)&send_buf[msg_len], oro_opts, ARRAY_SIZE(oro_opts)); - msg_len += _add_ia_pd_from_config(&send_buf[msg_len]); + msg_len += _add_ia_pd_from_config(&send_buf[msg_len], sizeof(send_buf) - msg_len); _flush_stale_replies(&sock); while (sock_udp_send(&sock, send_buf, msg_len, &remote) <= 0) {} while (((res = sock_udp_recv(&sock, recv_buf, sizeof(recv_buf), diff --git a/tests/gnrc_dhcpv6_client_6lbr/Makefile b/tests/gnrc_dhcpv6_client_6lbr/Makefile index 8682ed6a30..5a2439872a 100644 --- a/tests/gnrc_dhcpv6_client_6lbr/Makefile +++ b/tests/gnrc_dhcpv6_client_6lbr/Makefile @@ -5,6 +5,7 @@ export TAP ?= tap0 USEMODULE += auto_init_gnrc_netif USEMODULE += gnrc_dhcpv6_client_6lbr +USEMODULE += gnrc_dhcpv6_client_mud_url USEMODULE += gnrc_netdev_default USEMODULE += gnrc_pktdump USEMODULE += gnrc_sixlowpan_border_router_default diff --git a/tests/gnrc_dhcpv6_client_6lbr/tests/01-run.py b/tests/gnrc_dhcpv6_client_6lbr/tests/01-run.py index 8f83b53f5c..0fe215060a 100755 --- a/tests/gnrc_dhcpv6_client_6lbr/tests/01-run.py +++ b/tests/gnrc_dhcpv6_client_6lbr/tests/01-run.py @@ -14,13 +14,19 @@ import time from scapy.all import AsyncSniffer, sendp, Ether, IPv6, UDP from scapy.all import DHCP6_Solicit, DHCP6_Advertise, DHCP6_Request, DHCP6_Reply -from scapy.all import DHCP6OptClientId, DHCP6OptServerId, DHCP6OptIA_PD +from scapy.all import DHCP6OptClientId, DHCP6OptServerId, DHCP6OptIA_PD, DHCP6OptUnknown from scapy.all import DUID_LL, DHCP6OptIAPrefix from testrunner import run TIMEOUT = 1 +MUD_OPTION_CODE = 112 +MUD_TEST_URL = b'https://example.org' + +# MUD URL option in DHCPv6 is not yet supported by scapy +DHCP6OptMUD = DHCP6OptUnknown + def get_upstream_netif(child): child.sendline("ifconfig") @@ -123,6 +129,12 @@ def testfunc(child): # and it is asking for a prefix delegation assert DHCP6OptIA_PD in pkt + assert DHCP6OptMUD in pkt + mud_packet = pkt[DHCP6OptMUD] + assert mud_packet.optcode == 112 + assert mud_packet.optlen == len(MUD_TEST_URL) + assert mud_packet.data == MUD_TEST_URL + # reply to solicit with advertise and a prefix provided trid = pkt[DHCP6_Solicit].trid srv_duid = "aa:bb:cc:dd:ee:ff"