From 6e849e95ad52863c03b0d3ad3d830e0ef97f29f0 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:38 +0200 Subject: [PATCH 01/25] networkd: Move Router Advertisement functionality to a single file Centralize Router Advertisement functionality in networkd-radv instead of keeping it in networkd-address. --- src/network/networkd-address.c | 248 -------------------------------- src/network/networkd-address.h | 22 --- src/network/networkd-network.h | 1 + src/network/networkd-radv.c | 250 +++++++++++++++++++++++++++++++++ src/network/networkd-radv.h | 25 ++++ 5 files changed, 276 insertions(+), 270 deletions(-) diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index ff125e35de..ca5b54bdbf 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -984,251 +984,3 @@ bool address_is_ready(const Address *a) { else return !(a->flags & (IFA_F_TENTATIVE | IFA_F_DEPRECATED)); } - -int config_parse_router_preference(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) { - Network *network = userdata; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - if (streq(rvalue, "high")) - network->router_preference = SD_NDISC_PREFERENCE_HIGH; - else if (STR_IN_SET(rvalue, "medium", "normal", "default")) - network->router_preference = SD_NDISC_PREFERENCE_MEDIUM; - else if (streq(rvalue, "low")) - network->router_preference = SD_NDISC_PREFERENCE_LOW; - else - log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Router preference '%s' is invalid, ignoring assignment: %m", rvalue); - - return 0; -} - -void prefix_free(Prefix *prefix) { - if (!prefix) - return; - - if (prefix->network) { - LIST_REMOVE(prefixes, prefix->network->static_prefixes, prefix); - assert(prefix->network->n_static_prefixes > 0); - prefix->network->n_static_prefixes--; - - if (prefix->section) - hashmap_remove(prefix->network->prefixes_by_section, - prefix->section); - } - - prefix->radv_prefix = sd_radv_prefix_unref(prefix->radv_prefix); - - free(prefix); -} - -int prefix_new(Prefix **ret) { - _cleanup_prefix_free_ Prefix *prefix = NULL; - - prefix = new0(Prefix, 1); - if (!prefix) - return -ENOMEM; - - if (sd_radv_prefix_new(&prefix->radv_prefix) < 0) - return -ENOMEM; - - *ret = prefix; - prefix = NULL; - - return 0; -} - -int prefix_new_static(Network *network, const char *filename, - unsigned section_line, Prefix **ret) { - _cleanup_network_config_section_free_ NetworkConfigSection *n = NULL; - _cleanup_prefix_free_ Prefix *prefix = NULL; - int r; - - assert(network); - assert(ret); - assert(!!filename == (section_line > 0)); - - if (filename) { - r = network_config_section_new(filename, section_line, &n); - if (r < 0) - return r; - - if (section_line) { - prefix = hashmap_get(network->prefixes_by_section, n); - if (prefix) { - *ret = prefix; - prefix = NULL; - - return 0; - } - } - } - - r = prefix_new(&prefix); - if (r < 0) - return r; - - if (filename) { - prefix->section = n; - n = NULL; - - r = hashmap_put(network->prefixes_by_section, prefix->section, - prefix); - if (r < 0) - return r; - } - - prefix->network = network; - LIST_APPEND(prefixes, network->static_prefixes, prefix); - network->n_static_prefixes++; - - *ret = prefix; - prefix = NULL; - - return 0; -} - -int config_parse_prefix(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) { - - Network *network = userdata; - _cleanup_prefix_free_ Prefix *p = NULL; - uint8_t prefixlen = 64; - union in_addr_union in6addr; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = prefix_new_static(network, filename, section_line, &p); - if (r < 0) - return r; - - r = in_addr_prefix_from_string(rvalue, AF_INET6, &in6addr, &prefixlen); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Prefix is invalid, ignoring assignment: %s", rvalue); - return 0; - } - - if (sd_radv_prefix_set_prefix(p->radv_prefix, &in6addr.in6, prefixlen) < 0) - return -EADDRNOTAVAIL; - - log_syntax(unit, LOG_INFO, filename, line, r, "Found prefix %s", rvalue); - - p = NULL; - - return 0; -} - -int config_parse_prefix_flags(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) { - Network *network = userdata; - _cleanup_prefix_free_ Prefix *p = NULL; - int r, val; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = prefix_new_static(network, filename, section_line, &p); - if (r < 0) - return r; - - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address flag, ignoring: %s", rvalue); - return 0; - } - - val = r; - - if (streq(lvalue, "OnLink")) - r = sd_radv_prefix_set_onlink(p->radv_prefix, val); - else if (streq(lvalue, "AddressAutoconfiguration")) - r = sd_radv_prefix_set_address_autoconfiguration(p->radv_prefix, val); - if (r < 0) - return r; - - p = NULL; - - return 0; -} - -int config_parse_prefix_lifetime(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) { - Network *network = userdata; - _cleanup_prefix_free_ Prefix *p = NULL; - usec_t usec; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = prefix_new_static(network, filename, section_line, &p); - if (r < 0) - return r; - - r = parse_sec(rvalue, &usec); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Lifetime is invalid, ignoring assignment: %s", rvalue); - return 0; - } - - /* a value of 0xffffffff represents infinity */ - if (streq(lvalue, "PreferredLifetimeSec")) - r = sd_radv_prefix_set_preferred_lifetime(p->radv_prefix, - DIV_ROUND_UP(usec, USEC_PER_SEC)); - else if (streq(lvalue, "ValidLifetimeSec")) - r = sd_radv_prefix_set_valid_lifetime(p->radv_prefix, - DIV_ROUND_UP(usec, USEC_PER_SEC)); - if (r < 0) - return r; - - p = NULL; - - return 0; -} diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h index 3f5dffac4c..c2a241b571 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -26,7 +26,6 @@ #include "in-addr-util.h" typedef struct Address Address; -typedef struct Prefix Prefix; #include "networkd-link.h" #include "networkd-network.h" @@ -37,15 +36,6 @@ typedef struct Network Network; typedef struct Link Link; typedef struct NetworkConfigSection NetworkConfigSection; -struct Prefix { - Network *network; - NetworkConfigSection *section; - - sd_radv_prefix *radv_prefix; - - LIST_FIELDS(Prefix, prefixes); -}; - struct Address { Network *network; NetworkConfigSection *section; @@ -90,21 +80,9 @@ bool address_is_ready(const Address *a); DEFINE_TRIVIAL_CLEANUP_FUNC(Address*, address_free); #define _cleanup_address_free_ _cleanup_(address_freep) -int prefix_new(Prefix **ret); -void prefix_free(Prefix *prefix); -int prefix_new_static(Network *network, const char *filename, unsigned section, - Prefix **ret); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Prefix*, prefix_free); -#define _cleanup_prefix_free_ _cleanup_(prefix_freep) - int config_parse_address(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); int config_parse_broadcast(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); int config_parse_label(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); int config_parse_lifetime(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); int config_parse_address_flags(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); int config_parse_address_scope(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); -int config_parse_router_preference(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); -int config_parse_prefix(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); -int config_parse_prefix_flags(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); -int config_parse_prefix_lifetime(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); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 702aa7692a..c45f0f72f5 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -34,6 +34,7 @@ #include "networkd-fdb.h" #include "networkd-lldp-tx.h" #include "networkd-ipv6-proxy-ndp.h" +#include "networkd-radv.h" #include "networkd-route.h" #include "networkd-routing-policy-rule.h" #include "networkd-util.h" diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c index 535454c472..c72acc9b82 100644 --- a/src/network/networkd-radv.c +++ b/src/network/networkd-radv.c @@ -24,7 +24,257 @@ #include "networkd-address.h" #include "networkd-manager.h" #include "networkd-radv.h" +#include "parse-util.h" #include "sd-radv.h" +#include "string-util.h" + +int config_parse_router_preference(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) { + Network *network = userdata; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(rvalue, "high")) + network->router_preference = SD_NDISC_PREFERENCE_HIGH; + else if (STR_IN_SET(rvalue, "medium", "normal", "default")) + network->router_preference = SD_NDISC_PREFERENCE_MEDIUM; + else if (streq(rvalue, "low")) + network->router_preference = SD_NDISC_PREFERENCE_LOW; + else + log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Router preference '%s' is invalid, ignoring assignment: %m", rvalue); + + return 0; +} + +void prefix_free(Prefix *prefix) { + if (!prefix) + return; + + if (prefix->network) { + LIST_REMOVE(prefixes, prefix->network->static_prefixes, prefix); + assert(prefix->network->n_static_prefixes > 0); + prefix->network->n_static_prefixes--; + + if (prefix->section) + hashmap_remove(prefix->network->prefixes_by_section, + prefix->section); + } + + prefix->radv_prefix = sd_radv_prefix_unref(prefix->radv_prefix); + + free(prefix); +} + +int prefix_new(Prefix **ret) { + Prefix *prefix = NULL; + + prefix = new0(Prefix, 1); + if (!prefix) + return -ENOMEM; + + if (sd_radv_prefix_new(&prefix->radv_prefix) < 0) + return -ENOMEM; + + *ret = prefix; + prefix = NULL; + + return 0; +} + +int prefix_new_static(Network *network, const char *filename, + unsigned section_line, Prefix **ret) { + _cleanup_network_config_section_free_ NetworkConfigSection *n = NULL; + _cleanup_prefix_free_ Prefix *prefix = NULL; + int r; + + assert(network); + assert(ret); + assert(!!filename == (section_line > 0)); + + if (filename) { + r = network_config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + if (section_line) { + prefix = hashmap_get(network->prefixes_by_section, n); + if (prefix) { + *ret = prefix; + prefix = NULL; + + return 0; + } + } + } + + r = prefix_new(&prefix); + if (r < 0) + return r; + + if (filename) { + prefix->section = n; + n = NULL; + + r = hashmap_put(network->prefixes_by_section, prefix->section, + prefix); + if (r < 0) + return r; + } + + prefix->network = network; + LIST_APPEND(prefixes, network->static_prefixes, prefix); + network->n_static_prefixes++; + + *ret = prefix; + prefix = NULL; + + return 0; +} + +int config_parse_prefix(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) { + + Network *network = userdata; + _cleanup_prefix_free_ Prefix *p = NULL; + uint8_t prefixlen = 64; + union in_addr_union in6addr; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return r; + + r = in_addr_prefix_from_string(rvalue, AF_INET6, &in6addr, &prefixlen); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Prefix is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + if (sd_radv_prefix_set_prefix(p->radv_prefix, &in6addr.in6, prefixlen) < 0) + return -EADDRNOTAVAIL; + + log_syntax(unit, LOG_INFO, filename, line, r, "Found prefix %s", rvalue); + + p = NULL; + + return 0; +} + +int config_parse_prefix_flags(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) { + Network *network = userdata; + _cleanup_prefix_free_ Prefix *p = NULL; + int r, val; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return r; + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address flag, ignoring: %s", rvalue); + return 0; + } + + val = r; + + if (streq(lvalue, "OnLink")) + r = sd_radv_prefix_set_onlink(p->radv_prefix, val); + else if (streq(lvalue, "AddressAutoconfiguration")) + r = sd_radv_prefix_set_address_autoconfiguration(p->radv_prefix, val); + if (r < 0) + return r; + + p = NULL; + + return 0; +} + +int config_parse_prefix_lifetime(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) { + Network *network = userdata; + _cleanup_prefix_free_ Prefix *p = NULL; + usec_t usec; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = prefix_new_static(network, filename, section_line, &p); + if (r < 0) + return r; + + r = parse_sec(rvalue, &usec); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Lifetime is invalid, ignoring assignment: %s", rvalue); + return 0; + } + + /* a value of 0xffffffff represents infinity */ + if (streq(lvalue, "PreferredLifetimeSec")) + r = sd_radv_prefix_set_preferred_lifetime(p->radv_prefix, + DIV_ROUND_UP(usec, USEC_PER_SEC)); + else if (streq(lvalue, "ValidLifetimeSec")) + r = sd_radv_prefix_set_valid_lifetime(p->radv_prefix, + DIV_ROUND_UP(usec, USEC_PER_SEC)); + if (r < 0) + return r; + + p = NULL; + + return 0; +} static int radv_get_ip6dns(Network *network, struct in6_addr **dns, size_t *n_dns) { diff --git a/src/network/networkd-radv.h b/src/network/networkd-radv.h index f23029935a..a31a9f0e15 100644 --- a/src/network/networkd-radv.h +++ b/src/network/networkd-radv.h @@ -20,7 +20,32 @@ along with systemd; If not, see . ***/ +#include "networkd-address.h" #include "networkd-link.h" +typedef struct Prefix Prefix; + +struct Prefix { + Network *network; + NetworkConfigSection *section; + + sd_radv_prefix *radv_prefix; + + LIST_FIELDS(Prefix, prefixes); +}; + +int prefix_new(Prefix **ret); +void prefix_free(Prefix *prefix); +int prefix_new_static(Network *network, const char *filename, unsigned section, + Prefix **ret); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Prefix*, prefix_free); +#define _cleanup_prefix_free_ _cleanup_(prefix_freep) + +int config_parse_router_preference(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); +int config_parse_prefix(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); +int config_parse_prefix_flags(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); +int config_parse_prefix_lifetime(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); + int radv_emit_dns(Link *link); int radv_configure(Link *link); From 56a23cb40aadea95f7e24a911ba973fe132878b8 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:39 +0200 Subject: [PATCH 02/25] networkd: Add DHCPv6 as a configuration option to radv prefixes The Network section IPv6PrefixDelegation= option takes two new configuration values, namely "static" and "dhcpv6" in addition to boolean yes and no values. Static prefixes in IPv6Prefix sections are used when IPv6PrefixDelegation= option contains "static", and DHCPv6 is queried for prefixes when the option contains "dhcpv6". Both DHCPv6 and static prefixes are used when the option contains a boolean true value. The default value is false as before, meaning no prefixes are delegated. --- src/network/networkd-link.c | 2 +- src/network/networkd-network-gperf.gperf | 2 +- src/network/networkd-network.h | 9 ++++- src/network/networkd-radv.c | 49 ++++++++++++++++++++++-- src/network/networkd-radv.h | 1 + 5 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 60ac980ad9..8e1f0fe819 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -129,7 +129,7 @@ static bool link_radv_enabled(Link *link) { if (!link_ipv6ll_enabled(link)) return false; - return link->network->router_prefix_delegation; + return (link->network->router_prefix_delegation != RADV_PREFIX_DELEGATION_NONE); } static bool link_lldp_rx_enabled(Link *link) { diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index e1990b0302..816dbaf709 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -156,7 +156,7 @@ BridgeFDB.VLANId, config_parse_fdb_vlan_id, BridgeVLAN.PVID, config_parse_brvlan_pvid, 0, 0 BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0 BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0 -Network.IPv6PrefixDelegation, config_parse_bool, 0, offsetof(Network, router_prefix_delegation) +Network.IPv6PrefixDelegation, config_parse_router_prefix_delegation, 0, 0 IPv6PrefixDelegation.RouterLifetimeSec, config_parse_sec, 0, offsetof(Network, router_lifetime_usec) IPv6PrefixDelegation.Managed, config_parse_bool, 0, offsetof(Network, router_managed) IPv6PrefixDelegation.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information) diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index c45f0f72f5..ad56fc5f68 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -86,6 +86,13 @@ typedef struct DUID { uint8_t raw_data[MAX_DUID_LEN]; } DUID; +typedef enum RADVPrefixDelegation { + RADV_PREFIX_DELEGATION_NONE, + RADV_PREFIX_DELEGATION_STATIC, + RADV_PREFIX_DELEGATION_DHCP6, + RADV_PREFIX_DELEGATION_BOTH, +} RADVPrefixDelegation; + typedef struct NetworkConfigSection { unsigned line; char filename[]; @@ -165,7 +172,7 @@ struct Network { bool ipv4ll_route; /* IPv6 prefix delegation support */ - bool router_prefix_delegation; + RADVPrefixDelegation router_prefix_delegation; usec_t router_lifetime_usec; uint8_t router_preference; bool router_managed; diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c index c72acc9b82..a7e66f575b 100644 --- a/src/network/networkd-radv.c +++ b/src/network/networkd-radv.c @@ -28,6 +28,43 @@ #include "sd-radv.h" #include "string-util.h" +int config_parse_router_prefix_delegation(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) { + Network *network = userdata; + int d; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(rvalue, "static")) + network->router_prefix_delegation = RADV_PREFIX_DELEGATION_STATIC; + else if (streq(rvalue, "dhcpv6")) + network->router_prefix_delegation = RADV_PREFIX_DELEGATION_DHCP6; + else { + d = parse_boolean(rvalue); + if (d > 0) + network->router_prefix_delegation = RADV_PREFIX_DELEGATION_BOTH; + else + network->router_prefix_delegation = RADV_PREFIX_DELEGATION_NONE; + + if (d < 0) + log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Router prefix delegation '%s' is invalid, ignoring assignment: %m", rvalue); + } + + return 0; +} + int config_parse_router_preference(const char *unit, const char *filename, unsigned line, @@ -461,10 +498,14 @@ int radv_configure(Link *link) { return r; } - LIST_FOREACH(prefixes, p, link->network->static_prefixes) { - r = sd_radv_add_prefix(link->radv, p->radv_prefix); - if (r != -EEXIST && r < 0) - return r; + if (IN_SET(link->network->router_prefix_delegation, + RADV_PREFIX_DELEGATION_STATIC, + RADV_PREFIX_DELEGATION_BOTH)) { + LIST_FOREACH(prefixes, p, link->network->static_prefixes) { + r = sd_radv_add_prefix(link->radv, p->radv_prefix); + if (r != -EEXIST && r < 0) + return r; + } } return radv_emit_dns(link); diff --git a/src/network/networkd-radv.h b/src/network/networkd-radv.h index a31a9f0e15..22a169d263 100644 --- a/src/network/networkd-radv.h +++ b/src/network/networkd-radv.h @@ -42,6 +42,7 @@ int prefix_new_static(Network *network, const char *filename, unsigned section, DEFINE_TRIVIAL_CLEANUP_FUNC(Prefix*, prefix_free); #define _cleanup_prefix_free_ _cleanup_(prefix_freep) +int config_parse_router_prefix_delegation(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); int config_parse_router_preference(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); int config_parse_prefix(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); int config_parse_prefix_flags(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); From e0026dcbd21b172d51ac05e476044ac9504c1f71 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:40 +0200 Subject: [PATCH 03/25] dhcp6: Name structs in DHCP6IA Name structs containing IA NA with ID and T1 and T2 lifetimes and IA TA containing only the ID so that the structs can be expressed properly. --- src/libsystemd-network/dhcp6-internal.h | 21 +++++++++++++----- src/libsystemd-network/dhcp6-option.c | 28 ++++++++++++------------ src/libsystemd-network/sd-dhcp6-client.c | 20 ++++++++--------- src/libsystemd-network/sd-dhcp6-lease.c | 4 ++-- 4 files changed, 42 insertions(+), 31 deletions(-) diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index cb5b359cbe..9bff5c2dcd 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -41,13 +41,24 @@ struct DHCP6Address { } iaaddr _packed_; }; +/* Non-temporary Address option */ +struct ia_na { + be32_t id; + be32_t lifetime_t1; + be32_t lifetime_t2; +} _packed_; + +/* Temporary Address option */ +struct ia_ta { + be32_t id; +} _packed_; + struct DHCP6IA { uint16_t type; - struct { - be32_t id; - be32_t lifetime_t1; - be32_t lifetime_t2; - } _packed_; + union { + struct ia_na ia_na; + struct ia_ta ia_ta; + }; sd_event_source *timeout_t1; sd_event_source *timeout_t2; diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index f346bda5fc..139b7a4c5c 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -82,6 +82,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, DHCP6IA *ia) { uint16_t len; + be32_t *iaid; uint8_t *ia_hdr; size_t ia_buflen, ia_addrlen = 0; DHCP6Address *addr; @@ -92,10 +93,12 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) { switch (ia->type) { case SD_DHCP6_OPTION_IA_NA: len = DHCP6_OPTION_IA_NA_LEN; + iaid = &ia->ia_na.id; break; case SD_DHCP6_OPTION_IA_TA: len = DHCP6_OPTION_IA_TA_LEN; + iaid = &ia->ia_ta.id; break; default: @@ -111,7 +114,7 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) { *buf += sizeof(DHCP6Option); *buflen -= sizeof(DHCP6Option); - memcpy(*buf, &ia->id, len); + memcpy(*buf, iaid, len); *buf += len; *buflen -= len; @@ -232,10 +235,10 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, } iaaddr_offset = DHCP6_OPTION_IA_NA_LEN; - memcpy(&ia->id, *buf, iaaddr_offset); + memcpy(&ia->ia_na, *buf, sizeof(ia->ia_na)); - lt_t1 = be32toh(ia->lifetime_t1); - lt_t2 = be32toh(ia->lifetime_t2); + lt_t1 = be32toh(ia->ia_na.lifetime_t1); + lt_t2 = be32toh(ia->ia_na.lifetime_t2); if (lt_t1 && lt_t2 && lt_t1 > lt_t2) { log_dhcp6_client(client, "IA T1 %ds > T2 %ds", @@ -254,10 +257,7 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, } iaaddr_offset = DHCP6_OPTION_IA_TA_LEN; - memcpy(&ia->id, *buf, iaaddr_offset); - - ia->lifetime_t1 = 0; - ia->lifetime_t2 = 0; + memcpy(&ia->ia_ta.id, *buf, sizeof(ia->ia_ta)); break; @@ -327,19 +327,19 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, if (r == -ENOMSG) r = 0; - if (!ia->lifetime_t1 && !ia->lifetime_t2) { + if (*buflen) + r = -ENOMSG; + + if (!ia->ia_na.lifetime_t1 && !ia->ia_na.lifetime_t2) { lt_t1 = lt_min / 2; lt_t2 = lt_min / 10 * 8; - ia->lifetime_t1 = htobe32(lt_t1); - ia->lifetime_t2 = htobe32(lt_t2); + ia->ia_na.lifetime_t1 = htobe32(lt_t1); + ia->ia_na.lifetime_t2 = htobe32(lt_t2); log_dhcp6_client(client, "Computed IA T1 %ds and T2 %ds as both were zero", lt_t1, lt_t2); } - if (*buflen) - r = -ENOMSG; - error: *buf += *buflen; *buflen = 0; diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 074a409cbf..87f37dc0d4 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -230,7 +230,7 @@ int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) { assert_return(client, -EINVAL); assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); - client->ia_na.id = htobe32(iaid); + client->ia_na.ia_na.id = htobe32(iaid); return 0; } @@ -710,10 +710,10 @@ static int client_ensure_iaid(sd_dhcp6_client *client) { assert(client); - if (client->ia_na.id) + if (client->ia_na.ia_na.id) return 0; - r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, &client->ia_na.id); + r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, &client->ia_na.ia_na.id); if (r < 0) return r; @@ -815,7 +815,7 @@ static int client_parse_message( if (r < 0) return r; - if (client->ia_na.id != iaid_lease) { + if (client->ia_na.ia_na.id != iaid_lease) { log_dhcp6_client(client, "%s has wrong IAID", dhcp6_message_type_to_string(message->type)); return -EINVAL; @@ -1133,17 +1133,17 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) { case DHCP6_STATE_BOUND: - if (client->lease->ia.lifetime_t1 == 0xffffffff || - client->lease->ia.lifetime_t2 == 0xffffffff) { + if (client->lease->ia.ia_na.lifetime_t1 == 0xffffffff || + client->lease->ia.ia_na.lifetime_t2 == 0xffffffff) { log_dhcp6_client(client, "Infinite T1 0x%08x or T2 0x%08x", - be32toh(client->lease->ia.lifetime_t1), - be32toh(client->lease->ia.lifetime_t2)); + be32toh(client->lease->ia.ia_na.lifetime_t1), + be32toh(client->lease->ia.ia_na.lifetime_t2)); return 0; } - timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t1) * USEC_PER_SEC); + timeout = client_timeout_compute_random(be32toh(client->lease->ia.ia_na.lifetime_t1) * USEC_PER_SEC); log_dhcp6_client(client, "T1 expires in %s", format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC)); @@ -1165,7 +1165,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) { if (r < 0) goto error; - timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t2) * USEC_PER_SEC); + timeout = client_timeout_compute_random(be32toh(client->lease->ia.ia_na.lifetime_t2) * USEC_PER_SEC); log_dhcp6_client(client, "T2 expires in %s", format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC)); diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c index 6f604e072f..c0819b7383 100644 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -49,7 +49,7 @@ int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire) { valid = t; } - t = be32toh(ia->lifetime_t2); + t = be32toh(ia->ia_na.lifetime_t2); if (t > valid) return -EINVAL; @@ -144,7 +144,7 @@ int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid) { assert_return(lease, -EINVAL); assert_return(iaid, -EINVAL); - *iaid = lease->ia.id; + *iaid = lease->ia.ia_na.id; return 0; } From 3bc424a3cc0bacc688ec2f4f93a5560fb4ca393b Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:41 +0200 Subject: [PATCH 04/25] dhcp6: Sanitize DHCPv6 IA option parsing Sanitize code for parsing DHCPv6 IA NA and TA options and their nested Status options so that the options can be fully and properly ignored should they not be conformant to the specification. Do this by defining a proper DHCP6Option structure and sending that structure to the parsing function. The parsing function will then not manipulate either any option data pointers or their lengths in order to iterate over the current option. Needless to say, this affects a few files including the test program. --- src/libsystemd-network/dhcp6-internal.h | 10 +- src/libsystemd-network/dhcp6-option.c | 66 ++++++-------- src/libsystemd-network/dhcp6-protocol.h | 1 + src/libsystemd-network/sd-dhcp6-client.c | 32 ++++--- src/libsystemd-network/test-dhcp6-client.c | 101 ++++++++++++--------- 5 files changed, 113 insertions(+), 97 deletions(-) diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index 9bff5c2dcd..9e7d3976aa 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -29,6 +29,13 @@ #include "macro.h" #include "sparse-endian.h" +/* Common option header */ +typedef struct DHCP6Option { + be16_t code; + be16_t len; + uint8_t data[]; +} _packed_ DHCP6Option; + typedef struct DHCP6Address DHCP6Address; struct DHCP6Address { @@ -76,8 +83,7 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia); int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn); int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen, uint8_t **optvalue); -int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, - DHCP6IA *ia); +int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia); int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen, struct in6_addr **addrs, size_t count, size_t *allocated); diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index 139b7a4c5c..80925b3948 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -36,12 +36,6 @@ #define DHCP6_OPTION_IA_NA_LEN 12 #define DHCP6_OPTION_IA_TA_LEN 4 -typedef struct DHCP6Option { - be16_t code; - be16_t len; - uint8_t data[]; -} _packed_ DHCP6Option; - static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode, size_t optlen) { DHCP6Option *option = (DHCP6Option*) *buf; @@ -213,11 +207,11 @@ int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode, return 0; } -int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, - DHCP6IA *ia) { - int r; - uint16_t opt, status; - size_t optlen; +int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) { + uint16_t iatype, optlen; + size_t i, len; + int r = 0, status; + uint16_t opt; size_t iaaddr_offset; DHCP6Address *addr; uint32_t lt_t1, lt_t2, lt_valid, lt_pref, lt_min = ~0; @@ -225,17 +219,19 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, assert_return(ia, -EINVAL); assert_return(!ia->addresses, -EINVAL); + iatype = be16toh(iaoption->code); + len = be16toh(iaoption->len); + switch (iatype) { case SD_DHCP6_OPTION_IA_NA: - if (*buflen < DHCP6_OPTION_IA_NA_LEN + sizeof(DHCP6Option) + - sizeof(addr->iaaddr)) { + if (len < DHCP6_OPTION_IA_NA_LEN) { r = -ENOBUFS; goto error; } iaaddr_offset = DHCP6_OPTION_IA_NA_LEN; - memcpy(&ia->ia_na, *buf, sizeof(ia->ia_na)); + memcpy(&ia->ia_na, iaoption->data, sizeof(ia->ia_na)); lt_t1 = be32toh(ia->ia_na.lifetime_t1); lt_t2 = be32toh(ia->ia_na.lifetime_t2); @@ -250,14 +246,13 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, break; case SD_DHCP6_OPTION_IA_TA: - if (*buflen < DHCP6_OPTION_IA_TA_LEN + sizeof(DHCP6Option) + - sizeof(addr->iaaddr)) { + if (len < DHCP6_OPTION_IA_TA_LEN) { r = -ENOBUFS; goto error; } iaaddr_offset = DHCP6_OPTION_IA_TA_LEN; - memcpy(&ia->ia_ta.id, *buf, sizeof(ia->ia_ta)); + memcpy(&ia->ia_ta.id, iaoption->data, sizeof(ia->ia_ta)); break; @@ -267,15 +262,21 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, } ia->type = iatype; + i = iaaddr_offset; - *buflen -= iaaddr_offset; - *buf += iaaddr_offset; + while (i < len) { + DHCP6Option *option = (DHCP6Option *)&iaoption->data[i]; - while ((r = option_parse_hdr(buf, buflen, &opt, &optlen)) >= 0) { + if (len < i + sizeof(*option) || len < i + sizeof(*option) + be16toh(option->len)) { + r = -ENOBUFS; + goto error; + } + + opt = be16toh(option->code); + optlen = be16toh(option->len); switch (opt) { case SD_DHCP6_OPTION_IAADDR: - addr = new0(DHCP6Address, 1); if (!addr) { r = -ENOMEM; @@ -283,8 +284,7 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, } LIST_INIT(addresses, addr); - - memcpy(&addr->iaaddr, *buf, sizeof(addr->iaaddr)); + memcpy(&addr->iaaddr, option->data, sizeof(addr->iaaddr)); lt_valid = be32toh(addr->iaaddr.lifetime_valid); lt_pref = be32toh(addr->iaaddr.lifetime_valid); @@ -302,10 +302,12 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, break; case SD_DHCP6_OPTION_STATUS_CODE: - if (optlen < sizeof(status)) - break; + if (optlen < sizeof(status)) { + r = -ENOMSG; + goto error; + } - status = (*buf)[0] << 8 | (*buf)[1]; + status = option->data[0] << 8 | option->data[1]; if (status) { log_dhcp6_client(client, "IA status %d", status); @@ -320,16 +322,9 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, break; } - *buflen -= optlen; - *buf += optlen; + i += sizeof(*option) + optlen; } - if (r == -ENOMSG) - r = 0; - - if (*buflen) - r = -ENOMSG; - if (!ia->ia_na.lifetime_t1 && !ia->ia_na.lifetime_t2) { lt_t1 = lt_min / 2; lt_t2 = lt_min / 10 * 8; @@ -341,9 +336,6 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, } error: - *buf += *buflen; - *buflen = 0; - return r; } diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h index 7bbf183996..5f7e809ba1 100644 --- a/src/libsystemd-network/dhcp6-protocol.h +++ b/src/libsystemd-network/dhcp6-protocol.h @@ -34,6 +34,7 @@ struct DHCP6Message { } _packed_; be32_t transaction_id; }; + uint8_t options[]; } _packed_; typedef struct DHCP6Message DHCP6Message; diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 87f37dc0d4..9c758a4d0b 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -725,23 +725,32 @@ static int client_parse_message( DHCP6Message *message, size_t len, sd_dhcp6_lease *lease) { + size_t pos = 0; int r; - uint8_t *optval, *option, *id = NULL; - uint16_t optcode, status; - size_t optlen, id_len; bool clientid = false; - be32_t iaid_lease; + uint8_t *id = NULL; + size_t id_len; assert(client); assert(message); assert(len >= sizeof(DHCP6Message)); assert(lease); - option = (uint8_t *)message + sizeof(DHCP6Message); len -= sizeof(DHCP6Message); - while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen, - &optval)) >= 0) { + while (pos < len) { + DHCP6Option *option = (DHCP6Option *)&message->options[pos]; + uint16_t optcode, optlen, status; + uint8_t *optval; + be32_t iaid_lease; + + if (len < sizeof(DHCP6Option) || len < sizeof(DHCP6Option) + be16toh(option->len)) + return -ENOBUFS; + + optcode = be16toh(option->code); + optlen = be16toh(option->len); + optval = option->data; + switch (optcode) { case SD_DHCP6_OPTION_CLIENTID: if (clientid) { @@ -779,7 +788,7 @@ static int client_parse_message( if (optlen != 1) return -EINVAL; - r = dhcp6_lease_set_preference(lease, *optval); + r = dhcp6_lease_set_preference(lease, optval[0]); if (r < 0) return r; @@ -806,8 +815,7 @@ static int client_parse_message( break; } - r = dhcp6_option_parse_ia(&optval, &optlen, optcode, - &lease->ia); + r = dhcp6_option_parse_ia(option, &lease->ia); if (r < 0 && r != -ENOMSG) return r; @@ -859,11 +867,9 @@ static int client_parse_message( break; } + pos += sizeof(*option) + optlen; } - if (r == -ENOMSG) - r = 0; - if (r < 0 || !clientid) { log_dhcp6_client(client, "%s has incomplete options", dhcp6_message_type_to_string(message->type)); diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index a0418ecdb9..77084e53c7 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -217,14 +217,13 @@ static uint8_t fqdn_wire[16] = { static int test_advertise_option(sd_event *e) { _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; DHCP6Message *advertise = (DHCP6Message *)msg_advertise; - uint8_t *optval, *opt = msg_advertise + sizeof(DHCP6Message); - uint16_t optcode; - size_t optlen, len = sizeof(msg_advertise) - sizeof(DHCP6Message); + size_t len = sizeof(msg_advertise) - sizeof(DHCP6Message), pos = 0; be32_t val; uint8_t preference = 255; struct in6_addr addr; uint32_t lt_pref, lt_valid; int r; + uint8_t *opt; bool opt_clientid = false; struct in6_addr *addrs; char **domains; @@ -232,14 +231,19 @@ static int test_advertise_option(sd_event *e) { if (verbose) printf("* %s\n", __FUNCTION__); + assert_se(len >= sizeof(DHCP6Message)); + assert_se(dhcp6_lease_new(&lease) >= 0); assert_se(advertise->type == DHCP6_ADVERTISE); assert_se((be32toh(advertise->transaction_id) & 0x00ffffff) == 0x0fb4e5); - while ((r = dhcp6_option_parse(&opt, &len, &optcode, &optlen, - &optval)) >= 0) { + while (pos < len) { + DHCP6Option *option = (DHCP6Option *)&advertise->options[pos]; + const uint16_t optcode = be16toh(option->code); + const uint16_t optlen = be16toh(option->len); + uint8_t *optval = option->data; switch(optcode) { case SD_DHCP6_OPTION_CLIENTID: @@ -261,9 +265,7 @@ static int test_advertise_option(sd_event *e) { val = htobe32(120); assert_se(!memcmp(optval + 8, &val, sizeof(val))); - assert_se(dhcp6_option_parse_ia(&optval, &optlen, - optcode, - &lease->ia) >= 0); + assert_se(dhcp6_option_parse_ia(option, &lease->ia) >= 0); break; @@ -309,11 +311,11 @@ static int test_advertise_option(sd_event *e) { default: break; } + + pos += sizeof(*option) + optlen; } - - assert_se(r == -ENOMSG); - + assert_se(pos == len); assert_se(opt_clientid); sd_dhcp6_lease_reset_address_iter(lease); @@ -415,15 +417,11 @@ static int test_client_send_reply(DHCP6Message *request) { return 0; } -static int test_client_verify_request(DHCP6Message *request, uint8_t *option, - size_t len) { +static int test_client_verify_request(DHCP6Message *request, size_t len) { _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; - uint8_t *optval; - uint16_t optcode; - size_t optlen; + size_t pos = 0; bool found_clientid = false, found_iana = false, found_serverid = false, found_elapsed_time = false, found_fqdn = false; - int r; struct in6_addr addr; be32_t val; uint32_t lt_pref, lt_valid; @@ -432,8 +430,14 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option, assert_se(dhcp6_lease_new(&lease) >= 0); - while ((r = dhcp6_option_parse(&option, &len, - &optcode, &optlen, &optval)) >= 0) { + len -= sizeof(DHCP6Message); + + while (pos < len) { + DHCP6Option *option = (DHCP6Option *)&request->options[pos]; + uint16_t optcode = be16toh(option->code); + uint16_t optlen = be16toh(option->len); + uint8_t *optval = option->data; + switch(optcode) { case SD_DHCP6_OPTION_CLIENTID: assert_se(!found_clientid); @@ -458,8 +462,7 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option, val = htobe32(120); assert_se(!memcmp(optval + 8, &val, sizeof(val))); - assert_se(!dhcp6_option_parse_ia(&optval, &optlen, - optcode, &lease->ia)); + assert_se(!dhcp6_option_parse_ia(option, &lease->ia)); break; @@ -489,9 +492,10 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option, assert_se(!memcmp(optval + 1, fqdn_wire, sizeof(fqdn_wire))); break; } + + pos += sizeof(*option) + optlen; } - assert_se(r == -ENOMSG); assert_se(found_clientid && found_iana && found_serverid && found_elapsed_time); @@ -526,19 +530,21 @@ static int test_client_send_advertise(DHCP6Message *solicit) { return 0; } -static int test_client_verify_solicit(DHCP6Message *solicit, uint8_t *option, - size_t len) { - uint8_t *optval; - uint16_t optcode; - size_t optlen; +static int test_client_verify_solicit(DHCP6Message *solicit, size_t len) { bool found_clientid = false, found_iana = false, found_elapsed_time = false, found_fqdn = false; - int r; + size_t pos = 0; assert_se(solicit->type == DHCP6_SOLICIT); - while ((r = dhcp6_option_parse(&option, &len, - &optcode, &optlen, &optval)) >= 0) { + len -= sizeof(DHCP6Message); + + while (pos < len) { + DHCP6Option *option = (DHCP6Option *)&solicit->options[pos]; + uint16_t optcode = be16toh(option->code); + uint16_t optlen = be16toh(option->len); + uint8_t *optval = option->data; + switch(optcode) { case SD_DHCP6_OPTION_CLIENTID: assert_se(!found_clientid); @@ -578,9 +584,11 @@ static int test_client_verify_solicit(DHCP6Message *solicit, uint8_t *option, break; } + + pos += sizeof(*option) + optlen; } - assert_se(r == -ENOMSG); + assert_se(pos == len); assert_se(found_clientid && found_iana && found_elapsed_time); return 0; @@ -623,17 +631,15 @@ static void test_client_information_cb(sd_dhcp6_client *client, int event, assert_se(sd_dhcp6_client_set_local_address(client, &address) >= 0); assert_se(sd_dhcp6_client_start(client) >= 0); + } static int test_client_verify_information_request(DHCP6Message *information_request, - uint8_t *option, size_t len) { + size_t len) { _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; - uint8_t *optval; - uint16_t optcode; - size_t optlen; + size_t pos = 0; bool found_clientid = false, found_elapsed_time = false; - int r; struct in6_addr addr; uint32_t lt_pref, lt_valid; @@ -641,8 +647,14 @@ static int test_client_verify_information_request(DHCP6Message *information_requ assert_se(dhcp6_lease_new(&lease) >= 0); - while ((r = dhcp6_option_parse(&option, &len, - &optcode, &optlen, &optval)) >= 0) { + len -= sizeof(DHCP6Message); + + while (pos < len) { + DHCP6Option *option = (DHCP6Option *)&information_request->options[pos]; + uint16_t optcode = be16toh(option->code); + uint16_t optlen = be16toh(option->len); + uint8_t *optval = option->data; + switch(optcode) { case SD_DHCP6_OPTION_CLIENTID: assert_se(!found_clientid); @@ -671,9 +683,11 @@ static int test_client_verify_information_request(DHCP6Message *information_requ break; } + + pos += sizeof(*option) + optlen; } - assert_se(r == -ENOMSG); + assert_se(pos == len); assert_se(found_clientid && found_elapsed_time); sd_dhcp6_lease_reset_address_iter(lease); @@ -689,7 +703,6 @@ int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address, struct in6_addr mcast = IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT; DHCP6Message *message; - uint8_t *option; assert_se(s == test_dhcp_fd[0]); assert_se(server_address); @@ -699,21 +712,19 @@ int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address, assert_se(IN6_ARE_ADDR_EQUAL(server_address, &mcast)); message = (DHCP6Message *)packet; - option = (uint8_t *)(message + 1); - len -= sizeof(DHCP6Message); assert_se(message->transaction_id & 0x00ffffff); if (test_client_message_num == 0) { - test_client_verify_information_request(message, option, len); + test_client_verify_information_request(message, len); test_client_send_reply(message); test_client_message_num++; } else if (test_client_message_num == 1) { - test_client_verify_solicit(message, option, len); + test_client_verify_solicit(message, len); test_client_send_advertise(message); test_client_message_num++; } else if (test_client_message_num == 2) { - test_client_verify_request(message, option, len); + test_client_verify_request(message, len); test_client_send_reply(message); test_client_message_num++; } From c6b4f32a507c5ad885265309b2ecb56e618564d5 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:42 +0200 Subject: [PATCH 05/25] dhcp6: Add function for DHCPv6 Status option Factor out code to parse a DHCPv6 Status option using a common function. --- src/libsystemd-network/dhcp6-internal.h | 1 + src/libsystemd-network/dhcp6-option.c | 25 +++++++++++++++++++----- src/libsystemd-network/sd-dhcp6-client.c | 10 +++++----- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index 9e7d3976aa..982ce3608a 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -83,6 +83,7 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia); int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn); 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); int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia); int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen, struct in6_addr **addrs, size_t count, diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index 80925b3948..b18a996d86 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -26,6 +26,7 @@ #include "alloc-util.h" #include "dhcp6-internal.h" +#include "dhcp6-lease-internal.h" #include "dhcp6-protocol.h" #include "dns-domain.h" #include "sparse-endian.h" @@ -33,6 +34,12 @@ #include "unaligned.h" #include "util.h" +typedef struct DHCP6StatusOption { + struct DHCP6Option option; + be16_t status; + char msg[]; +} _packed_ DHCP6StatusOption; + #define DHCP6_OPTION_IA_NA_LEN 12 #define DHCP6_OPTION_IA_TA_LEN 4 @@ -207,6 +214,15 @@ int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode, return 0; } +int dhcp6_option_parse_status(DHCP6Option *option) { + DHCP6StatusOption *statusopt = (DHCP6StatusOption *)option; + + if (be16toh(option->len) + sizeof(DHCP6Option) < sizeof(*statusopt)) + return -ENOBUFS; + + return be16toh(statusopt->status); +} + int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) { uint16_t iatype, optlen; size_t i, len; @@ -302,15 +318,14 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) { break; case SD_DHCP6_OPTION_STATUS_CODE: - if (optlen < sizeof(status)) { - r = -ENOMSG; - goto error; - } - status = option->data[0] << 8 | option->data[1]; + status = dhcp6_option_parse_status(option); if (status) { log_dhcp6_client(client, "IA status %d", status); + + dhcp6_lease_free_ia(ia); + r = -EINVAL; goto error; } diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 9c758a4d0b..3659356b56 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -740,7 +740,8 @@ static int client_parse_message( while (pos < len) { DHCP6Option *option = (DHCP6Option *)&message->options[pos]; - uint16_t optcode, optlen, status; + uint16_t optcode, optlen; + int status; uint8_t *optval; be32_t iaid_lease; @@ -795,14 +796,13 @@ static int client_parse_message( break; case SD_DHCP6_OPTION_STATUS_CODE: - if (optlen < 2) - return -EINVAL; - - status = optval[0] << 8 | optval[1]; + status = dhcp6_option_parse_status(option); if (status) { log_dhcp6_client(client, "%s Status %s", dhcp6_message_type_to_string(message->type), dhcp6_message_status_to_string(status)); + dhcp6_lease_free_ia(&lease->ia); + return -EINVAL; } From 0dfe2a4b5680f47eafd987fc4cdae6d536b72cb6 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:43 +0200 Subject: [PATCH 06/25] dhcp6: Fix IA Address option parsing Factor out IA Address option parsing and fix it so that all conditions are checked before a new address is allocated and added to the address list. Note also that the IA Address option can contain a nested Status option. If the status in anything else than zero, the DHCPv6 server is communicating an error condition and the address cannot be used. Status option nesting is clarified in RFC 7550, Section 4.1. The IA Address option is included as a typedef so that the lifetimes can be inspected before allocating a new address and the option length needed is easily available. --- src/libsystemd-network/dhcp6-internal.h | 13 +++-- src/libsystemd-network/dhcp6-option.c | 73 ++++++++++++++++++------- 2 files changed, 60 insertions(+), 26 deletions(-) diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index 982ce3608a..8691c2ca02 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -36,16 +36,19 @@ typedef struct DHCP6Option { uint8_t data[]; } _packed_ DHCP6Option; +/* Address option */ +struct iaaddr { + struct in6_addr address; + be32_t lifetime_preferred; + be32_t lifetime_valid; +} _packed_; + typedef struct DHCP6Address DHCP6Address; struct DHCP6Address { LIST_FIELDS(DHCP6Address, addresses); - struct { - struct in6_addr address; - be32_t lifetime_preferred; - be32_t lifetime_valid; - } iaaddr _packed_; + struct iaaddr iaaddr; }; /* Non-temporary Address option */ diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index b18a996d86..c196078a71 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -40,6 +40,12 @@ typedef struct DHCP6StatusOption { char msg[]; } _packed_ DHCP6StatusOption; +typedef struct DHCP6AddressOption { + struct DHCP6Option option; + struct iaaddr iaaddr; + uint8_t options[]; +} _packed_ DHCP6AddressOption; + #define DHCP6_OPTION_IA_NA_LEN 12 #define DHCP6_OPTION_IA_TA_LEN 4 @@ -223,14 +229,53 @@ int dhcp6_option_parse_status(DHCP6Option *option) { return be16toh(statusopt->status); } +static int dhcp6_option_parse_address(DHCP6Option *option, DHCP6IA *ia, + uint32_t *lifetime_valid) { + DHCP6AddressOption *addr_option = (DHCP6AddressOption *)option; + DHCP6Address *addr; + uint32_t lt_valid, lt_pref; + int r; + + if (be16toh(option->len) + sizeof(DHCP6Option) < sizeof(*addr_option)) + return -ENOBUFS; + + lt_valid = be32toh(addr_option->iaaddr.lifetime_valid); + lt_pref = be32toh(addr_option->iaaddr.lifetime_preferred); + + if (lt_valid == 0 || lt_pref > lt_valid) { + log_dhcp6_client(client, "Valid lifetime of an IA address is zero or preferred lifetime %d > valid lifetime %d", + lt_pref, lt_valid); + + return 0; + } + + if (be16toh(option->len) + sizeof(DHCP6Option) > sizeof(*addr_option)) { + r = dhcp6_option_parse_status((DHCP6Option *)addr_option->options); + if (r != 0) + return r < 0 ? r: 0; + } + + addr = new0(DHCP6Address, 1); + if (!addr) + return -ENOMEM; + + LIST_INIT(addresses, addr); + memcpy(&addr->iaaddr, option->data, sizeof(addr->iaaddr)); + + LIST_PREPEND(addresses, ia->addresses, addr); + + *lifetime_valid = be32toh(addr->iaaddr.lifetime_valid); + + return 0; +} + int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) { uint16_t iatype, optlen; size_t i, len; int r = 0, status; uint16_t opt; size_t iaaddr_offset; - DHCP6Address *addr; - uint32_t lt_t1, lt_t2, lt_valid, lt_pref, lt_min = ~0; + uint32_t lt_t1, lt_t2, lt_valid, lt_min = ~0; assert_return(ia, -EINVAL); assert_return(!ia->addresses, -EINVAL); @@ -293,27 +338,13 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) { switch (opt) { case SD_DHCP6_OPTION_IAADDR: - addr = new0(DHCP6Address, 1); - if (!addr) { - r = -ENOMEM; + + r = dhcp6_option_parse_address(option, ia, <_valid); + if (r < 0) goto error; - } - LIST_INIT(addresses, addr); - memcpy(&addr->iaaddr, option->data, sizeof(addr->iaaddr)); - - lt_valid = be32toh(addr->iaaddr.lifetime_valid); - lt_pref = be32toh(addr->iaaddr.lifetime_valid); - - if (!lt_valid || lt_pref > lt_valid) { - log_dhcp6_client(client, "IA preferred %ds > valid %ds", - lt_pref, lt_valid); - free(addr); - } else { - LIST_PREPEND(addresses, ia->addresses, addr); - if (lt_valid < lt_min) - lt_min = lt_valid; - } + if (lt_valid < lt_min) + lt_min = lt_valid; break; From df296124dfe2fe8a27e372643033b371df53abce Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:44 +0200 Subject: [PATCH 07/25] test-dhcp6-client: Add Status option test Add Status option tests to verify that options with differently placed Status options are processed correctly. --- src/libsystemd-network/test-dhcp6-client.c | 78 ++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index 77084e53c7..b2e99bac42 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -156,6 +156,83 @@ static int test_option(sd_event *e) { return 0; } +static int test_option_status(sd_event *e) { + uint8_t option1[] = { + /* IA NA */ + 0x00, 0x03, 0x00, 0x12, 0x1a, 0x1d, 0x1a, 0x1d, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, + /* status option */ + 0x00, 0x0d, 0x00, 0x02, 0x00, 0x01, + }; + static const uint8_t option2[] = { + /* IA NA */ + 0x00, 0x03, 0x00, 0x2e, 0x1a, 0x1d, 0x1a, 0x1d, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, + /* IA Addr */ + 0x00, 0x05, 0x00, 0x1e, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x01, 0x02, 0x03, 0x04, 0x0a, 0x0b, 0x0c, 0x0d, + /* status option */ + 0x00, 0x0d, 0x00, 0x02, 0x00, 0x01, + }; + static const uint8_t option3[] = { + /* IA NA */ + 0x00, 0x03, 0x00, 0x34, 0x1a, 0x1d, 0x1a, 0x1d, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, + /* IA Addr */ + 0x00, 0x05, 0x00, 0x24, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x01, 0x02, 0x03, 0x04, 0x0a, 0x0b, 0x0c, 0x0d, + /* status option */ + 0x00, 0x0d, 0x00, 0x08, 0x00, 0x00, 'f', 'o', + 'o', 'b', 'a', 'r', + }; + DHCP6Option *option; + DHCP6IA ia; + int r = 0; + + if (verbose) + printf("* %s\n", __FUNCTION__); + + zero(ia); + option = (DHCP6Option *)option1; + assert_se(sizeof(option1) == sizeof(DHCP6Option) + be16toh(option->len)); + + r = dhcp6_option_parse_ia(option, &ia); + assert_se(r == -EINVAL); + assert_se(ia.addresses == NULL); + + option->len = htobe16(17); + r = dhcp6_option_parse_ia(option, &ia); + assert_se(r == -ENOBUFS); + assert_se(ia.addresses == NULL); + + option->len = htobe16(sizeof(DHCP6Option)); + r = dhcp6_option_parse_ia(option, &ia); + assert_se(r == -ENOBUFS); + assert_se(ia.addresses == NULL); + + zero(ia); + option = (DHCP6Option *)option2; + assert_se(sizeof(option2) == sizeof(DHCP6Option) + be16toh(option->len)); + + r = dhcp6_option_parse_ia(option, &ia); + assert_se(r >= 0); + assert_se(ia.addresses == NULL); + + zero(ia); + option = (DHCP6Option *)option3; + assert_se(sizeof(option3) == sizeof(DHCP6Option) + be16toh(option->len)); + + r = dhcp6_option_parse_ia(option, &ia); + assert_se(r >= 0); + assert_se(ia.addresses != NULL); + + return 0; +} + static uint8_t msg_advertise[198] = { 0x02, 0x0f, 0xb4, 0xe5, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x1a, 0x6b, 0xf3, 0x30, @@ -800,6 +877,7 @@ int main(int argc, char *argv[]) { test_client_basic(e); test_option(e); + test_option_status(e); test_advertise_option(e); test_client_solicit(e); From 831ad964457a9ce45b0ec32e82cbd272b4650eb7 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:45 +0200 Subject: [PATCH 08/25] dhcp6: Define IA PD and PD Prefix option numbers --- src/systemd/sd-dhcp6-client.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h index 37803c71d8..2741b54eb0 100644 --- a/src/systemd/sd-dhcp6-client.h +++ b/src/systemd/sd-dhcp6-client.h @@ -64,6 +64,8 @@ enum { SD_DHCP6_OPTION_DNS_SERVERS = 23, /* RFC 3646 */ SD_DHCP6_OPTION_DOMAIN_LIST = 24, /* RFC 3646 */ + SD_DHCP6_OPTION_IA_PD = 25, /* RFC 3633, prefix delegation */ + SD_DHCP6_OPTION_IA_PD_PREFIX = 26, /* RFC 3633, prefix delegation */ SD_DHCP6_OPTION_SNTP_SERVERS = 31, /* RFC 4075, deprecated */ From f8ad4dd45d761d839de49ddff9d7fbf46892c148 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:46 +0200 Subject: [PATCH 09/25] dhcp6: Parse IA PD and PD Prefix options Parse IA PD options and the prefixes in one or more PD Prefix options. As the PD option contains identical data as the IA NA option, re-use the same general data structures and sub-option parsing logic. Similar to IA NA addresses, PD and associated prefixes are stored in the address list of the IA PD lease. An IA sub-option Status code will affect the IA NA and IA PD option in question and cause those options to be ignored. A Status code option in an IA Address or IA PD Prefix option affects only that IA Address or Prefix. --- src/libsystemd-network/dhcp6-internal.h | 21 ++- src/libsystemd-network/dhcp6-lease-internal.h | 1 + src/libsystemd-network/dhcp6-option.c | 133 ++++++++++++++++-- 3 files changed, 144 insertions(+), 11 deletions(-) diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index 8691c2ca02..b1d7d63932 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -43,12 +43,23 @@ struct iaaddr { be32_t lifetime_valid; } _packed_; +/* Prefix Delegation Prefix option */ +struct iapdprefix { + be32_t lifetime_preferred; + be32_t lifetime_valid; + uint8_t prefixlen; + struct in6_addr address; +} _packed_; + typedef struct DHCP6Address DHCP6Address; struct DHCP6Address { LIST_FIELDS(DHCP6Address, addresses); - struct iaaddr iaaddr; + union { + struct iaaddr iaaddr; + struct iapdprefix iapdprefix; + }; }; /* Non-temporary Address option */ @@ -58,6 +69,13 @@ struct ia_na { be32_t lifetime_t2; } _packed_; +/* Prefix Delegation option */ +struct ia_pd { + be32_t id; + be32_t lifetime_t1; + be32_t lifetime_t2; +} _packed_; + /* Temporary Address option */ struct ia_ta { be32_t id; @@ -67,6 +85,7 @@ struct DHCP6IA { uint16_t type; union { struct ia_na ia_na; + struct ia_pd ia_pd; struct ia_ta ia_ta; }; sd_event_source *timeout_t1; diff --git a/src/libsystemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/dhcp6-lease-internal.h index a3f00d4a5d..ba9e9945dc 100644 --- a/src/libsystemd-network/dhcp6-lease-internal.h +++ b/src/libsystemd-network/dhcp6-lease-internal.h @@ -36,6 +36,7 @@ struct sd_dhcp6_lease { bool rapid_commit; DHCP6IA ia; + DHCP6IA pd; DHCP6Address *addr_iter; diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index c196078a71..c894ab4b34 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -46,8 +46,15 @@ typedef struct DHCP6AddressOption { uint8_t options[]; } _packed_ DHCP6AddressOption; -#define DHCP6_OPTION_IA_NA_LEN 12 -#define DHCP6_OPTION_IA_TA_LEN 4 +typedef struct DHCP6PDPrefixOption { + struct DHCP6Option option; + struct iapdprefix iapdprefix; + uint8_t options[]; +} _packed_ DHCP6PDPrefixOption; + +#define DHCP6_OPTION_IA_NA_LEN (sizeof(struct ia_na)) +#define DHCP6_OPTION_IA_PD_LEN (sizeof(struct ia_pd)) +#define DHCP6_OPTION_IA_TA_LEN (sizeof(struct ia_ta)) static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode, size_t optlen) { @@ -269,6 +276,46 @@ static int dhcp6_option_parse_address(DHCP6Option *option, DHCP6IA *ia, return 0; } +static int dhcp6_option_parse_pdprefix(DHCP6Option *option, DHCP6IA *ia, + uint32_t *lifetime_valid) { + DHCP6PDPrefixOption *pdprefix_option = (DHCP6PDPrefixOption *)option; + DHCP6Address *prefix; + uint32_t lt_valid, lt_pref; + int r; + + if (be16toh(option->len) + sizeof(DHCP6Option) < sizeof(*pdprefix_option)) + return -ENOBUFS; + + lt_valid = be32toh(pdprefix_option->iapdprefix.lifetime_valid); + lt_pref = be32toh(pdprefix_option->iapdprefix.lifetime_preferred); + + if (lt_valid == 0 || lt_pref > lt_valid) { + log_dhcp6_client(client, "Valid lifetieme of a PD prefix is zero or preferred lifetime %d > valid lifetime %d", + lt_pref, lt_valid); + + return 0; + } + + if (be16toh(option->len) + sizeof(DHCP6Option) > sizeof(*pdprefix_option)) { + r = dhcp6_option_parse_status((DHCP6Option *)pdprefix_option->options); + if (r != 0) + return r < 0 ? r: 0; + } + + prefix = new0(DHCP6Address, 1); + if (!prefix) + return -ENOMEM; + + LIST_INIT(addresses, prefix); + memcpy(&prefix->iapdprefix, option->data, sizeof(prefix->iapdprefix)); + + LIST_PREPEND(addresses, ia->addresses, prefix); + + *lifetime_valid = be32toh(prefix->iapdprefix.lifetime_valid); + + return 0; +} + int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) { uint16_t iatype, optlen; size_t i, len; @@ -298,7 +345,29 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) { lt_t2 = be32toh(ia->ia_na.lifetime_t2); if (lt_t1 && lt_t2 && lt_t1 > lt_t2) { - log_dhcp6_client(client, "IA T1 %ds > T2 %ds", + log_dhcp6_client(client, "IA NA T1 %ds > T2 %ds", + lt_t1, lt_t2); + r = -EINVAL; + goto error; + } + + break; + + case SD_DHCP6_OPTION_IA_PD: + + if (len < sizeof(ia->ia_pd)) { + r = -ENOBUFS; + goto error; + } + + iaaddr_offset = sizeof(ia->ia_pd); + memcpy(&ia->ia_pd, iaoption->data, sizeof(ia->ia_pd)); + + lt_t1 = be32toh(ia->ia_pd.lifetime_t1); + lt_t2 = be32toh(ia->ia_pd.lifetime_t2); + + if (lt_t1 && lt_t2 && lt_t1 > lt_t2) { + log_dhcp6_client(client, "IA PD T1 %ds > T2 %ds", lt_t1, lt_t2); r = -EINVAL; goto error; @@ -339,6 +408,12 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) { switch (opt) { case SD_DHCP6_OPTION_IAADDR: + if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_NA, SD_DHCP6_OPTION_IA_TA)) { + log_dhcp6_client(client, "IA Address option not in IA NA or TA option"); + r = -EINVAL; + goto error; + } + r = dhcp6_option_parse_address(option, ia, <_valid); if (r < 0) goto error; @@ -348,6 +423,23 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) { break; + case SD_DHCP6_OPTION_IA_PD_PREFIX: + + if (!IN_SET(ia->type, SD_DHCP6_OPTION_IA_PD)) { + log_dhcp6_client(client, "IA PD Prefix option not in IA PD option"); + r = -EINVAL; + goto error; + } + + r = dhcp6_option_parse_pdprefix(option, ia, <_valid); + if (r < 0) + goto error; + + if (lt_valid < lt_min) + lt_min = lt_valid; + + break; + case SD_DHCP6_OPTION_STATUS_CODE: status = dhcp6_option_parse_status(option); @@ -371,14 +463,35 @@ int dhcp6_option_parse_ia(DHCP6Option *iaoption, DHCP6IA *ia) { i += sizeof(*option) + optlen; } - if (!ia->ia_na.lifetime_t1 && !ia->ia_na.lifetime_t2) { - lt_t1 = lt_min / 2; - lt_t2 = lt_min / 10 * 8; - ia->ia_na.lifetime_t1 = htobe32(lt_t1); - ia->ia_na.lifetime_t2 = htobe32(lt_t2); + switch(iatype) { + case SD_DHCP6_OPTION_IA_NA: + if (!ia->ia_na.lifetime_t1 && !ia->ia_na.lifetime_t2) { + lt_t1 = lt_min / 2; + lt_t2 = lt_min / 10 * 8; + ia->ia_na.lifetime_t1 = htobe32(lt_t1); + ia->ia_na.lifetime_t2 = htobe32(lt_t2); - log_dhcp6_client(client, "Computed IA T1 %ds and T2 %ds as both were zero", - lt_t1, lt_t2); + log_dhcp6_client(client, "Computed IA NA T1 %ds and T2 %ds as both were zero", + lt_t1, lt_t2); + } + + break; + + case SD_DHCP6_OPTION_IA_PD: + if (!ia->ia_pd.lifetime_t1 && !ia->ia_pd.lifetime_t2) { + lt_t1 = lt_min / 2; + lt_t2 = lt_min / 10 * 8; + ia->ia_pd.lifetime_t1 = htobe32(lt_t1); + ia->ia_pd.lifetime_t2 = htobe32(lt_t2); + + log_dhcp6_client(client, "Computed IA PD T1 %ds and T2 %ds as both were zero", + lt_t1, lt_t2); + } + + break; + + default: + break; } error: From dce6563fc6f8537368e944a8f344682541ea1f30 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:47 +0200 Subject: [PATCH 10/25] dhcp6: Handle a received IA PD option Parse the received IA PD option and verify its IAID. --- src/libsystemd-network/sd-dhcp6-client.c | 29 +++++++++++++++++++++++- src/libsystemd-network/sd-dhcp6-lease.c | 1 + 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 3659356b56..6efe1a7283 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -54,6 +54,7 @@ struct sd_dhcp6_client { size_t mac_addr_len; uint16_t arp_type; DHCP6IA ia_na; + DHCP6IA ia_pd; be32_t transaction_id; usec_t transaction_start; struct sd_dhcp6_lease *lease; @@ -231,6 +232,7 @@ int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) { assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); client->ia_na.ia_na.id = htobe32(iaid); + client->ia_pd.ia_pd.id = htobe32(iaid); return 0; } @@ -802,6 +804,7 @@ static int client_parse_message( dhcp6_message_type_to_string(message->type), dhcp6_message_status_to_string(status)); dhcp6_lease_free_ia(&lease->ia); + dhcp6_lease_free_ia(&lease->pd); return -EINVAL; } @@ -824,7 +827,30 @@ static int client_parse_message( return r; if (client->ia_na.ia_na.id != iaid_lease) { - log_dhcp6_client(client, "%s has wrong IAID", + log_dhcp6_client(client, "%s has wrong IAID for IA NA", + dhcp6_message_type_to_string(message->type)); + return -EINVAL; + } + + break; + + case SD_DHCP6_OPTION_IA_PD: + if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { + log_dhcp6_client(client, "Information request ignoring IA PD option"); + + break; + } + + r = dhcp6_option_parse_ia(option, &lease->pd); + if (r < 0 && r != -ENOMSG) + return r; + + r = dhcp6_lease_get_iaid(lease, &iaid_lease); + if (r < 0) + return r; + + if (client->ia_pd.ia_pd.id != iaid_lease) { + log_dhcp6_client(client, "%s has wrong IAID for IA PD", dhcp6_message_type_to_string(message->type)); return -EINVAL; } @@ -1360,6 +1386,7 @@ int sd_dhcp6_client_new(sd_dhcp6_client **ret) { client->n_ref = 1; client->ia_na.type = SD_DHCP6_OPTION_IA_NA; + client->ia_pd.type = SD_DHCP6_OPTION_IA_PD; client->ifindex = -1; client->fd = -1; diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c index c0819b7383..32815a5386 100644 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -382,6 +382,7 @@ sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease) { free(lease->serverid); dhcp6_lease_free_ia(&lease->ia); + dhcp6_lease_free_ia(&lease->pd); free(lease->dns); From 69b4399748297a2a812e5f8e5d2d786b3b1d1a96 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:48 +0200 Subject: [PATCH 11/25] dhcp6: Ensure IAID is applied to both IA NA and IA PD Update the code to set IAID for both IA NA and IA PD options. --- src/libsystemd-network/sd-dhcp6-client.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 6efe1a7283..9586da6503 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -709,16 +709,20 @@ error: static int client_ensure_iaid(sd_dhcp6_client *client) { int r; + be32_t iaid; assert(client); if (client->ia_na.ia_na.id) return 0; - r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, &client->ia_na.ia_na.id); + r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, &iaid); if (r < 0) return r; + client->ia_na.ia_na.id = iaid; + client->ia_pd.ia_pd.id = iaid; + return 0; } From c77e3db19ecda329582b0659868913413db0bcd3 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:49 +0200 Subject: [PATCH 12/25] dhcp6: Add function appending an IA PD to the DHCP6 message Add function that appends an IA PD option and any number of IA PD Prefix options. --- src/libsystemd-network/dhcp6-internal.h | 1 + src/libsystemd-network/dhcp6-option.c | 36 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index b1d7d63932..13844a86c6 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -102,6 +102,7 @@ typedef struct DHCP6IA DHCP6IA; int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, size_t optlen, const void *optval); int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia); +int dhcp6_option_append_pd(uint8_t *buf, size_t len, DHCP6IA *pd); int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn); int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen, uint8_t **optvalue); diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index c894ab4b34..784a11dc9d 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -181,6 +181,42 @@ int dhcp6_option_append_fqdn(uint8_t **buf, size_t *buflen, const char *fqdn) { return r; } +int dhcp6_option_append_pd(uint8_t *buf, size_t len, DHCP6IA *pd) { + DHCP6Option *option = (DHCP6Option *)buf; + size_t i = sizeof(*option) + sizeof(pd->ia_pd); + DHCP6Address *prefix; + + assert_return(buf, -EINVAL); + assert_return(pd, -EINVAL); + assert_return(pd->type == SD_DHCP6_OPTION_IA_PD, -EINVAL); + + if (len < i) + return -ENOBUFS; + + option->code = htobe16(SD_DHCP6_OPTION_IA_PD); + + memcpy(&option->data, &pd->ia_pd, sizeof(pd->ia_pd)); + + LIST_FOREACH(addresses, prefix, pd->addresses) { + DHCP6PDPrefixOption *prefix_opt; + + if (len < i + sizeof(*prefix_opt)) + return -ENOBUFS; + + prefix_opt = (DHCP6PDPrefixOption *)&buf[i]; + prefix_opt->option.code = htobe16(SD_DHCP6_OPTION_IA_PD_PREFIX); + prefix_opt->option.len = htobe16(sizeof(prefix_opt->iapdprefix)); + + memcpy(&prefix_opt->iapdprefix, &prefix->iapdprefix, + sizeof(struct iapdprefix)); + + i += sizeof(*prefix_opt); + } + + option->len = htobe16(i - sizeof(*option)); + + return i; +} static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) { DHCP6Option *option = (DHCP6Option*) *buf; From 7c3de8f8cff2220dca2a27a8e85e40641a6ae9c4 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:50 +0200 Subject: [PATCH 13/25] dhcp6: Add functionality to request DHCPv6 IA PD Add a function to request IA Prefix Delegation when the DHCPv6 client is started and PD options to DHCPv6 messages. --- src/libsystemd-network/sd-dhcp6-client.c | 36 ++++++++++++++++++++++++ src/systemd/sd-dhcp6-client.h | 3 ++ 2 files changed, 39 insertions(+) diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 9586da6503..2c2d062ede 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -55,6 +55,7 @@ struct sd_dhcp6_client { uint16_t arp_type; DHCP6IA ia_na; DHCP6IA ia_pd; + bool prefix_delegation; be32_t transaction_id; usec_t transaction_start; struct sd_dhcp6_lease *lease; @@ -300,6 +301,14 @@ int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option) return 0; } +int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client, bool delegation) { + assert_return(client, -EINVAL); + + client->prefix_delegation = delegation; + + return 0; +} + int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) { assert_return(client, -EINVAL); @@ -413,6 +422,15 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { return r; } + if (client->prefix_delegation) { + r = dhcp6_option_append_pd(opt, optlen, &client->ia_pd); + if (r < 0) + return r; + + opt += r; + optlen -= r; + } + break; case DHCP6_STATE_REQUEST: @@ -439,6 +457,15 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { return r; } + if (client->prefix_delegation) { + r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd); + if (r < 0) + return r; + + opt += r; + optlen -= r; + } + break; case DHCP6_STATE_REBIND: @@ -454,6 +481,15 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { return r; } + if (client->prefix_delegation) { + r = dhcp6_option_append_pd(opt, optlen, &client->lease->pd); + if (r < 0) + return r; + + opt += r; + optlen -= r; + } + break; case DHCP6_STATE_STOPPED: diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h index 2741b54eb0..cadb32a051 100644 --- a/src/systemd/sd-dhcp6-client.h +++ b/src/systemd/sd-dhcp6-client.h @@ -23,6 +23,7 @@ #include #include +#include #include #include "sd-dhcp6-lease.h" @@ -118,6 +119,8 @@ int sd_dhcp6_client_get_information_request( int sd_dhcp6_client_set_request_option( sd_dhcp6_client *client, uint16_t option); +int sd_dhcp6_client_set_prefix_delegation(sd_dhcp6_client *client, + bool delegation); int sd_dhcp6_client_get_lease( sd_dhcp6_client *client, From 103b81ee8c633d44499ce6387b9ad70a135a0902 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:51 +0200 Subject: [PATCH 14/25] networkd: Request prefixes when configured to do so Request prefixes via DHCPv6 if there are networks that are configured to distribute them. As specified in RFC 3633, a DHCPv6 client cannot redistribute the prefixes via Router Advertisements on the same link. Ignore such networks, and print out a warning if the link where DHCPv6 is enabled tries to do so. --- src/network/networkd-dhcp6.c | 45 ++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index a46a11bf16..59dcf28764 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -30,6 +30,45 @@ static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link); +static bool dhcp6_verify_link(Link *link) { + if (!link->network) { + log_link_info(link, "Link is not managed by us"); + return false; + } + + if (!IN_SET(link->network->router_prefix_delegation, + RADV_PREFIX_DELEGATION_DHCP6, + RADV_PREFIX_DELEGATION_BOTH)) { + log_link_debug(link, "Link does not request DHCPv6 prefix delegation"); + return false; + } + + return true; +} + +static bool dhcp6_enable_prefix_delegation(Link *dhcp6_link) { + Manager *manager; + Link *l; + Iterator i; + + assert(dhcp6_link); + + manager = dhcp6_link->manager; + assert(manager); + + HASHMAP_FOREACH(l, manager->links, i) { + if (l == dhcp6_link) + continue; + + if (!dhcp6_verify_link(l)) + continue; + + return true; + } + + return false; +} + static int dhcp6_lease_information_acquired(sd_dhcp6_client *client, Link *link) { return 0; @@ -283,6 +322,12 @@ int dhcp6_configure(Link *link) { if (r < 0) goto error; + if (dhcp6_enable_prefix_delegation(link)) { + r = sd_dhcp6_client_set_prefix_delegation(client, true); + if (r < 0) + goto error; + } + link->dhcp6_client = client; return 0; From b47fb949b338a8e77be789542fffb8c86da79284 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:52 +0200 Subject: [PATCH 15/25] dhcp6: Compute the minimum lifetimes for T1 and T2 Compute one set of minimum lifetimes for T1 and T2, i.e. the smaller ones assigned to IA NA and IA PD. The lifetimes should be the same, see RFC 7550 for details. --- src/libsystemd-network/sd-dhcp6-client.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 2c2d062ede..0584059fec 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -772,6 +772,7 @@ static int client_parse_message( bool clientid = false; uint8_t *id = NULL; size_t id_len; + uint32_t lt_t1 = ~0, lt_t2 = ~0; assert(client); assert(message); @@ -872,6 +873,11 @@ static int client_parse_message( return -EINVAL; } + if (lease->ia.addresses) { + lt_t1 = MIN(lt_t1, be32toh(lease->ia.ia_na.lifetime_t1)); + lt_t2 = MIN(lt_t2, be32toh(lease->ia.ia_na.lifetime_t1)); + } + break; case SD_DHCP6_OPTION_IA_PD: @@ -895,6 +901,11 @@ static int client_parse_message( return -EINVAL; } + if (lease->pd.addresses) { + lt_t1 = MIN(lt_t1, be32toh(lease->pd.ia_pd.lifetime_t1)); + lt_t2 = MIN(lt_t2, be32toh(lease->pd.ia_pd.lifetime_t2)); + } + break; case SD_DHCP6_OPTION_RAPID_COMMIT: @@ -947,6 +958,17 @@ static int client_parse_message( if (r < 0) log_dhcp6_client(client, "%s has no server id", dhcp6_message_type_to_string(message->type)); + return r; + } + + if (lease->ia.addresses) { + lease->ia.ia_na.lifetime_t1 = htobe32(lt_t1); + lease->ia.ia_na.lifetime_t2 = htobe32(lt_t2); + } + + if (lease->pd.addresses) { + lease->pd.ia_pd.lifetime_t1 = htobe32(lt_t1); + lease->pd.ia_pd.lifetime_t2 = htobe32(lt_t2); } return r; From 819c56f6fa08a95eb53f6ba607225bf4e2306279 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:53 +0200 Subject: [PATCH 16/25] dhpc6: Add PD and PD Prefix tests Add tests for IA PD and PD Prefix options. --- src/libsystemd-network/test-dhcp6-client.c | 54 +++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index b2e99bac42..3eaf9111dc 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -189,8 +189,41 @@ static int test_option_status(sd_event *e) { 0x00, 0x0d, 0x00, 0x08, 0x00, 0x00, 'f', 'o', 'o', 'b', 'a', 'r', }; + static const uint8_t option4[] = { + /* IA PD */ + 0x00, 0x19, 0x00, 0x2f, 0x1a, 0x1d, 0x1a, 0x1d, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, + /* IA PD Prefix */ + 0x00, 0x1a, 0x00, 0x1f, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x80, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, + 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + /* status option */ + 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00, + }; + static const uint8_t option5[] = { + /* IA PD */ + 0x00, 0x19, 0x00, 0x52, 0x1a, 0x1d, 0x1a, 0x1d, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, + /* IA PD Prefix #1 */ + 0x00, 0x1a, 0x00, 0x1f, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x80, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, + 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + /* status option */ + 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00, + /* IA PD Prefix #2 */ + 0x00, 0x1a, 0x00, 0x1f, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x80, 0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x0l, 0xd0, + 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, + 0x00, 0x0d, 0x00, 0x02, 0x00, 0x00, + }; DHCP6Option *option; - DHCP6IA ia; + DHCP6IA ia, pd; int r = 0; if (verbose) @@ -230,6 +263,25 @@ static int test_option_status(sd_event *e) { assert_se(r >= 0); assert_se(ia.addresses != NULL); + zero(pd); + option = (DHCP6Option *)option4; + assert_se(sizeof(option4) == sizeof(DHCP6Option) + be16toh(option->len)); + + r = dhcp6_option_parse_ia(option, &pd); + assert_se(r == 0); + assert_se(pd.addresses != NULL); + assert_se(memcmp(&pd.ia_pd.id, &option4[4], 4) == 0); + assert_se(memcmp(&pd.ia_pd.lifetime_t1, &option4[8], 4) == 0); + assert_se(memcmp(&pd.ia_pd.lifetime_t2, &option4[12], 4) == 0); + + zero(pd); + option = (DHCP6Option *)option5; + assert_se(sizeof(option5) == sizeof(DHCP6Option) + be16toh(option->len)); + + r = dhcp6_option_parse_ia(option, &pd); + assert_se(r == 0); + assert_se(pd.addresses != NULL); + return 0; } From 652bf042549bb24ccb3d24c32701b614f4720e4f Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:54 +0200 Subject: [PATCH 17/25] dhcp6: Add function to iterate and fetch delegated prefixes Add a function for fetching the next delegated prefix and another one to reset the iteration to the first prefix. --- src/libsystemd-network/dhcp6-lease-internal.h | 1 + src/libsystemd-network/sd-dhcp6-lease.c | 31 +++++++++++++++++++ src/systemd/sd-dhcp6-lease.h | 5 +++ 3 files changed, 37 insertions(+) diff --git a/src/libsystemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/dhcp6-lease-internal.h index ba9e9945dc..45e0e82427 100644 --- a/src/libsystemd-network/dhcp6-lease-internal.h +++ b/src/libsystemd-network/dhcp6-lease-internal.h @@ -39,6 +39,7 @@ struct sd_dhcp6_lease { DHCP6IA pd; DHCP6Address *addr_iter; + DHCP6Address *prefix_iter; struct in6_addr *dns; size_t dns_count; diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c index 32815a5386..1f3a782f8c 100644 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -176,6 +176,37 @@ void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease) { lease->addr_iter = lease->ia.addresses; } +int sd_dhcp6_lease_get_pd(sd_dhcp6_lease *lease, struct in6_addr *prefix, + uint8_t *prefix_len, + uint32_t *lifetime_preferred, + uint32_t *lifetime_valid) { + assert_return(lease, -EINVAL); + assert_return(prefix, -EINVAL); + assert_return(prefix_len, -EINVAL); + assert_return(lifetime_preferred, -EINVAL); + assert_return(lifetime_valid, -EINVAL); + + if (!lease->prefix_iter) + return -ENOMSG; + + memcpy(prefix, &lease->prefix_iter->iapdprefix.address, + sizeof(struct in6_addr)); + *prefix_len = lease->prefix_iter->iapdprefix.prefixlen; + *lifetime_preferred = + be32toh(lease->prefix_iter->iapdprefix.lifetime_preferred); + *lifetime_valid = + be32toh(lease->prefix_iter->iapdprefix.lifetime_valid); + + lease->prefix_iter = lease->prefix_iter->addresses_next; + + return 0; +} + +void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease) { + if (lease) + lease->prefix_iter = lease->pd.addresses; +} + int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) { int r; diff --git a/src/systemd/sd-dhcp6-lease.h b/src/systemd/sd-dhcp6-lease.h index 5807b1836b..22a5f8ce75 100644 --- a/src/systemd/sd-dhcp6-lease.h +++ b/src/systemd/sd-dhcp6-lease.h @@ -36,6 +36,11 @@ int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, struct in6_addr *addr, uint32_t *lifetime_preferred, uint32_t *lifetime_valid); +void sd_dhcp6_lease_reset_pd_prefix_iter(sd_dhcp6_lease *lease); +int sd_dhcp6_lease_get_pd(sd_dhcp6_lease *lease, struct in6_addr *prefix, + uint8_t *prefix_len, + uint32_t *lifetime_preferred, + uint32_t *lifetime_valid); int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, struct in6_addr **addrs); int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains); From d601b566876e227abee18165e1a85673ac3ed41a Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:55 +0200 Subject: [PATCH 18/25] radv: Add prefixes with dynamically updated lifetimes Add a boolean that indicates whether the prefixes will always exist or if they will time out after the assigned valid lifetime. In the latter case calculate the expiry times for both preferred and valid lifetimes for the prefixes, and decrease the remaining lifetimes each time when a Router Advertisement is sent. Should the prefix be updated, re-calculate the prefix lifetime. When updating, update the existing entry, if any, with the lifetimes of the added entry as the existing entry has its lifetimes set according to its previously calculated expiry times. --- src/libsystemd-network/radv-internal.h | 3 ++ src/libsystemd-network/sd-radv.c | 68 +++++++++++++++++++++++--- src/libsystemd-network/test-ndisc-ra.c | 4 +- src/network/networkd-radv.c | 2 +- src/systemd/sd-radv.h | 3 +- 5 files changed, 70 insertions(+), 10 deletions(-) diff --git a/src/libsystemd-network/radv-internal.h b/src/libsystemd-network/radv-internal.h index 441939b717..837e7f2603 100644 --- a/src/libsystemd-network/radv-internal.h +++ b/src/libsystemd-network/radv-internal.h @@ -93,6 +93,9 @@ struct sd_radv_prefix { } _packed_ opt; LIST_FIELDS(struct sd_radv_prefix, prefix); + + usec_t valid_until; + usec_t preferred_until; }; #define log_radv_full(level, error, fmt, ...) log_internal(level, error, __FILE__, __LINE__, __func__, "RADV: " fmt, ##__VA_ARGS__) diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c index 46704acdef..169278d5e3 100644 --- a/src/libsystemd-network/sd-radv.c +++ b/src/libsystemd-network/sd-radv.c @@ -169,6 +169,12 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst, .msg_namelen = sizeof(dst_addr), .msg_iov = iov, }; + usec_t time_now; + int r; + + r = sd_event_now(ra->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return r; if (dst && !in_addr_is_null(AF_INET6, (union in_addr_union*) dst)) dst_addr.sin6_addr = *dst; @@ -198,6 +204,18 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst, } LIST_FOREACH(prefix, p, ra->prefixes) { + if (p->valid_until) { + + if (time_now > p->valid_until) + p->opt.valid_lifetime = 0; + else + p->opt.valid_lifetime = htobe32((p->valid_until - time_now) / USEC_PER_SEC); + + if (time_now > p->preferred_until) + p->opt.preferred_lifetime = 0; + else + p->opt.preferred_lifetime = htobe32((p->preferred_until - time_now) / USEC_PER_SEC); + } iov[msg.msg_iovlen].iov_base = &p->opt; iov[msg.msg_iovlen].iov_len = sizeof(p->opt); msg.msg_iovlen++; @@ -518,9 +536,13 @@ _public_ int sd_radv_set_preference(sd_radv *ra, unsigned preference) { return r; } -_public_ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p) { +_public_ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p, bool dynamic) { sd_radv_prefix *cur; + int r; _cleanup_free_ char *addr_p = NULL; + char time_string_preferred[FORMAT_TIMESPAN_MAX]; + char time_string_valid[FORMAT_TIMESPAN_MAX]; + usec_t time_now, valid, preferred, valid_until, preferred_until; assert_return(ra, -EINVAL); @@ -528,7 +550,6 @@ _public_ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p) { return -EINVAL; LIST_FOREACH(prefix, cur, ra->prefixes) { - int r; r = in_addr_prefix_intersect(AF_INET6, (union in_addr_union*) &cur->opt.in6_addr, @@ -538,13 +559,16 @@ _public_ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p) { if (r > 0) { _cleanup_free_ char *addr_cur = NULL; - (void) in_addr_to_string(AF_INET6, - (union in_addr_union*) &cur->opt.in6_addr, - &addr_cur); (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &p->opt.in6_addr, &addr_p); + if (dynamic && cur->opt.prefixlen == p->opt.prefixlen) + goto update; + + (void) in_addr_to_string(AF_INET6, + (union in_addr_union*) &cur->opt.in6_addr, + &addr_cur); log_radv("IPv6 prefix %s/%u already configured, ignoring %s/%u", addr_cur, cur->opt.prefixlen, addr_p, p->opt.prefixlen); @@ -560,7 +584,39 @@ _public_ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p) { ra->n_prefixes++; (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &p->opt.in6_addr, &addr_p); - log_radv("Added prefix %s/%d", addr_p, p->opt.prefixlen); + + if (!dynamic) { + log_radv("Added prefix %s/%d", addr_p, p->opt.prefixlen); + return 0; + } + + cur = p; + + update: + r = sd_event_now(ra->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return r; + + valid = be32toh(p->opt.valid_lifetime) * USEC_PER_SEC; + valid_until = usec_add(valid, time_now); + if (valid_until == USEC_INFINITY) + return -EOVERFLOW; + + preferred = be32toh(p->opt.preferred_lifetime) * USEC_PER_SEC; + preferred_until = usec_add(preferred, time_now); + if (preferred_until == USEC_INFINITY) + return -EOVERFLOW; + + cur->valid_until = valid_until; + cur->preferred_until = preferred_until; + + log_radv("%s prefix %s/%u preferred %s valid %s", + cur? "Updated": "Added", + addr_p, p->opt.prefixlen, + format_timespan(time_string_preferred, FORMAT_TIMESPAN_MAX, + preferred, USEC_PER_SEC), + format_timespan(time_string_valid, FORMAT_TIMESPAN_MAX, + valid, USEC_PER_SEC)); return 0; } diff --git a/src/libsystemd-network/test-ndisc-ra.c b/src/libsystemd-network/test-ndisc-ra.c index c1a8d5a00d..1fc8ca9eba 100644 --- a/src/libsystemd-network/test-ndisc-ra.c +++ b/src/libsystemd-network/test-ndisc-ra.c @@ -342,8 +342,8 @@ static void test_ra(void) { if (prefix[i].preferred) assert_se(sd_radv_prefix_set_preferred_lifetime(p, prefix[i].preferred) >= 0); - assert_se((sd_radv_add_prefix(ra, p) >= 0) == prefix[i].succesful); - assert_se(sd_radv_add_prefix(ra, p) < 0); + assert_se((sd_radv_add_prefix(ra, p, false) >= 0) == prefix[i].succesful); + assert_se(sd_radv_add_prefix(ra, p, false) < 0); p = sd_radv_prefix_unref(p); assert_se(!p); diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c index a7e66f575b..5fdab89f2d 100644 --- a/src/network/networkd-radv.c +++ b/src/network/networkd-radv.c @@ -502,7 +502,7 @@ int radv_configure(Link *link) { RADV_PREFIX_DELEGATION_STATIC, RADV_PREFIX_DELEGATION_BOTH)) { LIST_FOREACH(prefixes, p, link->network->static_prefixes) { - r = sd_radv_add_prefix(link->radv, p->radv_prefix); + r = sd_radv_add_prefix(link->radv, p->radv_prefix, false); if (r != -EEXIST && r < 0) return r; } diff --git a/src/systemd/sd-radv.h b/src/systemd/sd-radv.h index 94d5e71e8a..393c977bf1 100644 --- a/src/systemd/sd-radv.h +++ b/src/systemd/sd-radv.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "sd-ndisc.h" @@ -62,7 +63,7 @@ int sd_radv_set_router_lifetime(sd_radv *ra, uint32_t router_lifetime); int sd_radv_set_managed_information(sd_radv *ra, int managed); int sd_radv_set_other_information(sd_radv *ra, int other); int sd_radv_set_preference(sd_radv *ra, unsigned preference); -int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p); +int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p, bool dynamic); int sd_radv_set_rdnss(sd_radv *ra, uint32_t lifetime, const struct in6_addr *dns, size_t n_dns); int sd_radv_set_dnssl(sd_radv *ra, uint32_t lifetime, char **search_list); From 34c169c462cf48c80631f2ea0f29a5a7b578f2e5 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:56 +0200 Subject: [PATCH 19/25] radv: Add function to remove prefixes As DHCPv6 leases may expire at some point, the delegated prefixes have to be removed. Add a prefix removal function to the Router Advertisement handling code. --- src/libsystemd-network/sd-radv.c | 26 ++++++++++++++++++++++++++ src/systemd/sd-radv.h | 2 ++ 2 files changed, 28 insertions(+) diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c index 169278d5e3..b2483bcf2b 100644 --- a/src/libsystemd-network/sd-radv.c +++ b/src/libsystemd-network/sd-radv.c @@ -621,6 +621,32 @@ _public_ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p, bool dynamic) { return 0; } +_public_ sd_radv_prefix *sd_radv_remove_prefix(sd_radv *ra, + struct in6_addr *prefix, + uint8_t prefixlen) { + sd_radv_prefix *cur, *next; + + assert_return(ra, NULL); + assert_return(prefix, NULL); + + LIST_FOREACH_SAFE(prefix, cur, next, ra->prefixes) { + if (prefixlen != cur->opt.prefixlen) + continue; + + if (!in_addr_equal(AF_INET6, + (union in_addr_union *)prefix, + (union in_addr_union *)&cur->opt.in6_addr)) + continue; + + LIST_REMOVE(prefix, ra->prefixes, cur); + ra->n_prefixes--; + + break; + } + + return cur; +} + _public_ int sd_radv_set_rdnss(sd_radv *ra, uint32_t lifetime, const struct in6_addr *dns, size_t n_dns) { _cleanup_free_ struct sd_radv_opt_dns *opt_rdnss = NULL; diff --git a/src/systemd/sd-radv.h b/src/systemd/sd-radv.h index 393c977bf1..e319a82dbf 100644 --- a/src/systemd/sd-radv.h +++ b/src/systemd/sd-radv.h @@ -64,6 +64,8 @@ int sd_radv_set_managed_information(sd_radv *ra, int managed); int sd_radv_set_other_information(sd_radv *ra, int other); int sd_radv_set_preference(sd_radv *ra, unsigned preference); int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p, bool dynamic); +sd_radv_prefix *sd_radv_remove_prefix(sd_radv *ra, struct in6_addr *prefix, + uint8_t prefixlen); int sd_radv_set_rdnss(sd_radv *ra, uint32_t lifetime, const struct in6_addr *dns, size_t n_dns); int sd_radv_set_dnssl(sd_radv *ra, uint32_t lifetime, char **search_list); From de661ccec7b75f196dff5cd2546a94a4fbf46c82 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:57 +0200 Subject: [PATCH 20/25] radv: Allow setting MTU in all cases Setting MTU is allowed in all cases and the MTU value will be announced in the subsequent Router Advertisements. --- src/libsystemd-network/sd-radv.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c index b2483bcf2b..757eabaea7 100644 --- a/src/libsystemd-network/sd-radv.c +++ b/src/libsystemd-network/sd-radv.c @@ -464,9 +464,6 @@ _public_ int sd_radv_set_mtu(sd_radv *ra, uint32_t mtu) { assert_return(ra, -EINVAL); assert_return(mtu >= 1280, -EINVAL); - if (ra->state != SD_RADV_STATE_IDLE) - return -EBUSY; - ra->mtu = mtu; return 0; From e133b289aa50b18fb10285fa27d2daef1ddbafde Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:58 +0200 Subject: [PATCH 21/25] networkd: Add hashmap to store prefixes and associated link Add a hashmap to the Manager struct that stores the association between an IPv6 prefix and the network Link it is assigned to. This is added in order to keep assigning the same prefixes with the same links even though they are delegated at different times or by different DHCPv6 clients. --- src/network/networkd-manager.c | 79 ++++++++++++++++++++++++++++++++++ src/network/networkd-manager.h | 6 +++ 2 files changed, 85 insertions(+) diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index e3c514c804..9d9d96ec8f 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -1234,6 +1234,77 @@ static int manager_dirty_handler(sd_event_source *s, void *userdata) { return 1; } +Link *manager_dhcp6_prefix_get(Manager *m, struct in6_addr *addr) { + assert_return(m, NULL); + assert_return(m->dhcp6_prefixes, NULL); + assert_return(addr, NULL); + + return hashmap_get(m->dhcp6_prefixes, addr); +} + +int manager_dhcp6_prefix_add(Manager *m, struct in6_addr *addr, Link *link) { + assert_return(m, -EINVAL); + assert_return(m->dhcp6_prefixes, -ENODATA); + assert_return(addr, -EINVAL); + + return hashmap_put(m->dhcp6_prefixes, addr, link); +} + +int manager_dhcp6_prefix_remove(Manager *m, struct in6_addr *addr) { + Link *l; + + assert_return(m, -EINVAL); + assert_return(m->dhcp6_prefixes, -ENODATA); + assert_return(addr, -EINVAL); + + l = hashmap_remove(m->dhcp6_prefixes, addr); + if (!l) + return -EINVAL; + + (void) sd_radv_remove_prefix(l->radv, addr, 64); + + return 0; +} + +int manager_dhcp6_prefix_remove_all(Manager *m, Link *link) { + Iterator i; + Link *l; + struct in6_addr *addr; + + assert_return(m, -EINVAL); + assert_return(l, -EINVAL); + + HASHMAP_FOREACH_KEY(l, addr, m->dhcp6_prefixes, i) { + if (l != link) + continue; + + (void) sd_radv_remove_prefix(l->radv, addr, 64); + + hashmap_remove(m->dhcp6_prefixes, addr); + } + + return 0; +} + +static void dhcp6_prefixes_hash_func(const void *p, struct siphash *state) { + const struct in6_addr *addr = p; + + assert(p); + + siphash24_compress(addr, sizeof(*addr), state); +} + +static int dhcp6_prefixes_compare_func(const void *_a, const void *_b) { + const struct in6_addr *a = _a, *b = _b; + + return memcmp(&a, &b, sizeof(*a)); +} + +static const struct hash_ops dhcp6_prefixes_hash_ops = { + .hash = dhcp6_prefixes_hash_func, + .compare = dhcp6_prefixes_compare_func, +}; + int manager_new(Manager **ret, sd_event *event) { _cleanup_manager_free_ Manager *m = NULL; int r; @@ -1270,6 +1341,10 @@ int manager_new(Manager **ret, sd_event *event) { if (r < 0) return r; + m->dhcp6_prefixes = hashmap_new(&dhcp6_prefixes_hash_ops); + if (!m->dhcp6_prefixes) + return -ENOMEM; + m->duid.type = DUID_TYPE_EN; (void) routing_policy_load_rules(m->state_file, &m->rules_saved); @@ -1294,6 +1369,10 @@ void manager_free(Manager *m) { while ((network = m->networks)) network_free(network); + while ((link = hashmap_first(m->dhcp6_prefixes))) + link_unref(link); + hashmap_free(m->dhcp6_prefixes); + while ((link = hashmap_first(m->links))) link_unref(link); hashmap_free(m->links); diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index 186cb41891..c46914f78d 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -58,6 +58,7 @@ struct Manager { Hashmap *links; Hashmap *netdevs; Hashmap *networks_by_name; + Hashmap *dhcp6_prefixes; LIST_HEAD(Network, networks); LIST_HEAD(AddressPool, address_pools); @@ -109,5 +110,10 @@ Link* manager_find_uplink(Manager *m, Link *exclude); int manager_set_hostname(Manager *m, const char *hostname); int manager_set_timezone(Manager *m, const char *timezone); +Link *manager_dhcp6_prefix_get(Manager *m, struct in6_addr *addr); +int manager_dhcp6_prefix_add(Manager *m, struct in6_addr *addr, Link *link); +int manager_dhcp6_prefix_remove(Manager *m, struct in6_addr *addr); +int manager_dhcp6_prefix_remove_all(Manager *m, Link *link); + DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); #define _cleanup_manager_free_ _cleanup_(manager_freep) From 76c3246d8dd06d09139e18b5ab6f3789562b134d Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:11:59 +0200 Subject: [PATCH 22/25] networkd: Assign prefixes received via DHCPv6 When receiving one or more prefixes with variable length, assign a 64 bit long prefix for each link that has been configured for DHCPv6 prefix delegation and is not using DHCPv6 to fetch IPv6 adresses. Keep assigning prefixes with length 64 from each prefix received via DHCPv6 as long as there are prefixes left. If the number of prefixes available from a prefix received via DHCPv6 is smaller than the number of links, continue with the next delegated prefix, if any. Remember the prefixes used for each link by storing them in a hash and checking the hash each time a prefix is to be delegated. If an error occurs when assigning a prefix to a link, try assigning the prefix to another link. If the error occurs while updating the prefix, log the situation and continue delegating the rest of the prefixes. --- src/network/networkd-dhcp6.c | 171 +++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 59dcf28764..4bb42919df 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -20,13 +20,18 @@ #include #include +#include "sd-radv.h" #include "sd-dhcp6-client.h" +#include "hashmap.h" #include "hostname-util.h" #include "network-internal.h" #include "networkd-link.h" #include "networkd-manager.h" +#include "siphash24.h" +#include "string-util.h" +#include "radv-internal.h" static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link); @@ -74,6 +79,166 @@ static int dhcp6_lease_information_acquired(sd_dhcp6_client *client, return 0; } +static int dhcp6_pd_prefix_assign(Link *link, struct in6_addr *prefix, + uint8_t prefix_len, + uint32_t lifetime_preferred, + uint32_t lifetime_valid) { + sd_radv *radv = link->radv; + int r; + _cleanup_(sd_radv_prefix_unrefp) sd_radv_prefix *p = NULL; + + r = sd_radv_prefix_new(&p); + if (r < 0) + return r; + + r = sd_radv_prefix_set_prefix(p, prefix, prefix_len); + if (r < 0) + return r; + + r = sd_radv_prefix_set_preferred_lifetime(p, lifetime_preferred); + if (r < 0) + return r; + + r = sd_radv_prefix_set_valid_lifetime(p, lifetime_valid); + if (r < 0) + return r; + + r = sd_radv_stop(radv); + if (r < 0) + return r; + + r = sd_radv_add_prefix(radv, p, true); + if (r < 0 && r != -EEXIST) + return r; + + r = manager_dhcp6_prefix_add(link->manager, &p->opt.in6_addr, link); + if (r < 0) + return r; + + return sd_radv_start(radv); +} + +static Network *dhcp6_reset_pd_prefix_network(Link *link) { + assert(link); + assert(link->manager); + assert(link->manager->networks); + + return link->manager->networks; +} + +static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i, + struct in6_addr *pd_prefix, + uint8_t pd_prefix_len, + uint32_t lifetime_preferred, + uint32_t lifetime_valid) { + Link *link; + Manager *manager = dhcp6_link->manager; + union in_addr_union prefix; + uint8_t n_prefixes, n_used = 0; + _cleanup_free_ char *buf = NULL; + int r; + + assert(manager); + assert(pd_prefix_len <= 64); + + prefix.in6 = *pd_prefix; + + r = in_addr_mask(AF_INET6, &prefix, pd_prefix_len); + if (r < 0) + return r; + + n_prefixes = 1 << (64 - pd_prefix_len); + + (void) in_addr_to_string(AF_INET6, &prefix, &buf); + log_link_debug(dhcp6_link, "Assigning up to %u prefixes from %s/%u", + n_prefixes, strnull(buf), pd_prefix_len); + + while (hashmap_iterate(manager->links, i, (void **)&link, NULL)) { + Link *assigned_link; + + if (n_used == n_prefixes) { + log_link_debug(dhcp6_link, "Assigned %u/%u prefixes from %s/%u", + n_used, n_prefixes, strnull(buf), pd_prefix_len); + + return -EAGAIN; + } + + if (link == dhcp6_link) + continue; + + if (!dhcp6_verify_link(link)) + continue; + + assigned_link = manager_dhcp6_prefix_get(manager, &prefix.in6); + if (assigned_link != NULL && assigned_link != link) + continue; + + r = dhcp6_pd_prefix_assign(link, &prefix.in6, 64, + lifetime_preferred, lifetime_valid); + if (r < 0) { + log_link_error_errno(link, r, "Unable to %s prefix %s/%u for link: %m", + assigned_link ? "update": "assign", + strnull(buf), pd_prefix_len); + + if (assigned_link == NULL) + continue; + + } else + log_link_debug(link, "Assigned prefix %u/%u %s/64 to link", + n_used + 1, n_prefixes, strnull(buf)); + + n_used++; + + r = in_addr_prefix_next(AF_INET6, &prefix, pd_prefix_len); + if (r < 0 && n_used < n_prefixes) + return r; + } + + return n_used; +} + +static int dhcp6_lease_pd_prefix_acquired(sd_dhcp6_client *client, Link *link) { + int r; + sd_dhcp6_lease *lease; + struct in6_addr pd_prefix; + uint8_t pd_prefix_len; + uint32_t lifetime_preferred, lifetime_valid; + _cleanup_free_ char *buf = NULL; + Iterator i = ITERATOR_FIRST; + + r = sd_dhcp6_client_get_lease(client, &lease); + if (r < 0) + return r; + + (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &pd_prefix, &buf); + + dhcp6_reset_pd_prefix_network(link); + sd_dhcp6_lease_reset_pd_prefix_iter(lease); + + while (sd_dhcp6_lease_get_pd(lease, &pd_prefix, &pd_prefix_len, + &lifetime_preferred, + &lifetime_valid) >= 0) { + + if (pd_prefix_len > 64) { + log_link_debug(link, "PD Prefix length > 64, ignoring prefix %s/%u", + strnull(buf), pd_prefix_len); + continue; + } + + r = dhcp6_pd_prefix_distribute(link, &i, &pd_prefix, + pd_prefix_len, + lifetime_preferred, + lifetime_valid); + if (r < 0 && r != -EAGAIN) + return r; + + if (r >= 0) + i = ITERATOR_FIRST; + } + + return 0; +} + static int dhcp6_address_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { _cleanup_link_unref_ Link *link = userdata; @@ -178,6 +343,8 @@ static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) { if (sd_dhcp6_client_get_lease(client, NULL) >= 0) log_link_warning(link, "DHCPv6 lease lost"); + (void) manager_dhcp6_prefix_remove_all(link->manager, link); + link->dhcp6_configured = false; break; @@ -188,6 +355,10 @@ static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) { return; } + r = dhcp6_lease_pd_prefix_acquired(client, link); + if (r < 0) + log_link_debug(link, "DHCPv6 did not receive prefixes to delegate"); + _fallthrough_; case SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST: r = dhcp6_lease_information_acquired(client, link); From 9f386c6d3bd8bb103affece9221523d66d6932a0 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:12:00 +0200 Subject: [PATCH 23/25] manager: Add and remove routes for DHCPv6 Prefix Delegation Configure routes to the links they are delegated to. Remove routes once the delegation itself or the link is removed. --- src/network/networkd-manager.c | 74 ++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 9d9d96ec8f..2a0a546d54 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -1242,16 +1242,82 @@ Link *manager_dhcp6_prefix_get(Manager *m, struct in6_addr *addr) { return hashmap_get(m->dhcp6_prefixes, addr); } +static int dhcp6_route_add_callback(sd_netlink *nl, sd_netlink_message *m, + void *userdata) { + Link *l = userdata; + int r; + union in_addr_union prefix; + _cleanup_free_ char *buf = NULL; + + r = sd_netlink_message_get_errno(m); + if (r != 0) { + log_link_debug_errno(l, r, "Received error adding DHCPv6 Prefix Delegation route: %m"); + return 0; + } + + r = sd_netlink_message_read_in6_addr(m, RTA_DST, &prefix.in6); + if (r < 0) { + log_link_debug_errno(l, r, "Could not read IPv6 address from DHCPv6 Prefix Delegation while adding route: %m"); + return 0; + } + + (void) in_addr_to_string(AF_INET6, &prefix, &buf); + log_link_debug(l, "Added DHCPv6 Prefix Deleagtion route %s/64", + strnull(buf)); + + return 0; +} + int manager_dhcp6_prefix_add(Manager *m, struct in6_addr *addr, Link *link) { + int r; + Route *route; + assert_return(m, -EINVAL); assert_return(m->dhcp6_prefixes, -ENODATA); assert_return(addr, -EINVAL); + r = route_add(link, AF_INET6, (union in_addr_union *) addr, 64, + 0, 0, 0, &route); + if (r < 0) + return r; + + r = route_configure(route, link, dhcp6_route_add_callback); + if (r < 0) + return r; + return hashmap_put(m->dhcp6_prefixes, addr, link); } +static int dhcp6_route_remove_callback(sd_netlink *nl, sd_netlink_message *m, + void *userdata) { + Link *l = userdata; + int r; + union in_addr_union prefix; + _cleanup_free_ char *buf = NULL; + + r = sd_netlink_message_get_errno(m); + if (r != 0) { + log_link_debug_errno(l, r, "Received error on DHCPv6 Prefix Delegation route removal: %m"); + return 0; + } + + r = sd_netlink_message_read_in6_addr(m, RTA_DST, &prefix.in6); + if (r < 0) { + log_link_debug_errno(l, r, "Could not read IPv6 address from DHCPv6 Prefix Delegation while removing route: %m"); + return 0; + } + + (void) in_addr_to_string(AF_INET6, &prefix, &buf); + log_link_debug(l, "Removed DHCPv6 Prefix Delegation route %s/64", + strnull(buf)); + + return 0; +} + int manager_dhcp6_prefix_remove(Manager *m, struct in6_addr *addr) { Link *l; + int r; + Route *route; assert_return(m, -EINVAL); assert_return(m->dhcp6_prefixes, -ENODATA); @@ -1262,6 +1328,10 @@ int manager_dhcp6_prefix_remove(Manager *m, struct in6_addr *addr) { return -EINVAL; (void) sd_radv_remove_prefix(l->radv, addr, 64); + r = route_get(l, AF_INET6, (union in_addr_union *) addr, 64, + 0, 0, 0, &route); + if (r >= 0) + (void) route_remove(route, l, dhcp6_route_remove_callback); return 0; } @@ -1278,9 +1348,7 @@ int manager_dhcp6_prefix_remove_all(Manager *m, Link *link) { if (l != link) continue; - (void) sd_radv_remove_prefix(l->radv, addr, 64); - - hashmap_remove(m->dhcp6_prefixes, addr); + (void) manager_dhcp6_prefix_remove(m, addr); } return 0; From 739229033e2b7a400189b88a410225a1e1001529 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:12:01 +0200 Subject: [PATCH 24/25] networkd: Set unreachable routes for unassigned DHCPv6 prefixes Set unreachable routes for prefixes delegated via DHCPv6 that were not assigned to links. --- src/network/networkd-dhcp6.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 4bb42919df..d54fb05d40 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -194,6 +194,30 @@ static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i, return r; } + if (n_used < n_prefixes) { + Route *route; + int n = n_used; + + r = route_new(&route); + if (r < 0) + return r; + + while (n < n_prefixes) { + route_update(route, &prefix, pd_prefix_len, NULL, NULL, + 0, 0, RTN_UNREACHABLE); + + r = route_configure(route, link, NULL); + if (r < 0) { + route_free(route); + return r; + } + + r = in_addr_prefix_next(AF_INET6, &prefix, pd_prefix_len); + if (r < 0) + return r; + } + } + return n_used; } From 982be97c0047d952c148e4c8a3803ad3ceafff39 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Thu, 4 Jan 2018 15:12:02 +0200 Subject: [PATCH 25/25] man: Update man page regarding DHCPv6 Prefix Delegation --- man/systemd.network.xml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/man/systemd.network.xml b/man/systemd.network.xml index bdf8d9e0d9..9ed7b1c785 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -667,8 +667,16 @@ IPv6PrefixDelegation= Whether to enable or disable Router Advertisement sending on a link. - Defaults to false. See the [IPv6PrefixDelegation] - and the [IPv6Prefix] sections for configuration options. + Allowed values are static which distributes prefixes as defined in + the [IPv6PrefixDelegation] and any [IPv6Prefix] + sections, dhcpv6 which requests prefixes using a DHCPv6 client + configured for another link and any values configured in the + [IPv6PrefixDelegation] section while ignoring all static prefix + configuration sections, yes which uses both static configuration + and DHCPv6, and false which turns off IPv6 prefix delegation + altogether. Defaults to false. See the + [IPv6PrefixDelegation] and the [IPv6Prefix] + sections for more configuration options.