diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h index dd330ae8393..4bf2296ee38 100644 --- a/src/libsystemd-network/dhcp-protocol.h +++ b/src/libsystemd-network/dhcp-protocol.h @@ -11,6 +11,11 @@ #include "macro.h" #include "sparse-endian.h" +#include "time-util.h" + +/* RFC 8925 - IPv6-Only Preferred Option for DHCPv4 3.4. + * MIN_V6ONLY_WAIT: The lower boundary for V6ONLY_WAIT. Value: 300 seconds */ +#define MIN_V6ONLY_WAIT_USEC (300U * USEC_PER_SEC) struct DHCPMessage { uint8_t op; diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index a2dae1cd4a8..ae61cd84799 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -83,6 +83,7 @@ struct sd_dhcp_server { usec_t max_lease_time; usec_t default_lease_time; + usec_t ipv6_only_preferred_usec; sd_dhcp_server_callback_t callback; void *callback_userdata; @@ -105,6 +106,8 @@ typedef struct DHCPRequest { usec_t lifetime; const uint8_t *agent_info_option; char *hostname; + const uint8_t *parameter_request_list; + size_t parameter_request_list_len; } DHCPRequest; extern const struct hash_ops dhcp_lease_hash_ops; diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index a917406ceca..c69572d5b08 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -330,6 +330,15 @@ int sd_dhcp_server_stop(sd_dhcp_server *server) { return 0; } +static bool dhcp_request_contains(DHCPRequest *req, uint8_t option) { + assert(req); + + if (!req->parameter_request_list) + return false; + + return memchr(req->parameter_request_list, option, req->parameter_request_list_len); +} + static int dhcp_server_send_unicast_raw( sd_dhcp_server *server, uint8_t hlen, @@ -649,6 +658,21 @@ static int server_send_offer_or_ack( return r; } + /* RFC 8925 section 3.3. DHCPv4 Server Behavior + * The server MUST NOT include the IPv6-Only Preferred option in the DHCPOFFER or DHCPACK message if + * the option was not present in the Parameter Request List sent by the client. */ + if (dhcp_request_contains(req, SD_DHCP_OPTION_IPV6_ONLY_PREFERRED) && + server->ipv6_only_preferred_usec > 0) { + be32_t sec = usec_to_be32_sec(server->ipv6_only_preferred_usec); + + r = dhcp_option_append( + &packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_IPV6_ONLY_PREFERRED, + sizeof(sec), &sec); + if (r < 0) + return r; + } + ORDERED_SET_FOREACH(j, server->extra_options) { r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, j->option, j->length, j->data); @@ -778,6 +802,10 @@ static int parse_request(uint8_t code, uint8_t len, const void *option, void *us return 0; } + break; + case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST: + req->parameter_request_list = option; + req->parameter_request_list_len = len; break; } @@ -1501,6 +1529,19 @@ int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint64_t t) { return 0; } +int sd_dhcp_server_set_ipv6_only_preferred_usec(sd_dhcp_server *server, uint64_t t) { + assert_return(server, -EINVAL); + + /* When 0 is set, disables the IPv6 only mode. */ + + /* Refuse too short timespan unless test mode is enabled. */ + if (t > 0 && t < MIN_V6ONLY_WAIT_USEC && !network_test_mode_enabled()) + return -EINVAL; + + server->ipv6_only_preferred_usec = t; + return 0; +} + int sd_dhcp_server_set_servers( sd_dhcp_server *server, sd_dhcp_lease_server_type_t what, diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h index 64d95258fd6..1256076b833 100644 --- a/src/systemd/sd-dhcp-server.h +++ b/src/systemd/sd-dhcp-server.h @@ -84,6 +84,7 @@ int sd_dhcp_server_set_static_lease(sd_dhcp_server *server, const struct in_addr int sd_dhcp_server_set_max_lease_time(sd_dhcp_server *server, uint64_t t); int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint64_t t); +int sd_dhcp_server_set_ipv6_only_preferred_usec(sd_dhcp_server *server, uint64_t t); int sd_dhcp_server_forcerenew(sd_dhcp_server *server);