From 30b14a0886a42fe08b4cafbafc8002bd4d89cd2d Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 24 Mar 2026 03:53:51 +0900 Subject: [PATCH] dhcp: use struct iovec_wrapper to manage user class --- src/libsystemd-network/dhcp-client-internal.h | 4 +- src/libsystemd-network/dhcp-option.c | 38 ---------- src/libsystemd-network/sd-dhcp-client.c | 59 ++++++++------ src/network/networkd-dhcp-common.c | 76 +++++++++++++------ src/network/networkd-dhcp-common.h | 3 +- src/network/networkd-dhcp4.c | 8 +- src/network/networkd-network-gperf.gperf | 8 +- src/network/networkd-network.c | 2 +- src/network/networkd-network.h | 3 +- src/systemd/sd-dhcp-client.h | 3 - 10 files changed, 105 insertions(+), 99 deletions(-) diff --git a/src/libsystemd-network/dhcp-client-internal.h b/src/libsystemd-network/dhcp-client-internal.h index 8e23f7d9314..7db0a91fa94 100644 --- a/src/libsystemd-network/dhcp-client-internal.h +++ b/src/libsystemd-network/dhcp-client-internal.h @@ -5,6 +5,7 @@ #include "dhcp-client-id-internal.h" #include "ether-addr-util.h" +#include "iovec-wrapper.h" #include "network-common.h" #include "sd-forward.h" #include "socket-util.h" @@ -55,7 +56,7 @@ struct sd_dhcp_client { char *hostname; char *vendor_class_identifier; char *mudurl; - char **user_class; + struct iovec_wrapper user_class; uint32_t mtu; usec_t fallback_lease_lifetime; uint32_t xid; @@ -93,6 +94,7 @@ int dhcp_client_get_state(sd_dhcp_client *client); int dhcp_client_set_extra_options(sd_dhcp_client *client, TLV *options); int dhcp_client_set_vendor_options(sd_dhcp_client *client, TLV *options); +int dhcp_client_set_user_class(sd_dhcp_client *client, const struct iovec_wrapper *user_class); int client_receive_message_raw( sd_event_source *s, diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index da5fd64af67..c5e8b297944 100644 --- a/src/libsystemd-network/dhcp-option.c +++ b/src/libsystemd-network/dhcp-option.c @@ -12,7 +12,6 @@ #include "hostname-util.h" #include "memory-util.h" #include "string-util.h" -#include "strv.h" #include "utf8.h" /* Append type-length value structure to the options buffer */ @@ -57,43 +56,6 @@ static int option_append(uint8_t options[], size_t size, size_t *offset, *offset += 1; break; - case SD_DHCP_OPTION_USER_CLASS: { - /* When called with raw data (optlen > 0), e.g. from SendOption=, append as a plain TLV. - * The structured handling below expects optval to be a strv. */ - if (optlen > 0) - return dhcp_option_append_tlv(options, size, offset, code, optlen, optval); - - size_t total = 0; - - if (strv_isempty((char **) optval)) - return -EINVAL; - - STRV_FOREACH(s, (const char* const*) optval) { - size_t len = strlen(*s); - - if (len > 255 || len == 0) - return -EINVAL; - - total += 1 + len; - } - - if (*offset + 2 + total > size) - return -ENOBUFS; - - options[*offset] = code; - options[*offset + 1] = total; - *offset += 2; - - STRV_FOREACH(s, (const char* const*) optval) { - size_t len = strlen(*s); - - options[*offset] = len; - memcpy(&options[*offset + 1], *s, len); - *offset += 1 + len; - } - - break; - } case SD_DHCP_OPTION_SIP_SERVER: if (*offset + 3 + optlen > size) return -ENOBUFS; diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 0b13e925f26..8b517982b65 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -27,7 +27,6 @@ #include "sort-util.h" #include "string-table.h" #include "string-util.h" -#include "strv.h" #include "time-util.h" #include "web-util.h" @@ -434,28 +433,30 @@ int sd_dhcp_client_set_mud_url( return free_and_strdup(&client->mudurl, mudurl); } -int sd_dhcp_client_set_user_class( - sd_dhcp_client *client, - char * const *user_class) { - - char **s = NULL; +int dhcp_client_set_user_class(sd_dhcp_client *client, const struct iovec_wrapper *user_class) { + int r; assert_return(client, -EINVAL); assert_return(!sd_dhcp_client_is_running(client), -EBUSY); - assert_return(!strv_isempty(user_class), -EINVAL); - STRV_FOREACH(p, user_class) { - size_t n = strlen(*p); + if (iovw_isempty(user_class)) { + iovw_done_free(&client->user_class); + return 0; + } - if (n > 255 || n == 0) + _cleanup_(iovw_done_free) struct iovec_wrapper iovw = {}; + FOREACH_ARRAY(iovec, user_class->iovec, user_class->count) { + if (iovec->iov_len == 0 || iovec->iov_len > UINT8_MAX) return -EINVAL; - } - s = strv_copy(user_class); - if (!s) - return -ENOMEM; + r = iovw_extend_iov(&iovw, iovec); + if (r < 0) + return r; + } - return strv_free_and_replace(client->user_class, s); + iovw_done_free(&client->user_class); + client->user_class = TAKE_STRUCT(iovw); + return 0; } int sd_dhcp_client_set_client_port( @@ -933,12 +934,26 @@ static int client_append_common_discover_request_options(sd_dhcp_client *client, return r; } - if (client->user_class) { - r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, - SD_DHCP_OPTION_USER_CLASS, - /* optlen= */ 0, client->user_class); - if (r < 0) - return r; + if (!iovw_isempty(&client->user_class)) { + size_t sz = iovw_size(&client->user_class) + client->user_class.count; + if (sz <= UINT8_MAX) { + _cleanup_free_ uint8_t *buf = new(uint8_t, sz); + if (!buf) + return -ENOMEM; + + uint8_t *p = buf; + FOREACH_ARRAY(iovec, client->user_class.iovec, client->user_class.count) { + assert(iovec->iov_len > 0 && iovec->iov_len <= UINT8_MAX); + *p++ = iovec->iov_len; + p = mempcpy(p, iovec->iov_base, iovec->iov_len); + } + + r = dhcp_option_append(&packet->dhcp, optlen, optoffset, 0, + SD_DHCP_OPTION_USER_CLASS, + sz, buf); + if (r < 0) + return r; + } } if (client->extra_options) { @@ -2297,7 +2312,7 @@ static sd_dhcp_client* dhcp_client_free(sd_dhcp_client *client) { free(client->hostname); free(client->vendor_class_identifier); free(client->mudurl); - client->user_class = strv_free(client->user_class); + iovw_done_free(&client->user_class); tlv_unref(client->extra_options); tlv_unref(client->vendor_options); free(client->ifname); diff --git a/src/network/networkd-dhcp-common.c b/src/network/networkd-dhcp-common.c index d7353d07ed4..5d838355809 100644 --- a/src/network/networkd-dhcp-common.c +++ b/src/network/networkd-dhcp-common.c @@ -636,7 +636,7 @@ int config_parse_iaid( return 0; } -int config_parse_dhcp_user_or_vendor_class( +int config_parse_dhcp4_user_class( const char *unit, const char *filename, unsigned line, @@ -648,46 +648,76 @@ int config_parse_dhcp_user_or_vendor_class( void *data, void *userdata) { - char ***l = ASSERT_PTR(data); + struct iovec_wrapper *iovw = ASSERT_PTR(data); int r; assert(lvalue); assert(rvalue); - assert(IN_SET(ltype, AF_INET, AF_INET6)); if (isempty(rvalue)) { - *l = strv_free(*l); + iovw_done_free(iovw); return 0; } for (const char *p = rvalue;;) { _cleanup_free_ char *w = NULL; - size_t len; r = extract_first_word(&p, &w, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to split user classes option, ignoring: %s", rvalue); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); + if (r == 0) return 0; + + size_t len = strlen(w); + if (len > UINT8_MAX || len == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "The length of the %s entry '%s' is not in the range 1…255, ignoring.", lvalue, w); + continue; } + + r = iovw_consume(iovw, TAKE_PTR(w), len); + if (r < 0) + return log_oom(); + } +} + +int config_parse_dhcp6_user_or_vendor_class( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char ***l = ASSERT_PTR(data); + int r; + + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *l = strv_free(*l); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL; + + r = extract_first_word(&p, &w, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE); + if (r < 0) + return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue); if (r == 0) return 0; - len = strlen(w); - if (ltype == AF_INET) { - if (len > UINT8_MAX || len == 0) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "%s length is not in the range 1…255, ignoring.", w); - continue; - } - } else { - if (len > UINT16_MAX || len == 0) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "%s length is not in the range 1…65535, ignoring.", w); - continue; - } + size_t len = strlen(w); + if (len > UINT16_MAX || len == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "The length of the %s entry '%s' is not in the range 1…65535, ignoring.", lvalue, w); + continue; } r = strv_consume(l, TAKE_PTR(w)); diff --git a/src/network/networkd-dhcp-common.h b/src/network/networkd-dhcp-common.h index 0b88d7790c1..6be8bcd6dc3 100644 --- a/src/network/networkd-dhcp-common.h +++ b/src/network/networkd-dhcp-common.h @@ -81,7 +81,8 @@ CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_route_metric); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_send_hostname); CONFIG_PARSER_PROTOTYPE(config_parse_iaid); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_or_ra_route_table); -CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_user_or_vendor_class); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp4_user_class); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp6_user_or_vendor_class); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp6_send_option); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_option); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_option_tlv); diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 7304a049b69..b5210e9ddfc 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1643,11 +1643,9 @@ static int dhcp4_configure(Link *link) { return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set MUD URL: %m"); } - if (link->network->dhcp_user_class) { - r = sd_dhcp_client_set_user_class(link->dhcp_client, link->network->dhcp_user_class); - if (r < 0) - return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set user class: %m"); - } + r = dhcp_client_set_user_class(link->dhcp_client, &link->network->dhcp_user_class); + if (r < 0) + return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set user class: %m"); } if (link->network->dhcp_client_port > 0) { diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 0f1c4e57c46..b925cd3a2b4 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -278,7 +278,7 @@ DHCPv4.RequestBroadcast, config_parse_tristate, DHCPv4.VendorClassIdentifier, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_vendor_class_identifier) DHCPv4.MUDURL, config_parse_mud_url, 0, offsetof(Network, dhcp_mudurl) DHCPv4.MaxAttempts, config_parse_dhcp_max_attempts, 0, 0 -DHCPv4.UserClass, config_parse_dhcp_user_or_vendor_class, AF_INET, offsetof(Network, dhcp_user_class) +DHCPv4.UserClass, config_parse_dhcp4_user_class, 0, offsetof(Network, dhcp_user_class) DHCPv4.IAID, config_parse_iaid, AF_INET, 0 DHCPv4.DUIDType, config_parse_network_duid_type, 0, 0 DHCPv4.DUIDRawData, config_parse_network_duid_rawdata, 0, 0 @@ -318,8 +318,8 @@ DHCPv6.MUDURL, config_parse_mud_url, DHCPv6.SendHostname, config_parse_dhcp_send_hostname, AF_INET6, 0 DHCPv6.Hostname, config_parse_hostname, 0, offsetof(Network, dhcp6_hostname) DHCPv6.RequestOptions, config_parse_dhcp_request_options, AF_INET6, 0 -DHCPv6.UserClass, config_parse_dhcp_user_or_vendor_class, AF_INET6, offsetof(Network, dhcp6_user_class) -DHCPv6.VendorClass, config_parse_dhcp_user_or_vendor_class, AF_INET6, offsetof(Network, dhcp6_vendor_class) +DHCPv6.UserClass, config_parse_dhcp6_user_or_vendor_class, 0, offsetof(Network, dhcp6_user_class) +DHCPv6.VendorClass, config_parse_dhcp6_user_or_vendor_class, 0, offsetof(Network, dhcp6_vendor_class) DHCPv6.SendVendorOption, config_parse_dhcp6_send_option, 0, offsetof(Network, dhcp6_client_send_vendor_options) DHCPv6.PrefixDelegationHint, config_parse_dhcp6_pd_prefix_hint, 0, 0 DHCPv6.UnassignedSubnetPolicy, config_parse_dhcp_pd_prefix_route_type, 0, offsetof(Network, dhcp6_pd_prefix_route_type) @@ -664,7 +664,7 @@ DHCP.Hostname, config_parse_hostname, DHCP.RequestBroadcast, config_parse_tristate, 0, offsetof(Network, dhcp_broadcast) DHCP.CriticalConnection, config_parse_tristate, 0, offsetof(Network, dhcp_critical) DHCP.VendorClassIdentifier, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_vendor_class_identifier) -DHCP.UserClass, config_parse_dhcp_user_or_vendor_class, AF_INET, offsetof(Network, dhcp_user_class) +DHCP.UserClass, config_parse_dhcp4_user_class, 0, offsetof(Network, dhcp_user_class) DHCP.IAID, config_parse_iaid, AF_INET, 0 DHCP.DUIDType, config_parse_network_duid_type, 0, 0 DHCP.DUIDRawData, config_parse_network_duid_rawdata, 0, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 0397e77f810..5cd47ca0a81 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -786,7 +786,7 @@ static Network *network_free(Network *network) { free(network->dhcp_label); set_free(network->dhcp_deny_listed_ip); set_free(network->dhcp_allow_listed_ip); - strv_free(network->dhcp_user_class); + iovw_done_free(&network->dhcp_user_class); set_free(network->dhcp_request_options); tlv_done(&network->dhcp_extra_options); tlv_done(&network->dhcp_vendor_options); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 26012612a15..c4020a4341a 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -7,6 +7,7 @@ #include "bridge.h" #include "firewall-util.h" #include "ipoib.h" +#include "iovec-wrapper.h" #include "net-condition.h" #include "network-util.h" #include "networkd-bridge-vlan.h" @@ -124,7 +125,7 @@ typedef struct Network { bool dhcp_iaid_set; char *dhcp_vendor_class_identifier; char *dhcp_mudurl; - char **dhcp_user_class; + struct iovec_wrapper dhcp_user_class; char *dhcp_hostname; char *dhcp_label; uint64_t dhcp_max_attempts; diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index c83aaac2c9d..4e7e79da656 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -129,9 +129,6 @@ int sd_dhcp_client_set_vendor_class_identifier( int sd_dhcp_client_set_mud_url( sd_dhcp_client *client, const char *mudurl); -int sd_dhcp_client_set_user_class( - sd_dhcp_client *client, - char * const *user_class); int sd_dhcp_client_get_lease( sd_dhcp_client *client, sd_dhcp_lease **ret); -- 2.47.3