diff --git a/man/systemd.network.xml b/man/systemd.network.xml index e6d005400b..19b49702f3 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1774,6 +1774,20 @@ option numbers, the option number is an integer in the range 1..65536. + + + UserClass= + + A DHCPv6 client can use User Class option to identify the type or category of user or applications + it represents. The information contained in this option is a string that represents the user class of which + the client is a member. Each class sets an identifying string of information to be used by the DHCP + service to classify clients. Special characters in the data string may be escaped using + C-style + escapes. This setting can be specified multiple times. If an empty string is specified, + then all options specified earlier are cleared. Takes a whitespace-separated list of strings. Note that + currently NUL bytes are not allowed. + + diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index db80585a22..8b651636ca 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -97,6 +97,7 @@ int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, const DHCP6IA *ia); int dhcp6_option_append_pd(uint8_t *buf, size_t len, const DHCP6IA *pd, DHCP6Address *hint_pd_prefix); int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn); +int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char **user_class); int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen, uint8_t **optvalue); int dhcp6_option_parse_status(DHCP6Option *option, size_t len); diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index 3b7c89edd7..d1a02da35f 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -167,6 +167,36 @@ int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) { return r; } +int dhcp6_option_append_user_class(uint8_t **buf, size_t *buflen, char **user_class) { + _cleanup_free_ uint8_t *p = NULL; + size_t total = 0, offset = 0; + char **s; + + assert_return(buf && *buf && buflen && user_class, -EINVAL); + + STRV_FOREACH(s, user_class) { + size_t len = strlen(*s); + uint8_t *q; + + if (len > 0xffff) + return -ENAMETOOLONG; + + q = realloc(p, total + len + 2); + if (!q) + return -ENOMEM; + + p = q; + + unaligned_write_be16(&p[offset], len); + memcpy(&p[offset + 2], *s, len); + + offset += 2 + len; + total += 2 + len; + } + + return dhcp6_option_append(buf, buflen, SD_DHCP6_OPTION_USER_CLASS, total, p); +} + int dhcp6_option_append_pd(uint8_t *buf, size_t len, const DHCP6IA *pd, DHCP6Address *hint_pd_prefix) { DHCP6Option *option = (DHCP6Option *)buf; size_t i = sizeof(*option) + sizeof(pd->ia_pd); diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 51716c28bb..0240f1b7c7 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -67,6 +67,7 @@ struct sd_dhcp6_client { size_t req_opts_len; char *fqdn; char *mudurl; + char **user_class; sd_event_source *receive_message; usec_t retransmit_time; uint8_t retransmit_count; @@ -385,6 +386,27 @@ int sd_dhcp6_client_set_request_mud_url(sd_dhcp6_client *client, const char *mud return free_and_strdup(&client->mudurl, mudurl); } +int sd_dhcp6_client_set_request_user_class(sd_dhcp6_client *client, char **user_class) { + _cleanup_strv_free_ char **s = NULL; + char **p; + + assert_return(client, -EINVAL); + assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); + assert_return(user_class, -EINVAL); + + STRV_FOREACH(p, user_class) + if (strlen(*p) > UINT16_MAX) + return -ENAMETOOLONG; + + s = strv_copy((char **) user_class); + if (!s) + return -ENOMEM; + + client->user_class = TAKE_PTR(s); + + return 0; +} + int sd_dhcp6_client_get_prefix_delegation(sd_dhcp6_client *client, int *delegation) { assert_return(client, -EINVAL); assert_return(delegation, -EINVAL); @@ -565,6 +587,12 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { return r; } + if (client->user_class) { + r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class); + if (r < 0) + return r; + } + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) { r = dhcp6_option_append_pd(opt, optlen, &client->ia_pd, &client->hint_pd_prefix); if (r < 0) @@ -611,6 +639,12 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { return r; } + if (client->user_class) { + r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class); + if (r < 0) + return r; + } + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) { r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd, NULL); if (r < 0) @@ -645,6 +679,12 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { return r; } + if (client->user_class) { + r = dhcp6_option_append_user_class(&opt, &optlen, client->user_class); + if (r < 0) + return r; + } + if (FLAGS_SET(client->request, DHCP6_REQUEST_IA_PD)) { r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd, NULL); if (r < 0) @@ -1602,7 +1642,10 @@ static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) { free(client->req_opts); free(client->fqdn); free(client->mudurl); + ordered_hashmap_free(client->extra_options); + strv_free(client->user_class); + return mfree(client); } diff --git a/src/network/networkd-dhcp-common.c b/src/network/networkd-dhcp-common.c index ae1c4493c7..9ab0187d2d 100644 --- a/src/network/networkd-dhcp-common.c +++ b/src/network/networkd-dhcp-common.c @@ -267,7 +267,7 @@ int config_parse_dhcp6_pd_hint( return 0; } -int config_parse_dhcp6_mud_url( +int config_parse_dhcp_user_class( const char *unit, const char *filename, unsigned line, @@ -279,6 +279,67 @@ int config_parse_dhcp6_mud_url( void *data, void *userdata) { + char ***l = data; + int r; + + assert(l); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *l = strv_free(*l); + return 0; + } + + for (;;) { + _cleanup_free_ char *w = NULL; + + r = extract_first_word(&rvalue, &w, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Failed to split user classes option, ignoring: %s", rvalue); + break; + } + if (r == 0) + break; + + if (ltype == AF_INET) { + if (strlen(w) > UINT8_MAX) { + log_syntax(unit, LOG_ERR, filename, line, 0, + "%s length is not in the range 1-255, ignoring.", w); + continue; + } + } else { + if (strlen(w) > UINT16_MAX) { + log_syntax(unit, LOG_ERR, filename, line, 0, + "%s length is not in the range 1-65535, ignoring.", w); + continue; + } + } + + r = strv_push(l, w); + if (r < 0) + return log_oom(); + + w = NULL; + } + + return 0; +} + +int config_parse_dhcp6_mud_url( + 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) { _cleanup_free_ char *unescaped = NULL; Network *network = data; int r; @@ -299,7 +360,7 @@ int config_parse_dhcp6_mud_url( return 0; } - if (!http_url_is_valid(unescaped) || strlen(unescaped) > 255) { + if (!http_url_is_valid(unescaped) || strlen(unescaped) > UINT8_MAX) { log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse MUD URL '%s', ignoring: %m", rvalue); @@ -362,7 +423,7 @@ int config_parse_dhcp_send_option( "Invalid DHCP option, ignoring assignment: %s", rvalue); return 0; } - if (u16 < 1 || u16 >= 65535) { + if (u16 < 1 || u16 >= UINT16_MAX) { log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid DHCP option, valid range is 1-65535, ignoring assignment: %s", rvalue); return 0; @@ -374,7 +435,7 @@ int config_parse_dhcp_send_option( "Invalid DHCP option, ignoring assignment: %s", rvalue); return 0; } - if (u8 < 1 || u8 >= 255) { + if (u8 < 1 || u8 >= UINT8_MAX) { log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid DHCP option, valid range is 1-254, ignoring assignment: %s", rvalue); return 0; @@ -570,7 +631,7 @@ int config_parse_dhcp_request_options( continue; } - if (i < 1 || i >= 255) { + if (i < 1 || i >= UINT8_MAX) { log_syntax(unit, LOG_ERR, filename, line, r, "DHCP request option is invalid, valid range is 1-254, ignoring assignment: %s", n); continue; diff --git a/src/network/networkd-dhcp-common.h b/src/network/networkd-dhcp-common.h index efadcebe21..bf8bc2bf67 100644 --- a/src/network/networkd-dhcp-common.h +++ b/src/network/networkd-dhcp-common.h @@ -50,5 +50,6 @@ CONFIG_PARSER_PROTOTYPE(config_parse_iaid); CONFIG_PARSER_PROTOTYPE(config_parse_section_route_table); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp6_pd_hint); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp6_mud_url); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_user_class); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_send_option); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_request_options); diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 7d52a3a6a1..6bfa6e6dd0 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -1605,60 +1605,6 @@ int config_parse_dhcp_black_listed_ip_address( return 0; } -int config_parse_dhcp_user_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 = data; - int r; - - assert(l); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - *l = strv_free(*l); - return 0; - } - - for (;;) { - _cleanup_free_ char *w = NULL; - - r = extract_first_word(&rvalue, &w, NULL, 0); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, - "Failed to split user classes option, ignoring: %s", rvalue); - break; - } - if (r == 0) - break; - - if (strlen(w) > 255) { - log_syntax(unit, LOG_ERR, filename, line, 0, - "%s length is not in the range 1-255, ignoring.", w); - continue; - } - - r = strv_push(l, w); - if (r < 0) - return log_oom(); - - w = NULL; - } - - return 0; -} - int config_parse_dhcp_ip_service_type( const char *unit, const char *filename, diff --git a/src/network/networkd-dhcp4.h b/src/network/networkd-dhcp4.h index 78e10d9299..93fa7d372c 100644 --- a/src/network/networkd-dhcp4.h +++ b/src/network/networkd-dhcp4.h @@ -25,6 +25,5 @@ int dhcp4_set_promote_secondaries(Link *link); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_client_identifier); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_black_listed_ip_address); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_max_attempts); -CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_user_class); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_ip_service_type); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_mud_url); diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 05df41cd3a..a33f22880b 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -706,6 +706,12 @@ int dhcp6_configure(Link *link) { return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set request flag for '%u': %m", option); } + if (link->network->dhcp6_user_class) { + r = sd_dhcp6_client_set_request_user_class(client, link->network->dhcp6_user_class); + if (r < 0) + return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set user class: %m"); + } + r = sd_dhcp6_client_set_callback(client, dhcp6_handler, link); if (r < 0) return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set callback: %m"); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 711bec7588..798781cfe4 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -174,7 +174,7 @@ DHCPv4.RequestBroadcast, config_parse_bool, DHCPv4.VendorClassIdentifier, config_parse_string, 0, offsetof(Network, dhcp_vendor_class_identifier) DHCPv4.MUDURL, config_parse_dhcp_mud_url, 0, 0 DHCPv4.MaxAttempts, config_parse_dhcp_max_attempts, 0, 0 -DHCPv4.UserClass, config_parse_dhcp_user_class, 0, offsetof(Network, dhcp_user_class) +DHCPv4.UserClass, config_parse_dhcp_user_class, AF_INET, offsetof(Network, dhcp_user_class) DHCPv4.DUIDType, config_parse_duid_type, 0, offsetof(Network, duid) DHCPv4.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, duid) DHCPv4.RouteMetric, config_parse_unsigned, 0, offsetof(Network, dhcp_route_metric) @@ -194,6 +194,7 @@ DHCPv6.UseNTP, config_parse_bool, DHCPv6.RapidCommit, config_parse_bool, 0, offsetof(Network, rapid_commit) DHCPv6.MUDURL, config_parse_dhcp6_mud_url, 0, 0 DHCPv6.RequestOptions, config_parse_dhcp_request_options, AF_INET6, 0 +DHCPv6.UserClass, config_parse_dhcp_user_class, AF_INET6, offsetof(Network, dhcp6_user_class) DHCPv6.ForceDHCPv6PDOtherInformation, config_parse_bool, 0, offsetof(Network, dhcp6_force_pd_other_information) DHCPv6.PrefixDelegationHint, config_parse_dhcp6_pd_hint, 0, 0 DHCPv6.WithoutRA, config_parse_bool, 0, offsetof(Network, dhcp6_without_ra) @@ -384,7 +385,7 @@ DHCP.Hostname, config_parse_hostname, DHCP.RequestBroadcast, config_parse_bool, 0, offsetof(Network, dhcp_broadcast) DHCP.CriticalConnection, config_parse_tristate, 0, offsetof(Network, dhcp_critical) DHCP.VendorClassIdentifier, config_parse_string, 0, offsetof(Network, dhcp_vendor_class_identifier) -DHCP.UserClass, config_parse_dhcp_user_class, 0, offsetof(Network, dhcp_user_class) +DHCP.UserClass, config_parse_dhcp_user_class, AF_INET, offsetof(Network, dhcp_user_class) DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Network, duid) DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, duid) DHCP.RouteMetric, config_parse_unsigned, 0, offsetof(Network, dhcp_route_metric) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index a15f884ab0..1c2100d89f 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -653,6 +653,7 @@ static Network *network_free(Network *network) { set_free(network->dhcp6_request_options); free(network->mac); free(network->dhcp6_mudurl); + strv_free(network->dhcp6_user_class); if (network->dhcp_acd) sd_ipv4acd_unref(network->dhcp_acd); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 019bd7676b..8a6de4df01 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -132,6 +132,7 @@ struct Network { bool dhcp6_without_ra; uint8_t dhcp6_pd_length; char *dhcp6_mudurl; + char **dhcp6_user_class; struct in6_addr dhcp6_pd_address; OrderedHashmap *dhcp6_client_send_options; Set *dhcp6_request_options; diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h index d365fc7638..8011457b76 100644 --- a/src/systemd/sd-dhcp6-client.h +++ b/src/systemd/sd-dhcp6-client.h @@ -125,6 +125,10 @@ int sd_dhcp6_client_set_request_option( int sd_dhcp6_client_set_request_mud_url( sd_dhcp6_client *client, const char *mudurl); + +int sd_dhcp6_client_set_request_user_class( + sd_dhcp6_client *client, + char** user_class); int sd_dhcp6_client_set_prefix_delegation_hint( sd_dhcp6_client *client, uint8_t prefixlen, diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network index 743ab903ea..9ce0fc3f99 100644 --- a/test/fuzz/fuzz-network-parser/directives.network +++ b/test/fuzz/fuzz-network-parser/directives.network @@ -115,6 +115,7 @@ WithoutRA= MUDURL= SendOption= RequestOptions= +UserClass= [Route] Destination= Protocol=