diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index 35a74dd7c27..992ac9f3255 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -70,6 +70,12 @@ struct sd_dhcp_lease { char *timezone; + uint8_t sixrd_ipv4masklen; + uint8_t sixrd_prefixlen; + struct in6_addr sixrd_prefix; + struct in_addr *sixrd_br_addresses; + size_t sixrd_n_br_addresses; + LIST_HEAD(struct sd_dhcp_raw_option, private_options); }; diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index dd82163d9f7..7f6d15cb099 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -251,6 +251,33 @@ int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains) { return -ENODATA; } +int sd_dhcp_lease_get_6rd( + sd_dhcp_lease *lease, + uint8_t *ret_ipv4masklen, + uint8_t *ret_prefixlen, + struct in6_addr *ret_prefix, + const struct in_addr **ret_br_addresses, + size_t *ret_n_br_addresses) { + + assert_return(lease, -EINVAL); + + if (lease->sixrd_n_br_addresses <= 0) + return -ENODATA; + + if (ret_ipv4masklen) + *ret_ipv4masklen = lease->sixrd_ipv4masklen; + if (ret_prefixlen) + *ret_prefixlen = lease->sixrd_prefixlen; + if (ret_prefix) + *ret_prefix = lease->sixrd_prefix; + if (ret_br_addresses) + *ret_br_addresses = lease->sixrd_br_addresses; + if (ret_n_br_addresses) + *ret_n_br_addresses = lease->sixrd_n_br_addresses; + + return 0; +} + int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len) { assert_return(lease, -EINVAL); assert_return(data, -EINVAL); @@ -289,6 +316,7 @@ static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) { free(lease->client_id); free(lease->vendor_specific); strv_free(lease->search_domains); + free(lease->sixrd_br_addresses); return mfree(lease); } @@ -534,6 +562,61 @@ static int lease_parse_classless_routes( return 0; } +static int lease_parse_6rd(sd_dhcp_lease *lease, const uint8_t *option, size_t len) { + uint8_t ipv4masklen, prefixlen; + struct in6_addr prefix; + _cleanup_free_ struct in_addr *br_addresses = NULL; + size_t n_br_addresses; + + assert(lease); + assert(option); + + /* See RFC 5969 Section 7.1.1 */ + + if (lease->sixrd_n_br_addresses > 0) + /* Multiple 6rd option?? */ + return -EINVAL; + + /* option-length: The length of the DHCP option in octets (22 octets with one BR IPv4 address). */ + if (len < 2 + sizeof(struct in6_addr) + sizeof(struct in_addr) || + (len - 2 - sizeof(struct in6_addr)) % sizeof(struct in_addr) != 0) + return -EINVAL; + + /* IPv4MaskLen: The number of high-order bits that are identical across all CE IPv4 addresses + * within a given 6rd domain. This may be any value between 0 and 32. Any value + * greater than 32 is invalid. */ + ipv4masklen = option[0]; + if (ipv4masklen > 32) + return -EINVAL; + + /* 6rdPrefixLen: The IPv6 prefix length of the SP's 6rd IPv6 prefix in number of bits. For the + * purpose of bounds checking by DHCP option processing, the sum of + * (32 - IPv4MaskLen) + 6rdPrefixLen MUST be less than or equal to 128. */ + prefixlen = option[1]; + if (32 - ipv4masklen + prefixlen > 128) + return -EINVAL; + + /* 6rdPrefix: The service provider's 6rd IPv6 prefix represented as a 16-octet IPv6 address. + * The bits in the prefix after the 6rdPrefixlen number of bits are reserved and + * MUST be initialized to zero by the sender and ignored by the receiver. */ + memcpy(&prefix, option + 2, sizeof(struct in6_addr)); + (void) in6_addr_mask(&prefix, prefixlen); + + /* 6rdBRIPv4Address: One or more IPv4 addresses of the 6rd Border Relay(s) for a given 6rd domain. */ + n_br_addresses = (len - 2 - sizeof(struct in6_addr)) / sizeof(struct in_addr); + br_addresses = newdup(struct in_addr, option + 2 + sizeof(struct in6_addr), n_br_addresses); + if (!br_addresses) + return -ENOMEM; + + lease->sixrd_ipv4masklen = ipv4masklen; + lease->sixrd_prefixlen = prefixlen; + lease->sixrd_prefix = prefix; + lease->sixrd_br_addresses = TAKE_PTR(br_addresses); + lease->sixrd_n_br_addresses = n_br_addresses; + + return 0; +} + int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata) { sd_dhcp_lease *lease = userdata; int r; @@ -719,6 +802,12 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void lease->vendor_specific_len = len; break; + case SD_DHCP_OPTION_6RD: + r = lease_parse_6rd(lease, option, len); + if (r < 0) + log_debug_errno(r, "Failed to parse 6rd option, ignoring: %m"); + break; + case SD_DHCP_OPTION_PRIVATE_BASE ... SD_DHCP_OPTION_PRIVATE_LAST: r = dhcp_lease_insert_private_option(lease, code, option, len); if (r < 0) diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index 285febd0d4d..4af4b45f2d4 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -97,6 +97,7 @@ enum { SD_DHCP_OPTION_SIP_SERVER = 120, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE = 121, SD_DHCP_OPTION_MUD_URL = 161, + SD_DHCP_OPTION_6RD = 212, SD_DHCP_OPTION_PRIVATE_BASE = 224, /* Windows 10 option to send when Anonymize=true */ SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE = 249, diff --git a/src/systemd/sd-dhcp-lease.h b/src/systemd/sd-dhcp-lease.h index 5abf9a406c0..478bbfd7a6d 100644 --- a/src/systemd/sd-dhcp-lease.h +++ b/src/systemd/sd-dhcp-lease.h @@ -71,6 +71,13 @@ int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes); int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len); int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len); int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **timezone); +int sd_dhcp_lease_get_6rd( + sd_dhcp_lease *lease, + uint8_t *ret_ipv4masklen, + uint8_t *ret_prefixlen, + struct in6_addr *ret_prefix, + const struct in_addr **ret_br_addresses, + size_t *ret_n_br_addresses); int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination); int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length);