From 04473969ef07c953415d8ce7964f1e9664e96a6c Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 12 May 2017 16:48:27 +0300 Subject: [PATCH 01/14] sd-radv: Add Router Advertisement prefix handling Define Router Advertisement prefix structure. Add the Prefix Information ICMPv6 option defined in RFC 4861 to the prefix information structure, as it will simplify sending a Prefix Information option later on. In order to handle endianness correctly, the structure is redefined here instead of using the one in netinet/icmp6.h. Add functions to create and modify prefix information and set default values as defined in RFC 4861, Section 6.2.1. --- Makefile.am | 3 + src/libsystemd-network/radv-internal.h | 48 +++++++++ src/libsystemd-network/sd-radv.c | 136 +++++++++++++++++++++++++ src/systemd/sd-radv.h | 56 ++++++++++ 4 files changed, 243 insertions(+) create mode 100644 src/libsystemd-network/radv-internal.h create mode 100644 src/libsystemd-network/sd-radv.c create mode 100644 src/systemd/sd-radv.h diff --git a/Makefile.am b/Makefile.am index b62166cd16..29fa71df0d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3637,6 +3637,7 @@ libsystemd_network_la_SOURCES = \ src/systemd/sd-ipv4ll.h \ src/systemd/sd-ipv4acd.h \ src/systemd/sd-ndisc.h \ + src/systemd/sd-radv.h \ src/systemd/sd-dhcp6-client.h \ src/systemd/sd-dhcp6-lease.h \ src/systemd/sd-lldp.h \ @@ -3660,6 +3661,8 @@ libsystemd_network_la_SOURCES = \ src/libsystemd-network/ndisc-internal.h \ src/libsystemd-network/ndisc-router.h \ src/libsystemd-network/ndisc-router.c \ + src/libsystemd-network/sd-radv.c \ + src/libsystemd-network/radv-internal.h \ src/libsystemd-network/icmp6-util.h \ src/libsystemd-network/icmp6-util.c \ src/libsystemd-network/sd-dhcp6-client.c \ diff --git a/src/libsystemd-network/radv-internal.h b/src/libsystemd-network/radv-internal.h new file mode 100644 index 0000000000..0f92c8b55c --- /dev/null +++ b/src/libsystemd-network/radv-internal.h @@ -0,0 +1,48 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2017 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "sd-radv.h" + +#include "log.h" +#include "list.h" +#include "sparse-endian.h" + +struct sd_radv_prefix { + unsigned n_ref; + + struct { + uint8_t type; + uint8_t length; + uint8_t prefixlen; + uint8_t flags; + be32_t valid_lifetime; + be32_t preferred_lifetime; + uint32_t reserved; + struct in6_addr in6_addr; + } _packed_ opt; + + LIST_FIELDS(struct sd_radv_prefix, prefix); +}; + +#define log_radv_full(level, error, fmt, ...) log_internal(level, error, __FILE__, __LINE__, __func__, "RADV: " fmt, ##__VA_ARGS__) +#define log_radv_errno(error, fmt, ...) log_radv_full(LOG_DEBUG, error, fmt, ##__VA_ARGS__) +#define log_radv_warning_errno(error, fmt, ...) log_radv_full(LOG_WARNING, error, fmt, ##__VA_ARGS__) +#define log_radv(fmt, ...) log_radv_errno(0, fmt, ##__VA_ARGS__) diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c new file mode 100644 index 0000000000..54f126e0f6 --- /dev/null +++ b/src/libsystemd-network/sd-radv.c @@ -0,0 +1,136 @@ +/*** + This file is part of systemd. + + Copyright (C) 2017 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "sd-radv.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "icmp6-util.h" +#include "in-addr-util.h" +#include "radv-internal.h" +#include "socket-util.h" +#include "string-util.h" +#include "util.h" + +_public_ int sd_radv_prefix_new(sd_radv_prefix **ret) { + _cleanup_(sd_radv_prefix_unrefp) sd_radv_prefix *p = NULL; + + assert_return(ret, -EINVAL); + + p = new0(sd_radv_prefix, 1); + if (!p) + return -ENOMEM; + + p->n_ref = 1; + + p->opt.type = ND_OPT_PREFIX_INFORMATION; + p->opt.length = (sizeof(p->opt) - 1) /8 + 1; + + p->opt.prefixlen = 64; + + /* RFC 4861, Section 6.2.1 */ + SET_FLAG(p->opt.flags, ND_OPT_PI_FLAG_ONLINK, true); + SET_FLAG(p->opt.flags, ND_OPT_PI_FLAG_AUTO, true); + p->opt.preferred_lifetime = htobe32(604800); + p->opt.valid_lifetime = htobe32(2592000); + + *ret = p; + p = NULL; + + return 0; +} + +_public_ sd_radv_prefix *sd_radv_prefix_ref(sd_radv_prefix *p) { + if (!p) + return NULL; + + assert(p->n_ref > 0); + p->n_ref++; + + return p; +} + +_public_ sd_radv_prefix *sd_radv_prefix_unref(sd_radv_prefix *p) { + if (!p) + return NULL; + + assert(p->n_ref > 0); + p->n_ref--; + + if (p->n_ref > 0) + return NULL; + + return mfree(p); +} + +_public_ int sd_radv_prefix_set_prefix(sd_radv_prefix *p, struct in6_addr *in6_addr, + unsigned char prefixlen) { + assert_return(p, -EINVAL); + assert_return(in6_addr, -EINVAL); + + if (prefixlen < 3 || prefixlen > 128) + return -EINVAL; + + if (prefixlen > 64) + /* unusual but allowed, log it */ + log_radv("Unusual prefix length %d greater than 64", prefixlen); + + p->opt.in6_addr = *in6_addr; + p->opt.prefixlen = prefixlen; + + return 0; +} + +_public_ int sd_radv_prefix_set_onlink(sd_radv_prefix *p, int onlink) { + assert_return(p, -EINVAL); + + SET_FLAG(p->opt.flags, ND_OPT_PI_FLAG_ONLINK, onlink); + + return 0; +} + +_public_ int sd_radv_prefix_set_address_autoconfiguration(sd_radv_prefix *p, + int address_autoconfiguration) { + assert_return(p, -EINVAL); + + SET_FLAG(p->opt.flags, ND_OPT_PI_FLAG_AUTO, address_autoconfiguration); + + return 0; +} + +_public_ int sd_radv_prefix_set_valid_lifetime(sd_radv_prefix *p, + uint32_t valid_lifetime) { + assert_return(p, -EINVAL); + + p->opt.valid_lifetime = htobe32(valid_lifetime); + + return 0; +} + +_public_ int sd_radv_prefix_set_preferred_lifetime(sd_radv_prefix *p, + uint32_t preferred_lifetime) { + assert_return(p, -EINVAL); + + p->opt.preferred_lifetime = htobe32(preferred_lifetime); + + return 0; +} diff --git a/src/systemd/sd-radv.h b/src/systemd/sd-radv.h new file mode 100644 index 0000000000..c9933674bd --- /dev/null +++ b/src/systemd/sd-radv.h @@ -0,0 +1,56 @@ +#ifndef foosdradvfoo +#define foosdradvfoo + +/*** + This file is part of systemd. + + Copyright (C) 2017 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "sd-event.h" + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_radv_prefix sd_radv_prefix; + +/* Advertised prefixes */ +int sd_radv_prefix_new(sd_radv_prefix **ret); +sd_radv_prefix *sd_radv_prefix_ref(sd_radv_prefix *ra); +sd_radv_prefix *sd_radv_prefix_unref(sd_radv_prefix *ra); + +int sd_radv_prefix_set_prefix(sd_radv_prefix *p, struct in6_addr *in6_addr, + unsigned char prefixlen); +int sd_radv_prefix_set_onlink(sd_radv_prefix *p, int onlink); +int sd_radv_prefix_set_address_autoconfiguration(sd_radv_prefix *p, + int address_autoconfiguration); +int sd_radv_prefix_set_valid_lifetime(sd_radv_prefix *p, + uint32_t valid_lifetime); +int sd_radv_prefix_set_preferred_lifetime(sd_radv_prefix *p, + uint32_t preferred_lifetime); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_radv, sd_radv_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_radv_prefix, sd_radv_prefix_unref); + +_SD_END_DECLARATIONS; + +#endif From 057abfd88a827a3674bd77dab61927e9a031ecb8 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 12 May 2017 16:48:28 +0300 Subject: [PATCH 02/14] networkd: Add initial prefix handling for network configuration Add initial code for handling prefixes in network configuration files. Add hash map and list storing the information in systemd-networkd. --- src/network/networkd-address.c | 80 ++++++++++++++++++++++++++++++++++ src/network/networkd-address.h | 16 +++++++ src/network/networkd-network.c | 10 +++++ src/network/networkd-network.h | 3 ++ 4 files changed, 109 insertions(+) diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 2e6c763aba..dba394b5e6 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -932,3 +932,83 @@ bool address_is_ready(const Address *a) { return !(a->flags & (IFA_F_TENTATIVE | IFA_F_DEPRECATED)); } + +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); + } + + free(prefix); +} + +int prefix_new(Prefix **ret) { + Prefix *prefix = NULL; + + prefix = new0(Prefix, 1); + if (!prefix) + 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; +} diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h index 71a07ea7a3..fb79f7a03a 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -25,6 +25,7 @@ #include "in-addr-util.h" typedef struct Address Address; +typedef struct Prefix Prefix; #include "networkd-link.h" #include "networkd-network.h" @@ -35,6 +36,13 @@ typedef struct Network Network; typedef struct Link Link; typedef struct NetworkConfigSection NetworkConfigSection; +struct Prefix { + Network *network; + NetworkConfigSection *section; + + LIST_FIELDS(Prefix, prefixes); +}; + struct Address { Network *network; NetworkConfigSection *section; @@ -79,6 +87,14 @@ 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); diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 3c26c8609e..43f62e2c9b 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -115,6 +115,7 @@ static int network_load_one(Manager *manager, const char *filename) { LIST_HEAD_INIT(network->static_fdb_entries); LIST_HEAD_INIT(network->ipv6_proxy_ndp_addresses); LIST_HEAD_INIT(network->address_labels); + LIST_HEAD_INIT(network->static_prefixes); network->stacked_netdevs = hashmap_new(&string_hash_ops); if (!network->stacked_netdevs) @@ -134,6 +135,10 @@ static int network_load_one(Manager *manager, const char *filename) { network->address_labels_by_section = hashmap_new(&network_config_hash_ops); if (!network->address_labels_by_section) + log_oom(); + + network->prefixes_by_section = hashmap_new(&network_config_hash_ops); + if (!network->prefixes_by_section) return log_oom(); network->filename = strdup(filename); @@ -279,6 +284,7 @@ void network_free(Network *network) { FdbEntry *fdb_entry; IPv6ProxyNDPAddress *ipv6_proxy_ndp_address; AddressLabel *label; + Prefix *prefix; Iterator i; if (!network) @@ -329,10 +335,14 @@ void network_free(Network *network) { while ((label = network->address_labels)) address_label_free(label); + while ((prefix = network->static_prefixes)) + prefix_free(prefix); + hashmap_free(network->addresses_by_section); hashmap_free(network->routes_by_section); hashmap_free(network->fdb_entries_by_section); hashmap_free(network->address_labels_by_section); + hashmap_free(network->prefixes_by_section); if (network->manager) { if (network->manager->networks) diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index c9326c7f1e..a9d547b046 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -205,17 +205,20 @@ struct Network { LIST_HEAD(FdbEntry, static_fdb_entries); LIST_HEAD(IPv6ProxyNDPAddress, ipv6_proxy_ndp_addresses); LIST_HEAD(AddressLabel, address_labels); + LIST_HEAD(Prefix, static_prefixes); unsigned n_static_addresses; unsigned n_static_routes; unsigned n_static_fdb_entries; unsigned n_ipv6_proxy_ndp_addresses; unsigned n_address_labels; + unsigned n_static_prefixes; Hashmap *addresses_by_section; Hashmap *routes_by_section; Hashmap *fdb_entries_by_section; Hashmap *address_labels_by_section; + Hashmap *prefixes_by_section; struct in_addr_data *dns; unsigned n_dns; From 9d5d0090cae80e705c61e3fad49b5ca728e1479b Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 12 May 2017 16:48:29 +0300 Subject: [PATCH 03/14] networkd: Add IPv6Prefix sections to the network configuration files Support zero or more [IPv6Prefix] sections in the network configuration files. Each section can have one Prefix=[/] option, with the preferred and valid lifetimes specified by ValidLifetimeSec and PreferredLifetimeSec measured in seconds, and with onlink and address autoconfiguration booleans specified by OnLink and AddressAutoconfiguration variables. This adds a section like the following to .network configuration files: [IPv6Prefix] Prefix=2001:db8:dead:beef::/64 OnLink=true AddressAutoconfiguration=true ValidLifetimeSec=1500 PreferredLifetimeSec=1000 --- src/network/networkd-address.c | 139 +++++++++++++++++++++++ src/network/networkd-address.h | 5 + src/network/networkd-link.h | 1 + src/network/networkd-network-gperf.gperf | 5 + src/network/networkd-network.c | 3 +- 5 files changed, 152 insertions(+), 1 deletion(-) diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index dba394b5e6..700ae2adda 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -947,6 +947,8 @@ void prefix_free(Prefix *prefix) { prefix->section); } + prefix->radv_prefix = sd_radv_prefix_unref(prefix->radv_prefix); + free(prefix); } @@ -957,6 +959,9 @@ int prefix_new(Prefix **ret) { if (!prefix) return -ENOMEM; + if (sd_radv_prefix_new(&prefix->radv_prefix) < 0) + return -ENOMEM; + *ret = prefix; prefix = NULL; @@ -1012,3 +1017,137 @@ int prefix_new_static(Network *network, const char *filename, 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, 0x0 means this host is + not a router */ + if (streq(lvalue, "PreferredLifetimeSec")) + r = sd_radv_prefix_set_preferred_lifetime(p->radv_prefix, + (usec + USEC_PER_SEC - 1) / USEC_PER_SEC); + else if (streq(lvalue, "ValidLifetimeSec")) + r = sd_radv_prefix_set_valid_lifetime(p->radv_prefix, + (usec + USEC_PER_SEC - 1) / 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 fb79f7a03a..d9be045302 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -40,6 +40,8 @@ struct Prefix { Network *network; NetworkConfigSection *section; + sd_radv_prefix *radv_prefix; + LIST_FIELDS(Prefix, prefixes); }; @@ -100,3 +102,6 @@ int config_parse_broadcast(const char *unit, const char *filename, unsigned line 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_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-link.h b/src/network/networkd-link.h index be5c4f3284..4e53298cb4 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -28,6 +28,7 @@ #include "sd-ipv4ll.h" #include "sd-lldp.h" #include "sd-ndisc.h" +#include "sd-radv.h" #include "sd-netlink.h" #include "list.h" diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index b2c585f866..eb54a68629 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -137,6 +137,11 @@ 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 +IPv6Prefix.Prefix, config_parse_prefix, 0, 0 +IPv6Prefix.OnLink, config_parse_prefix_flags, 0, 0 +IPv6Prefix.AddressAutoconfiguration, config_parse_prefix_flags, 0, 0 +IPv6Prefix.ValidLifetimeSec, config_parse_prefix_lifetime, 0, 0 +IPv6Prefix.PreferredLifetimeSec, config_parse_prefix_lifetime, 0, 0 /* backwards compatibility: do not add new entries to this section */ Network.IPv4LL, config_parse_ipv4ll, 0, offsetof(Network, link_local) DHCPv4.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 43f62e2c9b..f5d11d6b99 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -212,7 +212,8 @@ static int network_load_one(Manager *manager, const char *filename) { "IPv6NDPProxyAddress\0" "Bridge\0" "BridgeFDB\0" - "BridgeVLAN\0", + "BridgeVLAN\0" + "IPv6Prefix\0", config_item_perf_lookup, network_network_gperf_lookup, false, network); if (r < 0) From 7d5cac19b5d04b39405273c6ab6bfc8811bd4f3a Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 12 May 2017 16:48:30 +0300 Subject: [PATCH 04/14] networkd: Add Router Advertisement variables Add variables for enabling Router Advertisements, router lifetime as well as managed and other information flags indicating use of DHCPv6. Add configuration of default router preferences as defined in RFC 4191. IPv6PrefixDelegation in the [Network] section has to be set in order to enable prefix delegation. The rest of the prefix delegation values are stored in the [IPv6PrefixDelegation] section. The host will act as a default router if it is given a non-zero lifetime with RouterLifetimeSec. Managed and OtherInformation booleans set the level of DHCPv6 support, and the RouterPreference configures the router's preference between low, medium and high. Words 'normal' and 'default' are added as synonyms for 'medium' just to make configuration simpler. This adds a section like the following to .network configuration files: [Network] IPv6PrefixDelegation=true [IPv6PrefixDelegation] RouterLifetimeSec=2000 Managed=false OtherInformation=true RouterPreference=medium --- src/network/networkd-address.c | 30 ++++++++++++++++++++++++ src/network/networkd-address.h | 1 + src/network/networkd-network-gperf.gperf | 5 ++++ src/network/networkd-network.c | 1 + src/network/networkd-network.h | 7 ++++++ 5 files changed, 44 insertions(+) diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 700ae2adda..de918c3c29 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -933,6 +933,36 @@ bool address_is_ready(const Address *a) { 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; diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h index d9be045302..065328482e 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -102,6 +102,7 @@ int config_parse_broadcast(const char *unit, const char *filename, unsigned line 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_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-gperf.gperf b/src/network/networkd-network-gperf.gperf index eb54a68629..a2d38501a5 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -137,6 +137,11 @@ 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) +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) +IPv6PrefixDelegation.RouterPreference, config_parse_router_preference, 0, 0 IPv6Prefix.Prefix, config_parse_prefix, 0, 0 IPv6Prefix.OnLink, config_parse_prefix_flags, 0, 0 IPv6Prefix.AddressAutoconfiguration, config_parse_prefix_flags, 0, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index f5d11d6b99..6f2ae66d40 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -213,6 +213,7 @@ static int network_load_one(Manager *manager, const char *filename) { "Bridge\0" "BridgeFDB\0" "BridgeVLAN\0" + "IPv6PrefixDelegation\0" "IPv6Prefix\0", config_item_perf_lookup, network_network_gperf_lookup, false, network); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index a9d547b046..b31921947d 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -158,6 +158,13 @@ struct Network { AddressFamilyBoolean link_local; bool ipv4ll_route; + /* IPv6 prefix delegation support */ + bool router_prefix_delegation; + usec_t router_lifetime_usec; + uint8_t router_preference; + bool router_managed; + bool router_other_information; + /* Bridge Support */ bool use_bpdu; bool hairpin; From 204f99d21ed6ee2427f889f46a3c38e0e1e132b9 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 12 May 2017 16:48:31 +0300 Subject: [PATCH 05/14] sd-radv: Add Router Advertisement functionality Add Router Advertisement header files, data structures and core functionality that is quite similar to other parts of networkd. --- src/libsystemd-network/radv-internal.h | 25 +++ src/libsystemd-network/sd-radv.c | 261 +++++++++++++++++++++++++ src/systemd/sd-radv.h | 25 +++ 3 files changed, 311 insertions(+) diff --git a/src/libsystemd-network/radv-internal.h b/src/libsystemd-network/radv-internal.h index 0f92c8b55c..40a5506b95 100644 --- a/src/libsystemd-network/radv-internal.h +++ b/src/libsystemd-network/radv-internal.h @@ -25,6 +25,31 @@ #include "list.h" #include "sparse-endian.h" +enum RAdvState { + SD_RADV_STATE_IDLE = 0, + SD_RADV_STATE_ADVERTISING = 1, +}; +typedef enum RAdvState RAdvState; + +struct sd_radv { + unsigned n_ref; + RAdvState state; + + int ifindex; + + sd_event *event; + int event_priority; + + struct ether_addr mac_addr; + uint8_t hop_limit; + uint8_t flags; + uint32_t mtu; + uint16_t lifetime; + + unsigned n_prefixes; + LIST_HEAD(sd_radv_prefix, prefixes); +}; + struct sd_radv_prefix { unsigned n_ref; diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c index 54f126e0f6..9336132e19 100644 --- a/src/libsystemd-network/sd-radv.c +++ b/src/libsystemd-network/sd-radv.c @@ -19,9 +19,11 @@ #include #include +#include #include "sd-radv.h" +#include "macro.h" #include "alloc-util.h" #include "fd-util.h" #include "icmp6-util.h" @@ -31,6 +33,263 @@ #include "string-util.h" #include "util.h" +_public_ int sd_radv_new(sd_radv **ret) { + _cleanup_(sd_radv_unrefp) sd_radv *ra = NULL; + + assert_return(ret, -EINVAL); + + ra = new0(sd_radv, 1); + if (!ra) + return -ENOMEM; + + ra->n_ref = 1; + + LIST_HEAD_INIT(ra->prefixes); + + *ret = ra; + ra = NULL; + + return 0; +} + +_public_ int sd_radv_attach_event(sd_radv *ra, sd_event *event, int64_t priority) { + int r; + + assert_return(ra, -EINVAL); + assert_return(!ra->event, -EBUSY); + + if (event) + ra->event = sd_event_ref(event); + else { + r = sd_event_default(&ra->event); + if (r < 0) + return 0; + } + + ra->event_priority = priority; + + return 0; +} + +_public_ int sd_radv_detach_event(sd_radv *ra) { + + assert_return(ra, -EINVAL); + + ra->event = sd_event_unref(ra->event); + return 0; +} + +_public_ sd_event *sd_radv_get_event(sd_radv *ra) { + assert_return(ra, NULL); + + return ra->event; +} + +_public_ sd_radv *sd_radv_ref(sd_radv *ra) { + if (!ra) + return NULL; + + assert(ra->n_ref > 0); + ra->n_ref++; + + return ra; +} + +_public_ sd_radv *sd_radv_unref(sd_radv *ra) { + if (!ra) + return NULL; + + assert(ra->n_ref > 0); + ra->n_ref--; + + if (ra->n_ref > 0) + return NULL; + + while (ra->prefixes) { + sd_radv_prefix *p = ra->prefixes; + + LIST_REMOVE(prefix, ra->prefixes, p); + sd_radv_prefix_unref(p); + } + + sd_radv_detach_event(ra); + return mfree(ra); +} + +_public_ int sd_radv_stop(sd_radv *ra) { + assert_return(ra, -EINVAL); + + log_radv("Stopping IPv6 Router Advertisement daemon"); + + ra->state = SD_RADV_STATE_IDLE; + + return 0; +} + +_public_ int sd_radv_start(sd_radv *ra) { + assert_return(ra, -EINVAL); + assert_return(ra->event, -EINVAL); + assert_return(ra->ifindex > 0, -EINVAL); + + if (ra->state != SD_RADV_STATE_IDLE) + return 0; + + ra->state = SD_RADV_STATE_ADVERTISING; + + log_radv("Started IPv6 Router Advertisement daemon"); + + return 0; +} + +_public_ int sd_radv_set_ifindex(sd_radv *ra, int ifindex) { + assert_return(ra, -EINVAL); + assert_return(ifindex >= -1, -EINVAL); + + if (ra->state != SD_RADV_STATE_IDLE) + return -EBUSY; + + ra->ifindex = ifindex; + + return 0; +} + +_public_ int sd_radv_set_mac(sd_radv *ra, const struct ether_addr *mac_addr) { + assert_return(ra, -EINVAL); + + if (ra->state != SD_RADV_STATE_IDLE) + return -EBUSY; + + if (mac_addr) + ra->mac_addr = *mac_addr; + else + zero(ra->mac_addr); + + return 0; +} + +_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; +} + +_public_ int sd_radv_set_hop_limit(sd_radv *ra, uint8_t hop_limit) { + assert_return(ra, -EINVAL); + + if (ra->state != SD_RADV_STATE_IDLE) + return -EBUSY; + + ra->hop_limit = hop_limit; + + return 0; +} + +_public_ int sd_radv_set_router_lifetime(sd_radv *ra, uint32_t router_lifetime) { + assert_return(ra, -EINVAL); + + if (ra->state != SD_RADV_STATE_IDLE) + return -EBUSY; + + /* RFC 4191, Section 2.2, "...If the Router Lifetime is zero, the + preference value MUST be set to (00) by the sender..." */ + if (router_lifetime == 0 && + (ra->flags & (0x3 << 3)) != (SD_NDISC_PREFERENCE_MEDIUM << 3)) + return -ETIME; + + ra->lifetime = router_lifetime; + + return 0; +} + +_public_ int sd_radv_set_managed_information(sd_radv *ra, int managed) { + assert_return(ra, -EINVAL); + + if (ra->state != SD_RADV_STATE_IDLE) + return -EBUSY; + + SET_FLAG(ra->flags, ND_RA_FLAG_MANAGED, managed); + + return 0; +} + +_public_ int sd_radv_set_other_information(sd_radv *ra, int other) { + assert_return(ra, -EINVAL); + + if (ra->state != SD_RADV_STATE_IDLE) + return -EBUSY; + + SET_FLAG(ra->flags, ND_RA_FLAG_OTHER, other); + + return 0; +} + +_public_ int sd_radv_set_preference(sd_radv *ra, unsigned preference) { + int r = 0; + + assert_return(ra, -EINVAL); + assert_return(IN_SET(preference, + SD_NDISC_PREFERENCE_LOW, + SD_NDISC_PREFERENCE_MEDIUM, + SD_NDISC_PREFERENCE_HIGH), -EINVAL); + + ra->flags = (ra->flags & ~(0x3 << 3)) | (preference << 3); + + return r; +} + +_public_ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p) { + sd_radv_prefix *cur; + _cleanup_free_ char *addr_p = NULL; + + assert_return(ra, -EINVAL); + + if (!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, + cur->opt.prefixlen, + (union in_addr_union*) &p->opt.in6_addr, + p->opt.prefixlen); + 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); + + log_radv("IPv6 prefix %s/%u already configured, ignoring %s/%u", + addr_cur, cur->opt.prefixlen, + addr_p, p->opt.prefixlen); + + return -EEXIST; + } + } + + p = sd_radv_prefix_ref(p); + + LIST_APPEND(prefix, ra->prefixes, 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); + + return 0; +} + _public_ int sd_radv_prefix_new(sd_radv_prefix **ret) { _cleanup_(sd_radv_prefix_unrefp) sd_radv_prefix *p = NULL; @@ -53,6 +312,8 @@ _public_ int sd_radv_prefix_new(sd_radv_prefix **ret) { p->opt.preferred_lifetime = htobe32(604800); p->opt.valid_lifetime = htobe32(2592000); + LIST_INIT(prefix, p); + *ret = p; p = NULL; diff --git a/src/systemd/sd-radv.h b/src/systemd/sd-radv.h index c9933674bd..4cbd80db68 100644 --- a/src/systemd/sd-radv.h +++ b/src/systemd/sd-radv.h @@ -25,14 +25,39 @@ #include #include +#include "sd-ndisc.h" + #include "sd-event.h" #include "_sd-common.h" _SD_BEGIN_DECLARATIONS; +typedef struct sd_radv sd_radv; typedef struct sd_radv_prefix sd_radv_prefix; +/* Router Advertisment */ +int sd_radv_new(sd_radv **ret); +sd_radv *sd_radv_ref(sd_radv *ra); +sd_radv *sd_radv_unref(sd_radv *ra); + +int sd_radv_attach_event(sd_radv *ra, sd_event *event, int64_t priority); +int sd_radv_detach_event(sd_radv *nd); +sd_event *sd_radv_get_event(sd_radv *ra); + +int sd_radv_start(sd_radv *ra); +int sd_radv_stop(sd_radv *ra); + +int sd_radv_set_ifindex(sd_radv *ra, int interface_index); +int sd_radv_set_mac(sd_radv *ra, const struct ether_addr *mac_addr); +int sd_radv_set_mtu(sd_radv *ra, uint32_t mtu); +int sd_radv_set_hop_limit(sd_radv *ra, uint8_t hop_limit); +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); + /* Advertised prefixes */ int sd_radv_prefix_new(sd_radv_prefix **ret); sd_radv_prefix *sd_radv_prefix_ref(sd_radv_prefix *ra); From 091214b636e1eb88acf3a5c980b5b66feb647de6 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 12 May 2017 16:48:32 +0300 Subject: [PATCH 06/14] networkd-radv: Helper function for Router Advertisement initialization Add a helper function for configuring Router Advertisement on a specific network link. Add the prefixes that are going to be advertised. --- Makefile.am | 2 + src/network/networkd-link.h | 2 + src/network/networkd-radv.c | 77 +++++++++++++++++++++++++++++++++++++ src/network/networkd-radv.h | 24 ++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 src/network/networkd-radv.c create mode 100644 src/network/networkd-radv.h diff --git a/Makefile.am b/Makefile.am index 29fa71df0d..ad79ccde76 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5860,6 +5860,8 @@ libnetworkd_core_la_SOURCES = \ src/network/networkd-dhcp4.c \ src/network/networkd-dhcp6.c \ src/network/networkd-ndisc.h \ + src/network/networkd-radv.c \ + src/network/networkd-radv.h \ src/network/networkd-ndisc.c \ src/network/networkd-network.h \ src/network/networkd-network.c \ diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 4e53298cb4..6479f4a2e5 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -118,6 +118,8 @@ typedef struct Link { Set *ndisc_rdnss; Set *ndisc_dnssl; + sd_radv *radv; + sd_dhcp6_client *dhcp6_client; bool rtnl_extended_attrs; diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c new file mode 100644 index 0000000000..e5be145146 --- /dev/null +++ b/src/network/networkd-radv.c @@ -0,0 +1,77 @@ +/*** + This file is part of systemd. + + Copyright (C) 2017 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "networkd-address.h" +#include "networkd-radv.h" +#include "sd-radv.h" + +int radv_configure(Link *link) { + int r; + Prefix *p; + + assert(link); + assert(link->network); + + r = sd_radv_new(&link->radv); + if (r < 0) + return r; + + r = sd_radv_attach_event(link->radv, NULL, 0); + if (r < 0) + return r; + + r = sd_radv_set_mac(link->radv, &link->mac); + if (r < 0) + return r; + + r = sd_radv_set_ifindex(link->radv, link->ifindex); + if (r < 0) + return r; + + r = sd_radv_set_managed_information(link->radv, link->network->router_managed); + if (r < 0) + return r; + + r = sd_radv_set_other_information(link->radv, link->network->router_other_information); + if (r < 0) + return r; + + r = sd_radv_set_router_lifetime(link->radv, + link->network->router_lifetime_usec); + if (r < 0) + return r; + + if (link->network->router_lifetime_usec > 0) { + r = sd_radv_set_preference(link->radv, + link->network->router_preference); + if (r < 0) + 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; + } + + return 0; +} diff --git a/src/network/networkd-radv.h b/src/network/networkd-radv.h new file mode 100644 index 0000000000..a186b111a1 --- /dev/null +++ b/src/network/networkd-radv.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2017 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "networkd-link.h" + +int radv_configure(Link *link); From 7465dd22ad4326e97401ce0850c7d7febb47e7e2 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 12 May 2017 16:48:33 +0300 Subject: [PATCH 07/14] networkd-link.c: Add Router Advertisement starting and stopping Start and stop Router Advertisement sending once the link has acquired a link-local IPv6 address. --- src/network/networkd-link.c | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 37bebcf191..98a5e3109e 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -32,6 +32,7 @@ #include "networkd-lldp-tx.h" #include "networkd-manager.h" #include "networkd-ndisc.h" +#include "networkd-radv.h" #include "set.h" #include "socket-util.h" #include "stdio-util.h" @@ -119,6 +120,15 @@ static bool link_ipv6_enabled(Link *link) { return link_ipv6ll_enabled(link) || network_has_static_ipv6_addresses(link->network); } +static bool link_radv_enabled(Link *link) { + assert(link); + + if (!link_ipv6ll_enabled(link)) + return false; + + return link->network->router_prefix_delegation; +} + static bool link_lldp_rx_enabled(Link *link) { assert(link); @@ -521,6 +531,7 @@ static void link_free(Link *link) { sd_ipv4ll_unref(link->ipv4ll); sd_dhcp6_client_unref(link->dhcp6_client); sd_ndisc_unref(link->ndisc); + sd_radv_unref(link->radv); if (link->manager) hashmap_remove(link->manager->links, INT_TO_PTR(link->ifindex)); @@ -640,6 +651,12 @@ static int link_stop_clients(Link *link) { r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Discovery: %m"); } + if (link->radv) { + k = sd_radv_stop(link->radv); + if (k < 0) + r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Advertisement: %m"); + } + link_lldp_emit_stop(link); return r; } @@ -1554,6 +1571,17 @@ static int link_acquire_ipv6_conf(Link *link) { return log_link_warning_errno(link, r, "Could not start IPv6 Router Discovery: %m"); } + if (link_radv_enabled(link)) { + assert(link->radv); + assert(in_addr_is_link_local(AF_INET6, (const union in_addr_union*)&link->ipv6ll_address) > 0); + + log_link_debug(link, "Starting IPv6 Router Advertisements"); + + r = sd_radv_start(link->radv); + if (r < 0 && r != -EBUSY) + return log_link_warning_errno(link, r, "Could not start IPv6 Router Advertisement: %m"); + } + return 0; } @@ -2562,6 +2590,12 @@ static int link_configure(Link *link) { return r; } + if (link_radv_enabled(link)) { + r = radv_configure(link); + if (r < 0) + return r; + } + if (link_lldp_rx_enabled(link)) { r = sd_lldp_new(&link->lldp); if (r < 0) @@ -3098,6 +3132,12 @@ int link_update(Link *link, sd_netlink_message *m) { return r; } } + + if (link->radv) { + r = sd_radv_set_mtu(link->radv, link->mtu); + if (r < 0) + return log_link_warning_errno(link, r, "Could not set MTU for Router Advertisement: %m"); + } } /* The kernel may broadcast NEWLINK messages without the MAC address @@ -3166,6 +3206,12 @@ int link_update(Link *link, sd_netlink_message *m) { if (r < 0) return log_link_warning_errno(link, r, "Could not update DHCPv6 DUID: %m"); } + + if (link->radv) { + r = sd_radv_set_mac(link->radv, &link->mac); + if (r < 0) + return log_link_warning_errno(link, r, "Could not update MAC for Router Advertisement: %m"); + } } } From 204fb681f600ea9216579fc6e5a49b392cad61c7 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 12 May 2017 16:48:34 +0300 Subject: [PATCH 08/14] sd-radv: Implement Router Advertisement timeout handling Router Advertisements are sent uniformly distributed between a minimum and maximum time according to RFC 4861, Section 6.2.4. Default values from RFC 4861 are for now used as minimum and maximum Router Advertisement timeouts. When stopping, a Router Advertisement with a router lifetime set to zero is sent in order to inform any nodes that the interface on this host no longer is a router. --- src/libsystemd-network/radv-internal.h | 13 +++ src/libsystemd-network/sd-radv.c | 113 +++++++++++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/src/libsystemd-network/radv-internal.h b/src/libsystemd-network/radv-internal.h index 40a5506b95..7dcc3e0451 100644 --- a/src/libsystemd-network/radv-internal.h +++ b/src/libsystemd-network/radv-internal.h @@ -25,6 +25,16 @@ #include "list.h" #include "sparse-endian.h" +#define SD_RADV_DEFAULT_MIN_TIMEOUT_USEC (200*USEC_PER_SEC) +#define SD_RADV_DEFAULT_MAX_TIMEOUT_USEC (600*USEC_PER_SEC) +assert_cc(SD_RADV_DEFAULT_MIN_TIMEOUT_USEC <= SD_RADV_DEFAULT_MAX_TIMEOUT_USEC) + +#define SD_RADV_MAX_INITIAL_RTR_ADVERT_INTERVAL_USEC (16*USEC_PER_SEC) +#define SD_RADV_MAX_INITIAL_RTR_ADVERTISEMENTS 3 +#define SD_RADV_MAX_FINAL_RTR_ADVERTISEMENTS 3 +#define SD_RADV_MIN_DELAY_BETWEEN_RAS 3 +#define SD_RADV_MAX_RA_DELAY_TIME_USEC (500*USEC_PER_MSEC) + enum RAdvState { SD_RADV_STATE_IDLE = 0, SD_RADV_STATE_ADVERTISING = 1, @@ -46,6 +56,9 @@ struct sd_radv { uint32_t mtu; uint16_t lifetime; + unsigned ra_sent; + sd_event_source *timeout_event_source; + unsigned n_prefixes; LIST_HEAD(sd_radv_prefix, prefixes); }; diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c index 9336132e19..71054eb5fe 100644 --- a/src/libsystemd-network/sd-radv.c +++ b/src/libsystemd-network/sd-radv.c @@ -32,6 +32,7 @@ #include "socket-util.h" #include "string-util.h" #include "util.h" +#include "random-util.h" _public_ int sd_radv_new(sd_radv **ret) { _cleanup_(sd_radv_unrefp) sd_radv *ra = NULL; @@ -85,6 +86,14 @@ _public_ sd_event *sd_radv_get_event(sd_radv *ra) { return ra->event; } +static void radv_reset(sd_radv *ra) { + + ra->timeout_event_source = + sd_event_source_unref(ra->timeout_event_source); + + ra->ra_sent = 0; +} + _public_ sd_radv *sd_radv_ref(sd_radv *ra) { if (!ra) return NULL; @@ -112,21 +121,106 @@ _public_ sd_radv *sd_radv_unref(sd_radv *ra) { sd_radv_prefix_unref(p); } + radv_reset(ra); + sd_radv_detach_event(ra); return mfree(ra); } +static int radv_send(sd_radv *ra, const struct in6_addr *dst, + const uint32_t router_lifetime) { + + return -ENOSYS; +} + +static usec_t radv_compute_timeout(usec_t min, usec_t max) { + assert_return(min <= max, SD_RADV_DEFAULT_MIN_TIMEOUT_USEC); + + return min + (random_u32() % (max - min)); +} + +static int radv_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + int r; + sd_radv *ra = userdata; + usec_t min_timeout = SD_RADV_DEFAULT_MIN_TIMEOUT_USEC; + usec_t max_timeout = SD_RADV_DEFAULT_MAX_TIMEOUT_USEC; + usec_t time_now, timeout; + char time_string[FORMAT_TIMESPAN_MAX]; + + assert(s); + assert(ra); + assert(ra->event); + + ra->timeout_event_source = sd_event_source_unref(ra->timeout_event_source); + + r = sd_event_now(ra->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + goto fail; + + r = radv_send(ra, NULL, ra->lifetime); + if (r < 0) + log_radv_warning_errno(r, "Unable to send Router Advertisement: %m"); + + /* RFC 4861, Section 6.2.4, sending initial Router Advertisements */ + if (ra->ra_sent < SD_RADV_MAX_INITIAL_RTR_ADVERTISEMENTS) { + max_timeout = SD_RADV_MAX_INITIAL_RTR_ADVERT_INTERVAL_USEC; + min_timeout = SD_RADV_MAX_INITIAL_RTR_ADVERT_INTERVAL_USEC / 3; + } + + timeout = radv_compute_timeout(min_timeout, max_timeout); + + log_radv("Next Router Advertisement in %s", + format_timespan(time_string, FORMAT_TIMESPAN_MAX, + timeout, USEC_PER_SEC)); + + r = sd_event_add_time(ra->event, &ra->timeout_event_source, + clock_boottime_or_monotonic(), + time_now + timeout, MSEC_PER_SEC, + radv_timeout, ra); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(ra->timeout_event_source, + ra->event_priority); + if (r < 0) + goto fail; + + r = sd_event_source_set_description(ra->timeout_event_source, + "radv-timeout"); + if (r < 0) + goto fail; + + ra->ra_sent++; + +fail: + if (r < 0) + sd_radv_stop(ra); + + return 0; +} + _public_ int sd_radv_stop(sd_radv *ra) { + int r; + assert_return(ra, -EINVAL); log_radv("Stopping IPv6 Router Advertisement daemon"); + /* RFC 4861, Section 6.2.5, send at least one Router Advertisement + with zero lifetime */ + r = radv_send(ra, NULL, 0); + if (r < 0) + log_radv_warning_errno(r, "Unable to send last Router Advertisement with router lifetime set to zero: %m"); + + radv_reset(ra); ra->state = SD_RADV_STATE_IDLE; return 0; } _public_ int sd_radv_start(sd_radv *ra) { + int r = 0; + assert_return(ra, -EINVAL); assert_return(ra->event, -EINVAL); assert_return(ra->ifindex > 0, -EINVAL); @@ -134,11 +228,30 @@ _public_ int sd_radv_start(sd_radv *ra) { if (ra->state != SD_RADV_STATE_IDLE) return 0; + r = sd_event_add_time(ra->event, &ra->timeout_event_source, + clock_boottime_or_monotonic(), 0, 0, + radv_timeout, ra); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(ra->timeout_event_source, + ra->event_priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(ra->timeout_event_source, + "radv-timeout"); + ra->state = SD_RADV_STATE_ADVERTISING; log_radv("Started IPv6 Router Advertisement daemon"); return 0; + + fail: + radv_reset(ra); + + return r; } _public_ int sd_radv_set_ifindex(sd_radv *ra, int ifindex) { From 6142bb37a594183bfc8989d598bc1913a30f7c03 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 12 May 2017 16:48:35 +0300 Subject: [PATCH 09/14] icmp6-util: Bind Router Advertisement socket Reuse and refactor the functionality already present for Router Solicitations in order to create a socket for sending Router Advertisements. Anticipate reception of incoming Router Solicitations by setting the ICMPv6 filter accordingly. Also set the unicast hop limit to 255 for ICMPv6 sockets as unicast Router Advertisments are to be sent in response to Router Solicitations. Update the Router Solicitation test case code with a function definition in order to keep the test case working. --- src/libsystemd-network/icmp6-util.c | 45 ++++++++++++++++++++------ src/libsystemd-network/icmp6-util.h | 1 + src/libsystemd-network/test-ndisc-rs.c | 5 +++ 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c index c2e4b0e9e3..f1cb0bc8a0 100644 --- a/src/libsystemd-network/icmp6-util.c +++ b/src/libsystemd-network/icmp6-util.c @@ -41,12 +41,9 @@ { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } } -int icmp6_bind_router_solicitation(int index) { - struct icmp6_filter filter = { }; - struct ipv6_mreq mreq = { - .ipv6mr_multiaddr = IN6ADDR_ALL_NODES_MULTICAST_INIT, - .ipv6mr_interface = index, - }; +static int icmp6_bind_router_message(const struct icmp6_filter *filter, + const struct ipv6_mreq *mreq) { + int index = mreq->ipv6mr_interface; _cleanup_close_ int s = -1; char ifname[IF_NAMESIZE] = ""; static const int zero = 0, one = 1, hops = 255; @@ -56,9 +53,11 @@ int icmp6_bind_router_solicitation(int index) { if (s < 0) return -errno; - ICMP6_FILTER_SETBLOCKALL(&filter); - ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); - r = setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)); + r = setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, filter, sizeof(*filter)); + if (r < 0) + return -errno; + + r = setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, mreq, sizeof(*mreq)); if (r < 0) return -errno; @@ -78,7 +77,7 @@ int icmp6_bind_router_solicitation(int index) { if (r < 0) return -errno; - r = setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + r = setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hops, sizeof(hops)); if (r < 0) return -errno; @@ -102,6 +101,32 @@ int icmp6_bind_router_solicitation(int index) { return r; } +int icmp6_bind_router_solicitation(int index) { + struct icmp6_filter filter = {}; + struct ipv6_mreq mreq = { + .ipv6mr_multiaddr = IN6ADDR_ALL_NODES_MULTICAST_INIT, + .ipv6mr_interface = index, + }; + + ICMP6_FILTER_SETBLOCKALL(&filter); + ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); + + return icmp6_bind_router_message(&filter, &mreq); +} + +int icmp6_bind_router_advertisement(int index) { + struct icmp6_filter filter = {}; + struct ipv6_mreq mreq = { + .ipv6mr_multiaddr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT, + .ipv6mr_interface = index, + }; + + ICMP6_FILTER_SETBLOCKALL(&filter); + ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter); + + return icmp6_bind_router_message(&filter, &mreq); +} + int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { struct sockaddr_in6 dst = { .sin6_family = AF_INET6, diff --git a/src/libsystemd-network/icmp6-util.h b/src/libsystemd-network/icmp6-util.h index 2b4dbc76ce..5cf63cb2c5 100644 --- a/src/libsystemd-network/icmp6-util.h +++ b/src/libsystemd-network/icmp6-util.h @@ -22,4 +22,5 @@ #include int icmp6_bind_router_solicitation(int index); +int icmp6_bind_router_advertisement(int index); int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr); diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c index d9669488be..e212b8a967 100644 --- a/src/libsystemd-network/test-ndisc-rs.c +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -193,6 +193,11 @@ int icmp6_bind_router_solicitation(int index) { return test_fd[0]; } +int icmp6_bind_router_advertisement(int index) { + + return -ENOSYS; +} + static int send_ra(uint8_t flags) { uint8_t advertisement[] = { 0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4, From e2e8122838565670b34f0dbed320c3b2a949f135 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 12 May 2017 16:48:36 +0300 Subject: [PATCH 10/14] icmp6-util: Move multicast address definitions As the Router Advertisment sending code needs these multicast address definitions, move them to the header file. --- src/libsystemd-network/icmp6-util.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libsystemd-network/icmp6-util.h b/src/libsystemd-network/icmp6-util.h index 5cf63cb2c5..13c237f5fb 100644 --- a/src/libsystemd-network/icmp6-util.h +++ b/src/libsystemd-network/icmp6-util.h @@ -21,6 +21,14 @@ #include +#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \ + { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } } + +#define IN6ADDR_ALL_NODES_MULTICAST_INIT \ + { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } } + int icmp6_bind_router_solicitation(int index); int icmp6_bind_router_advertisement(int index); int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr); From 77baf5aee68de59b21592c93d05ddb75e3fe65c2 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 12 May 2017 16:48:37 +0300 Subject: [PATCH 11/14] sd-radv: Send Router Advertisments Create and remove the ICMPv6 Router Advertisement socket file descriptor and implement Router Advertisment sending. As not all options are mandatory, use IO vectors to point to the included options and the prefix information. --- src/libsystemd-network/radv-internal.h | 1 + src/libsystemd-network/sd-radv.c | 75 +++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/libsystemd-network/radv-internal.h b/src/libsystemd-network/radv-internal.h index 7dcc3e0451..536923d7df 100644 --- a/src/libsystemd-network/radv-internal.h +++ b/src/libsystemd-network/radv-internal.h @@ -56,6 +56,7 @@ struct sd_radv { uint32_t mtu; uint16_t lifetime; + int fd; unsigned ra_sent; sd_event_source *timeout_event_source; diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c index 71054eb5fe..1b60af0dd7 100644 --- a/src/libsystemd-network/sd-radv.c +++ b/src/libsystemd-network/sd-radv.c @@ -20,6 +20,7 @@ #include #include #include +#include #include "sd-radv.h" @@ -44,6 +45,7 @@ _public_ int sd_radv_new(sd_radv **ret) { return -ENOMEM; ra->n_ref = 1; + ra->fd = -1; LIST_HEAD_INIT(ra->prefixes); @@ -129,8 +131,71 @@ _public_ sd_radv *sd_radv_unref(sd_radv *ra) { static int radv_send(sd_radv *ra, const struct in6_addr *dst, const uint32_t router_lifetime) { + static const struct ether_addr mac_zero = {}; + sd_radv_prefix *p; + struct sockaddr_in6 dst_addr = { + .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_ALL_NODES_MULTICAST_INIT, + }; + struct nd_router_advert adv = {}; + struct { + struct nd_opt_hdr opthdr; + struct ether_addr slladdr; + } _packed_ opt_mac = { + .opthdr = { + .nd_opt_type = ND_OPT_SOURCE_LINKADDR, + .nd_opt_len = (sizeof(struct nd_opt_hdr) + + sizeof(struct ether_addr) - 1) /8 + 1, + }, + }; + struct nd_opt_mtu opt_mtu = { + .nd_opt_mtu_type = ND_OPT_MTU, + .nd_opt_mtu_len = 1, + }; + /* Reserve iov space for RA header, linkaddr, MTU + N prefixes */ + struct iovec iov[3 + ra->n_prefixes]; + struct msghdr msg = { + .msg_name = &dst_addr, + .msg_namelen = sizeof(dst_addr), + .msg_iov = iov, + }; - return -ENOSYS; + if (dst) + dst_addr.sin6_addr = *dst; + adv.nd_ra_type = ND_ROUTER_ADVERT; + adv.nd_ra_curhoplimit = ra->hop_limit; + adv.nd_ra_flags_reserved = ra->flags; + adv.nd_ra_router_lifetime = htobe16(router_lifetime); + iov[msg.msg_iovlen].iov_base = &adv; + iov[msg.msg_iovlen].iov_len = sizeof(adv); + msg.msg_iovlen++; + + /* MAC address is optional, either because the link does not use L2 + addresses or load sharing is desired. See RFC 4861, Section 4.2 */ + if (memcmp(&mac_zero, &ra->mac_addr, sizeof(mac_zero))) { + opt_mac.slladdr = ra->mac_addr; + iov[msg.msg_iovlen].iov_base = &opt_mac; + iov[msg.msg_iovlen].iov_len = sizeof(opt_mac); + msg.msg_iovlen++; + } + + if (ra->mtu) { + opt_mtu.nd_opt_mtu_mtu = htobe32(ra->mtu); + iov[msg.msg_iovlen].iov_base = &opt_mtu; + iov[msg.msg_iovlen].iov_len = sizeof(opt_mtu); + msg.msg_iovlen++; + } + + LIST_FOREACH(prefix, p, ra->prefixes) { + iov[msg.msg_iovlen].iov_base = &p->opt; + iov[msg.msg_iovlen].iov_len = sizeof(p->opt); + msg.msg_iovlen++; + } + + if (sendmsg(ra->fd, &msg, 0) < 0) + return -errno; + + return 0; } static usec_t radv_compute_timeout(usec_t min, usec_t max) { @@ -213,6 +278,7 @@ _public_ int sd_radv_stop(sd_radv *ra) { log_radv_warning_errno(r, "Unable to send last Router Advertisement with router lifetime set to zero: %m"); radv_reset(ra); + ra->fd = safe_close(ra->fd); ra->state = SD_RADV_STATE_IDLE; return 0; @@ -242,6 +308,13 @@ _public_ int sd_radv_start(sd_radv *ra) { (void) sd_event_source_set_description(ra->timeout_event_source, "radv-timeout"); + r = icmp6_bind_router_advertisement(ra->ifindex); + if (r < 0) + goto fail; + + ra->fd = r; + r = 0; + ra->state = SD_RADV_STATE_ADVERTISING; log_radv("Started IPv6 Router Advertisement daemon"); From 88d5a3db555af62dcde306e9de88ecfc73db80ab Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 12 May 2017 16:48:38 +0300 Subject: [PATCH 12/14] sd-radv: Receive Router Solicitations Receive Router Solicitations and send a unicast Router Advertisment in response. Refactor ICMPv6 packet handling code so that the common ICMPv6 validation parts are reused between the existing router discovery and the new functionality adding reception of Router Solicitation messages. --- src/libsystemd-network/icmp6-util.c | 72 +++++++++++++++++++++ src/libsystemd-network/icmp6-util.h | 4 ++ src/libsystemd-network/radv-internal.h | 1 + src/libsystemd-network/sd-ndisc.c | 89 ++++++-------------------- src/libsystemd-network/sd-radv.c | 74 ++++++++++++++++++++- src/libsystemd-network/test-ndisc-rs.c | 10 +++ 6 files changed, 177 insertions(+), 73 deletions(-) diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c index f1cb0bc8a0..7fbebd6f27 100644 --- a/src/libsystemd-network/icmp6-util.c +++ b/src/libsystemd-network/icmp6-util.c @@ -32,6 +32,7 @@ #include "fd-util.h" #include "icmp6-util.h" #include "socket-util.h" +#include "in-addr-util.h" #define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \ { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ @@ -164,3 +165,74 @@ int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { return 0; } + +int icmp6_receive(int fd, void *buffer, size_t size, struct in6_addr *dst, + triple_timestamp *timestamp) { + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int)) + /* ttl */ + CMSG_SPACE(sizeof(struct timeval))]; + } control = {}; + struct iovec iov = {}; + union sockaddr_union sa = {}; + struct msghdr msg = { + .msg_name = &sa.sa, + .msg_namelen = sizeof(sa), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + ssize_t len; + + iov.iov_base = buffer; + iov.iov_len = size; + + len = recvmsg(fd, &msg, MSG_DONTWAIT); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return -errno; + } + + if ((size_t) len != size) + return -EINVAL; + + if (msg.msg_namelen == sizeof(struct sockaddr_in6) && + sa.in6.sin6_family == AF_INET6) { + + *dst = sa.in6.sin6_addr; + if (in_addr_is_link_local(AF_INET6, (union in_addr_union*) dst) <= 0) + return -EADDRNOTAVAIL; + + } else if (msg.msg_namelen > 0) + return -EPFNOSUPPORT; + + /* namelen == 0 only happens when running the test-suite over a socketpair */ + + assert(!(msg.msg_flags & MSG_CTRUNC)); + assert(!(msg.msg_flags & MSG_TRUNC)); + + CMSG_FOREACH(cmsg, &msg) { + if (cmsg->cmsg_level == SOL_IPV6 && + cmsg->cmsg_type == IPV6_HOPLIMIT && + cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { + int hops = *(int*) CMSG_DATA(cmsg); + + if (hops != 255) + return -EMULTIHOP; + } + + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SO_TIMESTAMP && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) + triple_timestamp_from_realtime(timestamp, timeval_load((struct timeval*) CMSG_DATA(cmsg))); + } + + if (!triple_timestamp_is_set(timestamp)) + triple_timestamp_get(timestamp); + + return 0; +} diff --git a/src/libsystemd-network/icmp6-util.h b/src/libsystemd-network/icmp6-util.h index 13c237f5fb..16b8be8298 100644 --- a/src/libsystemd-network/icmp6-util.h +++ b/src/libsystemd-network/icmp6-util.h @@ -21,6 +21,8 @@ #include +#include "time-util.h" + #define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \ { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } } @@ -32,3 +34,5 @@ int icmp6_bind_router_solicitation(int index); int icmp6_bind_router_advertisement(int index); int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr); +int icmp6_receive(int fd, void *buffer, size_t size, struct in6_addr *dst, + triple_timestamp *timestamp); diff --git a/src/libsystemd-network/radv-internal.h b/src/libsystemd-network/radv-internal.h index 536923d7df..b21d4e54cb 100644 --- a/src/libsystemd-network/radv-internal.h +++ b/src/libsystemd-network/radv-internal.h @@ -58,6 +58,7 @@ struct sd_radv { int fd; unsigned ra_sent; + sd_event_source *recv_event_source; sd_event_source *timeout_event_source; unsigned n_prefixes; diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index 83e57d43f7..0437e0b0b7 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -222,23 +222,9 @@ static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) { static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL; sd_ndisc *nd = userdata; - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(int)) + /* ttl */ - CMSG_SPACE(sizeof(struct timeval))]; - } control = {}; - struct iovec iov = {}; - union sockaddr_union sa = {}; - struct msghdr msg = { - .msg_name = &sa.sa, - .msg_namelen = sizeof(sa), - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = &control, - .msg_controllen = sizeof(control), - }; - struct cmsghdr *cmsg; - ssize_t len, buflen; + ssize_t buflen; + int r; + _cleanup_free_ char *addr = NULL; assert(s); assert(nd); @@ -252,66 +238,27 @@ static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userda if (!rt) return -ENOMEM; - iov.iov_base = NDISC_ROUTER_RAW(rt); - iov.iov_len = rt->raw_size; + r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address, + &rt->timestamp); + if (r < 0) { + switch (r) { + case -EADDRNOTAVAIL: + (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &rt->address, &addr); + log_ndisc("Received RA from non-link-local address %s. Ignoring", addr); + break; - len = recvmsg(fd, &msg, MSG_DONTWAIT); - if (len < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; + case -EMULTIHOP: + log_ndisc("Received RA with invalid hop limit. Ignoring."); + break; - return log_ndisc_errno(errno, "Could not receive message from ICMPv6 socket: %m"); - } - - if ((size_t) len != rt->raw_size) { - log_ndisc("Packet size mismatch."); - return -EINVAL; - } - - if (msg.msg_namelen == sizeof(struct sockaddr_in6) && - sa.in6.sin6_family == AF_INET6) { - - if (in_addr_is_link_local(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr) <= 0) { - _cleanup_free_ char *addr = NULL; - - (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr, &addr); - log_ndisc("Received RA from non-link-local address %s. Ignoring.", strna(addr)); - return 0; + case -EPFNOSUPPORT: + log_ndisc("Received invalid source address from ICMPv6 socket."); + break; } - rt->address = sa.in6.sin6_addr; - - } else if (msg.msg_namelen > 0) { - log_ndisc("Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t) msg.msg_namelen); - return -EINVAL; + return 0; } - /* namelen == 0 only happens when running the test-suite over a socketpair */ - - assert(!(msg.msg_flags & MSG_CTRUNC)); - assert(!(msg.msg_flags & MSG_TRUNC)); - - CMSG_FOREACH(cmsg, &msg) { - if (cmsg->cmsg_level == SOL_IPV6 && - cmsg->cmsg_type == IPV6_HOPLIMIT && - cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { - int hops = *(int*) CMSG_DATA(cmsg); - - if (hops != 255) { - log_ndisc("Received RA with invalid hop limit %d. Ignoring.", hops); - return 0; - } - } - - if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SO_TIMESTAMP && - cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) - triple_timestamp_from_realtime(&rt->timestamp, timeval_load((struct timeval*) CMSG_DATA(cmsg))); - } - - if (!triple_timestamp_is_set(&rt->timestamp)) - triple_timestamp_get(&rt->timestamp); - nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); return ndisc_handle_datagram(nd, rt); diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c index 1b60af0dd7..f23275a80c 100644 --- a/src/libsystemd-network/sd-radv.c +++ b/src/libsystemd-network/sd-radv.c @@ -93,6 +93,9 @@ static void radv_reset(sd_radv *ra) { ra->timeout_event_source = sd_event_source_unref(ra->timeout_event_source); + ra->recv_event_source = + sd_event_source_unref(ra->recv_event_source); + ra->ra_sent = 0; } @@ -160,8 +163,9 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst, .msg_iov = iov, }; - if (dst) + if (dst && !in_addr_is_null(AF_INET6, (union in_addr_union*) dst)) dst_addr.sin6_addr = *dst; + adv.nd_ra_type = ND_ROUTER_ADVERT; adv.nd_ra_curhoplimit = ra->hop_limit; adv.nd_ra_flags_reserved = ra->flags; @@ -198,6 +202,63 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst, return 0; } +static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_radv *ra = userdata; + _cleanup_free_ char *addr = NULL; + struct in6_addr src; + triple_timestamp timestamp; + int r; + ssize_t buflen; + _cleanup_free_ char *buf = NULL; + + assert(s); + assert(ra); + assert(ra->event); + + buflen = next_datagram_size_fd(fd); + + if ((unsigned) buflen < sizeof(struct nd_router_solicit)) + return log_radv("Too short packet received"); + + buf = new0(char, buflen); + if (!buf) + return 0; + + r = icmp6_receive(fd, buf, buflen, &src, ×tamp); + if (r < 0) { + switch (r) { + case -EADDRNOTAVAIL: + (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &src, &addr); + log_radv("Received RS from non-link-local address %s. Ignoring", addr); + break; + + case -EMULTIHOP: + log_radv("Received RS with invalid hop limit. Ignoring."); + break; + + case -EPFNOSUPPORT: + log_radv("Received invalid source address from ICMPv6 socket. Ignoring."); + break; + + default: + log_radv_warning_errno(r, "Error receiving from ICMPv6 socket: %m"); + break; + } + + return 0; + } + + (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &src, &addr); + + r = radv_send(ra, &src, ra->lifetime); + if (r < 0) + log_radv_warning_errno(r, "Unable to send solicited Router Advertisment to %s: %m", addr); + else + log_radv("Sent solicited Router Advertisement to %s", addr); + + return 0; +} + static usec_t radv_compute_timeout(usec_t min, usec_t max) { assert_return(min <= max, SD_RADV_DEFAULT_MIN_TIMEOUT_USEC); @@ -313,7 +374,16 @@ _public_ int sd_radv_start(sd_radv *ra) { goto fail; ra->fd = r; - r = 0; + + r = sd_event_add_io(ra->event, &ra->recv_event_source, ra->fd, EPOLLIN, radv_recv, ra); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(ra->recv_event_source, ra->event_priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(ra->recv_event_source, "radv-receive-message"); ra->state = SD_RADV_STATE_ADVERTISING; diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c index e212b8a967..b4a6539599 100644 --- a/src/libsystemd-network/test-ndisc-rs.c +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -198,6 +198,16 @@ int icmp6_bind_router_advertisement(int index) { return -ENOSYS; } +int icmp6_receive(int fd, void *iov_base, size_t iov_len, + struct in6_addr *dst, triple_timestamp *timestamp) { + assert (read (fd, iov_base, iov_len) == (ssize_t)iov_len); + + if (timestamp) + triple_timestamp_get(timestamp); + + return 0; +} + static int send_ra(uint8_t flags) { uint8_t advertisement[] = { 0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4, From 24852172b9c9414b02a3b5d8536e2fec89612be9 Mon Sep 17 00:00:00 2001 From: Patrik Flykt Date: Fri, 12 May 2017 16:48:39 +0300 Subject: [PATCH 13/14] test-ndisc-ra: Add Router Advertisement test cases Add tests for prefix creation, router variable setting and finally verify that a Router Advertisement is properly formatted when sending. Also check that there is a Router Advertisment with zero lifetime when Router Advertisement sending is stopped. --- Makefile.am | 11 + src/libsystemd-network/test-ndisc-ra.c | 359 +++++++++++++++++++++++++ 2 files changed, 370 insertions(+) create mode 100644 src/libsystemd-network/test-ndisc-ra.c diff --git a/Makefile.am b/Makefile.am index ad79ccde76..73c1bd6732 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3756,6 +3756,16 @@ test_ndisc_rs_LDADD = \ libudev.la \ libsystemd-shared.la +test_ndisc_ra_SOURCES = \ + src/systemd/sd-ndisc.h \ + src/libsystemd-network/icmp6-util.h \ + src/libsystemd-network/test-ndisc-ra.c + +test_ndisc_ra_LDADD = \ + libsystemd-network.la \ + libudev.la \ + libsystemd-shared.la + test_dhcp6_client_SOURCES = \ src/systemd/sd-dhcp6-client.h \ src/libsystemd-network/dhcp6-internal.h \ @@ -3782,6 +3792,7 @@ tests += \ test-sd-dhcp-lease \ test-ipv4ll \ test-ndisc-rs \ + test-ndisc-ra \ test-dhcp6-client \ test-lldp diff --git a/src/libsystemd-network/test-ndisc-ra.c b/src/libsystemd-network/test-ndisc-ra.c new file mode 100644 index 0000000000..6e6d05642d --- /dev/null +++ b/src/libsystemd-network/test-ndisc-ra.c @@ -0,0 +1,359 @@ +/*** + This file is part of systemd. + + Copyright (C) 2017 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +#include "sd-radv.h" + +#include "alloc-util.h" +#include "hexdecoct.h" +#include "icmp6-util.h" +#include "socket-util.h" +#include "strv.h" + +static struct ether_addr mac_addr = { + .ether_addr_octet = { 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53 } +}; + +static uint8_t advertisement[] = { + /* ICMPv6 Router Advertisement, no checksum */ + 0x86, 0x00, 0x00, 0x00, 0x40, 0xc0, 0x00, 0xb4, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* Source Link Layer Address Option */ + 0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53, + /* Prefix Information Option */ + 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x01, 0xf4, + 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* Prefix Information Option */ + 0x03, 0x04, 0x40, 0xc0, 0x00, 0x27, 0x8d, 0x00, + 0x00, 0x09, 0x3a, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* Prefix Information Option */ + 0x03, 0x04, 0x30, 0xc0, 0x00, 0x27, 0x8d, 0x00, + 0x00, 0x09, 0x3a, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x01, 0x0d, 0xad, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* Recursive DNS Server Option - not yet supported */ + 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + /* DNS Search List Option - not yet supported */ + 0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, + 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74, + 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static sd_event_source *test_hangcheck; +static bool test_stopped; +static int test_fd[2]; +static sd_event_source *recv_router_advertisement; +static struct { + struct in6_addr address; + unsigned char prefixlen; + uint32_t valid; + uint32_t preferred; + bool succesful; +} prefix[] = { + { { { { 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 64, + 500, 440, true }, + { { { { 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 64, + /* indicate default valid and preferred lifetimes for the test code */ + 0, 0, true }, + { { { { 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 58, + 0, 0, + /* indicate that this prefix already exists */ + false }, + { { { { 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 120, + 0, 0, + /* indicate that this prefix already exists */ + false }, + { { { { 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 12, + 0, 0, + /* indicate that this prefix already exists */ + false }, + { { { { 0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x01, 0x0d, 0xad, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 48, + 0, 0, true }, + { { { { 0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x01, 0x0d, 0xad, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, 60, + 0, 0, + /* indicate that this prefix already exists */ + false }, +}; + +static int test_rs_hangcheck(sd_event_source *s, uint64_t usec, + void *userdata) { + assert_se(false); + + return 0; +} + +static void test_radv_prefix(void) { + sd_radv_prefix *p; + + printf("* %s\n", __FUNCTION__); + + assert_se(sd_radv_prefix_new(&p) >= 0); + + assert_se(sd_radv_prefix_set_onlink(NULL, true) < 0); + assert_se(sd_radv_prefix_set_onlink(p, true) >= 0); + assert_se(sd_radv_prefix_set_onlink(p, false) >= 0); + + assert_se(sd_radv_prefix_set_address_autoconfiguration(NULL, true) < 0); + assert_se(sd_radv_prefix_set_address_autoconfiguration(p, true) >= 0); + assert_se(sd_radv_prefix_set_address_autoconfiguration(p, false) >= 0); + + assert_se(sd_radv_prefix_set_valid_lifetime(NULL, true) < 0); + assert_se(sd_radv_prefix_set_valid_lifetime(p, ~0) >= 0); + assert_se(sd_radv_prefix_set_valid_lifetime(p, 42) >= 0); + assert_se(sd_radv_prefix_set_valid_lifetime(p, 0) >= 0); + + assert_se(sd_radv_prefix_set_preferred_lifetime(NULL, true) < 0); + assert_se(sd_radv_prefix_set_preferred_lifetime(p, ~0) >= 0); + assert_se(sd_radv_prefix_set_preferred_lifetime(p, 42) >= 0); + assert_se(sd_radv_prefix_set_preferred_lifetime(p, 0) >= 0); + + assert_se(sd_radv_prefix_set_prefix(NULL, NULL, 0) < 0); + assert_se(sd_radv_prefix_set_prefix(p, NULL, 0) < 0); + + assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 64) >= 0); + assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 0) < 0); + assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 1) < 0); + assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 2) < 0); + assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 3) >= 0); + assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 125) >= 0); + assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 128) >= 0); + assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 129) < 0); + assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 255) < 0); + + p = sd_radv_prefix_unref(p); + assert_se(!p); +} + +static void test_radv(void) { + sd_radv *ra; + + printf("* %s\n", __FUNCTION__); + + assert_se(sd_radv_new(&ra) >= 0); + assert_se(ra); + + assert_se(sd_radv_set_ifindex(NULL, 0) < 0); + assert_se(sd_radv_set_ifindex(ra, 0) >= 0); + assert_se(sd_radv_set_ifindex(ra, -1) >= 0); + assert_se(sd_radv_set_ifindex(ra, -2) < 0); + assert_se(sd_radv_set_ifindex(ra, 42) >= 0); + + assert_se(sd_radv_set_mac(NULL, NULL) < 0); + assert_se(sd_radv_set_mac(ra, NULL) >= 0); + assert_se(sd_radv_set_mac(ra, &mac_addr) >= 0); + + assert_se(sd_radv_set_mtu(NULL, 0) < 0); + assert_se(sd_radv_set_mtu(ra, 0) < 0); + assert_se(sd_radv_set_mtu(ra, 1279) < 0); + assert_se(sd_radv_set_mtu(ra, 1280) >= 0); + assert_se(sd_radv_set_mtu(ra, ~0) >= 0); + + assert_se(sd_radv_set_hop_limit(NULL, 0) < 0); + assert_se(sd_radv_set_hop_limit(ra, 0) >= 0); + assert_se(sd_radv_set_hop_limit(ra, ~0) >= 0); + + assert_se(sd_radv_set_router_lifetime(NULL, 0) < 0); + assert_se(sd_radv_set_router_lifetime(ra, 0) >= 0); + assert_se(sd_radv_set_router_lifetime(ra, ~0) >= 0); + + assert_se(sd_radv_set_preference(NULL, 0) < 0); + assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_LOW) >= 0); + assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_MEDIUM) >= 0); + assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_HIGH) >= 0); + assert_se(sd_radv_set_preference(ra, ~0) < 0); + + assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_HIGH) >= 0); + assert_se(sd_radv_set_router_lifetime(ra, 42000) >= 0); + assert_se(sd_radv_set_router_lifetime(ra, 0) < 0); + assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_MEDIUM) >= 0); + assert_se(sd_radv_set_router_lifetime(ra, 0) >= 0); + + assert_se(sd_radv_set_managed_information(NULL, true) < 0); + assert_se(sd_radv_set_managed_information(ra, true) >= 0); + assert_se(sd_radv_set_managed_information(ra, false) >= 0); + + assert_se(sd_radv_set_other_information(NULL, true) < 0); + assert_se(sd_radv_set_other_information(ra, true) >= 0); + assert_se(sd_radv_set_other_information(ra, false) >= 0); + + ra = sd_radv_unref(ra); + assert_se(!ra); +} + +int icmp6_bind_router_solicitation(int index) { + return -ENOSYS; +} + +int icmp6_bind_router_advertisement(int index) { + assert_se(index == 42); + + return test_fd[1]; +} + +int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { + + return 0; +} + +int icmp6_receive(int fd, void *iov_base, size_t iov_len, + struct in6_addr *dst, triple_timestamp *timestamp) { + assert_se(read (fd, iov_base, iov_len) == (ssize_t)iov_len); + + if (timestamp) + triple_timestamp_get(timestamp); + + return 0; +} + +static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_radv *ra = userdata; + unsigned char buf[120]; + size_t i; + + read(test_fd[0], &buf, sizeof(buf)); + + /* router lifetime must be zero when test is stopped */ + if (test_stopped) { + advertisement[6] = 0x00; + advertisement[7] = 0x00; + } + + printf ("Received Router Advertisement with lifetime %u\n", + (advertisement[6] << 8) + advertisement[7]); + + /* test only up to buf size, rest is not yet implemented */ + for (i = 0; i < sizeof(buf); i++) { + printf("0x%02x", buf[i]); + + assert_se(buf[i] == advertisement[i]); + + if ((i + 1) % 8) + printf(", "); + else + printf("\n"); + } + + if (test_stopped) { + sd_event *e; + + e = sd_radv_get_event(ra); + sd_event_exit(e, 0); + + return 0; + } + + assert_se(sd_radv_stop(ra) >= 0); + test_stopped = true; + + return 0; +} + +static void test_ra(void) { + sd_event *e; + sd_radv *ra; + usec_t time_now = now(clock_boottime_or_monotonic()); + unsigned int i; + + printf("* %s\n", __FUNCTION__); + + assert_se(socketpair(AF_UNIX, SOCK_SEQPACKET, 0, test_fd) >= 0); + + assert_se(sd_event_new(&e) >= 0); + + assert_se(sd_radv_new(&ra) >= 0); + assert_se(ra); + + assert_se(sd_radv_attach_event(ra, e, 0) >= 0); + + assert_se(sd_radv_set_ifindex(ra, 42) >= 0); + assert_se(sd_radv_set_mac(ra, &mac_addr) >= 0); + assert_se(sd_radv_set_router_lifetime(ra, 180) >= 0); + assert_se(sd_radv_set_hop_limit(ra, 64) >= 0); + assert_se(sd_radv_set_managed_information(ra, true) >= 0); + assert_se(sd_radv_set_other_information(ra, true) >= 0); + + for (i = 0; i < ELEMENTSOF(prefix); i++) { + sd_radv_prefix *p; + + printf("Test prefix %u\n", i); + assert_se(sd_radv_prefix_new(&p) >= 0); + + assert_se(sd_radv_prefix_set_prefix(p, &prefix[i].address, + prefix[i].prefixlen) >= 0); + if (prefix[i].valid) + assert_se(sd_radv_prefix_set_valid_lifetime(p, prefix[i].valid) >= 0); + 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); + + p = sd_radv_prefix_unref(p); + assert_se(!p); + } + + assert_se(sd_event_add_io(e, &recv_router_advertisement, test_fd[0], + EPOLLIN, radv_recv, ra) >= 0); + + assert_se(sd_event_add_time(e, &test_hangcheck, clock_boottime_or_monotonic(), + time_now + 2 *USEC_PER_SEC, 0, + test_rs_hangcheck, NULL) >= 0); + + assert_se(sd_radv_start(ra) >= 0); + + sd_event_loop(e); + + test_hangcheck = sd_event_source_unref(test_hangcheck); + + ra = sd_radv_unref(ra); + assert_se(!ra); + + close(test_fd[0]); + + sd_event_unref(e); +} + +int main(int argc, char *argv[]) { + + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + + test_radv_prefix(); + test_radv(); + test_ra(); + + printf("* done\n"); + return 0; +} From 4a24ba71a318acbc74ffc86662e189a63c06385c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 11 May 2017 09:19:21 -0400 Subject: [PATCH 14/14] meson: add rules for RA sources --- src/libsystemd-network/meson.build | 2 ++ src/network/meson.build | 2 ++ src/test/meson.build | 7 +++++++ 3 files changed, 11 insertions(+) diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 3285bcaed1..78c74560b8 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -18,6 +18,8 @@ sources = files(''' ndisc-internal.h ndisc-router.h ndisc-router.c + sd-radv.c + radv-internal.h icmp6-util.h icmp6-util.c sd-dhcp6-client.c diff --git a/src/network/meson.build b/src/network/meson.build index 5f97cc197b..521b21a18f 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -54,6 +54,8 @@ sources = files(''' networkd-manager.h networkd-ndisc.c networkd-ndisc.h + networkd-radv.c + networkd-radv.h networkd-network-bus.c networkd-network.c networkd-network.h diff --git a/src/test/meson.build b/src/test/meson.build index 6748de69ef..0d9b145333 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -850,6 +850,13 @@ tests += [ libsystemd_network], []], + [['src/libsystemd-network/test-ndisc-ra.c', + 'src/libsystemd-network/icmp6-util.h', + 'src/systemd/sd-ndisc.h'], + [libshared, + libsystemd_network], + []], + [['src/libsystemd-network/test-dhcp6-client.c', 'src/libsystemd-network/dhcp-identifier.h', 'src/libsystemd-network/dhcp-identifier.c',