diff --git a/src/libsystemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/dhcp6-lease-internal.h index 0a2434b5ab..e76a108f60 100644 --- a/src/libsystemd-network/dhcp6-lease-internal.h +++ b/src/libsystemd-network/dhcp6-lease-internal.h @@ -12,6 +12,7 @@ #include "dhcp6-option.h" #include "dhcp6-protocol.h" #include "macro.h" +#include "set.h" #include "time-util.h" struct sd_dhcp6_lease { @@ -45,6 +46,8 @@ struct sd_dhcp6_lease { size_t sntp_count; char *fqdn; char *captive_portal; + struct sd_dhcp6_option **sorted_vendor_options; + Set *vendor_options; }; int dhcp6_lease_set_clientid(sd_dhcp6_lease *lease, const uint8_t *id, size_t len); diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c index 3608586e5d..69d5a11f57 100644 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -587,6 +587,83 @@ int sd_dhcp6_lease_get_captive_portal(sd_dhcp6_lease *lease, const char **ret) { return 0; } +int sd_dhcp6_lease_get_vendor_options(sd_dhcp6_lease *lease, sd_dhcp6_option ***ret) { + int r; + + assert_return(lease, -EINVAL); + + if (set_isempty(lease->vendor_options)) + return -ENODATA; + + if (ret) { + if (!lease->sorted_vendor_options) { + r = set_dump_sorted(lease->vendor_options, (void***) &lease->sorted_vendor_options, NULL); + if (r < 0) + return r; + } + + *ret = lease->sorted_vendor_options; + } + + return set_size(lease->vendor_options); +} + +static int dhcp6_lease_insert_vendor_option( + sd_dhcp6_lease *lease, + uint16_t option_code, + const void *data, + size_t len, + uint16_t enterprise_id) { + + _cleanup_(sd_dhcp6_option_unrefp) sd_dhcp6_option *option = NULL; + + assert(lease); + + option = new(sd_dhcp6_option, 1); + if (!option) + return -ENOMEM; + + *option = (sd_dhcp6_option) { + .n_ref = 1, + .enterprise_identifier = enterprise_id, + .option = option_code, + .length = len, + }; + option->data = memdup_suffix0(data, len); + if (!option->data) + return -ENOMEM; + + return set_ensure_consume(&lease->vendor_options, &dhcp6_option_hash_ops, TAKE_PTR(option)); +} + +static int dhcp6_lease_add_vendor_option(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) { + int r; + uint32_t enterprise_id; + + assert(lease); + assert(optval || optlen == 0); + + if (optlen < sizeof(be32_t)) + return -EBADMSG; + + enterprise_id = unaligned_read_be32(optval); + + for (size_t offset = 4; offset < optlen;) { + const uint8_t *subval; + size_t sublen; + uint16_t subopt; + + r = dhcp6_option_parse(optval, optlen, &offset, &subopt, &sublen, &subval); + if (r < 0) + return r; + + r = dhcp6_lease_insert_vendor_option(lease, subopt, subval, sublen, enterprise_id); + if (r < 0) + return r; + } + return 0; +} + static int dhcp6_lease_parse_message( sd_dhcp6_client *client, sd_dhcp6_lease *lease, @@ -772,6 +849,13 @@ static int dhcp6_lease_parse_message( irt = unaligned_be32_sec_to_usec(optval, /* max_as_infinity = */ false); break; + + case SD_DHCP6_OPTION_VENDOR_OPTS: + r = dhcp6_lease_add_vendor_option(lease, optval, optlen); + if (r < 0) + log_dhcp6_client_errno(client, r, "Failed to parse vendor option, ignoring: %m"); + + break; } } @@ -812,6 +896,8 @@ static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) { if (!lease) return NULL; + set_free(lease->vendor_options); + free(lease->sorted_vendor_options); free(lease->clientid); free(lease->serverid); dhcp6_ia_free(lease->ia_na); diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index 109b23c639..ae3cdb8632 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -53,6 +53,8 @@ 0x00, 0x02, 0x00, 0x00, 0xab, 0x11, 0x61, 0x77, 0x40, 0xde, 0x13, 0x42, 0xc3, 0xa2 #define SERVER_ID_BYTES \ 0x00, 0x01, 0x00, 0x01, 0x19, 0x40, 0x5c, 0x53, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53 +#define VENDOR_SUBOPTION_BYTES \ + 0x01 static const struct in6_addr local_address = { { { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, } } }; @@ -70,10 +72,18 @@ static const struct in6_addr ntp1 = { { { NTP1_BYTES } } }; static const struct in6_addr ntp2 = { { { NTP2_BYTES } } }; static const uint8_t client_id[] = { CLIENT_ID_BYTES }; static const uint8_t server_id[] = { SERVER_ID_BYTES }; +static uint8_t vendor_suboption_data[] = { VENDOR_SUBOPTION_BYTES }; static const struct ether_addr mac = { .ether_addr_octet = { 'A', 'B', 'C', '1', '2', '3' }, }; static int test_fd[2] = EBADF_PAIR; +static sd_dhcp6_option vendor_suboption = { + .n_ref = 1, + .enterprise_identifier = 32, + .option = 247, + .data = vendor_suboption_data, + .length = 1, +}; static int test_ifindex = 42; static unsigned test_client_sent_message_count = 0; static sd_dhcp6_client *client_ref = NULL; @@ -100,6 +110,7 @@ TEST(client_basic) { assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVER) >= 0); assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NTP_SERVER) >= 0); assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_SNTP_SERVER) >= 0); + assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_VENDOR_OPTS) >= 0); assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DOMAIN) >= 0); assert_se(sd_dhcp6_client_set_request_option(client, 10) == -EINVAL); assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NIS_SERVER) >= 0); @@ -736,6 +747,9 @@ static const uint8_t msg_reply[] = { /* Client FQDN */ 0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x12, 0x01, 0x06, 'c', 'l', 'i', 'e', 'n', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', + /* Vendor specific options */ + 0x00, SD_DHCP6_OPTION_VENDOR_OPTS, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x20, 0x00, 0xf7, 0x00, 0x01, VENDOR_SUBOPTION_BYTES, }; static const uint8_t msg_advertise[] = { @@ -815,6 +829,9 @@ static const uint8_t msg_advertise[] = { /* Client FQDN */ 0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x12, 0x01, 0x06, 'c', 'l', 'i', 'e', 'n', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', + /* Vendor specific options */ + 0x00, SD_DHCP6_OPTION_VENDOR_OPTS, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x20, 0x00, 0xf7, 0x00, 0x01, VENDOR_SUBOPTION_BYTES, }; static void test_client_verify_information_request(const DHCP6Message *msg, size_t len) { @@ -855,6 +872,7 @@ static void test_client_verify_request(const DHCP6Message *msg, size_t len) { static void test_lease_common(sd_dhcp6_client *client) { sd_dhcp6_lease *lease; + sd_dhcp6_option **suboption; const struct in6_addr *addrs; const char *str; char **strv; @@ -888,6 +906,11 @@ static void test_lease_common(sd_dhcp6_client *client) { assert_se(lease->sntp_count == 2); assert_se(in6_addr_equal(&lease->sntp[0], &sntp1)); assert_se(in6_addr_equal(&lease->sntp[1], &sntp2)); + + assert_se(sd_dhcp6_lease_get_vendor_options(lease, &suboption) > 0); + assert_se((*suboption)->enterprise_identifier == vendor_suboption.enterprise_identifier); + assert_se((*suboption)->option == vendor_suboption.option); + assert_se(*(uint8_t*)(*suboption)->data == *(uint8_t*)vendor_suboption.data); } static void test_lease_managed(sd_dhcp6_client *client) { diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index ef3153642b..6e53d368d2 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -3,6 +3,7 @@ #include #include "dhcp-server-internal.h" +#include "dhcp6-internal.h" #include "dhcp6-lease-internal.h" #include "dns-domain.h" #include "ip-protocol-list.h" @@ -1036,6 +1037,32 @@ static int dhcp_server_append_json(Link *link, JsonVariant **v) { return json_variant_set_field_non_null(v, "DHCPServer", w); } +static int dhcp6_client_vendor_options_append_json(Link *link, JsonVariant **v) { + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; + sd_dhcp6_option **options = NULL; + int r, n_vendor_options; + + assert(link); + assert(v); + + if (!link->dhcp6_lease) + return 0; + + n_vendor_options = sd_dhcp6_lease_get_vendor_options(link->dhcp6_lease, &options); + + FOREACH_ARRAY(option, options, n_vendor_options) { + r = json_variant_append_arrayb(&array, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_UNSIGNED("EnterpriseId", (*option)->enterprise_identifier), + JSON_BUILD_PAIR_UNSIGNED("SubOptionCode", (*option)->option), + JSON_BUILD_PAIR_HEX("SubOptionData", (*option)->data, (*option)->length))); + if (r < 0) + return 0; + } + + return json_variant_set_field_non_null(v, "VendorSpecificOptions", array); +} + static int dhcp6_client_lease_append_json(Link *link, JsonVariant **v) { _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; usec_t ts, t1, t2; @@ -1125,6 +1152,10 @@ static int dhcp6_client_append_json(Link *link, JsonVariant **v) { if (r < 0) return r; + r = dhcp6_client_vendor_options_append_json(link, &w); + if (r < 0) + return r; + return json_variant_set_field_non_null(v, "DHCPv6Client", w); } diff --git a/src/systemd/sd-dhcp6-lease.h b/src/systemd/sd-dhcp6-lease.h index 543dc94025..e18d57817f 100644 --- a/src/systemd/sd-dhcp6-lease.h +++ b/src/systemd/sd-dhcp6-lease.h @@ -23,6 +23,8 @@ #include #include +#include "sd-dhcp6-option.h" + #include "_sd-common.h" _SD_BEGIN_DECLARATIONS; @@ -77,6 +79,7 @@ int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr ** int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret); int sd_dhcp6_lease_get_fqdn(sd_dhcp6_lease *lease, const char **ret); int sd_dhcp6_lease_get_captive_portal(sd_dhcp6_lease *lease, const char **ret); +int sd_dhcp6_lease_get_vendor_options(sd_dhcp6_lease *lease, sd_dhcp6_option ***ret); sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease); sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease);