diff options
Diffstat (limited to '')
105 files changed, 10059 insertions, 6018 deletions
diff --git a/src/network/generator/main.c b/src/network/generator/main.c index 0439a9d..0911656 100644 --- a/src/network/generator/main.c +++ b/src/network/generator/main.c @@ -3,6 +3,7 @@ #include <getopt.h> #include "build.h" +#include "creds-util.h" #include "fd-util.h" #include "fs-util.h" #include "generator.h" @@ -13,7 +14,7 @@ #include "path-util.h" #include "proc-cmdline.h" -#define NETWORKD_UNIT_DIRECTORY "/run/systemd/network" +#define NETWORK_UNIT_DIRECTORY "/run/systemd/network/" static const char *arg_root = NULL; @@ -25,7 +26,13 @@ static int network_save(Network *network, const char *dest_dir) { assert(network); - r = generator_open_unit_file_full(dest_dir, NULL, NULL, &f, &temp_path); + r = generator_open_unit_file_full( + dest_dir, + /* source= */ NULL, + /* name= */ NULL, + &f, + /* ret_final_path= */ NULL, + &temp_path); if (r < 0) return r; @@ -39,7 +46,7 @@ static int network_save(Network *network, const char *dest_dir) { r = conservative_rename(temp_path, p); if (r < 0) - return r; + return log_error_errno(r, "Failed to rename '%s' to '%s': %m", temp_path, p); temp_path = mfree(temp_path); return 0; @@ -53,7 +60,13 @@ static int netdev_save(NetDev *netdev, const char *dest_dir) { assert(netdev); - r = generator_open_unit_file_full(dest_dir, NULL, NULL, &f, &temp_path); + r = generator_open_unit_file_full( + dest_dir, + /* source= */ NULL, + /* name= */ NULL, + &f, + /* ret_final_path= */ NULL, + &temp_path); if (r < 0) return r; @@ -64,7 +77,7 @@ static int netdev_save(NetDev *netdev, const char *dest_dir) { r = conservative_rename(temp_path, p); if (r < 0) - return r; + return log_error_errno(r, "Failed to rename '%s' to '%s': %m", temp_path, p); temp_path = mfree(temp_path); return 0; @@ -78,7 +91,13 @@ static int link_save(Link *link, const char *dest_dir) { assert(link); - r = generator_open_unit_file_full(dest_dir, NULL, NULL, &f, &temp_path); + r = generator_open_unit_file_full( + dest_dir, + /* source= */ NULL, + /* name= */ NULL, + &f, + /* ret_final_path= */ NULL, + &temp_path); if (r < 0) return r; @@ -92,7 +111,7 @@ static int link_save(Link *link, const char *dest_dir) { r = conservative_rename(temp_path, p); if (r < 0) - return r; + return log_error_errno(r, "Failed to rename '%s' to '%s': %m", temp_path, p); temp_path = mfree(temp_path); return 0; @@ -104,11 +123,11 @@ static int context_save(Context *context) { Link *link; int r; - const char *p = prefix_roota(arg_root, NETWORKD_UNIT_DIRECTORY); + const char *p = prefix_roota(arg_root, NETWORK_UNIT_DIRECTORY); r = mkdir_p(p, 0755); if (r < 0) - return log_error_errno(r, "Failed to create directory " NETWORKD_UNIT_DIRECTORY ": %m"); + return log_error_errno(r, "Failed to create directory " NETWORK_UNIT_DIRECTORY ": %m"); HASHMAP_FOREACH(network, context->networks_by_name) RET_GATHER(r, network_save(network, p)); @@ -174,7 +193,7 @@ static int parse_argv(int argc, char *argv[]) { static int run(int argc, char *argv[]) { _cleanup_(context_clear) Context context = {}; - int r; + int r, ret = 0; log_setup(); @@ -212,7 +231,17 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_warning_errno(r, "Failed to merge multiple command line options: %m"); - return context_save(&context); + RET_GATHER(ret, context_save(&context)); + + static const PickUpCredential table[] = { + { "network.conf.", "/run/systemd/networkd.conf.d/", ".conf" }, + { "network.link.", NETWORK_UNIT_DIRECTORY, ".link" }, + { "network.netdev.", NETWORK_UNIT_DIRECTORY, ".netdev" }, + { "network.network.", NETWORK_UNIT_DIRECTORY, ".network" }, + }; + RET_GATHER(ret, pick_up_credentials(table, ELEMENTSOF(table))); + + return ret; } DEFINE_MAIN_FUNCTION(run); diff --git a/src/network/generator/network-generator.c b/src/network/generator/network-generator.c index 48527a2..ec66520 100644 --- a/src/network/generator/network-generator.c +++ b/src/network/generator/network-generator.c @@ -27,7 +27,7 @@ # .link ifname=<interface>:<MAC> - net.ifname-policy=policy1[,policy2,...][,<MAC>] # This is an original rule, not supported by other tools. + net.ifname_policy=policy1[,policy2,...][,<MAC>] # This is an original rule, not supported by other tools. # .netdev vlan=<vlanname>:<phydevice> @@ -373,13 +373,13 @@ static int network_set_dhcp_type(Context *context, const char *ifname, const cha t = dracut_dhcp_type_from_string(dhcp_type); if (t < 0) - return t; + return log_debug_errno(t, "Invalid DHCP type '%s'", dhcp_type); network = network_get(context, ifname); if (!network) { r = network_new(context, ifname, &network); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); } network->dhcp_type = t; @@ -394,13 +394,14 @@ static int network_set_hostname(Context *context, const char *ifname, const char network = network_get(context, ifname); if (!network) - return -ENODEV; + return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "No network found for '%s'", ifname); return free_and_strdup(&network->hostname, hostname); } static int network_set_mtu(Context *context, const char *ifname, const char *mtu) { Network *network; + int r; assert(context); assert(ifname); @@ -410,13 +411,18 @@ static int network_set_mtu(Context *context, const char *ifname, const char *mtu network = network_get(context, ifname); if (!network) - return -ENODEV; + return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "No network found for '%s'", ifname); - return parse_mtu(AF_UNSPEC, mtu, &network->mtu); + r = parse_mtu(AF_UNSPEC, mtu, &network->mtu); + if (r < 0) + return log_debug_errno(r, "Invalid MTU '%s' for '%s': %m", mtu, ifname); + + return r; } static int network_set_mac_address(Context *context, const char *ifname, const char *mac) { Network *network; + int r; assert(context); assert(ifname); @@ -424,9 +430,13 @@ static int network_set_mac_address(Context *context, const char *ifname, const c network = network_get(context, ifname); if (!network) - return -ENODEV; + return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "No network found for '%s'", ifname); - return parse_ether_addr(mac, &network->mac); + r = parse_ether_addr(mac, &network->mac); + if (r < 0) + return log_debug_errno(r, "Invalid MAC address '%s' for '%s'", mac, ifname); + + return r; } static int network_set_address(Context *context, const char *ifname, int family, unsigned char prefixlen, @@ -443,7 +453,7 @@ static int network_set_address(Context *context, const char *ifname, int family, network = network_get(context, ifname); if (!network) - return -ENODEV; + return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "No network found for '%s'", ifname); return address_new(network, family, prefixlen, addr, peer, NULL); } @@ -465,7 +475,7 @@ static int network_set_route(Context *context, const char *ifname, int family, u if (!network) { r = network_new(context, ifname, &network); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); } return route_new(network, family, prefixlen, dest, gateway, NULL); @@ -486,13 +496,13 @@ static int network_set_dns(Context *context, const char *ifname, int family, con else r = in_addr_from_string(family, dns, &a); if (r < 0) - return r; + return log_debug_errno(r, "Invalid DNS address '%s' for '%s'", dns, ifname); network = network_get(context, ifname); if (!network) { r = network_new(context, ifname, &network); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); } return strv_extend(&network->dns, dns); @@ -509,7 +519,7 @@ static int network_set_dhcp_use_dns(Context *context, const char *ifname, bool v if (!network) { r = network_new(context, ifname, &network); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); } network->dhcp_use_dns = value; @@ -528,7 +538,7 @@ static int network_set_vlan(Context *context, const char *ifname, const char *va if (!network) { r = network_new(context, ifname, &network); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); } return free_and_strdup(&network->vlan, value); @@ -545,7 +555,7 @@ static int network_set_bridge(Context *context, const char *ifname, const char * if (!network) { r = network_new(context, ifname, &network); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); } return free_and_strdup(&network->bridge, value); @@ -562,7 +572,7 @@ static int network_set_bond(Context *context, const char *ifname, const char *va if (!network) { r = network_new(context, ifname, &network); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); } return free_and_strdup(&network->bond, value); @@ -615,21 +625,21 @@ static int parse_ip_address_one(int family, const char **value, union in_addr_un if (family == AF_INET6) { if (p[0] != '[') - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv6 address '%s'", p); q = strchr(p + 1, ']'); if (!q) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv6 address '%s'", p); if (q[1] != ':') - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv6 address '%s'", p); buf = strndupa_safe(p + 1, q - p - 1); p = q + 2; } else { q = strchr(p, ':'); if (!q) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv4 address '%s'", p); buf = strndupa_safe(p, q - p); p = q + 1; @@ -637,7 +647,7 @@ static int parse_ip_address_one(int family, const char **value, union in_addr_un r = in_addr_from_string(family, buf, ret); if (r < 0) - return r; + return log_debug_errno(r, "Invalid IP address '%s': %m", buf); *value = p; return 1; @@ -657,7 +667,7 @@ static int parse_netmask_or_prefixlen(int family, const char **value, unsigned c if (r > 0) { if (family == AF_INET6) /* TODO: Not supported yet. */ - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "IPv6 prefix length is not supported yet"); *ret = in4_addr_netmask_to_prefixlen(&netmask.in); } else if (r == 0) @@ -665,12 +675,12 @@ static int parse_netmask_or_prefixlen(int family, const char **value, unsigned c else { p = strchr(*value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid netmask or prefix length '%s'", *value); q = strndupa_safe(*value, p - *value); r = safe_atou8(q, ret); if (r < 0) - return r; + return log_debug_errno(r, "Invalid netmask or prefix length '%s': %m", q); *value = p + 1; } @@ -693,10 +703,8 @@ static int parse_ip_dns_address_one(Context *context, const char *ifname, const if (p[0] == '[') { q = strchr(p + 1, ']'); - if (!q) - return -EINVAL; - if (!IN_SET(q[1], ':', '\0')) - return -EINVAL; + if (!q || !IN_SET(q[1], ':', '\0')) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IP DNS address '%s'", p); buf = strndupa_safe(p + 1, q - p - 1); p = q + 1; @@ -749,12 +757,12 @@ static int parse_cmdline_ip_address(Context *context, int family, const char *va /* hostname */ p = strchr(value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IP address '%s'", value); if (p != value) { hostname = strndupa_safe(value, p - value); if (!hostname_is_valid(hostname, 0)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid hostname '%s'", hostname); } value = p + 1; @@ -762,7 +770,7 @@ static int parse_cmdline_ip_address(Context *context, int family, const char *va /* ifname */ p = strchr(value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IP address '%s'", value); ifname = strndupa_safe(value, p - value); @@ -813,7 +821,7 @@ static int parse_cmdline_ip_address(Context *context, int family, const char *va /* refuse unexpected trailing strings */ if (!isempty(value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IP address '%s'", value); return 0; } @@ -829,7 +837,7 @@ static int parse_cmdline_ip_interface(Context *context, const char *value) { p = strchr(value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IP address '%s'", value); ifname = strndupa_safe(value, p - value); @@ -858,7 +866,7 @@ static int parse_cmdline_ip(Context *context, const char *key, const char *value assert(key); if (proc_cmdline_value_missing(key, value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); p = strchr(value, ':'); if (!p) @@ -887,15 +895,15 @@ static int parse_cmdline_rd_route(Context *context, const char *key, const char /* rd.route=<net>/<netmask>:<gateway>[:<interface>] */ if (proc_cmdline_value_missing(key, value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); if (value[0] == '[') { p = strchr(value, ']'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv6 address '%s'", value); if (p[1] != ':') - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv6 address '%s'", value); buf = strndupa_safe(value + 1, p - value - 1); value = p + 2; @@ -903,7 +911,7 @@ static int parse_cmdline_rd_route(Context *context, const char *key, const char } else { p = strchr(value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv4 address '%s'", value); buf = strndupa_safe(value, p - value); value = p + 1; @@ -912,7 +920,7 @@ static int parse_cmdline_rd_route(Context *context, const char *key, const char r = in_addr_prefix_from_string(buf, family, &addr, &prefixlen); if (r < 0) - return r; + return log_debug_errno(r, "Invalid IP address '%s': %m", buf); p = strchr(value, ':'); if (!p) @@ -930,7 +938,7 @@ static int parse_cmdline_nameserver(Context *context, const char *key, const cha assert(key); if (proc_cmdline_value_missing(key, value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); return network_set_dns(context, "", AF_UNSPEC, value); } @@ -946,7 +954,7 @@ static int parse_cmdline_rd_peerdns(Context *context, const char *key, const cha r = parse_boolean(value); if (r < 0) - return r; + return log_debug_errno(r, "Invalid boolean value '%s'", value); return network_set_dhcp_use_dns(context, "", r); } @@ -960,11 +968,11 @@ static int parse_cmdline_vlan(Context *context, const char *key, const char *val assert(key); if (proc_cmdline_value_missing(key, value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); p = strchr(value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid VLAN value '%s'", value); name = strndupa_safe(value, p - value); @@ -972,7 +980,7 @@ static int parse_cmdline_vlan(Context *context, const char *key, const char *val if (!netdev) { r = netdev_new(context, "vlan", name, &netdev); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create VLAN device for '%s': %m", name); } return network_set_vlan(context, p + 1, name); @@ -987,11 +995,11 @@ static int parse_cmdline_bridge(Context *context, const char *key, const char *v assert(key); if (proc_cmdline_value_missing(key, value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); p = strchr(value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bridge value '%s'", value); name = strndupa_safe(value, p - value); @@ -999,19 +1007,21 @@ static int parse_cmdline_bridge(Context *context, const char *key, const char *v if (!netdev) { r = netdev_new(context, "bridge", name, &netdev); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create bridge device for '%s': %m", name); } p++; if (isempty(p)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing slave interfaces for bridge '%s'", name); for (;;) { _cleanup_free_ char *word = NULL; r = extract_first_word(&p, &word, ",", 0); - if (r <= 0) - return r; + if (r < 0) + return log_debug_errno(r, "Failed to parse slave interfaces for bridge '%s'", name); + if (r == 0) + return 0; r = network_set_bridge(context, word, name); if (r < 0) @@ -1028,11 +1038,11 @@ static int parse_cmdline_bond(Context *context, const char *key, const char *val assert(key); if (proc_cmdline_value_missing(key, value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); p = strchr(value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bond value '%s'", value); name = strndupa_safe(value, p - value); @@ -1040,7 +1050,7 @@ static int parse_cmdline_bond(Context *context, const char *key, const char *val if (!netdev) { r = netdev_new(context, "bond", name, &netdev); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create bond device for '%s': %m", name); } value = p + 1; @@ -1051,7 +1061,7 @@ static int parse_cmdline_bond(Context *context, const char *key, const char *val slaves = strndupa_safe(value, p - value); if (isempty(slaves)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing slave interfaces for bond '%s'", name); for (const char *q = slaves; ; ) { _cleanup_free_ char *word = NULL; @@ -1060,7 +1070,7 @@ static int parse_cmdline_bond(Context *context, const char *key, const char *val if (r == 0) break; if (r < 0) - return r; + return log_debug_errno(r, "Failed to parse slave interfaces for bond '%s'", name); r = network_set_bond(context, word, name); if (r < 0) @@ -1090,19 +1100,23 @@ static int parse_cmdline_ifname(Context *context, const char *key, const char *v /* ifname=<interface>:<MAC> */ if (proc_cmdline_value_missing(key, value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); p = strchr(value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid ifname value '%s'", value); name = strndupa_safe(value, p - value); r = parse_hw_addr(p + 1, &mac); if (r < 0) - return r; + return log_debug_errno(r, "Invalid MAC address '%s' for '%s'", p + 1, name); - return link_new(context, name, &mac, NULL); + r = link_new(context, name, &mac, NULL); + if (r < 0) + return log_debug_errno(r, "Failed to create link for '%s': %m", name); + + return 0; } static int parse_cmdline_ifname_policy(Context *context, const char *key, const char *value) { @@ -1114,10 +1128,10 @@ static int parse_cmdline_ifname_policy(Context *context, const char *key, const assert(context); assert(key); - /* net.ifname-policy=policy1[,policy2,...][,<MAC>] */ + /* net.ifname_policy=policy1[,policy2,...][,<MAC>] */ if (proc_cmdline_value_missing(key, value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); for (const char *q = value; ; ) { _cleanup_free_ char *word = NULL; @@ -1127,19 +1141,19 @@ static int parse_cmdline_ifname_policy(Context *context, const char *key, const if (r == 0) break; if (r < 0) - return r; + return log_debug_errno(r, "Failed to parse ifname policy '%s'", value); p = name_policy_from_string(word); if (p < 0) { r = parse_hw_addr(word, &mac); if (r < 0) - return r; + return log_debug_errno(r, "Invalid MAC address '%s'", word); if (hw_addr_is_null(&mac)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "MAC address is not set"); if (!isempty(q)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected trailing string '%s' in ifname policy '%s'", q, value); break; } @@ -1147,20 +1161,20 @@ static int parse_cmdline_ifname_policy(Context *context, const char *key, const if (alternative_names_policy_from_string(word) >= 0) { r = strv_extend(&alt_policies, word); if (r < 0) - return r; + return log_oom_debug(); } r = strv_consume(&policies, TAKE_PTR(word)); if (r < 0) - return r; + return log_oom_debug(); } if (strv_isempty(policies)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "No ifname policy specified"); r = link_new(context, NULL, &mac, &link); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create link: %m"); link->policies = TAKE_PTR(policies); link->alt_policies = TAKE_PTR(alt_policies); @@ -1172,23 +1186,23 @@ int parse_cmdline_item(const char *key, const char *value, void *data) { assert(key); - if (streq(key, "ip")) + if (proc_cmdline_key_streq(key, "ip")) return parse_cmdline_ip(context, key, value); - if (streq(key, "rd.route")) + if (proc_cmdline_key_streq(key, "rd.route")) return parse_cmdline_rd_route(context, key, value); - if (streq(key, "nameserver")) + if (proc_cmdline_key_streq(key, "nameserver")) return parse_cmdline_nameserver(context, key, value); - if (streq(key, "rd.peerdns")) + if (proc_cmdline_key_streq(key, "rd.peerdns")) return parse_cmdline_rd_peerdns(context, key, value); - if (streq(key, "vlan")) + if (proc_cmdline_key_streq(key, "vlan")) return parse_cmdline_vlan(context, key, value); - if (streq(key, "bridge")) + if (proc_cmdline_key_streq(key, "bridge")) return parse_cmdline_bridge(context, key, value); - if (streq(key, "bond")) + if (proc_cmdline_key_streq(key, "bond")) return parse_cmdline_bond(context, key, value); - if (streq(key, "ifname")) + if (proc_cmdline_key_streq(key, "ifname")) return parse_cmdline_ifname(context, key, value); - if (streq(key, "net.ifname-policy")) + if (proc_cmdline_key_streq(key, "net.ifname_policy")) return parse_cmdline_ifname_policy(context, key, value); return 0; @@ -1220,12 +1234,12 @@ int context_merge_networks(Context *context) { r = strv_extend_strv(&network->dns, all->dns, false); if (r < 0) - return r; + return log_oom_debug(); LIST_FOREACH(routes, route, all->routes) { r = route_new(network, route->family, route->prefixlen, &route->dest, &route->gateway, NULL); if (r < 0) - return r; + return log_debug_errno(r, "Failed to copy route: %m"); } } @@ -1392,7 +1406,7 @@ int network_format(Network *network, char **ret) { f = memstream_init(&m); if (!f) - return -ENOMEM; + return log_oom_debug(); network_dump(network, f); @@ -1408,7 +1422,7 @@ int netdev_format(NetDev *netdev, char **ret) { f = memstream_init(&m); if (!f) - return -ENOMEM; + return log_oom_debug(); netdev_dump(netdev, f); @@ -1424,7 +1438,7 @@ int link_format(Link *link, char **ret) { f = memstream_init(&m); if (!f) - return -ENOMEM; + return log_oom_debug(); link_dump(link, f); diff --git a/src/network/meson.build b/src/network/meson.build index 5c05eba..a983dff 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -47,6 +47,7 @@ sources = files( 'networkd-dhcp4.c', 'networkd-dhcp6-bus.c', 'networkd-dhcp6.c', + 'networkd-dns.c', 'networkd-ipv4acd.c', 'networkd-ipv4ll.c', 'networkd-ipv6-proxy-ndp.c', @@ -56,18 +57,22 @@ sources = files( 'networkd-link.c', 'networkd-lldp-rx.c', 'networkd-lldp-tx.c', - 'networkd-manager-bus.c', 'networkd-manager.c', + 'networkd-manager-bus.c', + 'networkd-manager-varlink.c', 'networkd-ndisc.c', 'networkd-neighbor.c', 'networkd-netlabel.c', 'networkd-network-bus.c', 'networkd-network.c', 'networkd-nexthop.c', + 'networkd-ntp.c', 'networkd-queue.c', 'networkd-radv.c', - 'networkd-route-util.c', 'networkd-route.c', + 'networkd-route-metric.c', + 'networkd-route-nexthop.c', + 'networkd-route-util.c', 'networkd-routing-policy-rule.c', 'networkd-setlink.c', 'networkd-speed-meter.c', @@ -109,7 +114,10 @@ systemd_networkd_wait_online_sources = files( 'wait-online/wait-online.c', ) -networkctl_sources = files('networkctl.c') +networkctl_sources = files( + 'networkctl.c', + 'networkctl-config-file.c' +) network_generator_sources = files( 'generator/main.c', @@ -141,8 +149,7 @@ if get_option('link-networkd-shared') networkd_link_with = [libshared] else networkd_link_with = [libsystemd_static, - libshared_static, - libbasic_gcrypt] + libshared_static] endif network_includes = [libsystemd_network_includes, include_directories(['.', 'netdev', 'tc'])] diff --git a/src/network/netdev/bond.c b/src/network/netdev/bond.c index 4d75a0d..52a7f12 100644 --- a/src/network/netdev/bond.c +++ b/src/network/netdev/bond.c @@ -88,6 +88,12 @@ static int netdev_bond_fill_message_create(NetDev *netdev, Link *link, sd_netlin return r; } + if (b->peer_notify_delay != 0) { + r = sd_netlink_message_append_u32(m, IFLA_BOND_PEER_NOTIF_DELAY, b->peer_notify_delay / USEC_PER_MSEC); + if (r < 0) + return r; + } + if (b->downdelay != 0) { r = sd_netlink_message_append_u32(m, IFLA_BOND_DOWNDELAY, b->downdelay / USEC_PER_MSEC); if (r < 0) @@ -198,6 +204,12 @@ static int netdev_bond_fill_message_create(NetDev *netdev, Link *link, sd_netlin return r; } + if (b->arp_missed_max > 0) { + r = sd_netlink_message_append_u8(m, IFLA_BOND_MISSED_MAX, b->arp_missed_max); + if (r < 0) + return r; + } + if (b->arp_interval > 0 && !ordered_set_isempty(b->arp_ip_targets)) { void *val; int n = 0; diff --git a/src/network/netdev/bond.h b/src/network/netdev/bond.h index e4b0a0d..ea94001 100644 --- a/src/network/netdev/bond.h +++ b/src/network/netdev/bond.h @@ -34,11 +34,14 @@ typedef struct Bond { uint16_t ad_user_port_key; struct ether_addr ad_actor_system; + uint8_t arp_missed_max; + usec_t miimon; usec_t updelay; usec_t downdelay; usec_t arp_interval; usec_t lp_interval; + usec_t peer_notify_delay; OrderedSet *arp_ip_targets; } Bond; diff --git a/src/network/netdev/bridge.c b/src/network/netdev/bridge.c index 3e394ed..d426c0c 100644 --- a/src/network/netdev/bridge.c +++ b/src/network/netdev/bridge.c @@ -1,9 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> -#include <netinet/in.h> #include <linux/if_arp.h> #include <linux/if_bridge.h> +#include <netinet/in.h> #include "bridge.h" #include "netlink-util.h" diff --git a/src/network/netdev/fou-tunnel.c b/src/network/netdev/fou-tunnel.c index 3bf41a8..bddee5e 100644 --- a/src/network/netdev/fou-tunnel.c +++ b/src/network/netdev/fou-tunnel.c @@ -1,7 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include <linux/fou.h> +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> +#include <linux/fou.h> #include <netinet/in.h> #include <linux/ip.h> diff --git a/src/network/netdev/geneve.c b/src/network/netdev/geneve.c index bc655ec..22c2b00 100644 --- a/src/network/netdev/geneve.c +++ b/src/network/netdev/geneve.c @@ -1,8 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> -#include <netinet/in.h> #include <linux/if_arp.h> +#include <netinet/in.h> #include "alloc-util.h" #include "conf-parser.h" diff --git a/src/network/netdev/ipvlan.c b/src/network/netdev/ipvlan.c index 05d5d01..51ae643 100644 --- a/src/network/netdev/ipvlan.c +++ b/src/network/netdev/ipvlan.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> #include <netinet/in.h> #include <linux/if_arp.h> diff --git a/src/network/netdev/macsec.c b/src/network/netdev/macsec.c index 17d6ace..4b9f19c 100644 --- a/src/network/netdev/macsec.c +++ b/src/network/netdev/macsec.c @@ -18,6 +18,7 @@ #include "socket-util.h" #include "string-table.h" #include "string-util.h" +#include "unaligned.h" static void security_association_clear(SecurityAssociation *sa) { if (!sa) @@ -711,7 +712,7 @@ int config_parse_macsec_key( dest = a ? &a->sa : &b->sa; - r = unhexmem_full(rvalue, strlen(rvalue), true, &p, &l); + r = unhexmem_full(rvalue, SIZE_MAX, /* secure = */ true, &p, &l); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse key. Ignoring assignment: %m"); return 0; @@ -819,7 +820,7 @@ int config_parse_macsec_key_id( if (r < 0) return log_oom(); - r = unhexmem(rvalue, strlen(rvalue), &p, &l); + r = unhexmem(rvalue, &p, &l); if (r == -ENOMEM) return log_oom(); if (r < 0) { diff --git a/src/network/netdev/macvlan.c b/src/network/netdev/macvlan.c index 203807e..21933d3 100644 --- a/src/network/netdev/macvlan.c +++ b/src/network/netdev/macvlan.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> #include <netinet/in.h> #include <linux/if_arp.h> @@ -10,6 +11,11 @@ #include "networkd-network.h" #include "parse-util.h" +typedef enum BCQueueThreshold { + BC_QUEUE_THRESHOLD_UNDEF = INT32_MIN, + BC_QUEUE_THRESHOLD_DISABLE = -1, +} BCQueueThreshold; + DEFINE_CONFIG_PARSE_ENUM(config_parse_macvlan_mode, macvlan_mode, MacVlanMode, "Failed to parse macvlan mode"); static int netdev_macvlan_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *req) { @@ -62,6 +68,12 @@ static int netdev_macvlan_fill_message_create(NetDev *netdev, Link *link, sd_net return r; } + if (m->bc_queue_threshold != BC_QUEUE_THRESHOLD_UNDEF) { + r = sd_netlink_message_append_s32(req, IFLA_MACVLAN_BC_CUTOFF, m->bc_queue_threshold); + if (r < 0) + return r; + } + return 0; } @@ -96,6 +108,53 @@ int config_parse_macvlan_broadcast_queue_size( &m->bc_queue_length); } +int config_parse_macvlan_broadcast_queue_threshold( + 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) { + + assert(filename); + assert(lvalue); + assert(rvalue); + + int32_t v, *threshold = ASSERT_PTR(data); + int r; + + if (isempty(rvalue)) { + *threshold = BC_QUEUE_THRESHOLD_UNDEF; + return 0; + } + + if (streq(rvalue, "no")) { + *threshold = BC_QUEUE_THRESHOLD_DISABLE; + return 0; + } + + r = safe_atoi32(rvalue, &v); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + if (v < 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid %s= value specified, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + *threshold = v; + return 0; +} + static void macvlan_done(NetDev *netdev) { MacVlan *m = ASSERT_PTR(netdev)->kind == NETDEV_KIND_MACVLAN ? MACVLAN(netdev) : MACVTAP(netdev); @@ -107,6 +166,7 @@ static void macvlan_init(NetDev *netdev) { m->mode = _NETDEV_MACVLAN_MODE_INVALID; m->bc_queue_length = UINT32_MAX; + m->bc_queue_threshold = BC_QUEUE_THRESHOLD_UNDEF; } const NetDevVTable macvtap_vtable = { diff --git a/src/network/netdev/macvlan.h b/src/network/netdev/macvlan.h index c45fc4f..76b53a6 100644 --- a/src/network/netdev/macvlan.h +++ b/src/network/netdev/macvlan.h @@ -14,6 +14,7 @@ struct MacVlan { Set *match_source_mac; uint32_t bc_queue_length; + int32_t bc_queue_threshold; }; DEFINE_NETDEV_CAST(MACVLAN, MacVlan); @@ -23,3 +24,4 @@ extern const NetDevVTable macvtap_vtable; CONFIG_PARSER_PROTOTYPE(config_parse_macvlan_mode); CONFIG_PARSER_PROTOTYPE(config_parse_macvlan_broadcast_queue_size); +CONFIG_PARSER_PROTOTYPE(config_parse_macvlan_broadcast_queue_threshold); diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf index d5aa522..4883a26 100644 --- a/src/network/netdev/netdev-gperf.gperf +++ b/src/network/netdev/netdev-gperf.gperf @@ -64,6 +64,7 @@ VLAN.IngressQOSMaps, config_parse_vlan_qos_maps, MACVLAN.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode) MACVLAN.SourceMACAddress, config_parse_ether_addrs, 0, offsetof(MacVlan, match_source_mac) MACVLAN.BroadcastMulticastQueueLength, config_parse_macvlan_broadcast_queue_size, 0, offsetof(MacVlan, bc_queue_length) +MACVLAN.BroadcastQueueThreshold, config_parse_macvlan_broadcast_queue_threshold, 0, offsetof(MacVlan, bc_queue_threshold) MACVTAP.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode) MACVTAP.SourceMACAddress, config_parse_ether_addrs, 0, offsetof(MacVlan, match_source_mac) IPVLAN.Mode, config_parse_ipvlan_mode, 0, offsetof(IPVlan, mode) @@ -215,9 +216,11 @@ Bond.UpDelaySec, config_parse_sec, Bond.DownDelaySec, config_parse_sec, 0, offsetof(Bond, downdelay) Bond.ARPIntervalSec, config_parse_sec, 0, offsetof(Bond, arp_interval) Bond.LearnPacketIntervalSec, config_parse_sec, 0, offsetof(Bond, lp_interval) +Bond.PeerNotifyDelaySec, config_parse_sec, 0, offsetof(Bond, peer_notify_delay) Bond.AdActorSystemPriority, config_parse_ad_actor_sys_prio, 0, offsetof(Bond, ad_actor_sys_prio) Bond.AdUserPortKey, config_parse_ad_user_port_key, 0, offsetof(Bond, ad_user_port_key) Bond.AdActorSystem, config_parse_ad_actor_system, 0, offsetof(Bond, ad_actor_system) +Bond.ARPMissedMax, config_parse_uint8, 0, offsetof(Bond, arp_missed_max) Bridge.HelloTimeSec, config_parse_sec, 0, offsetof(Bridge, hello_time) Bridge.MaxAgeSec, config_parse_sec, 0, offsetof(Bridge, max_age) Bridge.AgeingTimeSec, config_parse_sec, 0, offsetof(Bridge, ageing_time) diff --git a/src/network/netdev/netdev.c b/src/network/netdev/netdev.c index 57127a8..2b41142 100644 --- a/src/network/netdev/netdev.c +++ b/src/network/netdev/netdev.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> #include <netinet/in.h> #include <linux/if_arp.h> @@ -198,14 +199,6 @@ static void netdev_detach_from_manager(NetDev *netdev) { static NetDev *netdev_free(NetDev *netdev) { assert(netdev); - netdev_detach_from_manager(netdev); - - free(netdev->filename); - - free(netdev->description); - free(netdev->ifname); - condition_free_list(netdev->conditions); - /* Invoke the per-kind done() destructor, but only if the state field is initialized. We conditionalize that * because we parse .netdev files twice: once to determine the kind (with a short, minimal NetDev structure * allocation, with no room for per-kind fields), and once to read the kind's properties (with a full, @@ -218,6 +211,13 @@ static NetDev *netdev_free(NetDev *netdev) { NETDEV_VTABLE(netdev)->done) NETDEV_VTABLE(netdev)->done(netdev); + netdev_detach_from_manager(netdev); + + condition_free_list(netdev->conditions); + free(netdev->filename); + free(netdev->description); + free(netdev->ifname); + return mfree(netdev); } @@ -449,8 +449,7 @@ int netdev_generate_hw_addr( memcpy(a.bytes, &result, a.length); if (ether_addr_is_null(&a.ether) || ether_addr_is_broadcast(&a.ether)) { - log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL), - "Failed to generate persistent MAC address, ignoring: %m"); + log_netdev_warning(netdev, "Failed to generate persistent MAC address, ignoring."); a = HW_ADDR_NULL; goto finalize; } @@ -458,8 +457,7 @@ int netdev_generate_hw_addr( break; case ARPHRD_INFINIBAND: if (result == 0) { - log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL), - "Failed to generate persistent MAC address: %m"); + log_netdev_warning(netdev, "Failed to generate persistent MAC address."); goto finalize; } diff --git a/src/network/netdev/tuntap.c b/src/network/netdev/tuntap.c index 9e909d1..f5be31e 100644 --- a/src/network/netdev/tuntap.c +++ b/src/network/netdev/tuntap.c @@ -1,13 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ +#include <net/if.h> #include <errno.h> #include <fcntl.h> -#include <net/if.h> +#include <linux/if_tun.h> #include <netinet/if_ether.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <sys/types.h> -#include <linux/if_tun.h> #include "alloc-util.h" #include "daemon-util.h" @@ -33,11 +34,6 @@ static TunTap* TUNTAP(NetDev *netdev) { } } -static void *close_fd_ptr(void *p) { - safe_close(PTR_TO_FD(p)); - return NULL; -} - DEFINE_PRIVATE_HASH_OPS_FULL(named_fd_hash_ops, char, string_hash_func, string_compare_func, free, void, close_fd_ptr); int manager_add_tuntap_fd(Manager *m, int fd, const char *name) { diff --git a/src/network/netdev/veth.c b/src/network/netdev/veth.c index e0f5b4e..7855528 100644 --- a/src/network/netdev/veth.c +++ b/src/network/netdev/veth.c @@ -1,10 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include <errno.h> +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> -#include <netinet/in.h> +#include <errno.h> #include <linux/if_arp.h> #include <linux/veth.h> +#include <netinet/in.h> #include "netlink-util.h" #include "veth.h" diff --git a/src/network/netdev/vlan.c b/src/network/netdev/vlan.c index 2390206..60e49a5 100644 --- a/src/network/netdev/vlan.c +++ b/src/network/netdev/vlan.c @@ -1,7 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include <errno.h> +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> +#include <errno.h> #include <linux/if_arp.h> #include <linux/if_vlan.h> @@ -91,8 +92,8 @@ static int netdev_vlan_fill_message_create(NetDev *netdev, Link *link, sd_netlin } static void vlan_qos_maps_hash_func(const struct ifla_vlan_qos_mapping *x, struct siphash *state) { - siphash24_compress(&x->from, sizeof(x->from), state); - siphash24_compress(&x->to, sizeof(x->to), state); + siphash24_compress_typesafe(x->from, state); + siphash24_compress_typesafe(x->to, state); } static int vlan_qos_maps_compare_func(const struct ifla_vlan_qos_mapping *a, const struct ifla_vlan_qos_mapping *b) { diff --git a/src/network/netdev/vrf.c b/src/network/netdev/vrf.c index b75ec2b..24079a7 100644 --- a/src/network/netdev/vrf.c +++ b/src/network/netdev/vrf.c @@ -1,8 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> -#include <netinet/in.h> #include <linux/if_arp.h> +#include <netinet/in.h> #include "vrf.h" diff --git a/src/network/netdev/vxlan.c b/src/network/netdev/vxlan.c index b11fdbb..37f6596 100644 --- a/src/network/netdev/vxlan.c +++ b/src/network/netdev/vxlan.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> #include <netinet/in.h> #include <linux/if_arp.h> @@ -289,7 +290,7 @@ int config_parse_port_range( VxLan *v = ASSERT_PTR(userdata); int r; - r = parse_ip_port_range(rvalue, &v->port_range.low, &v->port_range.high); + r = parse_ip_port_range(rvalue, &v->port_range.low, &v->port_range.high, /* allow_zero = */ false); if (r < 0) log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse VXLAN port range '%s'. Port should be greater than 0 and less than 65535.", rvalue); diff --git a/src/network/netdev/wireguard.c b/src/network/netdev/wireguard.c index 4c7d837..fed1be8 100644 --- a/src/network/netdev/wireguard.c +++ b/src/network/netdev/wireguard.c @@ -3,15 +3,17 @@ Copyright © 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. ***/ -#include <sys/ioctl.h> +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> -#include <netinet/in.h> #include <linux/if_arp.h> #include <linux/ipv6_route.h> +#include <netinet/in.h> +#include <sys/ioctl.h> #include "sd-resolve.h" #include "alloc-util.h" +#include "creds-util.h" #include "dns-domain.h" #include "event-util.h" #include "fd-util.h" @@ -25,6 +27,7 @@ #include "networkd-util.h" #include "parse-helpers.h" #include "parse-util.h" +#include "path-util.h" #include "random-util.h" #include "resolve-private.h" #include "string-util.h" @@ -480,6 +483,8 @@ static int wireguard_decode_key_and_warn( const char *lvalue) { _cleanup_(erase_and_freep) void *key = NULL; + _cleanup_(erase_and_freep) char *cred = NULL; + const char *cred_name; size_t len; int r; @@ -493,10 +498,22 @@ static int wireguard_decode_key_and_warn( return 0; } - if (!streq(lvalue, "PublicKey")) + cred_name = startswith(rvalue, "@"); + if (cred_name) { + r = read_credential(cred_name, (void**) &cred, /* ret_size = */ NULL); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to read credential for wireguard key (%s=), ignoring assignment: %m", + lvalue); + return 0; + } + + } else if (!streq(lvalue, "PublicKey")) (void) warn_file_is_world_accessible(filename, NULL, unit, line); - r = unbase64mem_full(rvalue, strlen(rvalue), true, &key, &len); + r = unbase64mem_full(cred ?: rvalue, SIZE_MAX, /* secure = */ true, &key, &len); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -721,23 +738,39 @@ int config_parse_wireguard_endpoint( void *data, void *userdata) { - assert(filename); - assert(rvalue); - assert(userdata); - Wireguard *w = WIREGUARD(userdata); _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL; - _cleanup_free_ char *host = NULL; - union in_addr_union addr; - const char *p; + _cleanup_free_ char *cred = NULL; + const char *cred_name, *endpoint; uint16_t port; - int family, r; + int r; + + assert(filename); + assert(rvalue); r = wireguard_peer_new_static(w, filename, section_line, &peer); if (r < 0) return log_oom(); - r = in_addr_port_ifindex_name_from_string_auto(rvalue, &family, &addr, &port, NULL, NULL); + cred_name = startswith(rvalue, "@"); + if (cred_name) { + r = read_credential(cred_name, (void**) &cred, /* ret_size = */ NULL); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to read credential for wireguard endpoint, ignoring assignment: %m"); + return 0; + } + + endpoint = strstrip(cred); + } else + endpoint = rvalue; + + union in_addr_union addr; + int family; + + r = in_addr_port_ifindex_name_from_string_auto(endpoint, &family, &addr, &port, NULL, NULL); if (r >= 0) { if (family == AF_INET) peer->endpoint.in = (struct sockaddr_in) { @@ -761,17 +794,23 @@ int config_parse_wireguard_endpoint( return 0; } - p = strrchr(rvalue, ':'); + _cleanup_free_ char *host = NULL; + const char *p; + + p = strrchr(endpoint, ':'); if (!p) { log_syntax(unit, LOG_WARNING, filename, line, 0, "Unable to find port of endpoint, ignoring assignment: %s", - rvalue); + rvalue); /* We log the original assignment instead of resolved credential here, + as the latter might be previously encrypted and we'd expose them in + unprotected logs otherwise. */ return 0; } - host = strndup(rvalue, p - rvalue); + host = strndup(endpoint, p - endpoint); if (!host) return log_oom(); + p++; if (!dns_name_is_valid(host)) { log_syntax(unit, LOG_WARNING, filename, line, 0, @@ -780,7 +819,6 @@ int config_parse_wireguard_endpoint( return 0; } - p++; r = parse_ip_port(p, &port); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, @@ -1078,6 +1116,55 @@ static int wireguard_peer_verify(WireguardPeer *peer) { return 0; } +static int wireguard_read_default_key_cred(NetDev *netdev, const char *filename) { + Wireguard *w = WIREGUARD(netdev); + _cleanup_free_ char *config_name = NULL; + int r; + + assert(filename); + + r = path_extract_filename(filename, &config_name); + if (r < 0) + return log_netdev_error_errno(netdev, r, + "%s: Failed to extract config name, ignoring network device: %m", + filename); + + char *p = endswith(config_name, ".netdev"); + if (!p) + /* Fuzzer run? Then we just ignore this device. */ + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: Invalid netdev config name, refusing default key lookup.", + filename); + *p = '\0'; + + _cleanup_(erase_and_freep) char *cred = NULL; + + r = read_credential(strjoina("network.wireguard.private.", config_name), (void**) &cred, /* ret_size = */ NULL); + if (r < 0) + return log_netdev_error_errno(netdev, r, + "%s: No private key specified and default key isn't available, " + "ignoring network device: %m", + filename); + + _cleanup_(erase_and_freep) void *key = NULL; + size_t len; + + r = unbase64mem_full(cred, SIZE_MAX, /* secure = */ true, &key, &len); + if (r < 0) + return log_netdev_error_errno(netdev, r, + "%s: No private key specified and default key cannot be parsed, " + "ignoring network device: %m", + filename); + if (len != WG_KEY_LEN) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: No private key specified and default key is invalid. " + "Ignoring network device.", + filename); + + memcpy(w->private_key, key, WG_KEY_LEN); + return 0; +} + static int wireguard_verify(NetDev *netdev, const char *filename) { Wireguard *w = WIREGUARD(netdev); int r; @@ -1088,10 +1175,11 @@ static int wireguard_verify(NetDev *netdev, const char *filename) { "Failed to read private key from %s. Ignoring network device.", w->private_key_file); - if (eqzero(w->private_key)) - return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), - "%s: Missing PrivateKey= or PrivateKeyFile=, " - "Ignoring network device.", filename); + if (eqzero(w->private_key)) { + r = wireguard_read_default_key_cred(netdev, filename); + if (r < 0) + return r; + } LIST_FOREACH(peers, peer, w->peers) { if (wireguard_peer_verify(peer) < 0) { @@ -1103,26 +1191,39 @@ static int wireguard_verify(NetDev *netdev, const char *filename) { continue; LIST_FOREACH(ipmasks, ipmask, peer->ipmasks) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; r = route_new(&route); if (r < 0) return log_oom(); + /* For route_section_verify() below. */ + r = config_section_new(peer->section->filename, peer->section->line, &route->section); + if (r < 0) + return log_oom(); + + route->source = NETWORK_CONFIG_SOURCE_STATIC; route->family = ipmask->family; route->dst = ipmask->ip; route->dst_prefixlen = ipmask->cidr; - route->scope = RT_SCOPE_UNIVERSE; route->protocol = RTPROT_STATIC; + route->protocol_set = true; route->table = peer->route_table_set ? peer->route_table : w->route_table; + route->table_set = true; route->priority = peer->route_priority_set ? peer->route_priority : w->route_priority; - if (route->priority == 0 && route->family == AF_INET6) - route->priority = IP6_RT_PRIO_USER; - route->source = NETWORK_CONFIG_SOURCE_STATIC; + route->priority_set = true; - r = set_ensure_consume(&w->routes, &route_hash_ops, TAKE_PTR(route)); + if (route_section_verify(route) < 0) + continue; + + r = set_ensure_put(&w->routes, &route_hash_ops, route); if (r < 0) return log_oom(); + if (r == 0) + continue; + + route->wireguard = w; + TAKE_PTR(route); } } diff --git a/src/network/networkctl-config-file.c b/src/network/networkctl-config-file.c new file mode 100644 index 0000000..216e9d4 --- /dev/null +++ b/src/network/networkctl-config-file.c @@ -0,0 +1,632 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "sd-daemon.h" +#include "sd-device.h" +#include "sd-netlink.h" +#include "sd-network.h" + +#include "bus-error.h" +#include "bus-locator.h" +#include "bus-util.h" +#include "bus-wait-for-jobs.h" +#include "conf-files.h" +#include "edit-util.h" +#include "mkdir-label.h" +#include "netlink-util.h" +#include "networkctl.h" +#include "networkctl-config-file.h" +#include "pager.h" +#include "path-lookup.h" +#include "path-util.h" +#include "pretty-print.h" +#include "selinux-util.h" +#include "strv.h" +#include "virt.h" + +typedef enum ReloadFlags { + RELOAD_NETWORKD = 1 << 0, + RELOAD_UDEVD = 1 << 1, +} ReloadFlags; + +static int get_config_files_by_name( + const char *name, + bool allow_masked, + char **ret_path, + char ***ret_dropins) { + + _cleanup_free_ char *path = NULL; + int r; + + assert(name); + assert(ret_path); + + STRV_FOREACH(i, NETWORK_DIRS) { + _cleanup_free_ char *p = NULL; + + p = path_join(*i, name); + if (!p) + return -ENOMEM; + + r = RET_NERRNO(access(p, F_OK)); + if (r >= 0) { + if (!allow_masked) { + r = null_or_empty_path(p); + if (r < 0) + return log_debug_errno(r, + "Failed to check if network config '%s' is masked: %m", + name); + if (r > 0) + return -ERFKILL; + } + + path = TAKE_PTR(p); + break; + } + + if (r != -ENOENT) + log_debug_errno(r, "Failed to determine whether '%s' exists, ignoring: %m", p); + } + + if (!path) + return -ENOENT; + + if (ret_dropins) { + _cleanup_free_ char *dropin_dirname = NULL; + + dropin_dirname = strjoin(name, ".d"); + if (!dropin_dirname) + return -ENOMEM; + + r = conf_files_list_dropins(ret_dropins, dropin_dirname, /* root = */ NULL, NETWORK_DIRS); + if (r < 0) + return r; + } + + *ret_path = TAKE_PTR(path); + + return 0; +} + +static int get_dropin_by_name( + const char *name, + char * const *dropins, + char **ret) { + + assert(name); + assert(ret); + + STRV_FOREACH(i, dropins) + if (path_equal_filename(*i, name)) { + _cleanup_free_ char *d = NULL; + + d = strdup(*i); + if (!d) + return -ENOMEM; + + *ret = TAKE_PTR(d); + return 1; + } + + *ret = NULL; + return 0; +} + +static int get_network_files_by_link( + sd_netlink **rtnl, + const char *link, + char **ret_path, + char ***ret_dropins) { + + _cleanup_strv_free_ char **dropins = NULL; + _cleanup_free_ char *path = NULL; + int r, ifindex; + + assert(rtnl); + assert(link); + assert(ret_path); + assert(ret_dropins); + + ifindex = rtnl_resolve_interface_or_warn(rtnl, link); + if (ifindex < 0) + return ifindex; + + r = sd_network_link_get_network_file(ifindex, &path); + if (r == -ENODATA) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), + "Link '%s' has no associated network file.", link); + if (r < 0) + return log_error_errno(r, "Failed to get network file for link '%s': %m", link); + + r = sd_network_link_get_network_file_dropins(ifindex, &dropins); + if (r < 0 && r != -ENODATA) + return log_error_errno(r, "Failed to get network drop-ins for link '%s': %m", link); + + *ret_path = TAKE_PTR(path); + *ret_dropins = TAKE_PTR(dropins); + + return 0; +} + +static int get_link_files_by_link(const char *link, char **ret_path, char ***ret_dropins) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + _cleanup_strv_free_ char **dropins_split = NULL; + _cleanup_free_ char *p = NULL; + const char *path, *dropins; + int r; + + assert(link); + assert(ret_path); + assert(ret_dropins); + + r = sd_device_new_from_ifname(&device, link); + if (r < 0) + return log_error_errno(r, "Failed to create sd-device object for link '%s': %m", link); + + r = sd_device_get_property_value(device, "ID_NET_LINK_FILE", &path); + if (r == -ENOENT) + return log_error_errno(r, "Link '%s' has no associated link file.", link); + if (r < 0) + return log_error_errno(r, "Failed to get link file for link '%s': %m", link); + + r = sd_device_get_property_value(device, "ID_NET_LINK_FILE_DROPINS", &dropins); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to get link drop-ins for link '%s': %m", link); + if (r >= 0) { + r = strv_split_full(&dropins_split, dropins, ":", EXTRACT_CUNESCAPE); + if (r < 0) + return log_error_errno(r, "Failed to parse link drop-ins for link '%s': %m", link); + } + + p = strdup(path); + if (!p) + return log_oom(); + + *ret_path = TAKE_PTR(p); + *ret_dropins = TAKE_PTR(dropins_split); + + return 0; +} + +static int get_config_files_by_link_config( + const char *link_config, + sd_netlink **rtnl, + char **ret_path, + char ***ret_dropins, + ReloadFlags *ret_reload) { + + _cleanup_strv_free_ char **dropins = NULL, **link_config_split = NULL; + _cleanup_free_ char *path = NULL; + const char *ifname, *type; + ReloadFlags reload; + size_t n; + int r; + + assert(link_config); + assert(rtnl); + assert(ret_path); + assert(ret_dropins); + + link_config_split = strv_split(link_config, ":"); + if (!link_config_split) + return log_oom(); + + n = strv_length(link_config_split); + if (n == 0 || isempty(link_config_split[0])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No link name is given."); + if (n > 2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid link config '%s'.", link_config); + + ifname = link_config_split[0]; + type = n == 2 ? link_config_split[1] : "network"; + + if (streq(type, "network")) { + if (!networkd_is_running()) + return log_error_errno(SYNTHETIC_ERRNO(ESRCH), + "Cannot get network file for link if systemd-networkd is not running."); + + r = get_network_files_by_link(rtnl, ifname, &path, &dropins); + if (r < 0) + return r; + + reload = RELOAD_NETWORKD; + } else if (streq(type, "link")) { + r = get_link_files_by_link(ifname, &path, &dropins); + if (r < 0) + return r; + + reload = RELOAD_UDEVD; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid config type '%s' for link '%s'.", type, ifname); + + *ret_path = TAKE_PTR(path); + *ret_dropins = TAKE_PTR(dropins); + + if (ret_reload) + *ret_reload = reload; + + return 0; +} + +static int add_config_to_edit( + EditFileContext *context, + const char *path, + char * const *dropins) { + + _cleanup_free_ char *new_path = NULL, *dropin_path = NULL, *old_dropin = NULL; + _cleanup_strv_free_ char **comment_paths = NULL; + int r; + + assert(context); + assert(path); + + /* If we're supposed to edit main config file in /run/, but a config with the same name is present + * under /etc/, we bail out since the one in /etc/ always overrides that in /run/. */ + if (arg_runtime && !arg_drop_in && path_startswith(path, "/etc")) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "Cannot edit runtime config file: overridden by %s", path); + + if (path_startswith(path, "/usr") || arg_runtime != !!path_startswith(path, "/run")) { + _cleanup_free_ char *name = NULL; + + r = path_extract_filename(path, &name); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from '%s': %m", path); + + new_path = path_join(NETWORK_DIRS[arg_runtime ? 1 : 0], name); + if (!new_path) + return log_oom(); + } + + if (!arg_drop_in) + return edit_files_add(context, new_path ?: path, path, NULL); + + bool need_new_dropin; + + r = get_dropin_by_name(arg_drop_in, dropins, &old_dropin); + if (r < 0) + return log_error_errno(r, "Failed to acquire drop-in '%s': %m", arg_drop_in); + if (r > 0) { + /* See the explanation above */ + if (arg_runtime && path_startswith(old_dropin, "/etc")) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "Cannot edit runtime config file: overridden by %s", old_dropin); + + need_new_dropin = path_startswith(old_dropin, "/usr") || arg_runtime != !!path_startswith(old_dropin, "/run"); + } else + need_new_dropin = true; + + if (!need_new_dropin) + /* An existing drop-in is found in the correct scope. Let's edit it directly. */ + dropin_path = TAKE_PTR(old_dropin); + else { + /* No drop-in was found or an existing drop-in is in a different scope. Let's create a new + * drop-in file. */ + dropin_path = strjoin(new_path ?: path, ".d/", arg_drop_in); + if (!dropin_path) + return log_oom(); + } + + comment_paths = strv_new(path); + if (!comment_paths) + return log_oom(); + + r = strv_extend_strv(&comment_paths, dropins, /* filter_duplicates = */ false); + if (r < 0) + return log_oom(); + + return edit_files_add(context, dropin_path, old_dropin, comment_paths); +} + +static int udevd_reload(sd_bus *bus) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + const char *job_path; + int r; + + assert(bus); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch jobs: %m"); + + r = bus_call_method(bus, + bus_systemd_mgr, + "ReloadUnit", + &error, + &reply, + "ss", + "systemd-udevd.service", + "replace"); + if (r < 0) + return log_error_errno(r, "Failed to reload systemd-udevd: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "o", &job_path); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, job_path, /* flags = */ 0, NULL); + if (r == -ENOEXEC) { + log_debug("systemd-udevd is not running, skipping reload."); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to reload systemd-udevd: %m"); + + return 1; +} + +static int reload_daemons(ReloadFlags flags) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r, ret = 1; + + if (arg_no_reload) + return 0; + + if (flags == 0) + return 0; + + if (!sd_booted() || running_in_chroot() > 0) { + log_debug("System is not booted with systemd or is running in chroot, skipping reload."); + return 0; + } + + r = sd_bus_open_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect to system bus: %m"); + + if (FLAGS_SET(flags, RELOAD_UDEVD)) + RET_GATHER(ret, udevd_reload(bus)); + + if (FLAGS_SET(flags, RELOAD_NETWORKD)) { + if (networkd_is_running()) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + r = bus_call_method(bus, bus_network_mgr, "Reload", &error, NULL, NULL); + if (r < 0) + RET_GATHER(ret, log_error_errno(r, "Failed to reload systemd-networkd: %s", bus_error_message(&error, r))); + } else + log_debug("systemd-networkd is not running, skipping reload."); + } + + return ret; +} + +int verb_edit(int argc, char *argv[], void *userdata) { + _cleanup_(edit_file_context_done) EditFileContext context = { + .marker_start = DROPIN_MARKER_START, + .marker_end = DROPIN_MARKER_END, + .remove_parent = !!arg_drop_in, + }; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + ReloadFlags reload = 0; + int r; + + if (!on_tty()) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit network config files if not on a tty."); + + r = mac_selinux_init(); + if (r < 0) + return r; + + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_strv_free_ char **dropins = NULL; + _cleanup_free_ char *path = NULL; + const char *link_config; + + link_config = startswith(*name, "@"); + if (link_config) { + ReloadFlags flags; + + r = get_config_files_by_link_config(link_config, &rtnl, &path, &dropins, &flags); + if (r < 0) + return r; + + reload |= flags; + + r = add_config_to_edit(&context, path, dropins); + if (r < 0) + return r; + + continue; + } + + if (ENDSWITH_SET(*name, ".network", ".netdev")) + reload |= RELOAD_NETWORKD; + else if (endswith(*name, ".link")) + reload |= RELOAD_UDEVD; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid network config name '%s'.", *name); + + r = get_config_files_by_name(*name, /* allow_masked = */ false, &path, &dropins); + if (r == -ERFKILL) + return log_error_errno(r, "Network config '%s' is masked.", *name); + if (r == -ENOENT) { + if (arg_drop_in) + return log_error_errno(r, "Cannot find network config '%s'.", *name); + + log_debug("No existing network config '%s' found, creating a new file.", *name); + + path = path_join(NETWORK_DIRS[arg_runtime ? 1 : 0], *name); + if (!path) + return log_oom(); + + r = edit_files_add(&context, path, NULL, NULL); + if (r < 0) + return r; + continue; + } + if (r < 0) + return log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); + + r = add_config_to_edit(&context, path, dropins); + if (r < 0) + return r; + } + + r = do_edit_files_and_install(&context); + if (r < 0) + return r; + + return reload_daemons(reload); +} + +int verb_cat(int argc, char *argv[], void *userdata) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + char **args = strv_skip(argv, 1); + int r, ret = 0; + + pager_open(arg_pager_flags); + + if (strv_isempty(args)) + return conf_files_cat(NULL, "systemd/networkd.conf", CAT_FORMAT_HAS_SECTIONS); + + bool first = true; + STRV_FOREACH(name, args) { + _cleanup_strv_free_ char **dropins = NULL; + _cleanup_free_ char *path = NULL; + const char *link_config; + + link_config = startswith(*name, "@"); + if (link_config) { + r = get_config_files_by_link_config(link_config, &rtnl, &path, &dropins, /* ret_reload = */ NULL); + if (r < 0) + return RET_GATHER(ret, r); + } else { + r = get_config_files_by_name(*name, /* allow_masked = */ false, &path, &dropins); + if (r == -ENOENT) { + RET_GATHER(ret, log_error_errno(r, "Cannot find network config file '%s'.", *name)); + continue; + } + if (r == -ERFKILL) { + RET_GATHER(ret, log_debug_errno(r, "Network config '%s' is masked, ignoring.", *name)); + continue; + } + if (r < 0) { + log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); + return RET_GATHER(ret, r); + } + } + + if (!first) + putchar('\n'); + + r = cat_files(path, dropins, /* flags = */ CAT_FORMAT_HAS_SECTIONS); + if (r < 0) + return RET_GATHER(ret, r); + + first = false; + } + + return ret; +} + +int verb_mask(int argc, char *argv[], void *userdata) { + ReloadFlags flags = 0; + int r; + + r = mac_selinux_init(); + if (r < 0) + return r; + + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_free_ char *config_path = NULL, *symlink_path = NULL; + ReloadFlags reload; + + /* We update the real 'flags' at last, since the operation can be skipped. */ + if (ENDSWITH_SET(*name, ".network", ".netdev")) + reload = RELOAD_NETWORKD; + else if (endswith(*name, ".link")) + reload = RELOAD_UDEVD; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid network config name '%s'.", *name); + + r = get_config_files_by_name(*name, /* allow_masked = */ true, &config_path, /* ret_dropins = */ NULL); + if (r == -ENOENT) + log_warning("No existing network config '%s' found, proceeding anyway.", *name); + else if (r < 0) + return log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); + else if (!path_startswith(config_path, "/usr")) { + r = null_or_empty_path(config_path); + if (r < 0) + return log_error_errno(r, + "Failed to check if '%s' is masked: %m", config_path); + if (r > 0) { + log_debug("%s is already masked, skipping.", config_path); + continue; + } + + /* At this point, we have found a config under mutable dir (/run/ or /etc/), + * so masking through /run/ (--runtime) is not possible. If it's under /etc/, + * then it doesn't work without --runtime either. */ + if (arg_runtime || path_startswith(config_path, "/etc")) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "Cannot mask network config %s: %s exists", + *name, config_path); + } + + symlink_path = path_join(NETWORK_DIRS[arg_runtime ? 1 : 0], *name); + if (!symlink_path) + return log_oom(); + + (void) mkdir_parents_label(symlink_path, 0755); + + if (symlink("/dev/null", symlink_path) < 0) + return log_error_errno(errno, + "Failed to create symlink '%s' to /dev/null: %m", symlink_path); + + flags |= reload; + log_info("Successfully created symlink '%s' to /dev/null.", symlink_path); + } + + return reload_daemons(flags); +} + +int verb_unmask(int argc, char *argv[], void *userdata) { + ReloadFlags flags = 0; + int r; + + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_free_ char *path = NULL; + ReloadFlags reload; + + if (ENDSWITH_SET(*name, ".network", ".netdev")) + reload = RELOAD_NETWORKD; + else if (endswith(*name, ".link")) + reload = RELOAD_UDEVD; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid network config name '%s'.", *name); + + r = get_config_files_by_name(*name, /* allow_masked = */ true, &path, /* ret_dropins = */ NULL); + if (r == -ENOENT) { + log_debug_errno(r, "Network configuration '%s' doesn't exist, skipping.", *name); + continue; + } + if (r < 0) + return log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); + + r = null_or_empty_path(path); + if (r < 0) + return log_error_errno(r, "Failed to check if '%s' is masked: %m", path); + if (r == 0) + continue; + + if (path_startswith(path, "/usr")) + return log_error_errno(r, "Cannot unmask network config under /usr/: %s", path); + + if (unlink(path) < 0) { + if (errno == ENOENT) + continue; + + return log_error_errno(errno, "Failed to remove '%s': %m", path); + } + + flags |= reload; + log_info("Successfully removed masked network config '%s'.", path); + } + + return reload_daemons(flags); +} diff --git a/src/network/networkctl-config-file.h b/src/network/networkctl-config-file.h new file mode 100644 index 0000000..38210a8 --- /dev/null +++ b/src/network/networkctl-config-file.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int verb_edit(int argc, char *argv[], void *userdata); +int verb_cat(int argc, char *argv[], void *userdata); + +int verb_mask(int argc, char *argv[], void *userdata); +int verb_unmask(int argc, char *argv[], void *userdata); diff --git a/src/network/networkctl.c b/src/network/networkctl.c index ec31e8e..a447c39 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -1,9 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ +#include <net/if.h> #include <arpa/inet.h> #include <getopt.h> #include <linux/if_addrlabel.h> -#include <net/if.h> #include <stdbool.h> #include <sys/stat.h> #include <sys/types.h> @@ -15,7 +16,6 @@ #include "sd-device.h" #include "sd-dhcp-client.h" #include "sd-hwdb.h" -#include "sd-lldp-rx.h" #include "sd-netlink.h" #include "sd-network.h" @@ -26,10 +26,7 @@ #include "bus-common-errors.h" #include "bus-error.h" #include "bus-locator.h" -#include "bus-wait-for-jobs.h" -#include "conf-files.h" #include "device-util.h" -#include "edit-util.h" #include "escape.h" #include "ether-addr-util.h" #include "ethtool-util.h" @@ -41,6 +38,7 @@ #include "glob-util.h" #include "hwdb-util.h" #include "ipvlan-util.h" +#include "journal-internal.h" #include "local-addresses.h" #include "locale-util.h" #include "logs-show.h" @@ -51,6 +49,8 @@ #include "netlink-util.h" #include "network-internal.h" #include "network-util.h" +#include "networkctl.h" +#include "networkctl-config-file.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -71,8 +71,8 @@ #include "terminal-util.h" #include "udev-util.h" #include "unit-def.h" +#include "varlink.h" #include "verbs.h" -#include "virt.h" #include "wifi-util.h" /* Kernel defines MODULE_NAME_LEN as 64 - sizeof(unsigned long). So, 64 is enough. */ @@ -81,47 +81,67 @@ /* use 128 kB for receive socket kernel queue, we shouldn't need more here */ #define RCVBUF_SIZE (128*1024) -static PagerFlags arg_pager_flags = 0; -static bool arg_legend = true; -static bool arg_no_reload = false; -static bool arg_all = false; -static bool arg_stats = false; -static bool arg_full = false; -static unsigned arg_lines = 10; -static char *arg_drop_in = NULL; -static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; +PagerFlags arg_pager_flags = 0; +bool arg_legend = true; +bool arg_no_reload = false; +bool arg_all = false; +bool arg_stats = false; +bool arg_full = false; +bool arg_runtime = false; +unsigned arg_lines = 10; +char *arg_drop_in = NULL; +JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; STATIC_DESTRUCTOR_REGISTER(arg_drop_in, freep); -static int check_netns_match(sd_bus *bus) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - struct stat st; +static int varlink_connect_networkd(Varlink **ret_varlink) { + _cleanup_(varlink_flush_close_unrefp) Varlink *vl = NULL; + JsonVariant *reply; uint64_t id; int r; - assert(bus); + r = varlink_connect_address(&vl, "/run/systemd/netif/io.systemd.Network"); + if (r < 0) + return log_error_errno(r, "Failed to connect to network service /run/systemd/netif/io.systemd.Network: %m"); - r = bus_get_property_trivial(bus, bus_network_mgr, "NamespaceId", &error, 't', &id); - if (r < 0) { - log_debug_errno(r, "Failed to query network namespace of networkd, ignoring: %s", bus_error_message(&error, r)); - return 0; - } - if (id == 0) { + (void) varlink_set_description(vl, "varlink-network"); + + r = varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to allow passing file descriptor through varlink: %m"); + + r = varlink_call_and_log(vl, "io.systemd.Network.GetNamespaceId", /* parameters= */ NULL, &reply); + if (r < 0) + return r; + + static const JsonDispatch dispatch_table[] = { + { "NamespaceId", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, 0, JSON_MANDATORY }, + {}, + }; + + r = json_dispatch(reply, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &id); + if (r < 0) + return r; + + if (id == 0) log_debug("systemd-networkd.service not running in a network namespace (?), skipping netns check."); - return 0; - } + else { + struct stat st; - if (stat("/proc/self/ns/net", &st) < 0) - return log_error_errno(errno, "Failed to determine our own network namespace ID: %m"); + if (stat("/proc/self/ns/net", &st) < 0) + return log_error_errno(errno, "Failed to determine our own network namespace ID: %m"); - if (id != st.st_ino) - return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), - "networkctl must be invoked in same network namespace as systemd-networkd.service."); + if (id != st.st_ino) + return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), + "networkctl must be invoked in same network namespace as systemd-networkd.service."); + } + if (ret_varlink) + *ret_varlink = TAKE_PTR(vl); return 0; } -static bool networkd_is_running(void) { +bool networkd_is_running(void) { static int cached = -1; int r; @@ -140,7 +160,7 @@ static bool networkd_is_running(void) { return cached; } -static int acquire_bus(sd_bus **ret) { +int acquire_bus(sd_bus **ret) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -151,7 +171,7 @@ static int acquire_bus(sd_bus **ret) { return log_error_errno(r, "Failed to connect to system bus: %m"); if (networkd_is_running()) { - r = check_netns_match(bus); + r = varlink_connect_networkd(/* ret_varlink = */ NULL); if (r < 0) return r; } else @@ -781,14 +801,14 @@ static void acquire_ether_link_info(int *fd, LinkInfo *link) { static void acquire_wlan_link_info(LinkInfo *link) { _cleanup_(sd_netlink_unrefp) sd_netlink *genl = NULL; - const char *type = NULL; int r, k = 0; assert(link); - if (link->sd_device) - (void) sd_device_get_devtype(link->sd_device, &type); - if (!streq_ptr(type, "wlan")) + if (!link->sd_device) + return; + + if (!device_is_devtype(link->sd_device, "wlan")) return; r = sd_genl_socket_open(&genl); @@ -1053,9 +1073,7 @@ static int get_gateway_description( } if (type != RTM_NEWNEIGH) { - log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Got unexpected netlink message type %u, ignoring", - type); + log_error("Got unexpected netlink message type %u, ignoring.", type); continue; } @@ -1066,7 +1084,7 @@ static int get_gateway_description( } if (fam != family) { - log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Got invalid rtnl family %d, ignoring", fam); + log_error("Got invalid rtnl family %d, ignoring.", fam); continue; } @@ -1096,7 +1114,7 @@ static int get_gateway_description( break; default: - continue; + assert_not_reached(); } if (!in_addr_equal(fam, &gw, gateway)) @@ -1198,7 +1216,7 @@ static int dump_addresses( r = strv_extendf(&buf, "%s%s%s%s%s%s", IN_ADDR_TO_STRING(local->family, &local->address), - dhcp4 ? " (DHCP4 via " : "", + dhcp4 ? " (DHCPv4 via " : "", dhcp4 ? IN4_ADDR_TO_STRING(&server_address) : "", dhcp4 ? ")" : "", ifindex <= 0 ? " on " : "", @@ -1300,96 +1318,96 @@ static int list_address_labels(int argc, char *argv[], void *userdata) { return dump_address_labels(rtnl); } -static int open_lldp_neighbors(int ifindex, FILE **ret) { - _cleanup_fclose_ FILE *f = NULL; - char p[STRLEN("/run/systemd/netif/lldp/") + DECIMAL_STR_MAX(int)]; - - assert(ifindex >= 0); - assert(ret); - - xsprintf(p, "/run/systemd/netif/lldp/%i", ifindex); - - f = fopen(p, "re"); - if (!f) - return -errno; - - *ret = TAKE_PTR(f); - return 0; -} - -static int next_lldp_neighbor(FILE *f, sd_lldp_neighbor **ret) { - _cleanup_free_ void *raw = NULL; - size_t l; - le64_t u; - int r; - - assert(f); - assert(ret); - - l = fread(&u, 1, sizeof(u), f); - if (l == 0 && feof(f)) - return 0; - if (l != sizeof(u)) - return -EBADMSG; - - /* each LLDP packet is at most MTU size, but let's allow up to 4KiB just in case */ - if (le64toh(u) >= 4096) - return -EBADMSG; - - raw = new(uint8_t, le64toh(u)); - if (!raw) - return -ENOMEM; - - if (fread(raw, 1, le64toh(u), f) != le64toh(u)) - return -EBADMSG; - - r = sd_lldp_neighbor_from_raw(ret, raw, le64toh(u)); - if (r < 0) - return r; +typedef struct InterfaceInfo { + int ifindex; + const char *ifname; + char **altnames; + JsonVariant *v; +} InterfaceInfo; - return 1; -} +static void interface_info_done(InterfaceInfo *p) { + if (!p) + return; -static int dump_lldp_neighbors(Table *table, const char *prefix, int ifindex) { + strv_free(p->altnames); + json_variant_unref(p->v); +} + +static const JsonDispatch interface_info_dispatch_table[] = { + { "InterfaceIndex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, offsetof(InterfaceInfo, ifindex), JSON_MANDATORY }, + { "InterfaceName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(InterfaceInfo, ifname), JSON_MANDATORY }, + { "InterfaceAlternativeNames", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(InterfaceInfo, altnames), 0 }, + { "Neighbors", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(InterfaceInfo, v), 0 }, + {}, +}; + +typedef struct LLDPNeighborInfo { + const char *chassis_id; + const char *port_id; + const char *port_description; + const char *system_name; + const char *system_description; + uint16_t capabilities; +} LLDPNeighborInfo; + +static const JsonDispatch lldp_neighbor_dispatch_table[] = { + { "ChassisID", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LLDPNeighborInfo, chassis_id), 0 }, + { "PortID", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LLDPNeighborInfo, port_id), 0 }, + { "PortDescription", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LLDPNeighborInfo, port_description), 0 }, + { "SystemName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LLDPNeighborInfo, system_name), 0 }, + { "SystemDescription", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LLDPNeighborInfo, system_description), 0 }, + { "EnabledCapabilities", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint16, offsetof(LLDPNeighborInfo, capabilities), 0 }, + {}, +}; + +static int dump_lldp_neighbors(Varlink *vl, Table *table, int ifindex) { _cleanup_strv_free_ char **buf = NULL; - _cleanup_fclose_ FILE *f = NULL; + JsonVariant *reply; int r; + assert(vl); assert(table); - assert(prefix); assert(ifindex > 0); - r = open_lldp_neighbors(ifindex, &f); - if (r == -ENOENT) - return 0; + r = varlink_callb_and_log(vl, "io.systemd.Network.GetLLDPNeighbors", &reply, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR_INTEGER("InterfaceIndex", ifindex))); if (r < 0) return r; - for (;;) { - const char *system_name = NULL, *port_id = NULL, *port_description = NULL; - _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; + JsonVariant *i; + JSON_VARIANT_ARRAY_FOREACH(i, json_variant_by_key(reply, "Neighbors")) { + _cleanup_(interface_info_done) InterfaceInfo info = {}; - r = next_lldp_neighbor(f, &n); + r = json_dispatch(i, interface_info_dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &info); if (r < 0) return r; - if (r == 0) - break; - (void) sd_lldp_neighbor_get_system_name(n, &system_name); - (void) sd_lldp_neighbor_get_port_id_as_string(n, &port_id); - (void) sd_lldp_neighbor_get_port_description(n, &port_description); + if (info.ifindex != ifindex) + continue; - r = strv_extendf(&buf, "%s on port %s%s%s%s", - strna(system_name), - strna(port_id), - isempty(port_description) ? "" : " (", - strempty(port_description), - isempty(port_description) ? "" : ")"); - if (r < 0) - return log_oom(); + JsonVariant *neighbor; + JSON_VARIANT_ARRAY_FOREACH(neighbor, info.v) { + LLDPNeighborInfo neighbor_info = {}; + + r = json_dispatch(neighbor, lldp_neighbor_dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &neighbor_info); + if (r < 0) + return r; + + r = strv_extendf(&buf, "%s%s%s%s on port %s%s%s%s", + strna(neighbor_info.system_name), + isempty(neighbor_info.system_description) ? "" : " (", + strempty(neighbor_info.system_description), + isempty(neighbor_info.system_description) ? "" : ")", + strna(neighbor_info.port_id), + isempty(neighbor_info.port_description) ? "" : " (", + strempty(neighbor_info.port_description), + isempty(neighbor_info.port_description) ? "" : ")"); + if (r < 0) + return log_oom(); + } } - return dump_list(table, prefix, buf); + return dump_list(table, "Connected To", buf); } static int dump_dhcp_leases(Table *table, const char *prefix, sd_bus *bus, const LinkInfo *link) { @@ -1447,7 +1465,7 @@ static int dump_dhcp_leases(Table *table, const char *prefix, sd_bus *bus, const if (r < 0) return bus_log_parse_error(r); - r = sd_dhcp_client_id_to_string(client_id, client_id_sz, &id); + r = sd_dhcp_client_id_to_string_from_raw(client_id, client_id_sz, &id); if (r < 0) return bus_log_parse_error(r); @@ -1586,7 +1604,7 @@ static int show_logs(const LinkInfo *info) { if (arg_lines == 0) return 0; - r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); + r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE); if (r < 0) return log_error_errno(r, "Failed to open journal: %m"); @@ -1595,22 +1613,12 @@ static int show_logs(const LinkInfo *info) { return log_error_errno(r, "Failed to add boot matches: %m"); if (info) { - char m1[STRLEN("_KERNEL_DEVICE=n") + DECIMAL_STR_MAX(int)]; - const char *m2, *m3; - - /* kernel */ - xsprintf(m1, "_KERNEL_DEVICE=n%i", info->ifindex); - /* networkd */ - m2 = strjoina("INTERFACE=", info->name); - /* udevd */ - m3 = strjoina("DEVICE=", info->name); - - (void)( - (r = sd_journal_add_match(j, m1, 0)) || + (void) ( + (r = journal_add_matchf(j, "_KERNEL_DEVICE=n%i", info->ifindex)) || /* kernel */ (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m2, 0)) || + (r = journal_add_match_pair(j, "INTERFACE", info->name)) || /* networkd */ (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m3, 0)) + (r = journal_add_match_pair(j, "DEVICE", info->name)) /* udevd */ ); if (r < 0) return log_error_errno(r, "Failed to add link matches: %m"); @@ -1672,6 +1680,7 @@ static int link_status_one( sd_bus *bus, sd_netlink *rtnl, sd_hwdb *hwdb, + Varlink *vl, const LinkInfo *info) { _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **sip = NULL, **search_domains = NULL, @@ -1687,6 +1696,7 @@ static int link_status_one( assert(bus); assert(rtnl); + assert(vl); assert(info); (void) sd_network_link_get_operational_state(info->ifindex, &operational_state); @@ -2260,8 +2270,7 @@ static int link_status_one( } if (lease) { - const void *client_id; - size_t client_id_len; + const sd_dhcp_client_id *client_id; const char *tz; r = sd_dhcp_lease_get_timezone(lease, &tz); @@ -2273,14 +2282,14 @@ static int link_status_one( return table_log_add_error(r); } - r = sd_dhcp_lease_get_client_id(lease, &client_id, &client_id_len); + r = sd_dhcp_lease_get_client_id(lease, &client_id); if (r >= 0) { _cleanup_free_ char *id = NULL; - r = sd_dhcp_client_id_to_string(client_id, client_id_len, &id); + r = sd_dhcp_client_id_to_string(client_id, &id); if (r >= 0) { r = table_add_many(table, - TABLE_FIELD, "DHCP4 Client ID", + TABLE_FIELD, "DHCPv4 Client ID", TABLE_STRING, id); if (r < 0) return table_log_add_error(r); @@ -2291,7 +2300,7 @@ static int link_status_one( r = sd_network_link_get_dhcp6_client_iaid_string(info->ifindex, &iaid); if (r >= 0) { r = table_add_many(table, - TABLE_FIELD, "DHCP6 Client IAID", + TABLE_FIELD, "DHCPv6 Client IAID", TABLE_STRING, iaid); if (r < 0) return table_log_add_error(r); @@ -2300,13 +2309,13 @@ static int link_status_one( r = sd_network_link_get_dhcp6_client_duid_string(info->ifindex, &duid); if (r >= 0) { r = table_add_many(table, - TABLE_FIELD, "DHCP6 Client DUID", + TABLE_FIELD, "DHCPv6 Client DUID", TABLE_STRING, duid); if (r < 0) return table_log_add_error(r); } - r = dump_lldp_neighbors(table, "Connected To", info->ifindex); + r = dump_lldp_neighbors(vl, table, info->ifindex); if (r < 0) return r; @@ -2414,6 +2423,7 @@ static int link_status(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + _cleanup_(varlink_flush_close_unrefp) Varlink *vl = NULL; _cleanup_(link_info_array_freep) LinkInfo *links = NULL; int r, c; @@ -2438,6 +2448,10 @@ static int link_status(int argc, char *argv[], void *userdata) { if (r < 0) log_debug_errno(r, "Failed to open hardware database: %m"); + r = varlink_connect_networkd(&vl); + if (r < 0) + return r; + if (arg_all) c = acquire_link_info(bus, rtnl, NULL, &links); else if (argc <= 1) @@ -2454,7 +2468,7 @@ static int link_status(int argc, char *argv[], void *userdata) { if (!first) putchar('\n'); - RET_GATHER(r, link_status_one(bus, rtnl, hwdb, i)); + RET_GATHER(r, link_status_one(bus, rtnl, hwdb, vl, i)); first = false; } @@ -2462,7 +2476,7 @@ static int link_status(int argc, char *argv[], void *userdata) { return r; } -static char *lldp_capabilities_to_string(uint16_t x) { +static char *lldp_capabilities_to_string(uint64_t x) { static const char characters[] = { 'o', 'p', 'b', 'w', 'r', 't', 'd', 'a', 'c', 's', 'm', }; @@ -2512,30 +2526,94 @@ static void lldp_capabilities_legend(uint16_t x) { puts(""); } +static bool interface_match_pattern(const InterfaceInfo *info, char * const *patterns) { + assert(info); + + if (strv_isempty(patterns)) + return true; + + if (strv_fnmatch(patterns, info->ifname)) + return true; + + char str[DECIMAL_STR_MAX(int)]; + xsprintf(str, "%i", info->ifindex); + if (strv_fnmatch(patterns, str)) + return true; + + STRV_FOREACH(a, info->altnames) + if (strv_fnmatch(patterns, *a)) + return true; + + return false; +} + +static int dump_lldp_neighbors_json(JsonVariant *reply, char * const *patterns) { + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL, *v = NULL; + int r; + + assert(reply); + + if (strv_isempty(patterns)) + return json_variant_dump(reply, arg_json_format_flags, NULL, NULL); + + /* Filter and dump the result. */ + + JsonVariant *i; + JSON_VARIANT_ARRAY_FOREACH(i, json_variant_by_key(reply, "Neighbors")) { + _cleanup_(interface_info_done) InterfaceInfo info = {}; + + r = json_dispatch(i, interface_info_dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &info); + if (r < 0) + return r; + + if (!interface_match_pattern(&info, patterns)) + continue; + + r = json_variant_append_array(&array, i); + if (r < 0) + return log_error_errno(r, "Failed to append json variant to array: %m"); + } + + r = json_build(&v, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_CONDITION(json_variant_is_blank_array(array), "Neighbors", JSON_BUILD_EMPTY_ARRAY), + JSON_BUILD_PAIR_CONDITION(!json_variant_is_blank_array(array), "Neighbors", JSON_BUILD_VARIANT(array)))); + if (r < 0) + return log_error_errno(r, "Failed to build json varinat: %m"); + + return json_variant_dump(v, arg_json_format_flags, NULL, NULL); +} + static int link_lldp_status(int argc, char *argv[], void *userdata) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - _cleanup_(link_info_array_freep) LinkInfo *links = NULL; + _cleanup_(varlink_flush_close_unrefp) Varlink *vl = NULL; _cleanup_(table_unrefp) Table *table = NULL; - int r, c, m = 0; - uint16_t all = 0; + JsonVariant *reply; + uint64_t all = 0; TableCell *cell; + size_t m = 0; + int r; - r = sd_netlink_open(&rtnl); + r = varlink_connect_networkd(&vl); if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); + return r; - c = acquire_link_info(NULL, rtnl, argc > 1 ? argv + 1 : NULL, &links); - if (c < 0) - return c; + r = varlink_call_and_log(vl, "io.systemd.Network.GetLLDPNeighbors", NULL, &reply); + if (r < 0) + return r; + + if (arg_json_format_flags != JSON_FORMAT_OFF) + return dump_lldp_neighbors_json(reply, strv_skip(argv, 1)); pager_open(arg_pager_flags); - table = table_new("link", - "chassis-id", + table = table_new("index", + "link", "system-name", - "caps", + "system-description", + "chassis-id", "port-id", - "port-description"); + "port-description", + "caps"); if (!table) return log_oom(); @@ -2543,53 +2621,46 @@ static int link_lldp_status(int argc, char *argv[], void *userdata) { table_set_width(table, 0); table_set_header(table, arg_legend); + table_set_ersatz_string(table, TABLE_ERSATZ_DASH); + table_set_sort(table, (size_t) 0, (size_t) 2); + table_hide_column_from_display(table, (size_t) 0); - assert_se(cell = table_get_cell(table, 0, 3)); + /* Make the capabilities not truncated */ + assert_se(cell = table_get_cell(table, 0, 7)); table_set_minimum_width(table, cell, 11); - table_set_ersatz_string(table, TABLE_ERSATZ_DASH); - FOREACH_ARRAY(link, links, c) { - _cleanup_fclose_ FILE *f = NULL; + JsonVariant *i; + JSON_VARIANT_ARRAY_FOREACH(i, json_variant_by_key(reply, "Neighbors")) { + _cleanup_(interface_info_done) InterfaceInfo info = {}; - r = open_lldp_neighbors(link->ifindex, &f); - if (r == -ENOENT) - continue; - if (r < 0) { - log_warning_errno(r, "Failed to open LLDP data for %i, ignoring: %m", link->ifindex); + r = json_dispatch(i, interface_info_dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &info); + if (r < 0) + return r; + + if (!interface_match_pattern(&info, strv_skip(argv, 1))) continue; - } - for (;;) { - const char *chassis_id = NULL, *port_id = NULL, *system_name = NULL, *port_description = NULL; - _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; - _cleanup_free_ char *capabilities = NULL; - uint16_t cc; + JsonVariant *neighbor; + JSON_VARIANT_ARRAY_FOREACH(neighbor, info.v) { + LLDPNeighborInfo neighbor_info = {}; - r = next_lldp_neighbor(f, &n); - if (r < 0) { - log_warning_errno(r, "Failed to read neighbor data: %m"); - break; - } - if (r == 0) - break; + r = json_dispatch(neighbor, lldp_neighbor_dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &neighbor_info); + if (r < 0) + return r; - (void) sd_lldp_neighbor_get_chassis_id_as_string(n, &chassis_id); - (void) sd_lldp_neighbor_get_port_id_as_string(n, &port_id); - (void) sd_lldp_neighbor_get_system_name(n, &system_name); - (void) sd_lldp_neighbor_get_port_description(n, &port_description); + all |= neighbor_info.capabilities; - if (sd_lldp_neighbor_get_enabled_capabilities(n, &cc) >= 0) { - capabilities = lldp_capabilities_to_string(cc); - all |= cc; - } + _cleanup_free_ char *cap_str = lldp_capabilities_to_string(neighbor_info.capabilities); r = table_add_many(table, - TABLE_STRING, link->name, - TABLE_STRING, chassis_id, - TABLE_STRING, system_name, - TABLE_STRING, capabilities, - TABLE_STRING, port_id, - TABLE_STRING, port_description); + TABLE_INT, info.ifindex, + TABLE_STRING, info.ifname, + TABLE_STRING, neighbor_info.system_name, + TABLE_STRING, neighbor_info.system_description, + TABLE_STRING, neighbor_info.chassis_id, + TABLE_STRING, neighbor_info.port_id, + TABLE_STRING, neighbor_info.port_description, + TABLE_STRING, cap_str); if (r < 0) return table_log_add_error(r); @@ -2603,7 +2674,7 @@ static int link_lldp_status(int argc, char *argv[], void *userdata) { if (arg_legend) { lldp_capabilities_legend(all); - printf("\n%i neighbors listed.\n", m); + printf("\n%zu neighbor(s) listed.\n", m); } return 0; @@ -2741,23 +2812,25 @@ static int link_renew_one(sd_bus *bus, int index, const char *name) { static int link_renew(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - int index, k = 0, r; + int r; r = acquire_bus(&bus); if (r < 0) return r; + r = 0; + for (int i = 1; i < argc; i++) { + int index; + index = rtnl_resolve_interface_or_warn(&rtnl, argv[i]); if (index < 0) return index; - r = link_renew_one(bus, index, argv[i]); - if (r < 0 && k >= 0) - k = r; + RET_GATHER(r, link_renew_one(bus, index, argv[i])); } - return k; + return r; } static int link_force_renew_one(sd_bus *bus, int index, const char *name) { @@ -2852,455 +2925,36 @@ static int verb_reconfigure(int argc, char *argv[], void *userdata) { return 0; } -typedef enum ReloadFlags { - RELOAD_NETWORKD = 1 << 0, - RELOAD_UDEVD = 1 << 1, -} ReloadFlags; - -static int get_config_files_by_name(const char *name, char **ret_path, char ***ret_dropins) { - _cleanup_free_ char *path = NULL; - int r; - - assert(name); - assert(ret_path); - - STRV_FOREACH(i, NETWORK_DIRS) { - _cleanup_free_ char *p = NULL; - - p = path_join(*i, name); - if (!p) - return -ENOMEM; - - r = RET_NERRNO(access(p, F_OK)); - if (r >= 0) { - path = TAKE_PTR(p); - break; - } - - if (r != -ENOENT) - log_debug_errno(r, "Failed to determine whether '%s' exists, ignoring: %m", p); - } - - if (!path) - return -ENOENT; - - if (ret_dropins) { - _cleanup_free_ char *dropin_dirname = NULL; - - dropin_dirname = strjoin(name, ".d"); - if (!dropin_dirname) - return -ENOMEM; - - r = conf_files_list_dropins(ret_dropins, dropin_dirname, /* root = */ NULL, NETWORK_DIRS); - if (r < 0) - return r; - } - - *ret_path = TAKE_PTR(path); - - return 0; -} - -static int get_dropin_by_name( - const char *name, - char * const *dropins, - char **ret) { - - assert(name); - assert(dropins); - assert(ret); - - STRV_FOREACH(i, dropins) - if (path_equal_filename(*i, name)) { - _cleanup_free_ char *d = NULL; - - d = strdup(*i); - if (!d) - return -ENOMEM; - - *ret = TAKE_PTR(d); - return 1; - } - - *ret = NULL; - return 0; -} - -static int get_network_files_by_link( - sd_netlink **rtnl, - const char *link, - char **ret_path, - char ***ret_dropins) { - - _cleanup_strv_free_ char **dropins = NULL; - _cleanup_free_ char *path = NULL; - int r, ifindex; - - assert(rtnl); - assert(link); - assert(ret_path); - assert(ret_dropins); - - ifindex = rtnl_resolve_interface_or_warn(rtnl, link); - if (ifindex < 0) - return ifindex; - - r = sd_network_link_get_network_file(ifindex, &path); - if (r == -ENODATA) - return log_error_errno(SYNTHETIC_ERRNO(ENOENT), - "Link '%s' has no associated network file.", link); - if (r < 0) - return log_error_errno(r, "Failed to get network file for link '%s': %m", link); - - r = sd_network_link_get_network_file_dropins(ifindex, &dropins); - if (r < 0 && r != -ENODATA) - return log_error_errno(r, "Failed to get network drop-ins for link '%s': %m", link); - - *ret_path = TAKE_PTR(path); - *ret_dropins = TAKE_PTR(dropins); - - return 0; -} - -static int get_link_files_by_link(const char *link, char **ret_path, char ***ret_dropins) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - _cleanup_strv_free_ char **dropins_split = NULL; - _cleanup_free_ char *p = NULL; - const char *path, *dropins; - int r; - - assert(link); - assert(ret_path); - assert(ret_dropins); - - r = sd_device_new_from_ifname(&device, link); - if (r < 0) - return log_error_errno(r, "Failed to create sd-device object for link '%s': %m", link); - - r = sd_device_get_property_value(device, "ID_NET_LINK_FILE", &path); - if (r == -ENOENT) - return log_error_errno(r, "Link '%s' has no associated link file.", link); - if (r < 0) - return log_error_errno(r, "Failed to get link file for link '%s': %m", link); - - r = sd_device_get_property_value(device, "ID_NET_LINK_FILE_DROPINS", &dropins); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "Failed to get link drop-ins for link '%s': %m", link); - if (r >= 0) { - r = strv_split_full(&dropins_split, dropins, ":", EXTRACT_CUNESCAPE); - if (r < 0) - return log_error_errno(r, "Failed to parse link drop-ins for link '%s': %m", link); - } - - p = strdup(path); - if (!p) - return log_oom(); - - *ret_path = TAKE_PTR(p); - *ret_dropins = TAKE_PTR(dropins_split); - - return 0; -} - -static int get_config_files_by_link_config( - const char *link_config, - sd_netlink **rtnl, - char **ret_path, - char ***ret_dropins, - ReloadFlags *ret_reload) { - - _cleanup_strv_free_ char **dropins = NULL, **link_config_split = NULL; - _cleanup_free_ char *path = NULL; - const char *ifname, *type; - ReloadFlags reload; - size_t n; +static int verb_persistent_storage(int argc, char *argv[], void *userdata) { + _cleanup_(varlink_flush_close_unrefp) Varlink *vl = NULL; + bool ready; int r; - assert(link_config); - assert(rtnl); - assert(ret_path); - assert(ret_dropins); - - link_config_split = strv_split(link_config, ":"); - if (!link_config_split) - return log_oom(); - - n = strv_length(link_config_split); - if (n == 0 || isempty(link_config_split[0])) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No link name is given."); - if (n > 2) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid link config '%s'.", link_config); - - ifname = link_config_split[0]; - type = n == 2 ? link_config_split[1] : "network"; - - if (streq(type, "network")) { - if (!networkd_is_running()) - return log_error_errno(SYNTHETIC_ERRNO(ESRCH), - "Cannot get network file for link if systemd-networkd is not running."); - - r = get_network_files_by_link(rtnl, ifname, &path, &dropins); - if (r < 0) - return r; - - reload = RELOAD_NETWORKD; - } else if (streq(type, "link")) { - r = get_link_files_by_link(ifname, &path, &dropins); - if (r < 0) - return r; - - reload = RELOAD_UDEVD; - } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid config type '%s' for link '%s'.", type, ifname); - - *ret_path = TAKE_PTR(path); - *ret_dropins = TAKE_PTR(dropins); - - if (ret_reload) - *ret_reload = reload; - - return 0; -} - -static int add_config_to_edit( - EditFileContext *context, - const char *path, - char * const *dropins) { - - _cleanup_free_ char *new_path = NULL, *dropin_path = NULL, *old_dropin = NULL; - _cleanup_strv_free_ char **comment_paths = NULL; - int r; - - assert(context); - assert(path); - assert(!arg_drop_in || dropins); - - if (path_startswith(path, "/usr")) { - _cleanup_free_ char *name = NULL; - - r = path_extract_filename(path, &name); - if (r < 0) - return log_error_errno(r, "Failed to extract filename from '%s': %m", path); - - new_path = path_join(NETWORK_DIRS[0], name); - if (!new_path) - return log_oom(); - } - - if (!arg_drop_in) - return edit_files_add(context, new_path ?: path, path, NULL); - - r = get_dropin_by_name(arg_drop_in, dropins, &old_dropin); + r = parse_boolean(argv[1]); if (r < 0) - return log_error_errno(r, "Failed to acquire drop-in '%s': %m", arg_drop_in); - - if (r > 0 && !path_startswith(old_dropin, "/usr")) - /* An existing drop-in is found and not in /usr/. Let's edit it directly. */ - dropin_path = TAKE_PTR(old_dropin); - else { - /* No drop-in was found or an existing drop-in resides in /usr/. Let's create - * a new drop-in file. */ - dropin_path = strjoin(new_path ?: path, ".d/", arg_drop_in); - if (!dropin_path) - return log_oom(); - } - - comment_paths = strv_new(path); - if (!comment_paths) - return log_oom(); + return log_error_errno(r, "Failed to parse argument: %s", argv[1]); + ready = r; - r = strv_extend_strv(&comment_paths, dropins, /* filter_duplicates = */ false); - if (r < 0) - return log_oom(); - - return edit_files_add(context, dropin_path, old_dropin, comment_paths); -} - -static int udevd_reload(sd_bus *bus) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; - const char *job_path; - int r; - - assert(bus); - - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_error_errno(r, "Could not watch jobs: %m"); - - r = bus_call_method(bus, - bus_systemd_mgr, - "ReloadUnit", - &error, - &reply, - "ss", - "systemd-udevd.service", - "replace"); - if (r < 0) - return log_error_errno(r, "Failed to reload systemd-udevd: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "o", &job_path); - if (r < 0) - return bus_log_parse_error(r); - - r = bus_wait_for_jobs_one(w, job_path, /* quiet = */ true, NULL); - if (r == -ENOEXEC) { - log_debug("systemd-udevd is not running, skipping reload."); - return 0; - } - if (r < 0) - return log_error_errno(r, "Failed to reload systemd-udevd: %m"); - - return 1; -} - -static int verb_edit(int argc, char *argv[], void *userdata) { - _cleanup_(edit_file_context_done) EditFileContext context = { - .marker_start = DROPIN_MARKER_START, - .marker_end = DROPIN_MARKER_END, - .remove_parent = !!arg_drop_in, - }; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - ReloadFlags reload = 0; - int r; - - if (!on_tty()) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit network config files if not on a tty."); - - r = mac_selinux_init(); - if (r < 0) - return r; - - STRV_FOREACH(name, strv_skip(argv, 1)) { - _cleanup_strv_free_ char **dropins = NULL; - _cleanup_free_ char *path = NULL; - const char *link_config; - - link_config = startswith(*name, "@"); - if (link_config) { - ReloadFlags flags; - - r = get_config_files_by_link_config(link_config, &rtnl, &path, &dropins, &flags); - if (r < 0) - return r; - - reload |= flags; - - r = add_config_to_edit(&context, path, dropins); - if (r < 0) - return r; - - continue; - } - - if (ENDSWITH_SET(*name, ".network", ".netdev")) - reload |= RELOAD_NETWORKD; - else if (endswith(*name, ".link")) - reload |= RELOAD_UDEVD; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid network config name '%s'.", *name); - - r = get_config_files_by_name(*name, &path, &dropins); - if (r == -ENOENT) { - if (arg_drop_in) - return log_error_errno(r, "Cannot find network config '%s'.", *name); - - log_debug("No existing network config '%s' found, creating a new file.", *name); - - path = path_join(NETWORK_DIRS[0], *name); - if (!path) - return log_oom(); - - r = edit_files_add(&context, path, NULL, NULL); - if (r < 0) - return r; - continue; - } - if (r < 0) - return log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); - - r = add_config_to_edit(&context, path, dropins); - if (r < 0) - return r; - } - - r = do_edit_files_and_install(&context); + r = varlink_connect_networkd(&vl); if (r < 0) return r; - if (arg_no_reload) - return 0; - - if (!sd_booted() || running_in_chroot() > 0) { - log_debug("System is not booted with systemd or is running in chroot, skipping reload."); - return 0; - } - - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + if (ready) { + _cleanup_close_ int fd = -EBADF; - r = sd_bus_open_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to connect to system bus: %m"); + fd = open("/var/lib/systemd/network/", O_CLOEXEC | O_DIRECTORY); + if (fd < 0) + return log_error_errno(errno, "Failed to open /var/lib/systemd/network/: %m"); - if (FLAGS_SET(reload, RELOAD_UDEVD)) { - r = udevd_reload(bus); + r = varlink_push_fd(vl, fd); if (r < 0) - return r; - } - - if (FLAGS_SET(reload, RELOAD_NETWORKD)) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - - if (!networkd_is_running()) { - log_debug("systemd-networkd is not running, skipping reload."); - return 0; - } + return log_error_errno(r, "Failed to push file descriptor of /var/lib/systemd/network/ into varlink: %m"); - r = bus_call_method(bus, bus_network_mgr, "Reload", &error, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to reload systemd-networkd: %s", bus_error_message(&error, r)); + TAKE_FD(fd); } - return 0; -} - -static int verb_cat(int argc, char *argv[], void *userdata) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - int r, ret = 0; - - pager_open(arg_pager_flags); - - STRV_FOREACH(name, strv_skip(argv, 1)) { - _cleanup_strv_free_ char **dropins = NULL; - _cleanup_free_ char *path = NULL; - const char *link_config; - - link_config = startswith(*name, "@"); - if (link_config) { - r = get_config_files_by_link_config(link_config, &rtnl, &path, &dropins, /* ret_reload = */ NULL); - if (r < 0) - return ret < 0 ? ret : r; - } else { - r = get_config_files_by_name(*name, &path, &dropins); - if (r == -ENOENT) { - log_error_errno(r, "Cannot find network config file '%s'.", *name); - ret = ret < 0 ? ret : r; - continue; - } - if (r < 0) { - log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); - return ret < 0 ? ret : r; - } - } - - r = cat_files(path, dropins, /* flags = */ CAT_FORMAT_HAS_SECTIONS); - if (r < 0) - return ret < 0 ? ret : r; - } - - return ret; + return varlink_callb_and_log(vl, "io.systemd.Network.SetPersistentStorage", /* reply = */ NULL, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR_BOOLEAN("Ready", ready))); } static int help(void) { @@ -3326,7 +2980,11 @@ static int help(void) { " reconfigure DEVICES... Reconfigure interfaces\n" " reload Reload .network and .netdev files\n" " edit FILES|DEVICES... Edit network configuration files\n" - " cat FILES|DEVICES... Show network configuration files\n" + " cat [FILES|DEVICES...] Show network configuration files\n" + " mask FILES... Mask network configuration files\n" + " unmask FILES... Unmask network configuration files\n" + " persistent-storage BOOL\n" + " Notify systemd-networkd if persistent storage is ready\n" "\nOptions:\n" " -h --help Show this help\n" " --version Show package version\n" @@ -3341,6 +2999,7 @@ static int help(void) { " --no-reload Do not reload systemd-networkd or systemd-udevd\n" " after editing network config\n" " --drop-in=NAME Edit specified drop-in instead of main config file\n" + " --runtime Edit runtime config files\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -3358,6 +3017,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_JSON, ARG_NO_RELOAD, ARG_DROP_IN, + ARG_RUNTIME, }; static const struct option options[] = { @@ -3372,6 +3032,7 @@ static int parse_argv(int argc, char *argv[]) { { "json", required_argument, NULL, ARG_JSON }, { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, { "drop-in", required_argument, NULL, ARG_DROP_IN }, + { "runtime", no_argument, NULL, ARG_RUNTIME }, {} }; @@ -3402,6 +3063,10 @@ static int parse_argv(int argc, char *argv[]) { arg_no_reload = true; break; + case ARG_RUNTIME: + arg_runtime = true; + break; + case ARG_DROP_IN: if (isempty(optarg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty drop-in file name."); @@ -3463,19 +3128,22 @@ static int parse_argv(int argc, char *argv[]) { static int networkctl_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, list_links }, - { "status", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, link_status }, - { "lldp", VERB_ANY, VERB_ANY, 0, link_lldp_status }, - { "label", 1, 1, 0, list_address_labels }, - { "delete", 2, VERB_ANY, 0, link_delete }, - { "up", 2, VERB_ANY, 0, link_up_down }, - { "down", 2, VERB_ANY, 0, link_up_down }, - { "renew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_renew }, - { "forcerenew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_force_renew }, - { "reconfigure", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_reconfigure }, - { "reload", 1, 1, VERB_ONLINE_ONLY, verb_reload }, - { "edit", 2, VERB_ANY, 0, verb_edit }, - { "cat", 2, VERB_ANY, 0, verb_cat }, + { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, list_links }, + { "status", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, link_status }, + { "lldp", VERB_ANY, VERB_ANY, 0, link_lldp_status }, + { "label", 1, 1, 0, list_address_labels }, + { "delete", 2, VERB_ANY, 0, link_delete }, + { "up", 2, VERB_ANY, 0, link_up_down }, + { "down", 2, VERB_ANY, 0, link_up_down }, + { "renew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_renew }, + { "forcerenew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_force_renew }, + { "reconfigure", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_reconfigure }, + { "reload", 1, 1, VERB_ONLINE_ONLY, verb_reload }, + { "edit", 2, VERB_ANY, 0, verb_edit }, + { "cat", 1, VERB_ANY, 0, verb_cat }, + { "mask", 2, VERB_ANY, 0, verb_mask }, + { "unmask", 2, VERB_ANY, 0, verb_unmask }, + { "persistent-storage", 2, 2, 0, verb_persistent_storage }, {} }; diff --git a/src/network/networkctl.h b/src/network/networkctl.h new file mode 100644 index 0000000..46b44f7 --- /dev/null +++ b/src/network/networkctl.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <stdbool.h> + +#include "sd-bus.h" + +#include "output-mode.h" +#include "pager.h" + +extern PagerFlags arg_pager_flags; +extern bool arg_legend; +extern bool arg_no_reload; +extern bool arg_all; +extern bool arg_stats; +extern bool arg_full; +extern bool arg_runtime; +extern unsigned arg_lines; +extern char *arg_drop_in; +extern JsonFormatFlags arg_json_format_flags; + +bool networkd_is_running(void); +int acquire_bus(sd_bus **ret); diff --git a/src/network/networkd-address-generation.c b/src/network/networkd-address-generation.c index 65f0009..816cdf7 100644 --- a/src/network/networkd-address-generation.c +++ b/src/network/networkd-address-generation.c @@ -34,11 +34,125 @@ typedef enum AddressGenerationType { _ADDRESS_GENERATION_TYPE_INVALID = -EINVAL, } AddressGenerationType; -typedef struct IPv6Token { +struct IPv6Token { + unsigned n_ref; AddressGenerationType type; struct in6_addr address; sd_id128_t secret_key; -} IPv6Token; +}; + +DEFINE_TRIVIAL_REF_UNREF_FUNC(IPv6Token, ipv6_token, mfree); +DEFINE_TRIVIAL_CLEANUP_FUNC(IPv6Token*, ipv6_token_unref); + +static void ipv6_token_hash_func(const IPv6Token *p, struct siphash *state) { + siphash24_compress_typesafe(p->type, state); + siphash24_compress_typesafe(p->address, state); + id128_hash_func(&p->secret_key, state); +} + +static int ipv6_token_compare_func(const IPv6Token *a, const IPv6Token *b) { + int r; + + r = CMP(a->type, b->type); + if (r != 0) + return r; + + r = memcmp(&a->address, &b->address, sizeof(struct in6_addr)); + if (r != 0) + return r; + + return id128_compare_func(&a->secret_key, &b->secret_key); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + ipv6_token_hash_ops, + IPv6Token, + ipv6_token_hash_func, + ipv6_token_compare_func, + ipv6_token_unref); + +DEFINE_PRIVATE_HASH_OPS_FULL( + ipv6_token_by_addr_hash_ops, + struct in6_addr, + in6_addr_hash_func, + in6_addr_compare_func, + free, + IPv6Token, + ipv6_token_unref); + +static int ipv6_token_new(AddressGenerationType type, const struct in6_addr *addr, const sd_id128_t *secret_key, IPv6Token **ret) { + IPv6Token *p; + + assert(type >= 0 && type < _ADDRESS_GENERATION_TYPE_MAX); + assert(addr); + assert(secret_key); + assert(ret); + + p = new(IPv6Token, 1); + if (!p) + return -ENOMEM; + + *p = (IPv6Token) { + .n_ref = 1, + .type = type, + .address = *addr, + .secret_key = *secret_key, + }; + + *ret = p; + return 0; +} + +static int ipv6_token_add(Set **tokens, AddressGenerationType type, const struct in6_addr *addr, const sd_id128_t *secret_key) { + IPv6Token *p; + int r; + + assert(tokens); + + r = ipv6_token_new(type, addr, secret_key, &p); + if (r < 0) + return r; + + return set_ensure_consume(tokens, &ipv6_token_hash_ops, p); +} + +static int ipv6_token_put_by_addr(Hashmap **tokens_by_address, const struct in6_addr *addr, IPv6Token *token) { + _cleanup_free_ struct in6_addr *copy = NULL; + int r; + + assert(tokens_by_address); + assert(addr); + assert(token); + + copy = newdup(struct in6_addr, addr, 1); + if (!copy) + return -ENOMEM; + + r = hashmap_ensure_put(tokens_by_address, &ipv6_token_by_addr_hash_ops, copy, token); + if (r == -EEXIST) + return 0; + if (r < 0) + return r; + + TAKE_PTR(copy); + ipv6_token_ref(token); + return 1; +} + +static int ipv6_token_type_put_by_addr(Hashmap **tokens_by_addr, const struct in6_addr *addr, AddressGenerationType type) { + _cleanup_(ipv6_token_unrefp) IPv6Token *token = NULL; + int r; + + assert(tokens_by_addr); + assert(addr); + + r = ipv6_token_new(type, &(struct in6_addr) {}, &SD_ID128_NULL, &token); + if (r < 0) + return r; + + return ipv6_token_put_by_addr(tokens_by_addr, addr, token); +} + static int generate_eui64_address(const Link *link, const struct in6_addr *prefix, struct in6_addr *ret) { assert(link); @@ -121,7 +235,7 @@ static void generate_stable_private_address_one( if (link->ssid) siphash24_compress_string(link->ssid, &state); - siphash24_compress(&dad_counter, sizeof(uint8_t), &state); + siphash24_compress_typesafe(dad_counter, &state); rid = htole64(siphash24_finalize(&state)); @@ -134,10 +248,12 @@ static int generate_stable_private_address( const sd_id128_t *app_id, const sd_id128_t *secret_key, const struct in6_addr *prefix, + const struct in6_addr *previous, struct in6_addr *ret) { sd_id128_t secret_machine_key; struct in6_addr addr; + bool found = false; uint8_t i; int r; @@ -162,16 +278,29 @@ static int generate_stable_private_address( for (i = 0; i < DAD_CONFLICTS_IDGEN_RETRIES_RFC7217; i++) { generate_stable_private_address_one(link, secret_key, prefix, i, &addr); - if (stable_private_address_is_valid(&addr)) - break; + if (!stable_private_address_is_valid(&addr)) + continue; + + /* When 'previous' is non-NULL, then this is called after DAD in the kernel triggered. + * Let's increment the counter and provide the next address. */ + if (previous && !found) { + found = in6_addr_equal(previous, &addr); + continue; + } + + break; } - if (i >= DAD_CONFLICTS_IDGEN_RETRIES_RFC7217) + if (i >= DAD_CONFLICTS_IDGEN_RETRIES_RFC7217) { /* propagate recognizable errors. */ - return log_link_debug_errno(link, SYNTHETIC_ERRNO(ENOANO), + if (previous && !found) + return -EADDRNOTAVAIL; + + return log_link_debug_errno(link, SYNTHETIC_ERRNO(EADDRINUSE), "Failed to generate stable private address."); + } *ret = addr; - return 0; + return 1; } static int generate_addresses( @@ -180,10 +309,10 @@ static int generate_addresses( const sd_id128_t *app_id, const struct in6_addr *prefix, uint8_t prefixlen, - Set **ret) { + Hashmap **ret) { - _cleanup_set_free_ Set *addresses = NULL; - struct in6_addr masked; + _cleanup_hashmap_free_ Hashmap *tokens_by_address = NULL; + struct in6_addr masked, addr; IPv6Token *j; int r; @@ -197,8 +326,6 @@ static int generate_addresses( in6_addr_mask(&masked, prefixlen); SET_FOREACH(j, tokens) { - struct in6_addr addr, *copy; - switch (j->type) { case ADDRESS_GENERATION_EUI64: if (generate_eui64_address(link, &masked, &addr) < 0) @@ -214,7 +341,7 @@ static int generate_addresses( if (in6_addr_is_set(&j->address) && !in6_addr_equal(&j->address, &masked)) continue; - if (generate_stable_private_address(link, app_id, &j->secret_key, &masked, &addr) < 0) + if (generate_stable_private_address(link, app_id, &j->secret_key, &masked, /* previous = */ NULL, &addr) < 0) continue; break; @@ -223,97 +350,77 @@ static int generate_addresses( assert_not_reached(); } - copy = newdup(struct in6_addr, &addr, 1); - if (!copy) - return -ENOMEM; - - r = set_ensure_consume(&addresses, &in6_addr_hash_ops_free, copy); + r = ipv6_token_put_by_addr(&tokens_by_address, &addr, j); if (r < 0) return r; } /* fall back to EUI-64 if no token is provided */ - if (set_isempty(addresses)) { - _cleanup_free_ struct in6_addr *addr = NULL; - - addr = new(struct in6_addr, 1); - if (!addr) - return -ENOMEM; + if (hashmap_isempty(tokens_by_address)) { + AddressGenerationType type; - if (IN_SET(link->iftype, ARPHRD_ETHER, ARPHRD_INFINIBAND)) - r = generate_eui64_address(link, &masked, addr); - else - r = generate_stable_private_address(link, app_id, &SD_ID128_NULL, &masked, addr); + if (IN_SET(link->iftype, ARPHRD_ETHER, ARPHRD_INFINIBAND)) { + type = ADDRESS_GENERATION_EUI64; + r = generate_eui64_address(link, &masked, &addr); + } else { + type = ADDRESS_GENERATION_PREFIXSTABLE; + r = generate_stable_private_address(link, app_id, &SD_ID128_NULL, &masked, /* previous = */ NULL, &addr); + } if (r < 0) return r; - r = set_ensure_consume(&addresses, &in6_addr_hash_ops_free, TAKE_PTR(addr)); + r = ipv6_token_type_put_by_addr(&tokens_by_address, &addr, type); if (r < 0) return r; } - *ret = TAKE_PTR(addresses); + *ret = TAKE_PTR(tokens_by_address); return 0; } -int dhcp_pd_generate_addresses(Link *link, const struct in6_addr *prefix, Set **ret) { +int dhcp_pd_generate_addresses(Link *link, const struct in6_addr *prefix, Hashmap **ret) { return generate_addresses(link, link->network->dhcp_pd_tokens, &DHCP_PD_APP_ID, prefix, 64, ret); } -int ndisc_generate_addresses(Link *link, const struct in6_addr *prefix, uint8_t prefixlen, Set **ret) { +int ndisc_generate_addresses(Link *link, const struct in6_addr *prefix, uint8_t prefixlen, Hashmap **ret) { return generate_addresses(link, link->network->ndisc_tokens, &NDISC_APP_ID, prefix, prefixlen, ret); } -int radv_generate_addresses(Link *link, Set *tokens, const struct in6_addr *prefix, uint8_t prefixlen, Set **ret) { +int radv_generate_addresses(Link *link, Set *tokens, const struct in6_addr *prefix, uint8_t prefixlen, Hashmap **ret) { return generate_addresses(link, tokens, &RADV_APP_ID, prefix, prefixlen, ret); } -static void ipv6_token_hash_func(const IPv6Token *p, struct siphash *state) { - siphash24_compress(&p->type, sizeof(p->type), state); - siphash24_compress(&p->address, sizeof(p->address), state); - id128_hash_func(&p->secret_key, state); -} - -static int ipv6_token_compare_func(const IPv6Token *a, const IPv6Token *b) { - int r; - - r = CMP(a->type, b->type); - if (r != 0) - return r; - - r = memcmp(&a->address, &b->address, sizeof(struct in6_addr)); - if (r != 0) - return r; - - return id128_compare_func(&a->secret_key, &b->secret_key); -} - -DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( - ipv6_token_hash_ops, - IPv6Token, - ipv6_token_hash_func, - ipv6_token_compare_func, - free); +int regenerate_address(Address *address, Link *link) { + struct in6_addr masked; + sd_id128_t app_id; -static int ipv6_token_add(Set **tokens, AddressGenerationType type, const struct in6_addr *addr, const sd_id128_t *secret_key) { - IPv6Token *p; + assert(link); + assert(address); + assert(address->family == AF_INET6); + assert(!address->link && !address->network); - assert(tokens); - assert(type >= 0 && type < _ADDRESS_GENERATION_TYPE_MAX); - assert(addr); - assert(secret_key); + if (!address->token || + address->token->type != ADDRESS_GENERATION_PREFIXSTABLE) + return 0; - p = new(IPv6Token, 1); - if (!p) - return -ENOMEM; + switch (address->source) { + case NETWORK_CONFIG_SOURCE_STATIC: + app_id = RADV_APP_ID; + break; + case NETWORK_CONFIG_SOURCE_DHCP_PD: + app_id = DHCP_PD_APP_ID; + break; + case NETWORK_CONFIG_SOURCE_NDISC: + app_id = NDISC_APP_ID; + break; + default: + assert_not_reached(); + } - *p = (IPv6Token) { - .type = type, - .address = *addr, - .secret_key = *secret_key, - }; + masked = address->in_addr.in6; + in6_addr_mask(&masked, address->prefixlen); - return set_ensure_consume(tokens, &ipv6_token_hash_ops, p); + return generate_stable_private_address(link, &app_id, &address->token->secret_key, &masked, &address->in_addr.in6, &address->in_addr.in6); } int config_parse_address_generation_type( diff --git a/src/network/networkd-address-generation.h b/src/network/networkd-address-generation.h index 901b2ec..2c60913 100644 --- a/src/network/networkd-address-generation.h +++ b/src/network/networkd-address-generation.h @@ -2,13 +2,20 @@ #pragma once #include "conf-parser.h" +#include "hashmap.h" #include "in-addr-util.h" -#include "set.h" +typedef struct Address Address; +typedef struct IPv6Token IPv6Token; typedef struct Link Link; -int dhcp_pd_generate_addresses(Link *link, const struct in6_addr *prefix, Set **ret); -int ndisc_generate_addresses(Link *link, const struct in6_addr *prefix, uint8_t prefixlen, Set **ret); -int radv_generate_addresses(Link *link, Set *tokens, const struct in6_addr *prefix, uint8_t prefixlen, Set **ret); +IPv6Token* ipv6_token_ref(IPv6Token *token); +IPv6Token* ipv6_token_unref(IPv6Token *token); + +int dhcp_pd_generate_addresses(Link *link, const struct in6_addr *prefix, Hashmap **ret); +int ndisc_generate_addresses(Link *link, const struct in6_addr *prefix, uint8_t prefixlen, Hashmap **ret); +int radv_generate_addresses(Link *link, Set *tokens, const struct in6_addr *prefix, uint8_t prefixlen, Hashmap **ret); + +int regenerate_address(Address *address, Link *link); CONFIG_PARSER_PROTOTYPE(config_parse_address_generation_type); diff --git a/src/network/networkd-address-label.c b/src/network/networkd-address-label.c index 745b959..e91ce3d 100644 --- a/src/network/networkd-address-label.c +++ b/src/network/networkd-address-label.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> #include <linux/if_addrlabel.h> diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 0e4d87b..b4ac0bc 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -10,9 +10,11 @@ #include "netlink-util.h" #include "networkd-address-pool.h" #include "networkd-address.h" +#include "networkd-dhcp-prefix-delegation.h" #include "networkd-dhcp-server.h" #include "networkd-ipv4acd.h" #include "networkd-manager.h" +#include "networkd-ndisc.h" #include "networkd-netlabel.h" #include "networkd-network.h" #include "networkd-queue.h" @@ -133,14 +135,40 @@ void link_get_address_states( *ret_all = address_state_from_scope(MIN(ipv4_scope, ipv6_scope)); } +static void address_hash_func(const Address *a, struct siphash *state); +static int address_compare_func(const Address *a1, const Address *a2); +static void address_detach(Address *address); + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + address_hash_ops_detach, + Address, + address_hash_func, + address_compare_func, + address_detach); + +DEFINE_HASH_OPS( + address_hash_ops, + Address, + address_hash_func, + address_compare_func); + +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + address_section_hash_ops, + ConfigSection, + config_section_hash_func, + config_section_compare_func, + Address, + address_detach); + int address_new(Address **ret) { - _cleanup_(address_freep) Address *address = NULL; + _cleanup_(address_unrefp) Address *address = NULL; address = new(Address, 1); if (!address) return -ENOMEM; *address = (Address) { + .n_ref = 1, .family = AF_UNSPEC, .scope = RT_SCOPE_UNIVERSE, .lifetime_valid_usec = USEC_INFINITY, @@ -155,7 +183,7 @@ int address_new(Address **ret) { int address_new_static(Network *network, const char *filename, unsigned section_line, Address **ret) { _cleanup_(config_section_freep) ConfigSection *n = NULL; - _cleanup_(address_freep) Address *address = NULL; + _cleanup_(address_unrefp) Address *address = NULL; int r; assert(network); @@ -186,7 +214,7 @@ int address_new_static(Network *network, const char *filename, unsigned section_ /* This will be adjusted in address_section_verify(). */ address->duplicate_address_detection = _ADDRESS_FAMILY_INVALID; - r = ordered_hashmap_ensure_put(&network->addresses_by_section, &config_section_hash_ops, address->section, address); + r = ordered_hashmap_ensure_put(&network->addresses_by_section, &address_section_hash_ops, address->section, address); if (r < 0) return r; @@ -194,32 +222,53 @@ int address_new_static(Network *network, const char *filename, unsigned section_ return 0; } -Address *address_free(Address *address) { - if (!address) - return NULL; +static Address* address_detach_impl(Address *address) { + assert(address); + assert(!address->link || !address->network); if (address->network) { assert(address->section); ordered_hashmap_remove(address->network->addresses_by_section, address->section); + + if (address->network->dhcp_server_address == address) + address->network->dhcp_server_address = NULL; + + address->network = NULL; + return address; } if (address->link) { set_remove(address->link->addresses, address); - if (address->family == AF_INET6 && - in6_addr_equal(&address->in_addr.in6, &address->link->ipv6ll_address)) - memzero(&address->link->ipv6ll_address, sizeof(struct in6_addr)); - - ipv4acd_detach(address->link, address); + address->link = NULL; + return address; } + return NULL; +} + +static void address_detach(Address *address) { + assert(address); + + address_unref(address_detach_impl(address)); +} + +static Address* address_free(Address *address) { + if (!address) + return NULL; + + address_detach_impl(address); + config_section_free(address->section); free(address->label); free(address->netlabel); + ipv6_token_unref(address->token); nft_set_context_clear(&address->nft_set_context); return mfree(address); } +DEFINE_TRIVIAL_REF_UNREF_FUNC(Address, address, address_free); + static bool address_lifetime_is_valid(const Address *a) { assert(a); @@ -400,25 +449,25 @@ static int address_ipv4_prefix(const Address *a, struct in_addr *ret) { static void address_hash_func(const Address *a, struct siphash *state) { assert(a); - siphash24_compress(&a->family, sizeof(a->family), state); + siphash24_compress_typesafe(a->family, state); switch (a->family) { case AF_INET: { struct in_addr prefix; - siphash24_compress(&a->prefixlen, sizeof(a->prefixlen), state); + siphash24_compress_typesafe(a->prefixlen, state); assert_se(address_ipv4_prefix(a, &prefix) >= 0); - siphash24_compress(&prefix, sizeof(prefix), state); + siphash24_compress_typesafe(prefix, state); - siphash24_compress(&a->in_addr.in, sizeof(a->in_addr.in), state); + siphash24_compress_typesafe(a->in_addr.in, state); break; } case AF_INET6: - siphash24_compress(&a->in_addr.in6, sizeof(a->in_addr.in6), state); + siphash24_compress_typesafe(a->in_addr.in6, state); if (in6_addr_is_null(&a->in_addr.in6)) - siphash24_compress(&a->prefixlen, sizeof(a->prefixlen), state); + siphash24_compress_typesafe(a->prefixlen, state); break; default: @@ -470,24 +519,9 @@ static int address_compare_func(const Address *a1, const Address *a2) { } } -DEFINE_HASH_OPS( - address_hash_ops, - Address, - address_hash_func, - address_compare_func); - -DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( - address_hash_ops_free, - Address, - address_hash_func, - address_compare_func, - address_free); - -static bool address_can_update(const Address *la, const Address *na) { - assert(la); - assert(la->link); - assert(na); - assert(na->network); +bool address_can_update(const Address *existing, const Address *requesting) { + assert(existing); + assert(requesting); /* * property | IPv4 | IPv6 @@ -512,32 +546,32 @@ static bool address_can_update(const Address *la, const Address *na) { * IPv6 : See inet6_addr_modify() in net/ipv6/addrconf.c. */ - if (la->family != na->family) + if (existing->family != requesting->family) return false; - if (la->prefixlen != na->prefixlen) + if (existing->prefixlen != requesting->prefixlen) return false; /* When a null address is requested, the address to be assigned/updated will be determined later. */ - if (!address_is_static_null(na) && - in_addr_equal(la->family, &la->in_addr, &na->in_addr) <= 0) + if (!address_is_static_null(requesting) && + in_addr_equal(existing->family, &existing->in_addr, &requesting->in_addr) <= 0) return false; - switch (la->family) { + switch (existing->family) { case AF_INET: { struct in_addr bcast; - if (la->scope != na->scope) + if (existing->scope != requesting->scope) return false; - if (((la->flags ^ na->flags) & KNOWN_FLAGS & ~IPV6ONLY_FLAGS & ~UNMANAGED_FLAGS) != 0) + if (((existing->flags ^ requesting->flags) & KNOWN_FLAGS & ~IPV6ONLY_FLAGS & ~UNMANAGED_FLAGS) != 0) return false; - if (!streq_ptr(la->label, na->label)) + if (!streq_ptr(existing->label, requesting->label)) return false; - if (!in4_addr_equal(&la->in_addr_peer.in, &na->in_addr_peer.in)) + if (!in4_addr_equal(&existing->in_addr_peer.in, &requesting->in_addr_peer.in)) return false; - if (address_get_broadcast(na, la->link, &bcast) >= 0) { + if (existing->link && address_get_broadcast(requesting, existing->link, &bcast) >= 0) { /* If the broadcast address can be determined now, check if they match. */ - if (!in4_addr_equal(&la->broadcast, &bcast)) + if (!in4_addr_equal(&existing->broadcast, &bcast)) return false; } else { /* When a null address is requested, then the broadcast address will be @@ -545,7 +579,7 @@ static bool address_can_update(const Address *la, const Address *na) { * 192.168.0.10/24 -> 192.168.0.255 * So, here let's only check if the broadcast is the last address in the range, e.g. * 0.0.0.0/24 -> 0.0.0.255 */ - if (!FLAGS_SET(la->broadcast.s_addr, htobe32(UINT32_C(0xffffffff) >> la->prefixlen))) + if (!FLAGS_SET(existing->broadcast.s_addr, htobe32(UINT32_C(0xffffffff) >> existing->prefixlen))) return false; } break; @@ -561,7 +595,7 @@ static bool address_can_update(const Address *la, const Address *na) { } int address_dup(const Address *src, Address **ret) { - _cleanup_(address_freep) Address *dest = NULL; + _cleanup_(address_unrefp) Address *dest = NULL; int r; assert(src); @@ -571,22 +605,24 @@ int address_dup(const Address *src, Address **ret) { if (!dest) return -ENOMEM; - /* clear all pointers */ + /* clear the reference counter and all pointers */ + dest->n_ref = 1; dest->network = NULL; dest->section = NULL; dest->link = NULL; dest->label = NULL; + dest->token = ipv6_token_ref(src->token); dest->netlabel = NULL; dest->nft_set_context.sets = NULL; dest->nft_set_context.n_sets = 0; if (src->family == AF_INET) { - r = free_and_strdup(&dest->label, src->label); + r = strdup_to(&dest->label, src->label); if (r < 0) return r; } - r = free_and_strdup(&dest->netlabel, src->netlabel); + r = strdup_to(&dest->netlabel, src->netlabel); if (r < 0) return r; @@ -674,8 +710,8 @@ static void address_modify_nft_set_context(Address *address, bool add, NFTSetCon } if (r < 0) - log_warning_errno(r, "Failed to %s NFT set: family %s, table %s, set %s, IP address %s, ignoring", - add? "add" : "delete", + log_warning_errno(r, "Failed to %s NFT set: family %s, table %s, set %s, IP address %s, ignoring: %m", + add ? "add" : "delete", nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set, IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen)); else @@ -712,19 +748,21 @@ static void address_modify_nft_set(Address *address, bool add) { } } -static int address_add(Link *link, Address *address) { +static int address_attach(Link *link, Address *address) { int r; assert(link); assert(address); + assert(!address->link); - r = set_ensure_put(&link->addresses, &address_hash_ops_free, address); + r = set_ensure_put(&link->addresses, &address_hash_ops_detach, address); if (r < 0) return r; if (r == 0) return -EEXIST; address->link = link; + address_ref(address); return 0; } @@ -766,8 +804,49 @@ static int address_update(Address *address) { return 0; } -static int address_drop(Address *address) { - Link *link = ASSERT_PTR(ASSERT_PTR(address)->link); +static int address_removed_maybe_kernel_dad(Link *link, Address *address) { + int r; + + assert(link); + assert(address); + + if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) + return 0; + + if (address->family != AF_INET6) + return 0; + + if (!FLAGS_SET(address->flags, IFA_F_TENTATIVE)) + return 0; + + log_link_info(link, "Address %s with tentative flag is removed, maybe a duplicated address is assigned on another node or link?", + IN6_ADDR_TO_STRING(&address->in_addr.in6)); + + /* Reset the address state, as the object may be reused in the below. */ + address->state = 0; + + switch (address->source) { + case NETWORK_CONFIG_SOURCE_STATIC: + r = link_reconfigure_radv_address(address, link); + break; + case NETWORK_CONFIG_SOURCE_DHCP_PD: + r = dhcp_pd_reconfigure_address(address, link); + break; + case NETWORK_CONFIG_SOURCE_NDISC: + r = ndisc_reconfigure_address(address, link); + break; + default: + r = 0; + } + if (r < 0) + return log_link_warning_errno(link, r, "Failed to configure an alternative address: %m"); + + return 0; +} + +static int address_drop(Address *in, bool removed_by_us) { + _cleanup_(address_unrefp) Address *address = address_ref(ASSERT_PTR(in)); + Link *link = ASSERT_PTR(address->link); int r; r = address_set_masquerade(address, /* add = */ false); @@ -778,7 +857,22 @@ static int address_drop(Address *address) { address_del_netlabel(address); - address_free(address); + /* FIXME: if the IPv6LL address is dropped, stop DHCPv6, NDISC, RADV. */ + if (address->family == AF_INET6 && + in6_addr_equal(&address->in_addr.in6, &link->ipv6ll_address)) + link->ipv6ll_address = (const struct in6_addr) {}; + + ipv4acd_detach(link, address); + + address_detach(address); + + if (!removed_by_us) { + r = address_removed_maybe_kernel_dad(link, address); + if (r < 0) { + link_enter_failed(link); + return r; + } + } link_update_operstate(link, /* also_update_master = */ true); link_check_ready(link); @@ -907,7 +1001,7 @@ int link_get_address(Link *link, int family, const union in_addr_union *address, * arbitrary prefixlen will be returned. */ if (family == AF_INET6 || prefixlen != 0) { - _cleanup_(address_freep) Address *tmp = NULL; + _cleanup_(address_unrefp) Address *tmp = NULL; /* In this case, we can use address_get(). */ @@ -975,7 +1069,7 @@ int manager_get_address(Manager *manager, int family, const union in_addr_union return -ENOENT; } -bool manager_has_address(Manager *manager, int family, const union in_addr_union *address, bool check_ready) { +bool manager_has_address(Manager *manager, int family, const union in_addr_union *address) { Address *a; assert(manager); @@ -985,7 +1079,7 @@ bool manager_has_address(Manager *manager, int family, const union in_addr_union if (manager_get_address(manager, family, address, 0, &a) < 0) return false; - return check_ready ? address_is_ready(a) : (address_exists(a) && address_lifetime_is_valid(a)); + return address_is_ready(a); } const char* format_lifetime(char *buf, size_t l, usec_t lifetime_usec) { @@ -1068,36 +1162,52 @@ static int address_set_netlink_message(const Address *address, sd_netlink_messag return 0; } -static int address_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { +static int address_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, RemoveRequest *rreq) { int r; assert(m); - assert(link); + assert(rreq); - if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + Link *link = ASSERT_PTR(rreq->link); + Address *address = ASSERT_PTR(rreq->userdata); + + if (link->state == LINK_STATE_LINGER) return 0; r = sd_netlink_message_get_errno(m); - if (r < 0 && r != -EADDRNOTAVAIL) - log_link_message_warning_errno(link, m, r, "Could not drop address"); + if (r < 0) { + log_link_message_full_errno(link, m, + (r == -EADDRNOTAVAIL || !address->link) ? LOG_DEBUG : LOG_WARNING, + r, "Could not drop address"); + + if (address->link) { + /* If the address cannot be removed, then assume the address is already removed. */ + log_address_debug(address, "Forgetting", link); + + Request *req; + if (address_get_request(link, address, &req) >= 0) + address_enter_removed(req->userdata); + + (void) address_drop(address, /* removed_by_us = */ true); + } + } return 1; } -int address_remove(Address *address) { +int address_remove(Address *address, Link *link) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - Request *req; - Link *link; int r; assert(address); assert(IN_SET(address->family, AF_INET, AF_INET6)); - assert(address->link); - assert(address->link->ifindex > 0); - assert(address->link->manager); - assert(address->link->manager->rtnl); + assert(link); + assert(link->ifindex > 0); + assert(link->manager); + assert(link->manager->rtnl); - link = address->link; + /* If the address is remembered, use the remembered object. */ + (void) address_get(link, address, &address); log_address_debug(address, "Removing", link); @@ -1110,17 +1220,11 @@ int address_remove(Address *address) { if (r < 0) return log_link_warning_errno(link, r, "Could not set netlink attributes: %m"); - r = netlink_call_async(link->manager->rtnl, NULL, m, - address_remove_handler, - link_netlink_destroy_callback, link); + r = link_remove_request_add(link, address, address, link->manager->rtnl, m, address_remove_handler); if (r < 0) - return log_link_warning_errno(link, r, "Could not send rtnetlink message: %m"); - - link_ref(link); + return log_link_warning_errno(link, r, "Could not queue rtnetlink message: %m"); address_enter_removing(address); - if (address_get_request(link, address, &req) >= 0) - address_enter_removing(req->userdata); /* The operational state is determined by address state and carrier state. Hence, if we remove * an address, the operational state may be changed. */ @@ -1128,22 +1232,38 @@ int address_remove(Address *address) { return 0; } -int address_remove_and_drop(Address *address) { - if (!address) - return 0; +int address_remove_and_cancel(Address *address, Link *link) { + _cleanup_(request_unrefp) Request *req = NULL; + bool waiting = false; + + assert(address); + assert(link); + assert(link->manager); - address_cancel_request(address); + /* If the address is remembered by the link, then use the remembered object. */ + (void) address_get(link, address, &address); - if (address_exists(address)) - return address_remove(address); + /* Cancel the request for the address. If the request is already called but we have not received the + * notification about the request, then explicitly remove the address. */ + if (address_get_request(link, address, &req) >= 0) { + request_ref(req); /* avoid the request freed by request_detach() */ + waiting = req->waiting_reply; + request_detach(req); + address_cancel_requesting(address); + } + + /* If we know the address will come or already exists, remove it. */ + if (waiting || (address->link && address_exists(address))) + return address_remove(address, link); - return address_drop(address); + return 0; } bool link_address_is_dynamic(const Link *link, const Address *address) { Route *route; assert(link); + assert(link->manager); assert(address); if (address->lifetime_preferred_usec != USEC_INFINITY) @@ -1152,7 +1272,7 @@ bool link_address_is_dynamic(const Link *link, const Address *address) { /* Even when the address is leased from a DHCP server, networkd assign the address * without lifetime when KeepConfiguration=dhcp. So, let's check that we have * corresponding routes with RTPROT_DHCP. */ - SET_FOREACH(route, link->routes) { + SET_FOREACH(route, link->manager->routes) { if (route->source != NETWORK_CONFIG_SOURCE_FOREIGN) continue; @@ -1163,6 +1283,9 @@ bool link_address_is_dynamic(const Link *link, const Address *address) { if (route->protocol != RTPROT_DHCP) continue; + if (route->nexthop.ifindex != link->ifindex) + continue; + if (address->family != route->family) continue; @@ -1200,10 +1323,9 @@ int link_drop_ipv6ll_addresses(Link *link) { return r; for (sd_netlink_message *addr = reply; addr; addr = sd_netlink_message_next(addr)) { - _cleanup_(address_freep) Address *a = NULL; + _cleanup_(address_unrefp) Address *a = NULL; unsigned char flags, prefixlen; struct in6_addr address; - Address *existing; int ifindex; /* NETLINK_GET_STRICT_CHK socket option is supported since kernel 4.20. To support @@ -1249,15 +1371,7 @@ int link_drop_ipv6ll_addresses(Link *link) { a->prefixlen = prefixlen; a->flags = flags; - if (address_get(link, a, &existing) < 0) { - r = address_add(link, a); - if (r < 0) - return r; - - existing = TAKE_PTR(a); - } - - r = address_remove(existing); + r = address_remove(a, link); if (r < 0) return r; } @@ -1317,28 +1431,29 @@ int link_drop_foreign_addresses(Link *link) { if (!address_is_marked(address)) continue; - RET_GATHER(r, address_remove(address)); + RET_GATHER(r, address_remove(address, link)); } return r; } -int link_drop_managed_addresses(Link *link) { +int link_drop_static_addresses(Link *link) { Address *address; int r = 0; assert(link); SET_FOREACH(address, link->addresses) { - /* Do not touch addresses managed by kernel or other tools. */ - if (address->source == NETWORK_CONFIG_SOURCE_FOREIGN) + /* Remove only static addresses here. Dynamic addresses will be removed e.g. on lease + * expiration or stopping the DHCP client. */ + if (address->source != NETWORK_CONFIG_SOURCE_STATIC) continue; /* Ignore addresses not assigned yet or already removing. */ if (!address_exists(address)) continue; - RET_GATHER(r, address_remove(address)); + RET_GATHER(r, address_remove(address, link)); } return r; @@ -1353,43 +1468,6 @@ void link_foreignize_addresses(Link *link) { address->source = NETWORK_CONFIG_SOURCE_FOREIGN; } -static int address_acquire(Link *link, const Address *original, Address **ret) { - _cleanup_(address_freep) Address *na = NULL; - union in_addr_union in_addr; - int r; - - assert(link); - assert(original); - assert(ret); - - /* Something useful was configured? just use it */ - if (in_addr_is_set(original->family, &original->in_addr)) - return address_dup(original, ret); - - /* The address is configured to be 0.0.0.0 or [::] by the user? - * Then let's acquire something more useful from the pool. */ - r = address_pool_acquire(link->manager, original->family, original->prefixlen, &in_addr); - if (r < 0) - return r; - if (r == 0) - return -EBUSY; - - /* Pick first address in range for ourselves. */ - if (original->family == AF_INET) - in_addr.in.s_addr = in_addr.in.s_addr | htobe32(1); - else if (original->family == AF_INET6) - in_addr.in6.s6_addr[15] |= 1; - - r = address_dup(original, &na); - if (r < 0) - return r; - - na->in_addr = in_addr; - - *ret = TAKE_PTR(na); - return 0; -} - int address_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, const char *error_msg) { int r; @@ -1462,25 +1540,75 @@ static int address_configure(const Address *address, const struct ifa_cacheinfo return request_call_netlink_async(link->manager->rtnl, m, req); } -static bool address_is_ready_to_configure(Link *link, const Address *address) { +static int address_acquire(Link *link, const Address *address, union in_addr_union *ret) { + union in_addr_union a; + int r; + assert(link); assert(address); + assert(ret); - if (!link_is_ready_to_configure(link, false)) - return false; + r = address_acquire_from_dhcp_server_leases_file(link, address, ret); + if (!IN_SET(r, -ENOENT, -ENXIO, -EINVAL)) + return r; - if (!ipv4acd_bound(link, address)) - return false; + r = address_pool_acquire(link->manager, address->family, address->prefixlen, &a); + if (r < 0) + return r; + if (r == 0) + return -EBUSY; - /* Refuse adding more than the limit */ - if (set_size(link->addresses) >= ADDRESSES_PER_LINK_MAX) - return false; + /* Pick first address in range for ourselves. */ + if (address->family == AF_INET) + a.in.s_addr |= htobe32(1); + else if (address->family == AF_INET6) + a.in6.s6_addr[15] |= 1; + else + assert_not_reached(); - return true; + *ret = a; + return 0; +} + +static int address_requeue_request(Request *req, Link *link, const Address *address) { + int r; + + assert(req); + assert(link); + assert(link->manager); + assert(link->network); + assert(address); + + /* Something useful was configured? just use it */ + if (in_addr_is_set(address->family, &address->in_addr)) + return 0; + + /* The address is configured to be 0.0.0.0 or [::] by the user? + * Then let's acquire something more useful. */ + union in_addr_union a; + r = address_acquire(link, address, &a); + if (r < 0) + return r; + + _cleanup_(address_unrefp) Address *tmp = NULL; + r = address_dup(address, &tmp); + if (r < 0) + return r; + + tmp->in_addr = a; + + r = link_requeue_request(link, req, tmp, NULL); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; /* Already queued?? Strange... */ + + TAKE_PTR(tmp); + return 1; /* A new request is queued. it is not necessary to process this request anymore. */ } static int address_process_request(Request *req, Link *link, Address *address) { - struct Address *existing; + Address *existing; struct ifa_cacheinfo c; int r; @@ -1488,7 +1616,26 @@ static int address_process_request(Request *req, Link *link, Address *address) { assert(link); assert(address); - if (!address_is_ready_to_configure(link, address)) + if (!link_is_ready_to_configure(link, false)) + return 0; + + /* Refuse adding more than the limit */ + if (set_size(link->addresses) >= ADDRESSES_PER_LINK_MAX) + return 0; + + r = address_requeue_request(req, link, address); + if (r == -EBUSY) + return 0; + if (r != 0) + return r; + + address_set_broadcast(address, link); + + r = ipv4acd_configure(link, address); + if (r < 0) + return r; + + if (!ipv4acd_bound(link, address)) return 0; address_set_cinfo(link->manager, address, &c); @@ -1521,7 +1668,7 @@ int link_request_address( address_netlink_handler_t netlink_handler, Request **ret) { - _cleanup_(address_freep) Address *tmp = NULL; + _cleanup_(address_unrefp) Address *tmp = NULL; Address *existing = NULL; int r; @@ -1533,22 +1680,14 @@ int link_request_address( /* The requested address is outdated. Let's ignore the request. */ return 0; - if (address_get(link, address, &existing) < 0) { - if (address_get_request(link, address, NULL) >= 0) - return 0; /* already requested, skipping. */ - - r = address_acquire(link, address, &tmp); - if (r < 0) - return log_link_warning_errno(link, r, "Failed to acquire an address from pool: %m"); + if (address_get_request(link, address, NULL) >= 0) + return 0; /* already requested, skipping. */ - /* Consider address tentative until we get the real flags from the kernel */ - tmp->flags |= IFA_F_TENTATIVE; - - } else { - r = address_dup(address, &tmp); - if (r < 0) - return log_oom(); + r = address_dup(address, &tmp); + if (r < 0) + return log_oom(); + if (address_get(link, address, &existing) >= 0) { /* Copy already assigned address when it is requested as a null address. */ if (address_is_static_null(address)) tmp->in_addr = existing->in_addr; @@ -1557,16 +1696,10 @@ int link_request_address( tmp->state = existing->state; } - address_set_broadcast(tmp, link); - - r = ipv4acd_configure(link, tmp); - if (r < 0) - return r; - log_address_debug(tmp, "Requesting", link); r = link_queue_request_safe(link, REQUEST_TYPE_ADDRESS, tmp, - address_free, + address_unref, address_hash_func, address_compare_func, address_process_request, @@ -1641,29 +1774,8 @@ int link_request_static_addresses(Link *link) { return 0; } -void address_cancel_request(Address *address) { - Request req; - - assert(address); - assert(address->link); - - if (!address_is_requesting(address)) - return; - - req = (Request) { - .link = address->link, - .type = REQUEST_TYPE_ADDRESS, - .userdata = address, - .hash_func = (hash_func_t) address_hash_func, - .compare_func = (compare_func_t) address_compare_func, - }; - - request_detach(address->link->manager, &req); - address_cancel_requesting(address); -} - int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { - _cleanup_(address_freep) Address *tmp = NULL; + _cleanup_(address_unrefp) Address *tmp = NULL; struct ifa_cacheinfo cinfo; Link *link; uint16_t type; @@ -1786,9 +1898,11 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, if (type == RTM_DELADDR) { if (address) { + bool removed_by_us = FLAGS_SET(address->state, NETWORK_CONFIG_STATE_REMOVING); + address_enter_removed(address); log_address_debug(address, "Forgetting removed", link); - (void) address_drop(address); + (void) address_drop(address, removed_by_us); } else log_address_debug(tmp, "Kernel removed unknown", link); @@ -1800,13 +1914,13 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, if (!address) { /* If we did not know the address, then save it. */ - r = address_add(link, tmp); + r = address_attach(link, tmp); if (r < 0) { log_link_warning_errno(link, r, "Failed to save received address %s, ignoring: %m", IN_ADDR_PREFIX_TO_STRING(tmp->family, &tmp->in_addr, tmp->prefixlen)); return 0; } - address = TAKE_PTR(tmp); + address = tmp; is_new = true; @@ -1827,6 +1941,10 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, (void) nft_set_context_dup(&a->nft_set_context, &address->nft_set_context); address->requested_as_null = a->requested_as_null; address->callback = a->callback; + + ipv6_token_ref(a->token); + ipv6_token_unref(address->token); + address->token = a->token; } /* Then, update miscellaneous info. */ @@ -1906,7 +2024,7 @@ int config_parse_broadcast( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; union in_addr_union u; int r; @@ -1983,7 +2101,7 @@ int config_parse_address( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; union in_addr_union buffer; unsigned char prefixlen; int r, f; @@ -1994,10 +2112,16 @@ int config_parse_address( assert(rvalue); assert(data); - if (streq(section, "Network")) + if (streq(section, "Network")) { + if (isempty(rvalue)) { + /* If an empty string specified in [Network] section, clear previously assigned addresses. */ + network->addresses_by_section = ordered_hashmap_free(network->addresses_by_section); + return 0; + } + /* we are not in an Address section, so use line number instead. */ r = address_new_static(network, filename, line, &n); - else + } else r = address_new_static(network, filename, section_line, &n); if (r == -ENOMEM) return log_oom(); @@ -2064,7 +2188,7 @@ int config_parse_label( void *data, void *userdata) { - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; Network *network = userdata; int r; @@ -2116,7 +2240,7 @@ int config_parse_lifetime( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; usec_t k; int r; @@ -2165,7 +2289,7 @@ int config_parse_address_flags( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; int r; assert(filename); @@ -2212,7 +2336,7 @@ int config_parse_address_scope( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; int r; assert(filename); @@ -2256,7 +2380,7 @@ int config_parse_address_route_metric( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; int r; assert(filename); @@ -2298,7 +2422,7 @@ int config_parse_duplicate_address_detection( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; int r; assert(filename); @@ -2352,7 +2476,7 @@ int config_parse_address_netlabel( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; int r; assert(filename); @@ -2494,8 +2618,8 @@ int network_drop_invalid_addresses(Network *network) { if (address_section_verify(address) < 0) { /* Drop invalid [Address] sections or Address= settings in [Network]. - * Note that address_free() will drop the address from addresses_by_section. */ - address_free(address); + * Note that address_detach() will drop the address from addresses_by_section. */ + address_detach(address); continue; } @@ -2508,12 +2632,13 @@ int network_drop_invalid_addresses(Network *network) { IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen), address->section->line, dup->section->line, dup->section->line); - /* address_free() will drop the address from addresses_by_section. */ - address_free(dup); + + /* address_detach() will drop the address from addresses_by_section. */ + address_detach(dup); } - /* Use address_hash_ops, instead of address_hash_ops_free. Otherwise, the Address objects - * will be freed. */ + /* Use address_hash_ops, instead of address_hash_ops_detach. Otherwise, the Address objects + * will be detached. */ r = set_ensure_put(&addresses, &address_hash_ops, address); if (r < 0) return log_oom(); @@ -2540,7 +2665,7 @@ int config_parse_address_ip_nft_set( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; int r; assert(filename); diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h index 5be2f77..e09551e 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -10,6 +10,7 @@ #include "hash-funcs.h" #include "in-addr-util.h" #include "network-util.h" +#include "networkd-address-generation.h" #include "networkd-link.h" #include "networkd-util.h" #include "time-util.h" @@ -34,6 +35,8 @@ struct Address { NetworkConfigState state; union in_addr_union provider; /* DHCP server or router address */ + unsigned n_ref; + int family; unsigned char prefixlen; unsigned char scope; @@ -55,11 +58,15 @@ struct Address { bool scope_set:1; bool ip_masquerade_done:1; bool requested_as_null:1; + bool used_by_dhcp_server:1; /* duplicate_address_detection is only used by static or IPv4 dynamic addresses. * To control DAD for IPv6 dynamic addresses, set IFA_F_NODAD to flags. */ AddressFamily duplicate_address_detection; + /* Used by address generator. */ + IPv6Token *token; + /* Called when address become ready */ address_ready_callback_t callback; @@ -83,21 +90,25 @@ void link_get_address_states( extern const struct hash_ops address_hash_ops; +bool address_can_update(const Address *existing, const Address *requesting); + +Address* address_ref(Address *address); +Address* address_unref(Address *address); + int address_new(Address **ret); int address_new_static(Network *network, const char *filename, unsigned section_line, Address **ret); -Address* address_free(Address *address); int address_get(Link *link, const Address *in, Address **ret); int address_get_harder(Link *link, const Address *in, Address **ret); int address_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, const char *error_msg); -int address_remove(Address *address); -int address_remove_and_drop(Address *address); +int address_remove(Address *address, Link *link); +int address_remove_and_cancel(Address *address, Link *link); int address_dup(const Address *src, Address **ret); bool address_is_ready(const Address *a); bool link_check_addresses_ready(Link *link, NetworkConfigSource source); -DEFINE_SECTION_CLEANUP_FUNCTIONS(Address, address_free); +DEFINE_SECTION_CLEANUP_FUNCTIONS(Address, address_unref); -int link_drop_managed_addresses(Link *link); +int link_drop_static_addresses(Link *link); int link_drop_foreign_addresses(Link *link); int link_drop_ipv6ll_addresses(Link *link); void link_foreignize_addresses(Link *link); @@ -112,9 +123,8 @@ static inline int link_get_ipv4_address(Link *link, const struct in_addr *addres return link_get_address(link, AF_INET, &(union in_addr_union) { .in = *address }, prefixlen, ret); } int manager_get_address(Manager *manager, int family, const union in_addr_union *address, unsigned char prefixlen, Address **ret); -bool manager_has_address(Manager *manager, int family, const union in_addr_union *address, bool check_ready); +bool manager_has_address(Manager *manager, int family, const union in_addr_union *address); -void address_cancel_request(Address *address); int link_request_address( Link *link, const Address *address, diff --git a/src/network/networkd-bridge-mdb.c b/src/network/networkd-bridge-mdb.c index bd1a974..7ff4a18 100644 --- a/src/network/networkd-bridge-mdb.c +++ b/src/network/networkd-bridge-mdb.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> #include <linux/if_bridge.h> diff --git a/src/network/networkd-bridge-vlan.c b/src/network/networkd-bridge-vlan.c index 36e3610..0deffa4 100644 --- a/src/network/networkd-bridge-vlan.c +++ b/src/network/networkd-bridge-vlan.c @@ -17,166 +17,327 @@ #include "parse-util.h" #include "vlan-util.h" -static bool is_bit_set(unsigned bit, uint32_t scope) { - assert(bit < sizeof(scope)*8); - return scope & (UINT32_C(1) << bit); +static bool is_bit_set(unsigned nr, const uint32_t *addr) { + assert(nr < BRIDGE_VLAN_BITMAP_MAX); + return addr[nr / 32] & (UINT32_C(1) << (nr % 32)); } static void set_bit(unsigned nr, uint32_t *addr) { - if (nr < BRIDGE_VLAN_BITMAP_MAX) - addr[nr / 32] |= (UINT32_C(1) << (nr % 32)); + assert(nr < BRIDGE_VLAN_BITMAP_MAX); + addr[nr / 32] |= (UINT32_C(1) << (nr % 32)); } -static int find_next_bit(int i, uint32_t x) { - int j; +static int add_single(sd_netlink_message *m, uint16_t id, bool untagged, bool is_pvid, char **str) { + assert(m); + assert(id < BRIDGE_VLAN_BITMAP_MAX); + + if (DEBUG_LOGGING) + (void) strextendf_with_separator(str, ",", "%u%s%s%s%s%s", id, + (untagged || is_pvid) ? "(" : "", + untagged ? "untagged" : "", + (untagged && is_pvid) ? "," : "", + is_pvid ? "pvid" : "", + (untagged || is_pvid) ? ")" : ""); + + return sd_netlink_message_append_data(m, IFLA_BRIDGE_VLAN_INFO, + &(struct bridge_vlan_info) { + .vid = id, + .flags = (untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0) | + (is_pvid ? BRIDGE_VLAN_INFO_PVID : 0), + }, + sizeof(struct bridge_vlan_info)); +} + +static int add_range(sd_netlink_message *m, uint16_t begin, uint16_t end, bool untagged, char **str) { + int r; + + assert(m); + assert(begin <= end); + assert(end < BRIDGE_VLAN_BITMAP_MAX); - if (i >= 32) - return -1; + if (begin == end) + return add_single(m, begin, untagged, /* is_pvid = */ false, str); + + if (DEBUG_LOGGING) + (void) strextendf_with_separator(str, ",", "%u-%u%s", begin, end, untagged ? "(untagged)" : ""); + + r = sd_netlink_message_append_data(m, IFLA_BRIDGE_VLAN_INFO, + &(struct bridge_vlan_info) { + .vid = begin, + .flags = (untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0) | + BRIDGE_VLAN_INFO_RANGE_BEGIN, + }, + sizeof(struct bridge_vlan_info)); + if (r < 0) + return r; - /* find first bit */ - if (i < 0) - return BUILTIN_FFS_U32(x); + r = sd_netlink_message_append_data(m, IFLA_BRIDGE_VLAN_INFO, + &(struct bridge_vlan_info) { + .vid = end, + .flags = (untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0) | + BRIDGE_VLAN_INFO_RANGE_END, + }, + sizeof(struct bridge_vlan_info)); + if (r < 0) + return r; - /* mask off prior finds to get next */ - j = __builtin_ffs(x >> i); - return j ? j + i : 0; + return 0; } -int bridge_vlan_append_info( - const Link *link, - sd_netlink_message *req, - uint16_t pvid, - const uint32_t *br_vid_bitmap, - const uint32_t *br_untagged_bitmap) { +static uint16_t link_get_pvid(Link *link, bool *ret_untagged) { + assert(link); + assert(link->network); + + if (vlanid_is_valid(link->network->bridge_vlan_pvid)) { + if (ret_untagged) + *ret_untagged = is_bit_set(link->network->bridge_vlan_pvid, + link->network->bridge_vlan_untagged_bitmap); + return link->network->bridge_vlan_pvid; + } - struct bridge_vlan_info br_vlan; - bool done, untagged = false; - uint16_t begin, end; - int r, cnt; + if (link->network->bridge_vlan_pvid == BRIDGE_VLAN_KEEP_PVID) { + if (ret_untagged) + *ret_untagged = link->bridge_vlan_pvid_is_untagged; + return link->bridge_vlan_pvid; + } + + if (ret_untagged) + *ret_untagged = false; + return UINT16_MAX; +} + +static int bridge_vlan_append_set_info(Link *link, sd_netlink_message *m) { + _cleanup_free_ char *str = NULL; + uint16_t pvid, begin = UINT16_MAX; + bool untagged, pvid_is_untagged; + int r; assert(link); - assert(req); - assert(br_vid_bitmap); - assert(br_untagged_bitmap); - - cnt = 0; - - begin = end = UINT16_MAX; - for (int k = 0; k < BRIDGE_VLAN_BITMAP_LEN; k++) { - uint32_t untagged_map = br_untagged_bitmap[k]; - uint32_t vid_map = br_vid_bitmap[k]; - unsigned base_bit = k * 32; - int i = -1; - - done = false; - do { - int j = find_next_bit(i, vid_map); - if (j > 0) { - /* first hit of any bit */ - if (begin == UINT16_MAX && end == UINT16_MAX) { - begin = end = j - 1 + base_bit; - untagged = is_bit_set(j - 1, untagged_map); - goto next; - } - - /* this bit is a continuation of prior bits */ - if (j - 2 + base_bit == end && untagged == is_bit_set(j - 1, untagged_map) && (uint16_t)j - 1 + base_bit != pvid && (uint16_t)begin != pvid) { - end++; - goto next; - } - } else - done = true; + assert(link->network); + assert(m); + + pvid = link_get_pvid(link, &pvid_is_untagged); + for (uint16_t k = 0; k < BRIDGE_VLAN_BITMAP_MAX; k++) { + + if (k == pvid) { + /* PVID needs to be sent alone. Finish previous bits. */ if (begin != UINT16_MAX) { - cnt++; - if (done && k < BRIDGE_VLAN_BITMAP_LEN - 1) - break; - - br_vlan.flags = 0; - if (untagged) - br_vlan.flags |= BRIDGE_VLAN_INFO_UNTAGGED; - - if (begin == end) { - br_vlan.vid = begin; - - if (begin == pvid) - br_vlan.flags |= BRIDGE_VLAN_INFO_PVID; - - r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan)); - if (r < 0) - return r; - } else { - br_vlan.vid = begin; - br_vlan.flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN; - - r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan)); - if (r < 0) - return r; - - br_vlan.vid = end; - br_vlan.flags &= ~BRIDGE_VLAN_INFO_RANGE_BEGIN; - br_vlan.flags |= BRIDGE_VLAN_INFO_RANGE_END; - - r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan)); - if (r < 0) - return r; - } - - if (done) - break; + assert(begin < k); + + r = add_range(m, begin, k - 1, untagged, &str); + if (r < 0) + return r; + + begin = UINT16_MAX; } - if (j > 0) { - begin = end = j - 1 + base_bit; - untagged = is_bit_set(j - 1, untagged_map); + + r = add_single(m, pvid, pvid_is_untagged, /* is_pvid = */ true, &str); + if (r < 0) + return r; + + continue; + } + + if (!is_bit_set(k, link->network->bridge_vlan_bitmap)) { + /* This bit is not set. Finish previous bits. */ + if (begin != UINT16_MAX) { + assert(begin < k); + + r = add_range(m, begin, k - 1, untagged, &str); + if (r < 0) + return r; + + begin = UINT16_MAX; } - next: - i = j; - } while (!done); + continue; + } + + if (begin != UINT16_MAX) { + bool u; + + assert(begin < k); + + u = is_bit_set(k, link->network->bridge_vlan_untagged_bitmap); + if (untagged == u) + continue; + + /* Tagging flag is changed from the previous bits. Finish them. */ + r = add_range(m, begin, k - 1, untagged, &str); + if (r < 0) + return r; + + begin = k; + untagged = u; + continue; + } + + /* This is the starting point of a new bit sequence. Save the position and the tagging flag. */ + begin = k; + untagged = is_bit_set(k, link->network->bridge_vlan_untagged_bitmap); } - assert(cnt > 0); - return cnt; + /* No pending bit sequence. + * Why? There is a trick. The conf parsers below only accepts vlan ID in the range 0…4094, but in + * the above loop, we run 0…4095. */ + assert_cc(BRIDGE_VLAN_BITMAP_MAX > VLANID_MAX); + assert(begin == UINT16_MAX); + + log_link_debug(link, "Setting Bridge VLAN IDs: %s", strna(str)); + return 0; } -void network_adjust_bridge_vlan(Network *network) { - assert(network); +static int bridge_vlan_append_del_info(Link *link, sd_netlink_message *m) { + _cleanup_free_ char *str = NULL; + uint16_t pvid, begin = UINT16_MAX; + int r; - if (!network->use_br_vlan) - return; + assert(link); + assert(link->network); + assert(m); - /* pvid might not be in br_vid_bitmap yet */ - if (network->pvid) - set_bit(network->pvid, network->br_vid_bitmap); -} + pvid = link_get_pvid(link, NULL); -int config_parse_brvlan_pvid( - 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) { + for (uint16_t k = 0; k < BRIDGE_VLAN_BITMAP_MAX; k++) { + + if (k == pvid || + !is_bit_set(k, link->bridge_vlan_bitmap) || + is_bit_set(k, link->network->bridge_vlan_bitmap)) { + /* This bit is not necessary to be removed. Finish previous bits. */ + if (begin != UINT16_MAX) { + assert(begin < k); + + r = add_range(m, begin, k - 1, /* untagged = */ false, &str); + if (r < 0) + return r; + + begin = UINT16_MAX; + } + + continue; + } + + if (begin != UINT16_MAX) + continue; + + /* This is the starting point of a new bit sequence. Save the position. */ + begin = k; + } + + /* No pending bit sequence. */ + assert(begin == UINT16_MAX); - Network *network = userdata; - uint16_t pvid; + log_link_debug(link, "Removing Bridge VLAN IDs: %s", strna(str)); + return 0; +} + +int bridge_vlan_set_message(Link *link, sd_netlink_message *m, bool is_set) { int r; - r = parse_vlanid(rvalue, &pvid); + assert(link); + assert(m); + + r = sd_rtnl_message_link_set_family(m, AF_BRIDGE); + if (r < 0) + return r; + + r = sd_netlink_message_open_container(m, IFLA_AF_SPEC); if (r < 0) return r; - network->pvid = pvid; - network->use_br_vlan = true; + if (link->master_ifindex <= 0) { + /* master needs BRIDGE_FLAGS_SELF flag */ + r = sd_netlink_message_append_u16(m, IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_SELF); + if (r < 0) + return r; + } + + if (is_set) + r = bridge_vlan_append_set_info(link, m); + else + r = bridge_vlan_append_del_info(link, m); + if (r < 0) + return r; + + r = sd_netlink_message_close_container(m); + if (r < 0) + return r; return 0; } -int config_parse_brvlan_vlan( +int link_update_bridge_vlan(Link *link, sd_netlink_message *m) { + _cleanup_free_ void *data = NULL; + size_t len; + uint16_t begin = UINT16_MAX; + int r, family; + + assert(link); + assert(m); + + r = sd_rtnl_message_get_family(m, &family); + if (r < 0) + return r; + + if (family != AF_BRIDGE) + return 0; + + r = sd_netlink_message_read_data(m, IFLA_AF_SPEC, &len, &data); + if (r == -ENODATA) + return 0; + if (r < 0) + return r; + + memzero(link->bridge_vlan_bitmap, sizeof(link->bridge_vlan_bitmap)); + + for (struct rtattr *rta = data; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { + struct bridge_vlan_info *p; + + if (RTA_TYPE(rta) != IFLA_BRIDGE_VLAN_INFO) + continue; + if (RTA_PAYLOAD(rta) != sizeof(struct bridge_vlan_info)) + continue; + + p = RTA_DATA(rta); + + if (FLAGS_SET(p->flags, BRIDGE_VLAN_INFO_RANGE_BEGIN)) { + begin = p->vid; + continue; + } + + if (FLAGS_SET(p->flags, BRIDGE_VLAN_INFO_RANGE_END)) { + for (uint16_t k = begin; k <= p->vid; k++) + set_bit(k, link->bridge_vlan_bitmap); + + begin = UINT16_MAX; + continue; + } + + if (FLAGS_SET(p->flags, BRIDGE_VLAN_INFO_PVID)) { + link->bridge_vlan_pvid = p->vid; + link->bridge_vlan_pvid_is_untagged = FLAGS_SET(p->flags, BRIDGE_VLAN_INFO_UNTAGGED); + } + + set_bit(p->vid, link->bridge_vlan_bitmap); + begin = UINT16_MAX; + } + + return 0; +} + +void network_adjust_bridge_vlan(Network *network) { + assert(network); + + for (uint16_t k = 0; k < BRIDGE_VLAN_BITMAP_MAX; k++) + if (is_bit_set(k, network->bridge_vlan_untagged_bitmap)) + set_bit(k, network->bridge_vlan_bitmap); + + if (vlanid_is_valid(network->bridge_vlan_pvid)) + set_bit(network->bridge_vlan_pvid, network->bridge_vlan_bitmap); +} + +int config_parse_bridge_vlan_id( const char *unit, const char *filename, unsigned line, @@ -188,30 +349,37 @@ int config_parse_brvlan_vlan( void *data, void *userdata) { - Network *network = userdata; - uint16_t vid, vid_end; + uint16_t v, *id = ASSERT_PTR(data); int r; assert(filename); assert(section); assert(lvalue); assert(rvalue); - assert(data); - r = parse_vid_range(rvalue, &vid, &vid_end); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse VLAN, ignoring: %s", rvalue); + if (isempty(rvalue)) { + *id = BRIDGE_VLAN_KEEP_PVID; return 0; } - for (; vid <= vid_end; vid++) - set_bit(vid, network->br_vid_bitmap); + if (parse_boolean(rvalue) == 0) { + *id = BRIDGE_VLAN_REMOVE_PVID; + return 0; + } - network->use_br_vlan = true; + r = parse_vlanid(rvalue, &v); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring: %s", + lvalue, rvalue); + return 0; + } + + *id = v; return 0; } -int config_parse_brvlan_untagged( +int config_parse_bridge_vlan_id_range( const char *unit, const char *filename, unsigned line, @@ -223,7 +391,7 @@ int config_parse_brvlan_untagged( void *data, void *userdata) { - Network *network = userdata; + uint32_t *bitmap = ASSERT_PTR(data); uint16_t vid, vid_end; int r; @@ -231,19 +399,22 @@ int config_parse_brvlan_untagged( assert(section); assert(lvalue); assert(rvalue); - assert(data); + + if (isempty(rvalue)) { + memzero(bitmap, BRIDGE_VLAN_BITMAP_LEN * sizeof(uint32_t)); + return 0; + } r = parse_vid_range(rvalue, &vid, &vid_end); if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, "Could not parse VLAN: %s", rvalue); + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring: %s", + lvalue, rvalue); return 0; } - for (; vid <= vid_end; vid++) { - set_bit(vid, network->br_vid_bitmap); - set_bit(vid, network->br_untagged_bitmap); - } + for (; vid <= vid_end; vid++) + set_bit(vid, bitmap); - network->use_br_vlan = true; return 0; } diff --git a/src/network/networkd-bridge-vlan.h b/src/network/networkd-bridge-vlan.h index f44b810..0366cc6 100644 --- a/src/network/networkd-bridge-vlan.h +++ b/src/network/networkd-bridge-vlan.h @@ -6,26 +6,28 @@ ***/ #include <inttypes.h> +#include <stdbool.h> #include "sd-netlink.h" #include "conf-parser.h" +#include "vlan-util.h" #define BRIDGE_VLAN_BITMAP_MAX 4096 #define BRIDGE_VLAN_BITMAP_LEN (BRIDGE_VLAN_BITMAP_MAX / 32) +#define BRIDGE_VLAN_KEEP_PVID UINT16_MAX +#define BRIDGE_VLAN_REMOVE_PVID (UINT16_MAX - 1) +assert_cc(BRIDGE_VLAN_REMOVE_PVID > VLANID_MAX); + typedef struct Link Link; typedef struct Network Network; void network_adjust_bridge_vlan(Network *network); -int bridge_vlan_append_info( - const Link * link, - sd_netlink_message *req, - uint16_t pvid, - const uint32_t *br_vid_bitmap, - const uint32_t *br_untagged_bitmap); +int bridge_vlan_set_message(Link *link, sd_netlink_message *m, bool is_set); + +int link_update_bridge_vlan(Link *link, sd_netlink_message *m); -CONFIG_PARSER_PROTOTYPE(config_parse_brvlan_pvid); -CONFIG_PARSER_PROTOTYPE(config_parse_brvlan_vlan); -CONFIG_PARSER_PROTOTYPE(config_parse_brvlan_untagged); +CONFIG_PARSER_PROTOTYPE(config_parse_bridge_vlan_id); +CONFIG_PARSER_PROTOTYPE(config_parse_bridge_vlan_id_range); diff --git a/src/network/networkd-can.c b/src/network/networkd-can.c index b8a1871..a5b003a 100644 --- a/src/network/networkd-can.c +++ b/src/network/networkd-can.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> #include <linux/can/netlink.h> diff --git a/src/network/networkd-conf.c b/src/network/networkd-conf.c index 063732a..6a6814d 100644 --- a/src/network/networkd-conf.c +++ b/src/network/networkd-conf.c @@ -14,14 +14,17 @@ int manager_parse_config_file(Manager *m) { assert(m); - r = config_parse_config_file("networkd.conf", - "Network\0" - "DHCPv4\0" - "DHCPv6\0" - "DHCP\0", - config_item_perf_lookup, networkd_gperf_lookup, - CONFIG_PARSE_WARN, - m); + r = config_parse_standard_file_with_dropins( + "systemd/networkd.conf", + "Network\0" + "IPv6AcceptRA\0" + "DHCPv4\0" + "DHCPv6\0" + "DHCPServer\0" + "DHCP\0", + config_item_perf_lookup, networkd_gperf_lookup, + CONFIG_PARSE_WARN, + /* userdata= */ m); if (r < 0) return r; diff --git a/src/network/networkd-dhcp-common.c b/src/network/networkd-dhcp-common.c index 080b153..9f0268d 100644 --- a/src/network/networkd-dhcp-common.c +++ b/src/network/networkd-dhcp-common.c @@ -5,7 +5,6 @@ #include "bus-error.h" #include "bus-locator.h" -#include "dhcp-identifier.h" #include "dhcp-option.h" #include "dhcp6-internal.h" #include "escape.h" @@ -41,12 +40,12 @@ uint32_t link_get_dhcp4_route_table(Link *link) { return link_get_vrf_table(link); } -uint32_t link_get_ipv6_accept_ra_route_table(Link *link) { +uint32_t link_get_ndisc_route_table(Link *link) { assert(link); assert(link->network); - if (link->network->ipv6_accept_ra_route_table_set) - return link->network->ipv6_accept_ra_route_table; + if (link->network->ndisc_route_table_set) + return link->network->ndisc_route_table; return link_get_vrf_table(link); } @@ -282,7 +281,7 @@ int link_get_captive_portal(Link *link, const char **ret) { return r; } - if (link->network->ipv6_accept_ra_use_captive_portal) { + if (link->network->ndisc_use_captive_portal) { NDiscCaptivePortal *cp; usec_t usec = 0; @@ -410,10 +409,10 @@ int config_parse_dhcp_route_metric( /* For backward compatibility. */ if (!network->dhcp_route_metric_set) network->dhcp_route_metric = metric; - if (!network->ipv6_accept_ra_route_metric_set) { - network->ipv6_accept_ra_route_metric_high = metric; - network->ipv6_accept_ra_route_metric_medium = metric; - network->ipv6_accept_ra_route_metric_low = metric; + if (!network->ndisc_route_metric_set) { + network->ndisc_route_metric_high = metric; + network->ndisc_route_metric_medium = metric; + network->ndisc_route_metric_low = metric; } break; default: @@ -423,7 +422,7 @@ int config_parse_dhcp_route_metric( return 0; } -int config_parse_ipv6_accept_ra_route_metric( +int config_parse_ndisc_route_metric( const char *unit, const char *filename, unsigned line, @@ -448,7 +447,7 @@ int config_parse_ipv6_accept_ra_route_metric( _cleanup_free_ char *high = NULL, *medium = NULL, *low = NULL; const char *p = rvalue; - r = extract_many_words(&p, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &high, &medium, &low, NULL); + r = extract_many_words(&p, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &high, &medium, &low); if (r == -ENOMEM) return log_oom(); if (r != 3 || !isempty(p)) { @@ -473,10 +472,10 @@ int config_parse_ipv6_accept_ra_route_metric( } } - network->ipv6_accept_ra_route_metric_high = metric_high; - network->ipv6_accept_ra_route_metric_medium = metric_medium; - network->ipv6_accept_ra_route_metric_low = metric_low; - network->ipv6_accept_ra_route_metric_set = true; + network->ndisc_route_metric_high = metric_high; + network->ndisc_route_metric_medium = metric_medium; + network->ndisc_route_metric_low = metric_low; + network->ndisc_route_metric_set = true; return 0; } @@ -531,158 +530,6 @@ int config_parse_dhcp_send_hostname( return 0; } -int config_parse_dhcp_use_dns( - 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 r; - - assert(filename); - assert(lvalue); - assert(IN_SET(ltype, AF_UNSPEC, AF_INET, AF_INET6)); - assert(rvalue); - assert(data); - - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to parse UseDNS=%s, ignoring assignment: %m", rvalue); - return 0; - } - - switch (ltype) { - case AF_INET: - network->dhcp_use_dns = r; - network->dhcp_use_dns_set = true; - break; - case AF_INET6: - network->dhcp6_use_dns = r; - network->dhcp6_use_dns_set = true; - break; - case AF_UNSPEC: - /* For backward compatibility. */ - if (!network->dhcp_use_dns_set) - network->dhcp_use_dns = r; - if (!network->dhcp6_use_dns_set) - network->dhcp6_use_dns = r; - break; - default: - assert_not_reached(); - } - - return 0; -} - -int config_parse_dhcp_use_domains( - 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; - DHCPUseDomains d; - - assert(filename); - assert(lvalue); - assert(IN_SET(ltype, AF_UNSPEC, AF_INET, AF_INET6)); - assert(rvalue); - assert(data); - - d = dhcp_use_domains_from_string(rvalue); - if (d < 0) { - log_syntax(unit, LOG_WARNING, filename, line, d, - "Failed to parse %s=%s, ignoring assignment: %m", lvalue, rvalue); - return 0; - } - - switch (ltype) { - case AF_INET: - network->dhcp_use_domains = d; - network->dhcp_use_domains_set = true; - break; - case AF_INET6: - network->dhcp6_use_domains = d; - network->dhcp6_use_domains_set = true; - break; - case AF_UNSPEC: - /* For backward compatibility. */ - if (!network->dhcp_use_domains_set) - network->dhcp_use_domains = d; - if (!network->dhcp6_use_domains_set) - network->dhcp6_use_domains = d; - break; - default: - assert_not_reached(); - } - - return 0; -} - -int config_parse_dhcp_use_ntp( - 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 r; - - assert(filename); - assert(lvalue); - assert(IN_SET(ltype, AF_UNSPEC, AF_INET, AF_INET6)); - assert(rvalue); - assert(data); - - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to parse UseNTP=%s, ignoring assignment: %m", rvalue); - return 0; - } - - switch (ltype) { - case AF_INET: - network->dhcp_use_ntp = r; - network->dhcp_use_ntp_set = true; - break; - case AF_INET6: - network->dhcp6_use_ntp = r; - network->dhcp6_use_ntp_set = true; - break; - case AF_UNSPEC: - /* For backward compatibility. */ - if (!network->dhcp_use_ntp_set) - network->dhcp_use_ntp = r; - if (!network->dhcp6_use_ntp_set) - network->dhcp6_use_ntp = r; - break; - default: - assert_not_reached(); - } - - return 0; -} int config_parse_dhcp_or_ra_route_table( const char *unit, @@ -718,8 +565,8 @@ int config_parse_dhcp_or_ra_route_table( network->dhcp_route_table_set = true; break; case AF_INET6: - network->ipv6_accept_ra_route_table = rt; - network->ipv6_accept_ra_route_table_set = true; + network->ndisc_route_table = rt; + network->ndisc_route_table_set = true; break; default: assert_not_reached(); @@ -1138,14 +985,6 @@ int config_parse_dhcp_request_options( } } -static const char* const dhcp_use_domains_table[_DHCP_USE_DOMAINS_MAX] = { - [DHCP_USE_DOMAINS_NO] = "no", - [DHCP_USE_DOMAINS_ROUTE] = "route", - [DHCP_USE_DOMAINS_YES] = "yes", -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dhcp_use_domains, DHCPUseDomains, DHCP_USE_DOMAINS_YES); - static const char * const dhcp_option_data_type_table[_DHCP_OPTION_DATA_MAX] = { [DHCP_OPTION_DATA_UINT8] = "uint8", [DHCP_OPTION_DATA_UINT16] = "uint16", diff --git a/src/network/networkd-dhcp-common.h b/src/network/networkd-dhcp-common.h index 6e3f3b2..3390d7d 100644 --- a/src/network/networkd-dhcp-common.h +++ b/src/network/networkd-dhcp-common.h @@ -4,7 +4,7 @@ #include <netinet/in.h> #include "conf-parser.h" -#include "dhcp-identifier.h" +#include "dhcp-duid-internal.h" #include "in-addr-util.h" #include "set.h" #include "time-util.h" @@ -24,14 +24,6 @@ typedef struct Link Link; typedef struct Manager Manager; typedef struct Network Network; -typedef enum DHCPUseDomains { - DHCP_USE_DOMAINS_NO, - DHCP_USE_DOMAINS_YES, - DHCP_USE_DOMAINS_ROUTE, - _DHCP_USE_DOMAINS_MAX, - _DHCP_USE_DOMAINS_INVALID = -EINVAL, -} DHCPUseDomains; - typedef enum DHCPOptionDataType { DHCP_OPTION_DATA_UINT8, DHCP_OPTION_DATA_UINT16, @@ -54,7 +46,7 @@ typedef struct DUID { } DUID; uint32_t link_get_dhcp4_route_table(Link *link); -uint32_t link_get_ipv6_accept_ra_route_table(Link *link); +uint32_t link_get_ndisc_route_table(Link *link); bool link_dhcp_enabled(Link *link, int family); static inline bool link_dhcp4_enabled(Link *link) { @@ -87,19 +79,13 @@ static inline bool in6_prefix_is_filtered(const struct in6_addr *prefix, uint8_t int link_get_captive_portal(Link *link, const char **ret); -const char* dhcp_use_domains_to_string(DHCPUseDomains p) _const_; -DHCPUseDomains dhcp_use_domains_from_string(const char *s) _pure_; - const char *dhcp_option_data_type_to_string(DHCPOptionDataType d) _const_; DHCPOptionDataType dhcp_option_data_type_from_string(const char *d) _pure_; CONFIG_PARSER_PROTOTYPE(config_parse_dhcp); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_route_metric); -CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_accept_ra_route_metric); +CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_route_metric); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_send_hostname); -CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_dns); -CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_domains); -CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_ntp); CONFIG_PARSER_PROTOTYPE(config_parse_iaid); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_or_ra_route_table); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_user_or_vendor_class); diff --git a/src/network/networkd-dhcp-prefix-delegation.c b/src/network/networkd-dhcp-prefix-delegation.c index af2fe9e..2e660b7 100644 --- a/src/network/networkd-dhcp-prefix-delegation.c +++ b/src/network/networkd-dhcp-prefix-delegation.c @@ -101,6 +101,7 @@ static int link_get_by_dhcp_pd_subnet_prefix(Manager *manager, const struct in6_ static int dhcp_pd_get_assigned_subnet_prefix(Link *link, const struct in6_addr *pd_prefix, uint8_t pd_prefix_len, struct in6_addr *ret) { assert(link); + assert(link->manager); assert(pd_prefix); if (!link_dhcp_pd_is_enabled(link)) @@ -128,11 +129,14 @@ static int dhcp_pd_get_assigned_subnet_prefix(Link *link, const struct in6_addr } else { Route *route; - SET_FOREACH(route, link->routes) { + SET_FOREACH(route, link->manager->routes) { if (route->source != NETWORK_CONFIG_SOURCE_DHCP_PD) continue; assert(route->family == AF_INET6); + if (route->nexthop.ifindex != link->ifindex) + continue; + if (in6_addr_prefix_covers(pd_prefix, pd_prefix_len, &route->dst.in6) > 0) { if (ret) *ret = route->dst.in6; @@ -145,7 +149,7 @@ static int dhcp_pd_get_assigned_subnet_prefix(Link *link, const struct in6_addr } int dhcp_pd_remove(Link *link, bool only_marked) { - int k, r = 0; + int ret = 0; assert(link); assert(link->manager); @@ -159,9 +163,11 @@ int dhcp_pd_remove(Link *link, bool only_marked) { if (!link->network->dhcp_pd_assign) { Route *route; - SET_FOREACH(route, link->routes) { + SET_FOREACH(route, link->manager->routes) { if (route->source != NETWORK_CONFIG_SOURCE_DHCP_PD) continue; + if (route->nexthop.ifindex != link->ifindex) + continue; if (only_marked && !route_is_marked(route)) continue; @@ -170,11 +176,7 @@ int dhcp_pd_remove(Link *link, bool only_marked) { link_remove_dhcp_pd_subnet_prefix(link, &route->dst.in6); - k = route_remove(route); - if (k < 0) - r = k; - - route_cancel_request(route, link); + RET_GATHER(ret, route_remove_and_cancel(route, link->manager)); } } else { Address *address; @@ -195,13 +197,11 @@ int dhcp_pd_remove(Link *link, bool only_marked) { link_remove_dhcp_pd_subnet_prefix(link, &prefix); - k = address_remove_and_drop(address); - if (k < 0) - r = k; + RET_GATHER(ret, address_remove_and_cancel(address, link)); } } - return r; + return ret; } static int dhcp_pd_check_ready(Link *link); @@ -270,7 +270,7 @@ static int dhcp_pd_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Reques assert(link); - r = route_configure_handler_internal(rtnl, m, link, "Failed to add prefix route for DHCP delegated subnet prefix"); + r = route_configure_handler_internal(rtnl, m, link, route, "Failed to add prefix route for DHCP delegated subnet prefix"); if (r <= 0) return r; @@ -282,11 +282,12 @@ static int dhcp_pd_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Reques } static int dhcp_pd_request_route(Link *link, const struct in6_addr *prefix, usec_t lifetime_usec) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; Route *existing; int r; assert(link); + assert(link->manager); assert(link->network); assert(prefix); @@ -305,13 +306,16 @@ static int dhcp_pd_request_route(Link *link, const struct in6_addr *prefix, usec route->priority = link->network->dhcp_pd_route_metric; route->lifetime_usec = lifetime_usec; - if (route_get(NULL, link, route, &existing) < 0) + r = route_adjust_nexthops(route, link); + if (r < 0) + return r; + + if (route_get(link->manager, route, &existing) < 0) link->dhcp_pd_configured = false; else route_unmark(existing); - r = link_request_route(link, TAKE_PTR(route), true, &link->dhcp_pd_messages, - dhcp_pd_route_handler, NULL); + r = link_request_route(link, route, &link->dhcp_pd_messages, dhcp_pd_route_handler); if (r < 0) return log_link_error_errno(link, r, "Failed to request DHCP-PD prefix route: %m"); @@ -349,14 +353,50 @@ static void log_dhcp_pd_address(Link *link, const Address *address) { FORMAT_LIFETIME(address->lifetime_preferred_usec)); } +static int dhcp_pd_request_address_one(Address *address, Link *link) { + Address *existing; + + assert(address); + assert(link); + + log_dhcp_pd_address(link, address); + + if (address_get(link, address, &existing) < 0) + link->dhcp_pd_configured = false; + else + address_unmark(existing); + + return link_request_address(link, address, &link->dhcp_pd_messages, dhcp_pd_address_handler, NULL); +} + +int dhcp_pd_reconfigure_address(Address *address, Link *link) { + int r; + + assert(address); + assert(address->source == NETWORK_CONFIG_SOURCE_DHCP_PD); + assert(link); + + r = regenerate_address(address, link); + if (r <= 0) + return r; + + r = dhcp_pd_request_address_one(address, link); + if (r < 0) + return r; + + if (!link->dhcp_pd_configured) + link_set_state(link, LINK_STATE_CONFIGURING); + + link_check_ready(link); + return 0; +} + static int dhcp_pd_request_address( Link *link, const struct in6_addr *prefix, usec_t lifetime_preferred_usec, usec_t lifetime_valid_usec) { - _cleanup_set_free_ Set *addresses = NULL; - struct in6_addr *a; int r; assert(link); @@ -366,13 +406,15 @@ static int dhcp_pd_request_address( if (!link->network->dhcp_pd_assign) return 0; - r = dhcp_pd_generate_addresses(link, prefix, &addresses); + _cleanup_hashmap_free_ Hashmap *tokens_by_address = NULL; + r = dhcp_pd_generate_addresses(link, prefix, &tokens_by_address); if (r < 0) return log_link_warning_errno(link, r, "Failed to generate addresses for acquired DHCP delegated prefix: %m"); - SET_FOREACH(a, addresses) { - _cleanup_(address_freep) Address *address = NULL; - Address *existing; + IPv6Token *token; + struct in6_addr *a; + HASHMAP_FOREACH_KEY(token, a, tokens_by_address) { + _cleanup_(address_unrefp) Address *address = NULL; r = address_new(&address); if (r < 0) @@ -386,20 +428,13 @@ static int dhcp_pd_request_address( address->lifetime_valid_usec = lifetime_valid_usec; SET_FLAG(address->flags, IFA_F_MANAGETEMPADDR, link->network->dhcp_pd_manage_temporary_address); address->route_metric = link->network->dhcp_pd_route_metric; - - log_dhcp_pd_address(link, address); + address->token = ipv6_token_ref(token); r = free_and_strdup_warn(&address->netlabel, link->network->dhcp_pd_netlabel); if (r < 0) return r; - if (address_get(link, address, &existing) < 0) - link->dhcp_pd_configured = false; - else - address_unmark(existing); - - r = link_request_address(link, address, &link->dhcp_pd_messages, - dhcp_pd_address_handler, NULL); + r = dhcp_pd_request_address_one(address, link); if (r < 0) return log_link_error_errno(link, r, "Failed to request DHCP delegated prefix address: %m"); } @@ -550,7 +585,7 @@ static int dhcp_pd_prepare(Link *link) { return 0; link_mark_addresses(link, NETWORK_CONFIG_SOURCE_DHCP_PD); - link_mark_routes(link, NETWORK_CONFIG_SOURCE_DHCP_PD); + manager_mark_routes(link->manager, link, NETWORK_CONFIG_SOURCE_DHCP_PD); return 1; } @@ -607,9 +642,7 @@ void dhcp_pd_prefix_lost(Link *uplink) { .address = route->dst })) continue; - (void) route_remove(route); - - route_cancel_request(route, uplink); + (void) route_remove_and_cancel(route, uplink->manager); } set_clear(uplink->dhcp_pd_prefixes); @@ -630,7 +663,7 @@ static int dhcp4_unreachable_route_handler(sd_netlink *rtnl, sd_netlink_message assert(link); - r = route_configure_handler_internal(rtnl, m, link, "Failed to set unreachable route for DHCPv4 delegated prefix"); + r = route_configure_handler_internal(rtnl, m, link, route, "Failed to set unreachable route for DHCPv4 delegated prefix"); if (r <= 0) return r; @@ -646,7 +679,7 @@ static int dhcp6_unreachable_route_handler(sd_netlink *rtnl, sd_netlink_message assert(link); - r = route_configure_handler_internal(rtnl, m, link, "Failed to set unreachable route for DHCPv6 delegated prefix"); + r = route_configure_handler_internal(rtnl, m, link, route, "Failed to set unreachable route for DHCPv6 delegated prefix"); if (r <= 0) return r; @@ -668,11 +701,12 @@ static int dhcp_request_unreachable_route( route_netlink_handler_t callback, bool *configured) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; Route *existing; int r; assert(link); + assert(link->manager); assert(addr); assert(IN_SET(source, NETWORK_CONFIG_SOURCE_DHCP4, NETWORK_CONFIG_SOURCE_DHCP6)); assert(server_address); @@ -700,12 +734,16 @@ static int dhcp_request_unreachable_route( route->priority = IP6_RT_PRIO_USER; route->lifetime_usec = lifetime_usec; - if (route_get(link->manager, NULL, route, &existing) < 0) + r = route_adjust_nexthops(route, link); + if (r < 0) + return r; + + if (route_get(link->manager, route, &existing) < 0) *configured = false; else route_unmark(existing); - r = link_request_route(link, TAKE_PTR(route), true, counter, callback, NULL); + r = link_request_route(link, route, counter, callback); if (r < 0) return log_link_error_errno(link, r, "Failed to request unreachable route for DHCP delegated prefix %s: %m", IN6_ADDR_PREFIX_TO_STRING(addr, prefixlen)); @@ -774,11 +812,12 @@ static int dhcp_pd_prefix_add(Link *link, const struct in6_addr *prefix, uint8_t } static int dhcp4_pd_request_default_gateway_on_6rd_tunnel(Link *link, const struct in_addr *br_address, usec_t lifetime_usec) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; Route *existing; int r; assert(link); + assert(link->manager); assert(br_address); r = route_new(&route); @@ -787,20 +826,23 @@ static int dhcp4_pd_request_default_gateway_on_6rd_tunnel(Link *link, const stru route->source = NETWORK_CONFIG_SOURCE_DHCP_PD; route->family = AF_INET6; - route->gw_family = AF_INET6; - route->gw.in6.s6_addr32[3] = br_address->s_addr; + route->nexthop.family = AF_INET6; + route->nexthop.gw.in6.s6_addr32[3] = br_address->s_addr; route->scope = RT_SCOPE_UNIVERSE; route->protocol = RTPROT_DHCP; route->priority = IP6_RT_PRIO_USER; route->lifetime_usec = lifetime_usec; - if (route_get(NULL, link, route, &existing) < 0) /* This is a new route. */ + r = route_adjust_nexthops(route, link); + if (r < 0) + return r; + + if (route_get(link->manager, route, &existing) < 0) /* This is a new route. */ link->dhcp_pd_configured = false; else route_unmark(existing); - r = link_request_route(link, TAKE_PTR(route), true, &link->dhcp_pd_messages, - dhcp_pd_route_handler, NULL); + r = link_request_route(link, route, &link->dhcp_pd_messages, dhcp_pd_route_handler); if (r < 0) return log_link_debug_errno(link, r, "Failed to request default gateway for DHCP delegated prefix: %m"); diff --git a/src/network/networkd-dhcp-prefix-delegation.h b/src/network/networkd-dhcp-prefix-delegation.h index e591b8a..4a8cca9 100644 --- a/src/network/networkd-dhcp-prefix-delegation.h +++ b/src/network/networkd-dhcp-prefix-delegation.h @@ -8,6 +8,7 @@ #include "conf-parser.h" +typedef struct Address Address; typedef struct Link Link; bool link_dhcp_pd_is_enabled(Link *link); @@ -19,5 +20,6 @@ int dhcp4_pd_prefix_acquired(Link *uplink); int dhcp6_pd_prefix_acquired(Link *uplink); void dhcp_pd_prefix_lost(Link *uplink); void dhcp4_pd_prefix_lost(Link *uplink); +int dhcp_pd_reconfigure_address(Address *address, Link *link); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_pd_subnet_id); diff --git a/src/network/networkd-dhcp-server-bus.c b/src/network/networkd-dhcp-server-bus.c index e3397c3..470e559 100644 --- a/src/network/networkd-dhcp-server-bus.c +++ b/src/network/networkd-dhcp-server-bus.c @@ -3,7 +3,7 @@ #include "alloc-util.h" #include "bus-common-errors.h" #include "bus-util.h" -#include "dhcp-server-internal.h" +#include "dhcp-server-lease-internal.h" #include "networkd-dhcp-server-bus.h" #include "networkd-link-bus.h" #include "networkd-manager.h" @@ -19,7 +19,7 @@ static int property_get_leases( sd_bus_error *error) { Link *l = ASSERT_PTR(userdata); sd_dhcp_server *s; - DHCPLease *lease; + sd_dhcp_server_lease *lease; int r; assert(reply); @@ -44,7 +44,7 @@ static int property_get_leases( if (r < 0) return r; - r = sd_bus_message_append_array(reply, 'y', lease->client_id.data, lease->client_id.length); + r = sd_bus_message_append_array(reply, 'y', lease->client_id.raw, lease->client_id.size); if (r < 0) return r; diff --git a/src/network/networkd-dhcp-server.c b/src/network/networkd-dhcp-server.c index 607fe00..c35102a 100644 --- a/src/network/networkd-dhcp-server.c +++ b/src/network/networkd-dhcp-server.c @@ -7,6 +7,7 @@ #include "sd-dhcp-server.h" #include "dhcp-protocol.h" +#include "dhcp-server-lease-internal.h" #include "fd-util.h" #include "fileio.h" #include "network-common.h" @@ -17,9 +18,11 @@ #include "networkd-link.h" #include "networkd-manager.h" #include "networkd-network.h" +#include "networkd-ntp.h" #include "networkd-queue.h" #include "networkd-route-util.h" #include "parse-util.h" +#include "path-util.h" #include "socket-netlink.h" #include "string-table.h" #include "string-util.h" @@ -81,6 +84,7 @@ int network_adjust_dhcp_server(Network *network, Set **addresses) { /* TODO: check if the prefix length is small enough for the pool. */ network->dhcp_server_address = address; + address->used_by_dhcp_server = true; break; } if (!network->dhcp_server_address) { @@ -92,7 +96,7 @@ int network_adjust_dhcp_server(Network *network, Set **addresses) { } } else { - _cleanup_(address_freep) Address *a = NULL; + _cleanup_(address_unrefp) Address *a = NULL; Address *existing; unsigned line; @@ -127,6 +131,7 @@ int network_adjust_dhcp_server(Network *network, Set **addresses) { a->prefixlen = network->dhcp_server_address_prefixlen; a->in_addr.in = network->dhcp_server_address_in_addr; a->requested_as_null = !in4_addr_is_set(&network->dhcp_server_address_in_addr); + a->used_by_dhcp_server = true; r = address_section_verify(a); if (r < 0) @@ -143,6 +148,139 @@ int network_adjust_dhcp_server(Network *network, Set **addresses) { return 0; } +static bool dhcp_server_persist_leases(Link *link) { + assert(link); + assert(link->manager); + assert(link->network); + + if (in4_addr_is_set(&link->network->dhcp_server_relay_target)) + return false; /* On relay mode. Nothing saved in the persistent storage. */ + + if (link->network->dhcp_server_persist_leases >= 0) + return link->network->dhcp_server_persist_leases; + + return link->manager->dhcp_server_persist_leases; +} + +int address_acquire_from_dhcp_server_leases_file(Link *link, const Address *address, union in_addr_union *ret) { + struct in_addr a; + uint8_t prefixlen; + int r; + + assert(link); + assert(link->manager); + assert(address); + assert(ret); + + /* If the DHCP server address is configured as a null address, reuse the server address of the + * previous instance. */ + if (address->family != AF_INET) + return -ENOENT; + + if (!address->used_by_dhcp_server) + return -ENOENT; + + if (!link_dhcp4_server_enabled(link)) + return -ENOENT; + + if (!dhcp_server_persist_leases(link)) + return -ENOENT; + + if (link->manager->persistent_storage_fd < 0) + return -EBUSY; /* The persistent storage is not ready, try later again. */ + + _cleanup_free_ char *lease_file = path_join("dhcp-server-lease", link->ifname); + if (!lease_file) + return -ENOMEM; + + r = dhcp_server_leases_file_get_server_address( + link->manager->persistent_storage_fd, + lease_file, + &a, + &prefixlen); + if (r == -ENOENT) + return r; + if (r < 0) + return log_warning_errno(r, "Failed to load lease file %s: %s", + lease_file, + r == -ENXIO ? "expected JSON content not found" : + r == -EINVAL ? "invalid JSON" : + STRERROR(r)); + + if (prefixlen != address->prefixlen) + return -ENOENT; + + ret->in = a; + return 0; +} + +int link_start_dhcp4_server(Link *link) { + int r; + + assert(link); + assert(link->manager); + + if (!link->dhcp_server) + return 0; /* Not configured yet. */ + + if (!link_has_carrier(link)) + return 0; + + if (sd_dhcp_server_is_running(link->dhcp_server)) + return 0; /* already started. */ + + /* TODO: Maybe, also check the system time is synced. If the system does not have RTC battery, then + * the realtime clock in not usable in the early boot stage, and all saved leases may be wrongly + * handled as expired and dropped. */ + if (dhcp_server_persist_leases(link)) { + + if (link->manager->persistent_storage_fd < 0) + return 0; /* persistent storage is not ready. */ + + _cleanup_free_ char *lease_file = path_join("dhcp-server-lease", link->ifname); + if (!lease_file) + return -ENOMEM; + + r = sd_dhcp_server_set_lease_file(link->dhcp_server, link->manager->persistent_storage_fd, lease_file); + if (r < 0) + return r; + } + + r = sd_dhcp_server_start(link->dhcp_server); + if (r < 0) + return r; + + log_link_debug(link, "Offering DHCPv4 leases"); + return 0; +} + +void manager_toggle_dhcp4_server_state(Manager *manager, bool start) { + Link *link; + int r; + + assert(manager); + + HASHMAP_FOREACH(link, manager->links_by_index) { + if (!link->dhcp_server) + continue; + if (!dhcp_server_persist_leases(link)) + continue; + + /* Even if 'start' is true, first we need to stop the server. Otherwise, we cannot (re)set + * the lease file in link_start_dhcp4_server(). */ + r = sd_dhcp_server_stop(link->dhcp_server); + if (r < 0) + log_link_debug_errno(link, r, "Failed to stop DHCP server, ignoring: %m"); + + if (!start) + continue; + + r = link_start_dhcp4_server(link); + if (r < 0) + log_link_debug_errno(link, r, "Failed to start DHCP server, ignoring: %m"); + } +} + static int dhcp_server_find_uplink(Link *link, Link **ret) { assert(link); @@ -205,7 +343,7 @@ static int link_push_uplink_to_dhcp_server( addresses[n_addresses++] = ia; } - use_dhcp_lease_data = link->network->dhcp_use_dns; + use_dhcp_lease_data = link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4); break; case SD_DHCP_LEASE_NTP: { @@ -228,7 +366,7 @@ static int link_push_uplink_to_dhcp_server( addresses[n_addresses++] = ia.in; } - use_dhcp_lease_data = link->network->dhcp_use_ntp; + use_dhcp_lease_data = link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4); break; } @@ -423,7 +561,7 @@ static int dhcp4_server_configure(Link *link) { return log_link_warning_errno(link, r, "Failed to %s Rapid Commit support for DHCPv4 server instance: %m", enable_disable(link->network->dhcp_server_rapid_commit)); - for (sd_dhcp_lease_server_type_t type = 0; type < _SD_DHCP_LEASE_SERVER_TYPE_MAX; type ++) { + for (sd_dhcp_lease_server_type_t type = 0; type < _SD_DHCP_LEASE_SERVER_TYPE_MAX; type++) { if (!link->network->dhcp_server_emit[type].emit) continue; @@ -522,11 +660,10 @@ static int dhcp4_server_configure(Link *link) { return log_link_error_errno(link, r, "Failed to set DHCPv4 static lease for DHCP server: %m"); } - r = sd_dhcp_server_start(link->dhcp_server); + r = link_start_dhcp4_server(link); if (r < 0) return log_link_error_errno(link, r, "Could not start DHCPv4 server instance: %m"); - log_link_debug(link, "Offering DHCPv4 leases"); return 0; } diff --git a/src/network/networkd-dhcp-server.h b/src/network/networkd-dhcp-server.h index 960232a..e839fac 100644 --- a/src/network/networkd-dhcp-server.h +++ b/src/network/networkd-dhcp-server.h @@ -2,15 +2,21 @@ #pragma once #include "conf-parser.h" +#include "in-addr-util.h" #include "set.h" +typedef struct Address Address; typedef struct Link Link; +typedef struct Manager Manager; typedef struct Network Network; int network_adjust_dhcp_server(Network *network, Set **addresses); - +int address_acquire_from_dhcp_server_leases_file(Link *link, const Address *address, union in_addr_union *ret); int link_request_dhcp_server(Link *link); +int link_start_dhcp4_server(Link *link); +void manager_toggle_dhcp4_server_state(Manager *manager, bool start); + CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_relay_agent_suboption); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_emit); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_address); diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 49c452d..4dd6044 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -20,6 +20,7 @@ #include "networkd-manager.h" #include "networkd-network.h" #include "networkd-nexthop.h" +#include "networkd-ntp.h" #include "networkd-queue.h" #include "networkd-route.h" #include "networkd-setlink.h" @@ -146,12 +147,11 @@ static int dhcp4_find_gateway_for_destination( Link *link, const struct in_addr *destination, uint8_t prefixlength, - bool allow_null, struct in_addr *ret) { _cleanup_free_ sd_dhcp_route **routes = NULL; size_t n_routes = 0; - bool is_classless, reachable; + bool is_classless; uint8_t max_prefixlen = UINT8_MAX; struct in_addr gw; int r; @@ -164,14 +164,21 @@ static int dhcp4_find_gateway_for_destination( /* This tries to find the most suitable gateway for an address or address range. * E.g. if the server provides the default gateway 192.168.0.1 and a classless static route for * 8.0.0.0/8 with gateway 192.168.0.2, then this returns 192.168.0.2 for 8.8.8.8/32, and 192.168.0.1 - * for 9.9.9.9/32. If 'allow_null' flag is set, and the input address or address range is in the - * assigned network, then the default gateway will be ignored and the null address will be returned - * unless a matching non-default gateway found. */ + * for 9.9.9.9/32. If the input address or address range is in the assigned network, then the null + * address will be returned. */ + /* First, check with the assigned prefix, and if the destination is in the prefix, set the null + * address for the gateway, and return it unless more suitable static route is found. */ r = dhcp4_prefix_covers(link, destination, prefixlength); if (r < 0) return r; - reachable = r > 0; + if (r > 0) { + r = sd_dhcp_lease_get_prefix(link->dhcp_lease, NULL, &max_prefixlen); + if (r < 0) + return r; + + gw = (struct in_addr) {}; + } r = dhcp4_get_classless_static_or_static_routes(link, &routes, &n_routes); if (r < 0 && r != -ENODATA) @@ -207,25 +214,17 @@ static int dhcp4_find_gateway_for_destination( max_prefixlen = len; } - /* Found a suitable gateway in classless static routes or static routes. */ + /* The destination is reachable. Note, the gateway address returned here may be NULL. */ if (max_prefixlen != UINT8_MAX) { - if (max_prefixlen == 0 && reachable && allow_null) - /* Do not return the default gateway, if the destination is in the assigned network. */ - *ret = (struct in_addr) {}; - else - *ret = gw; - return 0; - } - - /* When the destination is in the assigned network, return the null address if allowed. */ - if (reachable && allow_null) { - *ret = (struct in_addr) {}; + *ret = gw; return 0; } /* According to RFC 3442: If the DHCP server returns both a Classless Static Routes option and * a Router option, the DHCP client MUST ignore the Router option. */ if (!is_classless) { + /* No matching static route is found, and the destination is not in the acquired network, + * falling back to the Router option. */ r = dhcp4_get_router(link, ret); if (r >= 0) return 0; @@ -233,31 +232,26 @@ static int dhcp4_find_gateway_for_destination( return r; } - if (!reachable) - return -EHOSTUNREACH; /* Not in the same network, cannot reach the destination. */ - - assert(!allow_null); - return -ENODATA; /* No matching gateway found. */ + return -EHOSTUNREACH; /* Cannot reach the destination. */ } static int dhcp4_remove_address_and_routes(Link *link, bool only_marked) { Address *address; Route *route; - int k, r = 0; + int ret = 0; assert(link); + assert(link->manager); - SET_FOREACH(route, link->routes) { + SET_FOREACH(route, link->manager->routes) { if (route->source != NETWORK_CONFIG_SOURCE_DHCP4) continue; + if (route->nexthop.ifindex != 0 && route->nexthop.ifindex != link->ifindex) + continue; if (only_marked && !route_is_marked(route)) continue; - k = route_remove(route); - if (k < 0) - r = k; - - route_cancel_request(route, link); + RET_GATHER(ret, route_remove_and_cancel(route, link->manager)); } SET_FOREACH(address, link->addresses) { @@ -266,12 +260,10 @@ static int dhcp4_remove_address_and_routes(Link *link, bool only_marked) { if (only_marked && !address_is_marked(address)) continue; - k = address_remove_and_drop(address); - if (k < 0) - r = k; + RET_GATHER(ret, address_remove_and_cancel(address, link)); } - return r; + return ret; } static int dhcp4_address_get(Link *link, Address **ret) { @@ -347,12 +339,9 @@ static int dhcp4_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request assert(m); assert(link); - r = sd_netlink_message_get_errno(m); - if (r < 0 && r != -EEXIST) { - log_link_message_warning_errno(link, m, r, "Could not set DHCPv4 route"); - link_enter_failed(link); - return 1; - } + r = route_configure_handler_internal(rtnl, m, link, route, "Could not set DHCPv4 route"); + if (r <= 0) + return r; r = dhcp4_check_ready(link); if (r < 0) @@ -361,14 +350,14 @@ static int dhcp4_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request return 1; } -static int dhcp4_request_route(Route *in, Link *link) { - _cleanup_(route_freep) Route *route = in; +static int dhcp4_request_route(Route *route, Link *link) { struct in_addr server; Route *existing; int r; assert(route); assert(link); + assert(link->manager); assert(link->network); assert(link->dhcp_lease); @@ -385,22 +374,29 @@ static int dhcp4_request_route(Route *in, Link *link) { route->priority = link->network->dhcp_route_metric; if (!route->table_set) route->table = link_get_dhcp4_route_table(link); - if (route->mtu == 0) - route->mtu = link->network->dhcp_route_mtu; - if (route->quickack < 0) - route->quickack = link->network->dhcp_quickack; - if (route->initcwnd == 0) - route->initcwnd = link->network->dhcp_initial_congestion_window; - if (route->initrwnd == 0) - route->initrwnd = link->network->dhcp_advertised_receive_window; - - if (route_get(NULL, link, route, &existing) < 0) /* This is a new route. */ + r = route_metric_set(&route->metric, RTAX_MTU, link->network->dhcp_route_mtu); + if (r < 0) + return r; + r = route_metric_set(&route->metric, RTAX_INITCWND, link->network->dhcp_initial_congestion_window); + if (r < 0) + return r; + r = route_metric_set(&route->metric, RTAX_INITRWND, link->network->dhcp_advertised_receive_window); + if (r < 0) + return r; + r = route_metric_set(&route->metric, RTAX_QUICKACK, link->network->dhcp_quickack); + if (r < 0) + return r; + + r = route_adjust_nexthops(route, link); + if (r < 0) + return r; + + if (route_get(link->manager, route, &existing) < 0) /* This is a new route. */ link->dhcp4_configured = false; else route_unmark(existing); - return link_request_route(link, TAKE_PTR(route), true, &link->dhcp4_messages, - dhcp4_route_handler, NULL); + return link_request_route(link, route, &link->dhcp4_messages, dhcp4_route_handler); } static bool link_prefixroute(Link *link) { @@ -409,7 +405,7 @@ static bool link_prefixroute(Link *link) { } static int dhcp4_request_prefix_route(Link *link) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; int r; assert(link); @@ -433,11 +429,11 @@ static int dhcp4_request_prefix_route(Link *link) { if (r < 0) return r; - return dhcp4_request_route(TAKE_PTR(route), link); + return dhcp4_request_route(route, link); } static int dhcp4_request_route_to_gateway(Link *link, const struct in_addr *gw) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; struct in_addr address; int r; @@ -458,15 +454,14 @@ static int dhcp4_request_route_to_gateway(Link *link, const struct in_addr *gw) route->prefsrc.in = address; route->scope = RT_SCOPE_LINK; - return dhcp4_request_route(TAKE_PTR(route), link); + return dhcp4_request_route(route, link); } static int dhcp4_request_route_auto( - Route *in, + Route *route, Link *link, const struct in_addr *gw) { - _cleanup_(route_freep) Route *route = in; struct in_addr address; int r; @@ -486,8 +481,8 @@ static int dhcp4_request_route_auto( IPV4_ADDRESS_FMT_VAL(route->dst.in), route->dst_prefixlen, IPV4_ADDRESS_FMT_VAL(*gw)); route->scope = RT_SCOPE_HOST; - route->gw_family = AF_UNSPEC; - route->gw = IN_ADDR_NULL; + route->nexthop.family = AF_UNSPEC; + route->nexthop.gw = IN_ADDR_NULL; route->prefsrc = IN_ADDR_NULL; } else if (in4_addr_equal(&route->dst.in, &address)) { @@ -497,8 +492,8 @@ static int dhcp4_request_route_auto( IPV4_ADDRESS_FMT_VAL(route->dst.in), route->dst_prefixlen, IPV4_ADDRESS_FMT_VAL(*gw)); route->scope = RT_SCOPE_HOST; - route->gw_family = AF_UNSPEC; - route->gw = IN_ADDR_NULL; + route->nexthop.family = AF_UNSPEC; + route->nexthop.gw = IN_ADDR_NULL; route->prefsrc.in = address; } else if (in4_addr_is_null(gw)) { @@ -520,8 +515,8 @@ static int dhcp4_request_route_auto( } route->scope = RT_SCOPE_LINK; - route->gw_family = AF_UNSPEC; - route->gw = IN_ADDR_NULL; + route->nexthop.family = AF_UNSPEC; + route->nexthop.gw = IN_ADDR_NULL; route->prefsrc.in = address; } else { @@ -530,12 +525,12 @@ static int dhcp4_request_route_auto( return r; route->scope = RT_SCOPE_UNIVERSE; - route->gw_family = AF_INET; - route->gw.in = *gw; + route->nexthop.family = AF_INET; + route->nexthop.gw.in = *gw; route->prefsrc.in = address; } - return dhcp4_request_route(TAKE_PTR(route), link); + return dhcp4_request_route(route, link); } static int dhcp4_request_classless_static_or_static_routes(Link *link) { @@ -556,7 +551,7 @@ static int dhcp4_request_classless_static_or_static_routes(Link *link) { return r; FOREACH_ARRAY(e, routes, n_routes) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; struct in_addr gw; r = route_new(&route); @@ -575,7 +570,7 @@ static int dhcp4_request_classless_static_or_static_routes(Link *link) { if (r < 0) return r; - r = dhcp4_request_route_auto(TAKE_PTR(route), link, &gw); + r = dhcp4_request_route_auto(route, link, &gw); if (r < 0) return r; } @@ -584,7 +579,7 @@ static int dhcp4_request_classless_static_or_static_routes(Link *link) { } static int dhcp4_request_default_gateway(Link *link) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; struct in_addr address, router; int r; @@ -623,11 +618,11 @@ static int dhcp4_request_default_gateway(Link *link) { return r; /* Next, add a default gateway. */ - route->gw_family = AF_INET; - route->gw.in = router; + route->nexthop.family = AF_INET; + route->nexthop.gw.in = router; route->prefsrc.in = address; - return dhcp4_request_route(TAKE_PTR(route), link); + return dhcp4_request_route(route, link); } static int dhcp4_request_semi_static_routes(Link *link) { @@ -639,19 +634,19 @@ static int dhcp4_request_semi_static_routes(Link *link) { assert(link->network); HASHMAP_FOREACH(rt, link->network->routes_by_section) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; struct in_addr gw; if (!rt->gateway_from_dhcp_or_ra) continue; - if (rt->gw_family != AF_INET) + if (rt->nexthop.family != AF_INET) continue; assert(rt->family == AF_INET); - r = dhcp4_find_gateway_for_destination(link, &rt->dst.in, rt->dst_prefixlen, /* allow_null = */ false, &gw); - if (IN_SET(r, -EHOSTUNREACH, -ENODATA)) { + r = dhcp4_find_gateway_for_destination(link, &rt->dst.in, rt->dst_prefixlen, &gw); + if (r == -EHOSTUNREACH) { log_link_debug_errno(link, r, "DHCP: Cannot find suitable gateway for destination %s of semi-static route, ignoring: %m", IN4_ADDR_PREFIX_TO_STRING(&rt->dst.in, rt->dst_prefixlen)); continue; @@ -659,17 +654,23 @@ static int dhcp4_request_semi_static_routes(Link *link) { if (r < 0) return r; + if (in4_addr_is_null(&gw)) { + log_link_debug(link, "DHCP: Destination %s of semi-static route is in the acquired network, skipping configuration.", + IN4_ADDR_PREFIX_TO_STRING(&rt->dst.in, rt->dst_prefixlen)); + continue; + } + r = dhcp4_request_route_to_gateway(link, &gw); if (r < 0) return r; - r = route_dup(rt, &route); + r = route_dup(rt, NULL, &route); if (r < 0) return r; - route->gw.in = gw; + route->nexthop.gw.in = gw; - r = dhcp4_request_route(TAKE_PTR(route), link); + r = dhcp4_request_route(route, link); if (r < 0) return r; } @@ -690,13 +691,13 @@ static int dhcp4_request_routes_to_servers( assert(servers || n_servers == 0); FOREACH_ARRAY(dst, servers, n_servers) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; struct in_addr gw; if (in4_addr_is_null(dst)) continue; - r = dhcp4_find_gateway_for_destination(link, dst, 32, /* allow_null = */ true, &gw); + r = dhcp4_find_gateway_for_destination(link, dst, 32, &gw); if (r == -EHOSTUNREACH) { log_link_debug_errno(link, r, "DHCP: Cannot find suitable gateway for destination %s, ignoring: %m", IN4_ADDR_PREFIX_TO_STRING(dst, 32)); @@ -712,7 +713,7 @@ static int dhcp4_request_routes_to_servers( route->dst.in = *dst; route->dst_prefixlen = 32; - r = dhcp4_request_route_auto(TAKE_PTR(route), link, &gw); + r = dhcp4_request_route_auto(route, link, &gw); if (r < 0) return r; } @@ -728,7 +729,7 @@ static int dhcp4_request_routes_to_dns(Link *link) { assert(link->dhcp_lease); assert(link->network); - if (!link->network->dhcp_use_dns || + if (!link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4) || !link->network->dhcp_routes_to_dns) return 0; @@ -749,7 +750,7 @@ static int dhcp4_request_routes_to_ntp(Link *link) { assert(link->dhcp_lease); assert(link->network); - if (!link->network->dhcp_use_ntp || + if (!link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4) || !link->network->dhcp_routes_to_ntp) return 0; @@ -835,7 +836,7 @@ static int dhcp_reset_hostname(Link *link) { } int dhcp4_lease_lost(Link *link) { - int k, r = 0; + int r = 0; assert(link); assert(link->dhcp_lease); @@ -849,17 +850,9 @@ int dhcp4_lease_lost(Link *link) { sd_dhcp_lease_has_6rd(link->dhcp_lease)) dhcp4_pd_prefix_lost(link); - k = dhcp4_remove_address_and_routes(link, /* only_marked = */ false); - if (k < 0) - r = k; - - k = dhcp_reset_mtu(link); - if (k < 0) - r = k; - - k = dhcp_reset_hostname(link); - if (k < 0) - r = k; + RET_GATHER(r, dhcp4_remove_address_and_routes(link, /* only_marked = */ false)); + RET_GATHER(r, dhcp_reset_mtu(link)); + RET_GATHER(r, dhcp_reset_hostname(link)); link->dhcp_lease = sd_dhcp_lease_unref(link->dhcp_lease); link_dirty(link); @@ -892,7 +885,7 @@ static int dhcp4_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Reques } static int dhcp4_request_address(Link *link, bool announce) { - _cleanup_(address_freep) Address *addr = NULL; + _cleanup_(address_unrefp) Address *addr = NULL; struct in_addr address, server; uint8_t prefixlen; Address *existing; @@ -997,7 +990,7 @@ static int dhcp4_request_address_and_routes(Link *link, bool announce) { assert(link); link_mark_addresses(link, NETWORK_CONFIG_SOURCE_DHCP4); - link_mark_routes(link, NETWORK_CONFIG_SOURCE_DHCP4); + manager_mark_routes(link->manager, link, NETWORK_CONFIG_SOURCE_DHCP4); r = dhcp4_request_address(link, announce); if (r < 0) @@ -1181,7 +1174,7 @@ static int dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) { } r = sd_ipv4ll_start(link->ipv4ll); - if (r < 0) + if (r < 0 && r != -ESTALE) /* On exit, we cannot and should not start sd-ipv4ll. */ return log_link_warning_errno(link, r, "Could not acquire IPv4 link-local address: %m"); } @@ -1548,13 +1541,13 @@ static int dhcp4_configure(Link *link) { return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for classless static route: %m"); } - if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) { + if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP4) > 0) { r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_DOMAIN_SEARCH); if (r < 0) return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for domain search list: %m"); } - if (link->network->dhcp_use_ntp) { + if (link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4)) { r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_NTP_SERVER); if (r < 0) return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for NTP server: %m"); @@ -1642,6 +1635,11 @@ static int dhcp4_configure(Link *link) { if (r < 0) return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set listen port: %m"); } + if (link->network->dhcp_port > 0) { + r = sd_dhcp_client_set_port(link->dhcp_client, link->network->dhcp_port); + if (r < 0) + return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set server port: %m"); + } if (link->network->dhcp_max_attempts > 0) { r = sd_dhcp_client_set_max_attempts(link->dhcp_client, link->network->dhcp_max_attempts); diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index f499d03..852987b 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -14,6 +14,7 @@ #include "networkd-dhcp6.h" #include "networkd-link.h" #include "networkd-manager.h" +#include "networkd-ntp.h" #include "networkd-queue.h" #include "networkd-route.h" #include "networkd-state-file.h" @@ -48,24 +49,21 @@ static DHCP6ClientStartMode link_get_dhcp6_client_start_mode(Link *link) { static int dhcp6_remove(Link *link, bool only_marked) { Address *address; Route *route; - int k, r = 0; + int ret = 0; assert(link); + assert(link->manager); if (!only_marked) link->dhcp6_configured = false; - SET_FOREACH(route, link->routes) { + SET_FOREACH(route, link->manager->routes) { if (route->source != NETWORK_CONFIG_SOURCE_DHCP6) continue; if (only_marked && !route_is_marked(route)) continue; - k = route_remove(route); - if (k < 0) - r = k; - - route_cancel_request(route, link); + RET_GATHER(ret, route_remove_and_cancel(route, link->manager)); } SET_FOREACH(address, link->addresses) { @@ -74,12 +72,10 @@ static int dhcp6_remove(Link *link, bool only_marked) { if (only_marked && !address_is_marked(address)) continue; - k = address_remove_and_drop(address); - if (k < 0) - r = k; + RET_GATHER(ret, address_remove_and_cancel(address, link)); } - return r; + return ret; } static int dhcp6_address_ready_callback(Address *address) { @@ -164,8 +160,7 @@ static int verify_dhcp6_address(Link *link, const Address *address) { } else log_level = LOG_DEBUG; - if (address->prefixlen == existing->prefixlen) - /* Currently, only conflict in prefix length is reported. */ + if (address_can_update(existing, address)) goto simple_log; if (existing->source == NETWORK_CONFIG_SOURCE_NDISC) @@ -197,7 +192,7 @@ static int dhcp6_request_address( usec_t lifetime_preferred_usec, usec_t lifetime_valid_usec) { - _cleanup_(address_freep) Address *addr = NULL; + _cleanup_(address_unrefp) Address *addr = NULL; Address *existing; int r; @@ -298,7 +293,7 @@ static int dhcp6_lease_ip_acquired(sd_dhcp6_client *client, Link *link) { int r; link_mark_addresses(link, NETWORK_CONFIG_SOURCE_DHCP6); - link_mark_routes(link, NETWORK_CONFIG_SOURCE_DHCP6); + manager_mark_routes(link->manager, NULL, NETWORK_CONFIG_SOURCE_DHCP6); r = sd_dhcp6_client_get_lease(client, &lease); if (r < 0) @@ -358,6 +353,9 @@ static int dhcp6_lease_lost(Link *link) { assert(link); assert(link->manager); + if (!link->dhcp6_lease) + return 0; + log_link_info(link, "DHCPv6 lease lost"); if (sd_dhcp6_lease_has_pd_prefix(link->dhcp6_lease)) @@ -636,13 +634,13 @@ static int dhcp6_configure(Link *link) { return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set MUD URL: %m"); } - if (link->network->dhcp6_use_dns) { + if (link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP6)) { r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVER); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request DNS servers: %m"); } - if (link->network->dhcp6_use_domains > 0) { + if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP6) > 0) { r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DOMAIN); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request domains: %m"); @@ -654,7 +652,7 @@ static int dhcp6_configure(Link *link) { return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request captive portal: %m"); } - if (link->network->dhcp6_use_ntp) { + if (link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP6)) { r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NTP_SERVER); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request NTP servers: %m"); @@ -805,7 +803,7 @@ int link_request_dhcp6_client(Link *link) { assert(link); - if (!link_dhcp6_enabled(link) && !link_ipv6_accept_ra_enabled(link)) + if (!link_dhcp6_enabled(link) && !link_ndisc_enabled(link)) return 0; if (link->dhcp6_client) @@ -833,7 +831,7 @@ int link_serialize_dhcp6_client(Link *link, FILE *f) { if (r >= 0) fprintf(f, "DHCP6_CLIENT_IAID=0x%x\n", iaid); - r = sd_dhcp6_client_duid_as_string(link->dhcp6_client, &duid); + r = sd_dhcp6_client_get_duid_as_string(link->dhcp6_client, &duid); if (r >= 0) fprintf(f, "DHCP6_CLIENT_DUID=%s\n", duid); diff --git a/src/network/networkd-dns.c b/src/network/networkd-dns.c new file mode 100644 index 0000000..7078419 --- /dev/null +++ b/src/network/networkd-dns.c @@ -0,0 +1,294 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "dns-domain.h" +#include "hostname-util.h" +#include "networkd-dns.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "parse-util.h" +#include "string-table.h" + +UseDomains link_get_use_domains(Link *link, NetworkConfigSource proto) { + UseDomains n, c, m; + + assert(link); + assert(link->manager); + + if (!link->network) + return USE_DOMAINS_NO; + + switch (proto) { + case NETWORK_CONFIG_SOURCE_DHCP4: + n = link->network->dhcp_use_domains; + c = link->network->compat_dhcp_use_domains; + m = link->manager->dhcp_use_domains; + break; + case NETWORK_CONFIG_SOURCE_DHCP6: + n = link->network->dhcp6_use_domains; + c = link->network->compat_dhcp_use_domains; + m = link->manager->dhcp6_use_domains; + break; + case NETWORK_CONFIG_SOURCE_NDISC: + n = link->network->ndisc_use_domains; + c = _USE_DOMAINS_INVALID; + m = link->manager->ndisc_use_domains; + break; + default: + assert_not_reached(); + } + + /* If per-network and per-protocol setting is specified, use it. */ + if (n >= 0) + return n; + + /* If compat setting is specified, use it. */ + if (c >= 0) + return c; + + /* If per-network but protocol-independent setting is specified, use it. */ + if (link->network->use_domains >= 0) + return link->network->use_domains; + + /* If global per-protocol setting is specified, use it. */ + if (m >= 0) + return m; + + /* If none of them are specified, use the global protocol-independent value. */ + return link->manager->use_domains; +} + +bool link_get_use_dns(Link *link, NetworkConfigSource proto) { + int n, c; + + assert(link); + + if (!link->network) + return false; + + switch (proto) { + case NETWORK_CONFIG_SOURCE_DHCP4: + n = link->network->dhcp_use_dns; + c = link->network->compat_dhcp_use_dns; + break; + case NETWORK_CONFIG_SOURCE_DHCP6: + n = link->network->dhcp6_use_dns; + c = link->network->compat_dhcp_use_dns; + break; + case NETWORK_CONFIG_SOURCE_NDISC: + n = link->network->ndisc_use_dns; + c = -1; + break; + default: + assert_not_reached(); + } + + /* If per-network and per-protocol setting is specified, use it. */ + if (n >= 0) + return n; + + /* If compat setting is specified, use it. */ + if (c >= 0) + return c; + + /* Otherwise, defaults to yes. */ + return true; +} + +int config_parse_domains( + 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 *n = ASSERT_PTR(userdata); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + n->search_domains = ordered_set_free(n->search_domains); + n->route_domains = ordered_set_free(n->route_domains); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL, *normalized = NULL; + const char *domain; + bool is_route; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to extract search or route domain, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + is_route = w[0] == '~'; + domain = is_route ? w + 1 : w; + + if (dns_name_is_root(domain) || streq(domain, "*")) { + /* If the root domain appears as is, or the special token "*" is found, we'll + * consider this as routing domain, unconditionally. */ + is_route = true; + domain = "."; /* make sure we don't allow empty strings, thus write the root + * domain as "." */ + } else { + r = dns_name_normalize(domain, 0, &normalized); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "'%s' is not a valid domain name, ignoring.", domain); + continue; + } + + domain = normalized; + + if (is_localhost(domain)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "'localhost' domain may not be configured as search or route domain, ignoring assignment: %s", + domain); + continue; + } + } + + OrderedSet **set = is_route ? &n->route_domains : &n->search_domains; + r = ordered_set_put_strdup(set, domain); + if (r == -EEXIST) + continue; + if (r < 0) + return log_oom(); + } +} + +int config_parse_dns( + 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 *n = ASSERT_PTR(userdata); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + for (unsigned i = 0; i < n->n_dns; i++) + in_addr_full_free(n->dns[i]); + n->dns = mfree(n->dns); + n->n_dns = 0; + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_(in_addr_full_freep) struct in_addr_full *dns = NULL; + _cleanup_free_ char *w = NULL; + struct in_addr_full **m; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid syntax, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + r = in_addr_full_new_from_string(w, &dns); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse dns server address, ignoring: %s", w); + continue; + } + + if (IN_SET(dns->port, 53, 853)) + dns->port = 0; + + m = reallocarray(n->dns, n->n_dns + 1, sizeof(struct in_addr_full*)); + if (!m) + return log_oom(); + + m[n->n_dns++] = TAKE_PTR(dns); + n->dns = m; + } +} + +int config_parse_dnssec_negative_trust_anchors( + 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) { + + Set **nta = ASSERT_PTR(data); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *nta = set_free_free(*nta); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to extract negative trust anchor domain, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + r = dns_name_is_valid(w); + if (r <= 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "%s is not a valid domain name, ignoring.", w); + continue; + } + + r = set_ensure_consume(nta, &dns_name_hash_ops, TAKE_PTR(w)); + if (r < 0) + return log_oom(); + } +} + +static const char* const use_domains_table[_USE_DOMAINS_MAX] = { + [USE_DOMAINS_NO] = "no", + [USE_DOMAINS_ROUTE] = "route", + [USE_DOMAINS_YES] = "yes", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(use_domains, UseDomains, USE_DOMAINS_YES); +DEFINE_CONFIG_PARSE_ENUM(config_parse_use_domains, use_domains, UseDomains, "Failed to parse UseDomains=") diff --git a/src/network/networkd-dns.h b/src/network/networkd-dns.h new file mode 100644 index 0000000..915cb32 --- /dev/null +++ b/src/network/networkd-dns.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "conf-parser.h" +#include "macro.h" +#include "networkd-util.h" + +typedef struct Link Link; + +typedef enum UseDomains { + USE_DOMAINS_NO, + USE_DOMAINS_YES, + USE_DOMAINS_ROUTE, + _USE_DOMAINS_MAX, + _USE_DOMAINS_INVALID = -EINVAL, +} UseDomains; + +UseDomains link_get_use_domains(Link *link, NetworkConfigSource proto); +bool link_get_use_dns(Link *link, NetworkConfigSource proto); + +const char* use_domains_to_string(UseDomains p) _const_; +UseDomains use_domains_from_string(const char *s) _pure_; + +CONFIG_PARSER_PROTOTYPE(config_parse_domains); +CONFIG_PARSER_PROTOTYPE(config_parse_dns); +CONFIG_PARSER_PROTOTYPE(config_parse_dnssec_negative_trust_anchors); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_dns); +CONFIG_PARSER_PROTOTYPE(config_parse_use_domains); diff --git a/src/network/networkd-gperf.gperf b/src/network/networkd-gperf.gperf index 8542ffa..f02dfd7 100644 --- a/src/network/networkd-gperf.gperf +++ b/src/network/networkd-gperf.gperf @@ -7,6 +7,7 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") #include "conf-parser.h" #include "networkd-conf.h" #include "networkd-dhcp-common.h" +#include "networkd-dns.h" #include "networkd-manager.h" #include "networkd-route-util.h" %} @@ -25,12 +26,20 @@ Network.SpeedMeter, config_parse_bool, Network.SpeedMeterIntervalSec, config_parse_sec, 0, offsetof(Manager, speed_meter_interval_usec) Network.ManageForeignRoutingPolicyRules, config_parse_bool, 0, offsetof(Manager, manage_foreign_rules) Network.ManageForeignRoutes, config_parse_bool, 0, offsetof(Manager, manage_foreign_routes) +Network.ManageForeignNextHops, config_parse_bool, 0, offsetof(Manager, manage_foreign_nexthops) Network.RouteTable, config_parse_route_table_names, 0, 0 +Network.IPv4Forwarding, config_parse_tristate, 0, offsetof(Manager, ip_forwarding[0]) +Network.IPv6Forwarding, config_parse_tristate, 0, offsetof(Manager, ip_forwarding[1]) Network.IPv6PrivacyExtensions, config_parse_ipv6_privacy_extensions, 0, offsetof(Manager, ipv6_privacy_extensions) +Network.UseDomains, config_parse_use_domains, 0, offsetof(Manager, use_domains) +IPv6AcceptRA.UseDomains, config_parse_use_domains, 0, offsetof(Manager, ndisc_use_domains) +DHCPv4.UseDomains, config_parse_use_domains, 0, offsetof(Manager, dhcp_use_domains) DHCPv4.DUIDType, config_parse_duid_type, 0, offsetof(Manager, dhcp_duid) DHCPv4.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Manager, dhcp_duid) +DHCPv6.UseDomains, config_parse_use_domains, 0, offsetof(Manager, dhcp6_use_domains) DHCPv6.DUIDType, config_parse_duid_type, 0, offsetof(Manager, dhcp6_duid) DHCPv6.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Manager, dhcp6_duid) +DHCPServer.PersistLeases, config_parse_bool, 0, offsetof(Manager, dhcp_server_persist_leases) /* Deprecated */ DHCP.DUIDType, config_parse_manager_duid_type, 0, 0 DHCP.DUIDRawData, config_parse_manager_duid_rawdata, 0, 0 diff --git a/src/network/networkd-ipv4acd.c b/src/network/networkd-ipv4acd.c index 3d5e203..de03293 100644 --- a/src/network/networkd-ipv4acd.c +++ b/src/network/networkd-ipv4acd.c @@ -92,7 +92,9 @@ static int static_ipv4acd_address_remove(Link *link, Address *address, bool on_c else log_link_debug(link, "Removing address %s, as the ACD client is stopped.", IN4_ADDR_TO_STRING(&address->in_addr.in)); - r = address_remove(address); + /* Do not call address_remove_and_cancel() here. Otherwise, the request is cancelled, and the + * interface may be in configured state without the address. */ + r = address_remove(address, link); if (r < 0) return log_link_warning_errno(link, r, "Failed to remove address %s: %m", IN4_ADDR_TO_STRING(&address->in_addr.in)); diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c index c357382..299aaed 100644 --- a/src/network/networkd-ipv4ll.c +++ b/src/network/networkd-ipv4ll.c @@ -28,7 +28,7 @@ bool link_ipv4ll_enabled(Link *link) { } static int address_new_from_ipv4ll(Link *link, Address **ret) { - _cleanup_(address_freep) Address *address = NULL; + _cleanup_(address_unrefp) Address *address = NULL; struct in_addr addr; int r; @@ -56,8 +56,7 @@ static int address_new_from_ipv4ll(Link *link, Address **ret) { } static int ipv4ll_address_lost(Link *link) { - _cleanup_(address_freep) Address *address = NULL; - Address *existing; + _cleanup_(address_unrefp) Address *address = NULL; int r; assert(link); @@ -70,19 +69,10 @@ static int ipv4ll_address_lost(Link *link) { if (r < 0) return r; - if (address_get(link, address, &existing) < 0) - return 0; - - if (existing->source != NETWORK_CONFIG_SOURCE_IPV4LL) - return 0; - - if (!address_exists(existing)) - return 0; - log_link_debug(link, "IPv4 link-local release "IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(address->in_addr.in)); - return address_remove(existing); + return address_remove_and_cancel(address, link); } static int ipv4ll_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, Address *address) { @@ -102,7 +92,7 @@ static int ipv4ll_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Reque } static int ipv4ll_address_claimed(sd_ipv4ll *ll, Link *link) { - _cleanup_(address_freep) Address *address = NULL; + _cleanup_(address_unrefp) Address *address = NULL; int r; assert(ll); diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index eed8d9f..fb9f492 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -2,7 +2,8 @@ #include <linux/nexthop.h> -#include "dhcp-server-internal.h" +#include "dhcp-lease-internal.h" +#include "dhcp-server-lease-internal.h" #include "dhcp6-internal.h" #include "dhcp6-lease-internal.h" #include "dns-domain.h" @@ -16,6 +17,7 @@ #include "networkd-neighbor.h" #include "networkd-network.h" #include "networkd-nexthop.h" +#include "networkd-ntp.h" #include "networkd-route-util.h" #include "networkd-route.h" #include "networkd-routing-policy-rule.h" @@ -24,12 +26,12 @@ #include "user-util.h" #include "wifi-util.h" -static int address_build_json(Address *address, JsonVariant **ret) { +static int address_append_json(Address *address, JsonVariant **array) { _cleanup_free_ char *scope = NULL, *flags = NULL, *state = NULL; int r; assert(address); - assert(ret); + assert(array); r = route_scope_to_string_alloc(address->scope, &scope); if (r < 0) @@ -43,7 +45,9 @@ static int address_build_json(Address *address, JsonVariant **ret) { if (r < 0) return r; - return json_build(ret, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_INTEGER("Family", address->family), JSON_BUILD_PAIR_IN_ADDR("Address", &address->in_addr, address->family), JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Peer", &address->in_addr_peer, address->family), @@ -71,13 +75,7 @@ static int addresses_append_json(Set *addresses, JsonVariant **v) { assert(v); SET_FOREACH(address, addresses) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - - r = address_build_json(address, &e); - if (r < 0) - return r; - - r = json_variant_append_array(&array, e); + r = address_append_json(address, &array); if (r < 0) return r; } @@ -85,18 +83,20 @@ static int addresses_append_json(Set *addresses, JsonVariant **v) { return json_variant_set_field_non_null(v, "Addresses", array); } -static int neighbor_build_json(Neighbor *n, JsonVariant **ret) { +static int neighbor_append_json(Neighbor *n, JsonVariant **array) { _cleanup_free_ char *state = NULL; int r; assert(n); - assert(ret); + assert(array); r = network_config_state_to_string_alloc(n->state, &state); if (r < 0) return r; - return json_build(ret, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_INTEGER("Family", n->family), JSON_BUILD_PAIR_IN_ADDR("Destination", &n->in_addr, n->family), JSON_BUILD_PAIR_HW_ADDR("LinkLayerAddress", &n->ll_addr), @@ -112,13 +112,7 @@ static int neighbors_append_json(Set *neighbors, JsonVariant **v) { assert(v); SET_FOREACH(neighbor, neighbors) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - - r = neighbor_build_json(neighbor, &e); - if (r < 0) - return r; - - r = json_variant_append_array(&array, e); + r = neighbor_append_json(neighbor, &array); if (r < 0) return r; } @@ -148,13 +142,13 @@ static int nexthop_group_build_json(NextHop *nexthop, JsonVariant **ret) { return 0; } -static int nexthop_build_json(NextHop *n, JsonVariant **ret) { +static int nexthop_append_json(NextHop *n, JsonVariant **array) { _cleanup_(json_variant_unrefp) JsonVariant *group = NULL; _cleanup_free_ char *flags = NULL, *protocol = NULL, *state = NULL; int r; assert(n); - assert(ret); + assert(array); r = route_flags_to_string_alloc(n->flags, &flags); if (r < 0) @@ -172,7 +166,9 @@ static int nexthop_build_json(NextHop *n, JsonVariant **ret) { if (r < 0) return r; - return json_build(ret, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_UNSIGNED("ID", n->id), JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Gateway", &n->gw, n->family), JSON_BUILD_PAIR_UNSIGNED("Flags", n->flags), @@ -185,21 +181,19 @@ static int nexthop_build_json(NextHop *n, JsonVariant **ret) { JSON_BUILD_PAIR_STRING("ConfigState", state))); } -static int nexthops_append_json(Set *nexthops, JsonVariant **v) { +static int nexthops_append_json(Manager *manager, int ifindex, JsonVariant **v) { _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; NextHop *nexthop; int r; + assert(manager); assert(v); - SET_FOREACH(nexthop, nexthops) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - - r = nexthop_build_json(nexthop, &e); - if (r < 0) - return r; + HASHMAP_FOREACH(nexthop, manager->nexthops_by_id) { + if (nexthop->ifindex != ifindex) + continue; - r = json_variant_append_array(&array, e); + r = nexthop_append_json(nexthop, &array); if (r < 0) return r; } @@ -207,17 +201,12 @@ static int nexthops_append_json(Set *nexthops, JsonVariant **v) { return json_variant_set_field_non_null(v, "NextHops", array); } -static int route_build_json(Route *route, JsonVariant **ret) { +static int route_append_json(Route *route, JsonVariant **array) { _cleanup_free_ char *scope = NULL, *protocol = NULL, *table = NULL, *flags = NULL, *state = NULL; - Manager *manager; int r; assert(route); - assert(ret); - - manager = route->link ? route->link->manager : route->manager; - - assert(manager); + assert(array); r = route_scope_to_string_alloc(route->scope, &scope); if (r < 0) @@ -227,7 +216,7 @@ static int route_build_json(Route *route, JsonVariant **ret) { if (r < 0) return r; - r = manager_get_route_table_to_string(manager, route->table, /* append_num = */ false, &table); + r = manager_get_route_table_to_string(route->manager, route->table, /* append_num = */ false, &table); if (r < 0) return r; @@ -239,11 +228,13 @@ static int route_build_json(Route *route, JsonVariant **ret) { if (r < 0) return r; - return json_build(ret, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_INTEGER("Family", route->family), JSON_BUILD_PAIR_IN_ADDR("Destination", &route->dst, route->family), JSON_BUILD_PAIR_UNSIGNED("DestinationPrefixLength", route->dst_prefixlen), - JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Gateway", &route->gw, route->gw_family), + JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Gateway", &route->nexthop.gw, route->nexthop.family), JSON_BUILD_PAIR_CONDITION(route->src_prefixlen > 0, "Source", JSON_BUILD_IN_ADDR(&route->src, route->family)), JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("SourcePrefixLength", route->src_prefixlen), @@ -257,7 +248,7 @@ static int route_build_json(Route *route, JsonVariant **ret) { JSON_BUILD_PAIR_UNSIGNED("Priority", route->priority), JSON_BUILD_PAIR_UNSIGNED("Table", route->table), JSON_BUILD_PAIR_STRING("TableString", table), - JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("MTU", route->mtu), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("MTU", route_metric_get(&route->metric, RTAX_MTU)), JSON_BUILD_PAIR_UNSIGNED("Preference", route->pref), JSON_BUILD_PAIR_UNSIGNED("Flags", route->flags), JSON_BUILD_PAIR_STRING("FlagsString", strempty(flags)), @@ -267,21 +258,19 @@ static int route_build_json(Route *route, JsonVariant **ret) { JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", &route->provider, route->family))); } -static int routes_append_json(Set *routes, JsonVariant **v) { +static int routes_append_json(Manager *manager, int ifindex, JsonVariant **v) { _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; Route *route; int r; + assert(manager); assert(v); - SET_FOREACH(route, routes) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; + SET_FOREACH(route, manager->routes) { + if (route->nexthop.ifindex != ifindex) + continue; - r = route_build_json(route, &e); - if (r < 0) - return r; - - r = json_variant_append_array(&array, e); + r = route_append_json(route, &array); if (r < 0) return r; } @@ -289,13 +278,13 @@ static int routes_append_json(Set *routes, JsonVariant **v) { return json_variant_set_field_non_null(v, "Routes", array); } -static int routing_policy_rule_build_json(RoutingPolicyRule *rule, JsonVariant **ret) { +static int routing_policy_rule_append_json(RoutingPolicyRule *rule, JsonVariant **array) { _cleanup_free_ char *table = NULL, *protocol = NULL, *state = NULL; int r; assert(rule); assert(rule->manager); - assert(ret); + assert(array); r = manager_get_route_table_to_string(rule->manager, rule->table, /* append_num = */ false, &table); if (r < 0 && r != -EINVAL) @@ -309,7 +298,9 @@ static int routing_policy_rule_build_json(RoutingPolicyRule *rule, JsonVariant * if (r < 0) return r; - return json_build(ret, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_INTEGER("Family", rule->family), JSON_BUILD_PAIR_IN_ADDR_NON_NULL("FromPrefix", &rule->from, rule->family), JSON_BUILD_PAIR_CONDITION(in_addr_is_set(rule->family, &rule->from), @@ -354,13 +345,7 @@ static int routing_policy_rules_append_json(Set *rules, JsonVariant **v) { assert(v); SET_FOREACH(rule, rules) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - - r = routing_policy_rule_build_json(rule, &e); - if (r < 0) - return r; - - r = json_variant_append_array(&array, e); + r = routing_policy_rule_append_json(rule, &array); if (r < 0) return r; } @@ -464,7 +449,7 @@ static int dns_append_json(Link *link, JsonVariant **v) { return r; } - if (link->dhcp_lease && link->network->dhcp_use_dns) { + if (link->dhcp_lease && link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4)) { const struct in_addr *dns; union in_addr_union s; int n_dns; @@ -485,7 +470,7 @@ static int dns_append_json(Link *link, JsonVariant **v) { } } - if (link->dhcp6_lease && link->network->dhcp6_use_dns) { + if (link->dhcp6_lease && link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP6)) { const struct in6_addr *dns; union in_addr_union s; int n_dns; @@ -506,7 +491,7 @@ static int dns_append_json(Link *link, JsonVariant **v) { } } - if (link->network->ipv6_accept_ra_use_dns) { + if (link_get_use_dns(link, NETWORK_CONFIG_SOURCE_NDISC)) { NDiscRDNSS *a; SET_FOREACH(a, link->ndisc_rdnss) { @@ -525,40 +510,30 @@ static int dns_append_json(Link *link, JsonVariant **v) { } static int server_append_json_one_addr(int family, const union in_addr_union *a, NetworkConfigSource s, const union in_addr_union *p, JsonVariant **array) { - _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - int r; - assert(IN_SET(family, AF_INET, AF_INET6)); assert(a); assert(array); - r = json_build(&v, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_INTEGER("Family", family), JSON_BUILD_PAIR_IN_ADDR("Address", a, family), JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s)), JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", p, family))); - if (r < 0) - return r; - - return json_variant_append_array(array, v); } static int server_append_json_one_fqdn(int family, const char *fqdn, NetworkConfigSource s, const union in_addr_union *p, JsonVariant **array) { - _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - int r; - assert(IN_SET(family, AF_UNSPEC, AF_INET, AF_INET6)); assert(fqdn); assert(array); - r = json_build(&v, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_STRING("Server", fqdn), JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s)), JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", p, family))); - if (r < 0) - return r; - - return json_variant_append_array(array, v); } static int server_append_json_one_string(const char *str, NetworkConfigSource s, JsonVariant **array) { @@ -590,7 +565,7 @@ static int ntp_append_json(Link *link, JsonVariant **v) { } if (!link->ntp) { - if (link->dhcp_lease && link->network->dhcp_use_ntp) { + if (link->dhcp_lease && link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4)) { const struct in_addr *ntp; union in_addr_union s; int n_ntp; @@ -611,7 +586,7 @@ static int ntp_append_json(Link *link, JsonVariant **v) { } } - if (link->dhcp6_lease && link->network->dhcp6_use_ntp) { + if (link->dhcp6_lease && link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP6)) { const struct in6_addr *ntp_addr; union in_addr_union s; char **ntp_fqdn; @@ -682,27 +657,22 @@ static int sip_append_json(Link *link, JsonVariant **v) { } static int domain_append_json(int family, const char *domain, NetworkConfigSource s, const union in_addr_union *p, JsonVariant **array) { - _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - int r; - assert(IN_SET(family, AF_UNSPEC, AF_INET, AF_INET6)); assert(domain); assert(array); - r = json_build(&v, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_STRING("Domain", domain), JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s)), JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", p, family))); - if (r < 0) - return r; - - return json_variant_append_array(array, v); } static int domains_append_json(Link *link, bool is_route, JsonVariant **v) { _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; OrderedSet *link_domains, *network_domains; - DHCPUseDomains use_domains; + UseDomains use_domains; union in_addr_union s; char **domains; const char *domain; @@ -716,7 +686,7 @@ static int domains_append_json(Link *link, bool is_route, JsonVariant **v) { link_domains = is_route ? link->route_domains : link->search_domains; network_domains = is_route ? link->network->route_domains : link->network->search_domains; - use_domains = is_route ? DHCP_USE_DOMAINS_ROUTE : DHCP_USE_DOMAINS_YES; + use_domains = is_route ? USE_DOMAINS_ROUTE : USE_DOMAINS_YES; ORDERED_SET_FOREACH(domain, link_domains ?: network_domains) { r = domain_append_json(AF_UNSPEC, domain, @@ -728,7 +698,7 @@ static int domains_append_json(Link *link, bool is_route, JsonVariant **v) { if (!link_domains) { if (link->dhcp_lease && - link->network->dhcp_use_domains == use_domains) { + link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP4) == use_domains) { r = sd_dhcp_lease_get_server_identifier(link->dhcp_lease, &s.in); if (r < 0) return r; @@ -748,7 +718,7 @@ static int domains_append_json(Link *link, bool is_route, JsonVariant **v) { } if (link->dhcp6_lease && - link->network->dhcp6_use_domains == use_domains) { + link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP6) == use_domains) { r = sd_dhcp6_lease_get_server_address(link->dhcp6_lease, &s.in6); if (r < 0) return r; @@ -761,7 +731,7 @@ static int domains_append_json(Link *link, bool is_route, JsonVariant **v) { } } - if (link->network->ipv6_accept_ra_use_domains == use_domains) { + if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_NDISC) == use_domains) { NDiscDNSSL *a; SET_FOREACH(a, link->ndisc_dnssl) { @@ -778,19 +748,14 @@ static int domains_append_json(Link *link, bool is_route, JsonVariant **v) { } static int nta_append_json(const char *nta, NetworkConfigSource s, JsonVariant **array) { - _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - int r; - assert(nta); assert(array); - r = json_build(&v, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_STRING("DNSSECNegativeTrustAnchor", nta), JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s)))); - if (r < 0) - return r; - - return json_variant_append_array(array, v); } static int ntas_append_json(Link *link, JsonVariant **v) { @@ -830,70 +795,54 @@ static int dns_misc_append_json(Link *link, JsonVariant **v) { resolve_support = link->llmnr >= 0 ? link->llmnr : link->network->llmnr; if (resolve_support >= 0) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - source = link->llmnr >= 0 ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC; - r = json_build(&e, JSON_BUILD_OBJECT( + r = json_variant_append_arrayb( + &array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_STRING("LLMNR", resolve_support_to_string(resolve_support)), JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(source)))); if (r < 0) return r; - - r = json_variant_append_array(&array, e); - if (r < 0) - return r; } resolve_support = link->mdns >= 0 ? link->mdns : link->network->mdns; if (resolve_support >= 0) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - source = link->mdns >= 0 ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC; - r = json_build(&e, JSON_BUILD_OBJECT( + r = json_variant_append_arrayb( + &array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_STRING("MDNS", resolve_support_to_string(resolve_support)), JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(source)))); if (r < 0) return r; - - r = json_variant_append_array(&array, e); - if (r < 0) - return r; } t = link->dns_default_route >= 0 ? link->dns_default_route : link->network->dns_default_route; if (t >= 0) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - source = link->dns_default_route >= 0 ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC; - r = json_build(&e, JSON_BUILD_OBJECT( + r = json_variant_append_arrayb( + &array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_BOOLEAN("DNSDefaultRoute", t), JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(source)))); if (r < 0) return r; - - r = json_variant_append_array(&array, e); - if (r < 0) - return r; } mode = link->dns_over_tls_mode >= 0 ? link->dns_over_tls_mode : link->network->dns_over_tls_mode; if (mode >= 0) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - source = link->dns_over_tls_mode >= 0 ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC; - r = json_build(&e, JSON_BUILD_OBJECT( + r = json_variant_append_arrayb( + &array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_STRING("DNSOverTLS", dns_over_tls_mode_to_string(mode)), JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(source)))); if (r < 0) return r; - - r = json_variant_append_array(&array, e); - if (r < 0) - return r; } return json_variant_set_field_non_null(v, "DNSSettings", array); @@ -921,7 +870,7 @@ static int pref64_append_json(Link *link, JsonVariant **v) { assert(link); assert(v); - if (!link->network || !link->network->ipv6_accept_ra_use_pref64) + if (!link->network || !link->network->ndisc_use_pref64) return 0; SET_FOREACH(i, link->ndisc_pref64) { @@ -942,71 +891,6 @@ static int pref64_append_json(Link *link, JsonVariant **v) { return json_variant_set_field_non_null(v, "NDisc", w); } -static int dhcp_server_offered_leases_append_json(Link *link, JsonVariant **v) { - _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; - DHCPLease *lease; - int r; - - assert(link); - assert(v); - - if (!link->dhcp_server) - return 0; - - HASHMAP_FOREACH(lease, link->dhcp_server->bound_leases_by_client_id) { - struct in_addr address = { .s_addr = lease->address }; - - r = json_variant_append_arrayb( - &array, - JSON_BUILD_OBJECT( - JSON_BUILD_PAIR_BYTE_ARRAY( - "ClientId", - lease->client_id.data, - lease->client_id.length), - JSON_BUILD_PAIR_IN4_ADDR_NON_NULL("Address", &address), - JSON_BUILD_PAIR_STRING_NON_EMPTY("Hostname", lease->hostname), - JSON_BUILD_PAIR_FINITE_USEC( - "ExpirationUSec", lease->expiration))); - if (r < 0) - return r; - } - - return json_variant_set_field_non_null(v, "Leases", array); -} - -static int dhcp_server_static_leases_append_json(Link *link, JsonVariant **v) { - _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; - DHCPLease *lease; - int r; - - assert(link); - assert(v); - - if (!link->dhcp_server) - return 0; - - HASHMAP_FOREACH(lease, link->dhcp_server->static_leases_by_client_id) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - struct in_addr address = { .s_addr = lease->address }; - - r = json_build(&e, - JSON_BUILD_OBJECT( - JSON_BUILD_PAIR_BYTE_ARRAY( - "ClientId", - lease->client_id.data, - lease->client_id.length), - JSON_BUILD_PAIR_IN4_ADDR_NON_NULL("Address", &address))); - if (r < 0) - return r; - - r = json_variant_append_array(&array, e); - if (r < 0) - return r; - } - - return json_variant_set_field_non_null(v, "StaticLeases", array); -} - static int dhcp_server_append_json(Link *link, JsonVariant **v) { _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; int r; @@ -1024,11 +908,11 @@ static int dhcp_server_append_json(Link *link, JsonVariant **v) { if (r < 0) return r; - r = dhcp_server_offered_leases_append_json(link, &w); + r = dhcp_server_bound_leases_append_json(link->dhcp_server, &w); if (r < 0) return r; - r = dhcp_server_static_leases_append_json(link, &w); + r = dhcp_server_static_leases_append_json(link->dhcp_server, &w); if (r < 0) return r; @@ -1132,6 +1016,29 @@ static int dhcp6_client_pd_append_json(Link *link, JsonVariant **v) { return json_variant_set_field_non_null(v, "Prefixes", array); } +static int dhcp6_client_duid_append_json(Link *link, JsonVariant **v) { + const sd_dhcp_duid *duid; + const void *data; + size_t data_size; + int r; + + assert(link); + assert(v); + + if (!link->dhcp6_client) + return 0; + + r = sd_dhcp6_client_get_duid(link->dhcp6_client, &duid); + if (r < 0) + return 0; + + r = sd_dhcp_duid_get_raw(duid, &data, &data_size); + if (r < 0) + return 0; + + return json_variant_merge_objectb(v, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_BYTE_ARRAY("DUID", data, data_size))); +} + static int dhcp6_client_append_json(Link *link, JsonVariant **v) { _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; int r; @@ -1154,6 +1061,10 @@ static int dhcp6_client_append_json(Link *link, JsonVariant **v) { if (r < 0) return r; + r = dhcp6_client_duid_append_json(link, &w); + if (r < 0) + return r; + return json_variant_set_field_non_null(v, "DHCPv6Client", w); } @@ -1226,6 +1137,52 @@ static int dhcp_client_pd_append_json(Link *link, JsonVariant **v) { return json_variant_set_field_non_null(v, "6rdPrefix", array); } +static int dhcp_client_private_options_append_json(Link *link, JsonVariant **v) { + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; + int r; + + assert(link); + assert(v); + + if (!link->dhcp_lease) + return 0; + + LIST_FOREACH(options, option, link->dhcp_lease->private_options) { + + r = json_variant_append_arrayb( + &array, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_UNSIGNED("Option", option->tag), + JSON_BUILD_PAIR_HEX("PrivateOptionData", option->data, option->length))); + if (r < 0) + return 0; + } + return json_variant_set_field_non_null(v, "PrivateOptions", array); +} + +static int dhcp_client_id_append_json(Link *link, JsonVariant **v) { + const sd_dhcp_client_id *client_id; + const void *data; + size_t l; + int r; + + assert(link); + assert(v); + + if (!link->dhcp_client) + return 0; + + r = sd_dhcp_client_get_client_id(link->dhcp_client, &client_id); + if (r < 0) + return 0; + + r = sd_dhcp_client_id_get_raw(client_id, &data, &l); + if (r < 0) + return 0; + + return json_variant_merge_objectb(v, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_BYTE_ARRAY("ClientIdentifier", data, l))); +} + static int dhcp_client_append_json(Link *link, JsonVariant **v) { _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; int r; @@ -1244,6 +1201,14 @@ static int dhcp_client_append_json(Link *link, JsonVariant **v) { if (r < 0) return r; + r = dhcp_client_private_options_append_json(link, &w); + if (r < 0) + return r; + + r = dhcp_client_id_append_json(link, &w); + if (r < 0) + return r; + return json_variant_set_field_non_null(v, "DHCPv4Client", w); } @@ -1354,11 +1319,11 @@ int link_build_json(Link *link, JsonVariant **ret) { if (r < 0) return r; - r = nexthops_append_json(link->nexthops, &v); + r = nexthops_append_json(link->manager, link->ifindex, &v); if (r < 0) return r; - r = routes_append_json(link->routes, &v); + r = routes_append_json(link->manager, link->ifindex, &v); if (r < 0) return r; @@ -1417,11 +1382,11 @@ int manager_build_json(Manager *manager, JsonVariant **ret) { if (r < 0) return r; - r = nexthops_append_json(manager->nexthops, &v); + r = nexthops_append_json(manager, /* ifindex = */ 0, &v); if (r < 0) return r; - r = routes_append_json(manager->routes, &v); + r = routes_append_json(manager, /* ifindex = */ 0, &v); if (r < 0) return r; diff --git a/src/network/networkd-link-bus.c b/src/network/networkd-link-bus.c index 58d4875..743957d 100644 --- a/src/network/networkd-link-bus.c +++ b/src/network/networkd-link-bus.c @@ -100,10 +100,12 @@ int bus_link_method_set_ntp_servers(sd_bus_message *message, void *userdata, sd_ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid NTP server: %s", *i); } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-ntp-servers", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-ntp-servers", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -134,10 +136,12 @@ static int bus_link_method_set_dns_servers_internal(sd_bus_message *message, voi if (r < 0) return r; - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-dns-servers", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-dns-servers", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) goto finalize; if (r == 0) { @@ -231,10 +235,12 @@ int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_ if (r < 0) return r; - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-domains", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-domains", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -266,10 +272,12 @@ int bus_link_method_set_default_route(sd_bus_message *message, void *userdata, s if (r < 0) return r; - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-default-route", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-default-route", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -310,10 +318,12 @@ int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_er return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid LLMNR setting: %s", llmnr); } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-llmnr", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-llmnr", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -354,10 +364,12 @@ int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_err return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid MulticastDNS setting: %s", mdns); } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-mdns", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-mdns", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -398,10 +410,12 @@ int bus_link_method_set_dns_over_tls(sd_bus_message *message, void *userdata, sd return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNSOverTLS setting: %s", dns_over_tls); } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-dns-over-tls", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-dns-over-tls", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -442,10 +456,12 @@ int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_e return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNSSEC setting: %s", dnssec); } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-dnssec", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-dnssec", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -496,10 +512,12 @@ int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, v return r; } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-dnssec-negative-trust-anchors", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-dnssec-negative-trust-anchors", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -525,10 +543,11 @@ int bus_link_method_revert_ntp(sd_bus_message *message, void *userdata, sd_bus_e if (r < 0) return r; - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.revert-ntp", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.revert-ntp", + /* details= */ NULL, + &l->manager->polkit_registry, error); if (r < 0) return r; if (r == 0) @@ -553,10 +572,12 @@ int bus_link_method_revert_dns(sd_bus_message *message, void *userdata, sd_bus_e if (r < 0) return r; - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.revert-dns", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.revert-dns", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -580,10 +601,12 @@ int bus_link_method_force_renew(sd_bus_message *message, void *userdata, sd_bus_ "Interface %s is not managed by systemd-networkd", l->ifname); - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.forcerenew", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.forcerenew", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -607,10 +630,12 @@ int bus_link_method_renew(sd_bus_message *message, void *userdata, sd_bus_error "Interface %s is not managed by systemd-networkd", l->ifname); - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.renew", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.renew", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -629,10 +654,12 @@ int bus_link_method_reconfigure(sd_bus_message *message, void *userdata, sd_bus_ assert(message); - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.reconfigure", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.reconfigure", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 4ef1be4..6b0f099 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> #include <netinet/in.h> #include <linux/if.h> @@ -17,7 +18,6 @@ #include "bus-util.h" #include "device-private.h" #include "device-util.h" -#include "dhcp-identifier.h" #include "dhcp-lease-internal.h" #include "env-file.h" #include "ethtool-util.h" @@ -35,6 +35,7 @@ #include "networkd-address.h" #include "networkd-bridge-fdb.h" #include "networkd-bridge-mdb.h" +#include "networkd-bridge-vlan.h" #include "networkd-can.h" #include "networkd-dhcp-prefix-delegation.h" #include "networkd-dhcp-server.h" @@ -71,6 +72,53 @@ #include "udev-util.h" #include "vrf.h" +void link_required_operstate_for_online(Link *link, LinkOperationalStateRange *ret) { + assert(link); + assert(ret); + + if (link->network && operational_state_range_is_valid(&link->network->required_operstate_for_online)) + /* If explicitly specified, use it as is. */ + *ret = link->network->required_operstate_for_online; + else if (link->iftype == ARPHRD_CAN) + /* CAN devices do not support addressing, hence defaults to 'carrier'. */ + *ret = (const LinkOperationalStateRange) { + .min = LINK_OPERSTATE_CARRIER, + .max = LINK_OPERSTATE_CARRIER, + }; + else if (link->network && link->network->bond) + /* Bonding slaves do not support addressing. */ + *ret = (const LinkOperationalStateRange) { + .min = LINK_OPERSTATE_ENSLAVED, + .max = LINK_OPERSTATE_ENSLAVED, + }; + else if (STRPTR_IN_SET(link->kind, "batadv", "bond", "bridge", "vrf")) + /* Some of slave interfaces may be offline. */ + *ret = (const LinkOperationalStateRange) { + .min = LINK_OPERSTATE_DEGRADED_CARRIER, + .max = LINK_OPERSTATE_ROUTABLE, + }; + else + *ret = LINK_OPERSTATE_RANGE_DEFAULT; +} + +AddressFamily link_required_family_for_online(Link *link) { + assert(link); + + if (link->network && link->network->required_family_for_online >= 0) + return link->network->required_family_for_online; + + if (link->network && operational_state_range_is_valid(&link->network->required_operstate_for_online)) + /* If RequiredForOnline= is explicitly specified, defaults to no. */ + return ADDRESS_FAMILY_NO; + + if (STRPTR_IN_SET(link->kind, "batadv", "bond", "bridge", "vrf")) + /* As the minimum required operstate for master interfaces is 'degraded-carrier', + * we should request an address assigned to the link for backward compatibility. */ + return ADDRESS_FAMILY_YES; + + return ADDRESS_FAMILY_NO; +} + bool link_ipv6_enabled(Link *link) { assert(link); @@ -207,8 +255,6 @@ static Link *link_free(Link *link) { link_ntp_settings_clear(link); link_dns_settings_clear(link); - link->routes = set_free(link->routes); - link->nexthops = set_free(link->nexthops); link->neighbors = set_free(link->neighbors); link->addresses = set_free(link->addresses); link->qdiscs = set_free(link->qdiscs); @@ -227,7 +273,6 @@ static Link *link_free(Link *link) { free(link->driver); unlink_and_free(link->lease_file); - unlink_and_free(link->lldp_file); unlink_and_free(link->state_file); sd_device_unref(link->dev); @@ -251,7 +296,9 @@ int link_get_by_index(Manager *m, int ifindex, Link **ret) { Link *link; assert(m); - assert(ifindex > 0); + + if (ifindex <= 0) + return -EINVAL; link = hashmap_get(m->links_by_index, INT_TO_PTR(ifindex)); if (!link) @@ -320,7 +367,7 @@ void link_set_state(Link *link, LinkState state) { } int link_stop_engines(Link *link, bool may_keep_dhcp) { - int r = 0, k; + int r, ret = 0; assert(link); assert(link->manager); @@ -333,53 +380,55 @@ int link_stop_engines(Link *link, bool may_keep_dhcp) { FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP_ON_STOP)); if (!keep_dhcp) { - k = sd_dhcp_client_stop(link->dhcp_client); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop DHCPv4 client: %m"); + r = sd_dhcp_client_stop(link->dhcp_client); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCPv4 client: %m")); } - k = sd_dhcp_server_stop(link->dhcp_server); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop DHCPv4 server: %m"); + r = sd_dhcp_server_stop(link->dhcp_server); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCPv4 server: %m")); - k = sd_lldp_rx_stop(link->lldp_rx); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop LLDP Rx: %m"); + r = sd_lldp_rx_stop(link->lldp_rx); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop LLDP Rx: %m")); - k = sd_lldp_tx_stop(link->lldp_tx); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop LLDP Tx: %m"); + r = sd_lldp_tx_stop(link->lldp_tx); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop LLDP Tx: %m")); - k = sd_ipv4ll_stop(link->ipv4ll); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop IPv4 link-local: %m"); + r = sd_ipv4ll_stop(link->ipv4ll); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv4 link-local: %m")); - k = ipv4acd_stop(link); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop IPv4 ACD client: %m"); + r = ipv4acd_stop(link); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv4 ACD client: %m")); - k = sd_dhcp6_client_stop(link->dhcp6_client); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop DHCPv6 client: %m"); + r = sd_dhcp6_client_stop(link->dhcp6_client); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCPv6 client: %m")); - k = dhcp_pd_remove(link, /* only_marked = */ false); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not remove DHCPv6 PD addresses and routes: %m"); + r = dhcp_pd_remove(link, /* only_marked = */ false); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not remove DHCPv6 PD addresses and routes: %m")); - k = ndisc_stop(link); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Discovery: %m"); + r = ndisc_stop(link); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv6 Router Discovery: %m")); ndisc_flush(link); - k = sd_radv_stop(link->radv); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Advertisement: %m"); + r = sd_radv_stop(link->radv); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv6 Router Advertisement: %m")); - return r; + return ret; } void link_enter_failed(Link *link) { + int r; + assert(link); if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) @@ -389,7 +438,22 @@ void link_enter_failed(Link *link) { link_set_state(link, LINK_STATE_FAILED); - (void) link_stop_engines(link, false); + if (!ratelimit_below(&link->automatic_reconfigure_ratelimit)) { + log_link_warning(link, "The interface entered the failed state frequently, refusing to reconfigure it automatically."); + goto stop; + } + + log_link_info(link, "Trying to reconfigure the interface."); + r = link_reconfigure(link, /* force = */ true); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to reconfigure interface: %m"); + goto stop; + } + + return; + +stop: + (void) link_stop_engines(link, /* may_keep_dhcp = */ false); } void link_check_ready(Link *link) { @@ -415,11 +479,9 @@ void link_check_ready(Link *link) { if (!link->activated) return (void) log_link_debug(link, "%s(): link is not activated.", __func__); - if (link->iftype == ARPHRD_CAN) { + if (link->iftype == ARPHRD_CAN) /* let's shortcut things for CAN which doesn't need most of checks below. */ - link_set_state(link, LINK_STATE_CONFIGURED); - return; - } + goto ready; if (!link->stacked_netdevs_created) return (void) log_link_debug(link, "%s(): stacked netdevs are not created.", __func__); @@ -479,7 +541,7 @@ void link_check_ready(Link *link) { * Note, ignore NDisc when ConfigureWithoutCarrier= is enabled, as IPv6AcceptRA= is enabled by default. */ if (!link_ipv4ll_enabled(link) && !link_dhcp4_enabled(link) && !link_dhcp6_enabled(link) && !link_dhcp_pd_is_enabled(link) && - (link->network->configure_without_carrier || !link_ipv6_accept_ra_enabled(link))) + (link->network->configure_without_carrier || !link_ndisc_enabled(link))) goto ready; bool ipv4ll_ready = @@ -497,8 +559,8 @@ void link_check_ready(Link *link) { (!link->network->dhcp_pd_assign || link_check_addresses_ready(link, NETWORK_CONFIG_SOURCE_DHCP_PD)); bool ndisc_ready = - link_ipv6_accept_ra_enabled(link) && link->ndisc_configured && - (!link->network->ipv6_accept_ra_use_autonomous_prefix || + link_ndisc_enabled(link) && link->ndisc_configured && + (!link->network->ndisc_use_autonomous_prefix || link_check_addresses_ready(link, NETWORK_CONFIG_SOURCE_NDISC)); /* If the uplink for PD is self, then request the corresponding DHCP protocol is also ready. */ @@ -652,11 +714,9 @@ static int link_acquire_dynamic_ipv4_conf(Link *link) { log_link_debug(link, "Acquiring IPv4 link-local address."); } - if (link->dhcp_server) { - r = sd_dhcp_server_start(link->dhcp_server); - if (r < 0) - return log_link_warning_errno(link, r, "Could not start DHCP server: %m"); - } + r = link_start_dhcp4_server(link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not start DHCP server: %m"); r = ipv4acd_start(link); if (r < 0) @@ -930,15 +990,58 @@ static void link_drop_from_master(Link *link) { link_unref(set_remove(master->slaves, link)); } -static void link_drop_requests(Link *link) { +static int link_drop_requests(Link *link) { Request *req; + int ret = 0; assert(link); assert(link->manager); - ORDERED_SET_FOREACH(req, link->manager->request_queue) - if (req->link == link) - request_detach(link->manager, req); + ORDERED_SET_FOREACH(req, link->manager->request_queue) { + if (req->link != link) + continue; + + /* If the request is already called, but its reply is not received, then we need to + * drop the configuration (e.g. address) here. Note, if the configuration is known, + * it will be handled later by link_drop_foreign_addresses() or so. */ + if (req->waiting_reply && link->state != LINK_STATE_LINGER) + switch (req->type) { + case REQUEST_TYPE_ADDRESS: { + Address *address = ASSERT_PTR(req->userdata); + + if (address_get(link, address, NULL) < 0) + RET_GATHER(ret, address_remove(address, link)); + break; + } + case REQUEST_TYPE_NEIGHBOR: { + Neighbor *neighbor = ASSERT_PTR(req->userdata); + + if (neighbor_get(link, neighbor, NULL) < 0) + RET_GATHER(ret, neighbor_remove(neighbor, link)); + break; + } + case REQUEST_TYPE_NEXTHOP: { + NextHop *nexthop = ASSERT_PTR(req->userdata); + + if (nexthop_get_by_id(link->manager, nexthop->id, NULL) < 0) + RET_GATHER(ret, nexthop_remove(nexthop, link->manager)); + break; + } + case REQUEST_TYPE_ROUTE: { + Route *route = ASSERT_PTR(req->userdata); + + if (route_get(link->manager, route, NULL) < 0) + RET_GATHER(ret, route_remove(route, link->manager)); + break; + } + default: + ; + } + + request_detach(req); + } + + return ret; } static Link *link_drop(Link *link) { @@ -952,7 +1055,7 @@ static Link *link_drop(Link *link) { /* Drop all references from other links and manager. Note that async netlink calls may have * references to the link, and they will be dropped when we receive replies. */ - link_drop_requests(link); + (void) link_drop_requests(link); link_free_bound_to_list(link); link_free_bound_by_list(link); @@ -1010,12 +1113,11 @@ static int link_drop_managed_config(Link *link) { assert(link); assert(link->manager); - r = link_drop_managed_routes(link); - - RET_GATHER(r, link_drop_managed_nexthops(link)); - RET_GATHER(r, link_drop_managed_addresses(link)); - RET_GATHER(r, link_drop_managed_neighbors(link)); - RET_GATHER(r, link_drop_managed_routing_policy_rules(link)); + r = link_drop_static_routes(link); + RET_GATHER(r, link_drop_static_nexthops(link)); + RET_GATHER(r, link_drop_static_addresses(link)); + RET_GATHER(r, link_drop_static_neighbors(link)); + RET_GATHER(r, link_drop_static_routing_policy_rules(link)); return r; } @@ -1239,10 +1341,20 @@ int link_reconfigure_impl(Link *link, bool force) { return 0; if (network) { + _cleanup_free_ char *joined = strv_join(network->dropins, ", "); + if (link->state == LINK_STATE_INITIALIZED) - log_link_info(link, "Configuring with %s.", network->filename); + log_link_info(link, "Configuring with %s%s%s%s.", + network->filename, + isempty(joined) ? "" : " (dropins: ", + joined, + isempty(joined) ? "" : ")"); else - log_link_info(link, "Reconfiguring with %s.", network->filename); + log_link_info(link, "Reconfiguring with %s%s%s%s.", + network->filename, + isempty(joined) ? "" : " (dropins: ", + joined, + isempty(joined) ? "" : ")"); } else log_link_full(link, link->state == LINK_STATE_INITIALIZED ? LOG_DEBUG : LOG_INFO, "Unmanaging interface."); @@ -1252,7 +1364,9 @@ int link_reconfigure_impl(Link *link, bool force) { if (r < 0) return r; - link_drop_requests(link); + r = link_drop_requests(link); + if (r < 0) + return r; if (network && !force && network->keep_configuration != KEEP_CONFIGURATION_YES) /* When a new/updated .network file is assigned, first make all configs (addresses, @@ -1346,6 +1460,80 @@ int link_reconfigure(Link *link, bool force) { return 1; /* 1 means the interface will be reconfigured. */ } +typedef struct ReconfigureData { + Link *link; + Manager *manager; + sd_bus_message *message; +} ReconfigureData; + +static void reconfigure_data_destroy_callback(ReconfigureData *data) { + int r; + + assert(data); + assert(data->link); + assert(data->manager); + assert(data->manager->reloading > 0); + assert(data->message); + + link_unref(data->link); + + data->manager->reloading--; + if (data->manager->reloading <= 0) { + r = sd_bus_reply_method_return(data->message, NULL); + if (r < 0) + log_warning_errno(r, "Failed to send reply for 'Reload' DBus method, ignoring: %m"); + } + + sd_bus_message_unref(data->message); + free(data); +} + +static int reconfigure_handler_on_bus_method_reload(sd_netlink *rtnl, sd_netlink_message *m, ReconfigureData *data) { + assert(data); + assert(data->link); + return link_reconfigure_handler_internal(rtnl, m, data->link, /* force = */ false); +} + +int link_reconfigure_on_bus_method_reload(Link *link, sd_bus_message *message) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + _cleanup_free_ ReconfigureData *data = NULL; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + assert(message); + + /* See comments in link_reconfigure() above. */ + if (IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_INITIALIZED, LINK_STATE_LINGER)) + return 0; + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_GETLINK, link->ifindex); + if (r < 0) + return r; + + data = new(ReconfigureData, 1); + if (!data) + return -ENOMEM; + + r = netlink_call_async(link->manager->rtnl, NULL, req, + reconfigure_handler_on_bus_method_reload, + reconfigure_data_destroy_callback, data); + if (r < 0) + return r; + + *data = (ReconfigureData) { + .link = link_ref(link), + .manager = link->manager, + .message = sd_bus_message_ref(message), + }; + + link->manager->reloading++; + + TAKE_PTR(data); + return 0; +} + static int link_initialized_and_synced(Link *link) { int r; @@ -1443,9 +1631,9 @@ static int link_check_initialized(Link *link) { return 0; } - r = sd_device_get_is_initialized(device); + r = device_is_processed(device); if (r < 0) - return log_link_warning_errno(link, r, "Could not determine whether the device is initialized: %m"); + return log_link_warning_errno(link, r, "Could not determine whether the device is processed by udevd: %m"); if (r == 0) { /* not yet ready */ log_link_debug(link, "link pending udev initialization..."); @@ -1693,7 +1881,7 @@ static int link_admin_state_up(Link *link) { /* We set the ipv6 mtu after the device mtu, but the kernel resets * ipv6 mtu on NETDEV_UP, so we need to reset it. */ - r = link_set_ipv6_mtu(link); + r = link_set_ipv6_mtu(link, LOG_INFO); if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv6 MTU, ignoring: %m"); @@ -1781,14 +1969,18 @@ void link_update_operstate(Link *link, bool also_update_master) { else operstate = LINK_OPERSTATE_ENSLAVED; + LinkOperationalStateRange req; + link_required_operstate_for_online(link, &req); + /* Only determine online state for managed links with RequiredForOnline=yes */ if (!link->network || !link->network->required_for_online) online_state = _LINK_ONLINE_STATE_INVALID; - else if (operstate < link->network->required_operstate_for_online.min || - operstate > link->network->required_operstate_for_online.max) + + else if (!operational_state_is_in_range(operstate, &req)) online_state = LINK_ONLINE_STATE_OFFLINE; + else { - AddressFamily required_family = link->network->required_family_for_online; + AddressFamily required_family = link_required_family_for_online(link); bool needs_ipv4 = required_family & ADDRESS_FAMILY_IPV4; bool needs_ipv6 = required_family & ADDRESS_FAMILY_IPV6; @@ -1797,14 +1989,14 @@ void link_update_operstate(Link *link, bool also_update_master) { * to offline in the blocks below. */ online_state = LINK_ONLINE_STATE_ONLINE; - if (link->network->required_operstate_for_online.min >= LINK_OPERSTATE_DEGRADED) { + if (req.min >= LINK_OPERSTATE_DEGRADED) { if (needs_ipv4 && ipv4_address_state < LINK_ADDRESS_STATE_DEGRADED) online_state = LINK_ONLINE_STATE_OFFLINE; if (needs_ipv6 && ipv6_address_state < LINK_ADDRESS_STATE_DEGRADED) online_state = LINK_ONLINE_STATE_OFFLINE; } - if (link->network->required_operstate_for_online.min >= LINK_OPERSTATE_ROUTABLE) { + if (req.min >= LINK_OPERSTATE_ROUTABLE) { if (needs_ipv4 && ipv4_address_state < LINK_ADDRESS_STATE_ROUTABLE) online_state = LINK_ONLINE_STATE_OFFLINE; if (needs_ipv6 && ipv6_address_state < LINK_ADDRESS_STATE_ROUTABLE) @@ -2242,6 +2434,13 @@ static int link_update_mtu(Link *link, sd_netlink_message *message) { link->mtu = mtu; + if (IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) { + /* The kernel resets IPv6 MTU after changing device MTU. So, we need to re-set IPv6 MTU again. */ + r = link_set_ipv6_mtu(link, LOG_INFO); + if (r < 0) + log_link_warning_errno(link, r, "Failed to set IPv6 MTU, ignoring: %m"); + } + if (link->dhcp_client) { r = sd_dhcp_client_set_mtu(link->dhcp_client, link->mtu); if (r < 0) @@ -2339,7 +2538,7 @@ static int link_update_name(Link *link, sd_netlink_message *message) { if (link->dhcp6_client) { r = sd_dhcp6_client_set_ifname(link->dhcp6_client, link->ifname); if (r < 0) - return log_link_debug_errno(link, r, "Failed to update interface name in DHCP6 client: %m"); + return log_link_debug_errno(link, r, "Failed to update interface name in DHCPv6 client: %m"); } if (link->ndisc) { @@ -2433,6 +2632,10 @@ static int link_update(Link *link, sd_netlink_message *message) { if (r < 0) return r; + r = link_update_bridge_vlan(link, message); + if (r < 0) + return r; + return needs_reconfigure; } @@ -2447,7 +2650,7 @@ static Link *link_drop_or_unref(Link *link) { DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_drop_or_unref); static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) { - _cleanup_free_ char *ifname = NULL, *kind = NULL, *state_file = NULL, *lease_file = NULL, *lldp_file = NULL; + _cleanup_free_ char *ifname = NULL, *kind = NULL, *state_file = NULL, *lease_file = NULL; _cleanup_(link_drop_or_unrefp) Link *link = NULL; unsigned short iftype; int r, ifindex; @@ -2488,9 +2691,6 @@ static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) { if (asprintf(&lease_file, "/run/systemd/netif/leases/%d", ifindex) < 0) return log_oom_debug(); - - if (asprintf(&lldp_file, "/run/systemd/netif/lldp/%d", ifindex) < 0) - return log_oom_debug(); } link = new(Link, 1); @@ -2501,16 +2701,18 @@ static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) { .n_ref = 1, .state = LINK_STATE_PENDING, .online_state = _LINK_ONLINE_STATE_INVALID, + .automatic_reconfigure_ratelimit = (const RateLimit) { .interval = 10 * USEC_PER_SEC, .burst = 5 }, .ifindex = ifindex, .iftype = iftype, .ifname = TAKE_PTR(ifname), .kind = TAKE_PTR(kind), + .bridge_vlan_pvid = UINT16_MAX, + .ipv6ll_address_gen_mode = _IPV6_LINK_LOCAL_ADDRESS_GEN_MODE_INVALID, .state_file = TAKE_PTR(state_file), .lease_file = TAKE_PTR(lease_file), - .lldp_file = TAKE_PTR(lldp_file), .n_dns = UINT_MAX, .dns_default_route = -1, diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 938bbf4..b1b2fe4 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -21,9 +21,11 @@ #include "log-link.h" #include "netif-util.h" #include "network-util.h" +#include "networkd-bridge-vlan.h" #include "networkd-ipv6ll.h" #include "networkd-util.h" #include "ordered-set.h" +#include "ratelimit.h" #include "resolve-util.h" #include "set.h" @@ -72,6 +74,11 @@ typedef struct Link { sd_device *dev; char *driver; + /* bridge vlan */ + uint16_t bridge_vlan_pvid; + bool bridge_vlan_pvid_is_untagged; + uint32_t bridge_vlan_bitmap[BRIDGE_VLAN_BITMAP_LEN]; + /* to prevent multiple ethtool calls */ bool ethtool_driver_read; bool ethtool_permanent_hw_addr_read; @@ -100,6 +107,7 @@ typedef struct Link { LinkAddressState ipv4_address_state; LinkAddressState ipv6_address_state; LinkOnlineState online_state; + RateLimit automatic_reconfigure_ratelimit; unsigned static_address_messages; unsigned static_address_label_messages; @@ -118,8 +126,6 @@ typedef struct Link { Set *addresses; Set *neighbors; - Set *routes; - Set *nexthops; Set *qdiscs; Set *tclasses; @@ -149,15 +155,19 @@ typedef struct Link { bool activated:1; bool master_set:1; bool stacked_netdevs_created:1; + bool bridge_vlan_set:1; sd_dhcp_server *dhcp_server; sd_ndisc *ndisc; sd_event_source *ndisc_expire; + Hashmap *ndisc_routers_by_sender; Set *ndisc_rdnss; Set *ndisc_dnssl; Set *ndisc_captive_portals; Set *ndisc_pref64; + Set *ndisc_redirects; + uint32_t ndisc_mtu; unsigned ndisc_messages; bool ndisc_configured:1; @@ -174,7 +184,6 @@ typedef struct Link { /* This is about LLDP reception */ sd_lldp_rx *lldp_rx; - char *lldp_file; /* This is about LLDP transmission */ sd_lldp_tx *lldp_tx; @@ -245,9 +254,13 @@ LinkState link_state_from_string(const char *s) _pure_; int link_reconfigure_impl(Link *link, bool force); int link_reconfigure(Link *link, bool force); +int link_reconfigure_on_bus_method_reload(Link *link, sd_bus_message *message); int manager_udev_process_link(Manager *m, sd_device *device, sd_device_action_t action); int manager_rtnl_process_link(sd_netlink *rtnl, sd_netlink_message *message, Manager *m); int link_flags_to_string_alloc(uint32_t flags, char **ret); const char *kernel_operstate_to_string(int t) _const_; + +void link_required_operstate_for_online(Link *link, LinkOperationalStateRange *ret); +AddressFamily link_required_family_for_online(Link *link); diff --git a/src/network/networkd-lldp-rx.c b/src/network/networkd-lldp-rx.c index 3a59884..f744854 100644 --- a/src/network/networkd-lldp-rx.c +++ b/src/network/networkd-lldp-rx.c @@ -52,8 +52,6 @@ static void lldp_rx_handler(sd_lldp_rx *lldp_rx, sd_lldp_rx_event_t event, sd_ll Link *link = ASSERT_PTR(userdata); int r; - (void) link_lldp_save(link); - if (link->lldp_tx && event == SD_LLDP_RX_EVENT_ADDED) { /* If we received information about a new neighbor, restart the LLDP "fast" logic */ @@ -104,70 +102,3 @@ int link_lldp_rx_configure(Link *link) { return 0; } - -int link_lldp_save(Link *link) { - _cleanup_(unlink_and_freep) char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - sd_lldp_neighbor **l = NULL; - int n = 0, r, i; - - assert(link); - - if (isempty(link->lldp_file)) - return 0; /* Do not update state file when running in test mode. */ - - if (!link->lldp_rx) { - (void) unlink(link->lldp_file); - return 0; - } - - r = sd_lldp_rx_get_neighbors(link->lldp_rx, &l); - if (r < 0) - return r; - if (r == 0) { - (void) unlink(link->lldp_file); - return 0; - } - - n = r; - - r = fopen_temporary(link->lldp_file, &f, &temp_path); - if (r < 0) - goto finish; - - (void) fchmod(fileno(f), 0644); - - for (i = 0; i < n; i++) { - const void *p; - le64_t u; - size_t sz; - - r = sd_lldp_neighbor_get_raw(l[i], &p, &sz); - if (r < 0) - goto finish; - - u = htole64(sz); - (void) fwrite(&u, 1, sizeof(u), f); - (void) fwrite(p, 1, sz, f); - } - - r = fflush_and_check(f); - if (r < 0) - goto finish; - - r = conservative_rename(temp_path, link->lldp_file); - if (r < 0) - goto finish; - -finish: - if (r < 0) - log_link_error_errno(link, r, "Failed to save LLDP data to %s: %m", link->lldp_file); - - if (l) { - for (i = 0; i < n; i++) - sd_lldp_neighbor_unref(l[i]); - free(l); - } - - return r; -} diff --git a/src/network/networkd-lldp-rx.h b/src/network/networkd-lldp-rx.h index 22f6602..75c9f8c 100644 --- a/src/network/networkd-lldp-rx.h +++ b/src/network/networkd-lldp-rx.h @@ -14,7 +14,6 @@ typedef enum LLDPMode { } LLDPMode; int link_lldp_rx_configure(Link *link); -int link_lldp_save(Link *link); const char* lldp_mode_to_string(LLDPMode m) _const_; LLDPMode lldp_mode_from_string(const char *s) _pure_; diff --git a/src/network/networkd-lldp-tx.c b/src/network/networkd-lldp-tx.c index fc9196f..f48781e 100644 --- a/src/network/networkd-lldp-tx.c +++ b/src/network/networkd-lldp-tx.c @@ -8,6 +8,7 @@ #include "networkd-link.h" #include "networkd-lldp-tx.h" #include "networkd-manager.h" +#include "networkd-sysctl.h" #include "parse-util.h" #include "string-table.h" #include "string-util.h" @@ -69,9 +70,8 @@ int link_lldp_tx_configure(Link *link) { SD_LLDP_SYSTEM_CAPABILITIES_STATION | SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE | SD_LLDP_SYSTEM_CAPABILITIES_ROUTER, - (link->network->ip_forward != ADDRESS_FAMILY_NO) ? - SD_LLDP_SYSTEM_CAPABILITIES_ROUTER : - SD_LLDP_SYSTEM_CAPABILITIES_STATION); + (link_get_ip_forwarding(link, AF_INET) > 0 || link_get_ip_forwarding(link, AF_INET6) > 0) ? + SD_LLDP_SYSTEM_CAPABILITIES_ROUTER : SD_LLDP_SYSTEM_CAPABILITIES_STATION); if (r < 0) return r; diff --git a/src/network/networkd-manager-bus.c b/src/network/networkd-manager-bus.c index aecbc1d..3c3d815 100644 --- a/src/network/networkd-manager-bus.c +++ b/src/network/networkd-manager-bus.c @@ -198,22 +198,30 @@ static int bus_method_reconfigure_link(sd_bus_message *message, void *userdata, } static int bus_method_reload(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *manager = userdata; + Manager *manager = ASSERT_PTR(userdata); int r; - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.reload", - NULL, true, UID_INVALID, - &manager->polkit_registry, error); + if (manager->reloading > 0) + return sd_bus_error_set(error, BUS_ERROR_NETWORK_ALREADY_RELOADING, "Already reloading."); + + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.reload", + /* details= */ NULL, + &manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) return 1; /* Polkit will call us back */ - r = manager_reload(manager); + r = manager_reload(manager, message); if (r < 0) return r; + if (manager->reloading > 0) + return 1; /* Will reply later. */ + return sd_bus_reply_method_return(message, NULL); } @@ -277,6 +285,31 @@ static int property_get_namespace_id( return sd_bus_message_append(reply, "t", id); } +static int property_get_namespace_nsid( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + uint32_t nsid = UINT32_MAX; + int r; + + assert(bus); + assert(reply); + + /* Returns our own "nsid", which is another ID for the network namespace, different from the inode + * number. */ + + r = netns_get_nsid(/* netnsfd= */ -EBADF, &nsid); + if (r < 0) + log_warning_errno(r, "Failed to query network nsid, ignoring: %m"); + + return sd_bus_message_append(reply, "u", nsid); +} + static const sd_bus_vtable manager_vtable[] = { SD_BUS_VTABLE_START(0), @@ -287,6 +320,7 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PROPERTY("IPv6AddressState", "s", property_get_address_state, offsetof(Manager, ipv6_address_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("OnlineState", "s", property_get_online_state, offsetof(Manager, online_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("NamespaceId", "t", property_get_namespace_id, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NamespaceNSID", "u", property_get_namespace_nsid, 0, 0), SD_BUS_METHOD_WITH_ARGS("ListLinks", SD_BUS_NO_ARGS, diff --git a/src/network/networkd-manager-varlink.c b/src/network/networkd-manager-varlink.c new file mode 100644 index 0000000..5eeed95 --- /dev/null +++ b/src/network/networkd-manager-varlink.c @@ -0,0 +1,314 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <unistd.h> + +#include "bus-polkit.h" +#include "fd-util.h" +#include "lldp-rx-internal.h" +#include "networkd-dhcp-server.h" +#include "networkd-manager-varlink.h" +#include "stat-util.h" +#include "varlink.h" +#include "varlink-io.systemd.Network.h" + +static int vl_method_get_states(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + + assert(link); + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + return varlink_replyb(link, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_STRING("AddressState", link_address_state_to_string(m->address_state)), + JSON_BUILD_PAIR_STRING("IPv4AddressState", link_address_state_to_string(m->ipv4_address_state)), + JSON_BUILD_PAIR_STRING("IPv6AddressState", link_address_state_to_string(m->ipv6_address_state)), + JSON_BUILD_PAIR_STRING("CarrierState", link_carrier_state_to_string(m->carrier_state)), + JSON_BUILD_PAIR_CONDITION(m->online_state >= 0, "OnlineState", JSON_BUILD_STRING(link_online_state_to_string(m->online_state))), + JSON_BUILD_PAIR_STRING("OperationalState", link_operstate_to_string(m->operational_state)))); +} + +static int vl_method_get_namespace_id(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + uint64_t inode = 0; + uint32_t nsid = UINT32_MAX; + int r; + + assert(link); + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + /* Network namespaces have two identifiers: the inode number (which all namespace types have), and + * the "nsid" (aka the "cookie"), which only network namespaces know as a concept, and which is not + * assigned by default, but once it is, is fixed. Let's return both, to avoid any confusion which one + * this is. */ + + struct stat st; + if (stat("/proc/self/ns/net", &st) < 0) + log_warning_errno(errno, "Failed to stat network namespace, ignoring: %m"); + else + inode = st.st_ino; + + r = netns_get_nsid(/* netnsfd= */ -EBADF, &nsid); + if (r < 0) + log_full_errno(r == -ENODATA ? LOG_DEBUG : LOG_WARNING, r, "Failed to query network nsid, ignoring: %m"); + + return varlink_replyb(link, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_UNSIGNED("NamespaceId", inode), + JSON_BUILD_PAIR_CONDITION(nsid == UINT32_MAX, "NamespaceNSID", JSON_BUILD_NULL), + JSON_BUILD_PAIR_CONDITION(nsid != UINT32_MAX, "NamespaceNSID", JSON_BUILD_UNSIGNED(nsid)))); +} + +typedef struct InterfaceInfo { + int ifindex; + const char *ifname; +} InterfaceInfo; + +static int dispatch_interface(Varlink *vlink, JsonVariant *parameters, Manager *manager, Link **ret) { + static const JsonDispatch dispatch_table[] = { + { "InterfaceIndex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, offsetof(InterfaceInfo, ifindex), 0 }, + { "InterfaceName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(InterfaceInfo, ifname), 0 }, + {} + }; + + InterfaceInfo info = {}; + Link *link = NULL; + int r; + + assert(vlink); + assert(manager); + + r = varlink_dispatch(vlink, parameters, dispatch_table, &info); + if (r != 0) + return r; + + if (info.ifindex < 0) + return varlink_error_invalid_parameter(vlink, JSON_VARIANT_STRING_CONST("InterfaceIndex")); + if (info.ifindex > 0 && link_get_by_index(manager, info.ifindex, &link) < 0) + return varlink_error_invalid_parameter(vlink, JSON_VARIANT_STRING_CONST("InterfaceIndex")); + if (info.ifname) { + Link *link_by_name; + + if (link_get_by_name(manager, info.ifname, &link_by_name) < 0) + return varlink_error_invalid_parameter(vlink, JSON_VARIANT_STRING_CONST("InterfaceName")); + + if (link && link_by_name != link) + /* If both arguments are specified, then these must be consistent. */ + return varlink_error_invalid_parameter(vlink, JSON_VARIANT_STRING_CONST("InterfaceName")); + + link = link_by_name; + } + + /* If neither InterfaceIndex nor InterfaceName specified, this function returns NULL. */ + *ret = link; + return 0; +} + +static int link_append_lldp_neighbors(Link *link, JsonVariant *v, JsonVariant **array) { + assert(link); + assert(array); + + return json_variant_append_arrayb(array, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_INTEGER("InterfaceIndex", link->ifindex), + JSON_BUILD_PAIR_STRING("InterfaceName", link->ifname), + JSON_BUILD_PAIR_STRV_NON_EMPTY("InterfaceAlternativeNames", link->alternative_names), + JSON_BUILD_PAIR_CONDITION(json_variant_is_blank_array(v), "Neighbors", JSON_BUILD_EMPTY_ARRAY), + JSON_BUILD_PAIR_CONDITION(!json_variant_is_blank_array(v), "Neighbors", JSON_BUILD_VARIANT(v)))); +} + +static int vl_method_get_lldp_neighbors(Varlink *vlink, JsonVariant *parameters, VarlinkMethodFlags flags, Manager *manager) { + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; + Link *link = NULL; + int r; + + assert(vlink); + assert(manager); + + r = dispatch_interface(vlink, parameters, manager, &link); + if (r != 0) + return r; + + if (link) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + if (link->lldp_rx) { + r = lldp_rx_build_neighbors_json(link->lldp_rx, &v); + if (r < 0) + return r; + } + + r = link_append_lldp_neighbors(link, v, &array); + if (r < 0) + return r; + } else + HASHMAP_FOREACH(link, manager->links_by_index) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + if (!link->lldp_rx) + continue; + + r = lldp_rx_build_neighbors_json(link->lldp_rx, &v); + if (r < 0) + return r; + + if (json_variant_is_blank_array(v)) + continue; + + r = link_append_lldp_neighbors(link, v, &array); + if (r < 0) + return r; + } + + return varlink_replyb(vlink, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_CONDITION(json_variant_is_blank_array(array), "Neighbors", JSON_BUILD_EMPTY_ARRAY), + JSON_BUILD_PAIR_CONDITION(!json_variant_is_blank_array(array), "Neighbors", JSON_BUILD_VARIANT(array)))); +} + +static int vl_method_set_persistent_storage(Varlink *vlink, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + static const JsonDispatch dispatch_table[] = { + { "Ready", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, 0, 0 }, + {} + }; + + Manager *manager = ASSERT_PTR(userdata); + bool ready; + int r; + + assert(vlink); + + r = varlink_dispatch(vlink, parameters, dispatch_table, &ready); + if (r != 0) + return r; + + if (ready) { + struct stat st, st_prev; + int fd; + + fd = varlink_peek_fd(vlink, 0); + if (fd < 0) + return log_warning_errno(fd, "Failed to peek file descriptor of the persistent storage: %m"); + + r = fd_verify_safe_flags_full(fd, O_DIRECTORY); + if (r == -EREMOTEIO) + return log_warning_errno(r, "Passed persistent storage fd has unexpected flags, refusing."); + if (r < 0) + return log_warning_errno(r, "Failed to verify flags of passed persistent storage fd: %m"); + + r = fd_is_read_only_fs(fd); + if (r < 0) + return log_warning_errno(r, "Failed to check if the persistent storage is writable: %m"); + if (r > 0) { + log_warning("The persistent storage is on read-only filesystem."); + return varlink_error(vlink, "io.systemd.Network.StorageReadOnly", NULL); + } + + if (fstat(fd, &st) < 0) + return log_warning_errno(errno, "Failed to stat the passed persistent storage fd: %m"); + + r = stat_verify_directory(&st); + if (r < 0) + return log_warning_errno(r, "The passed persistent storage fd is not a directory, refusing: %m"); + + if (manager->persistent_storage_fd >= 0 && + fstat(manager->persistent_storage_fd, &st_prev) >= 0 && + stat_inode_same(&st, &st_prev)) + return varlink_reply(vlink, NULL); + + } else { + if (manager->persistent_storage_fd < 0) + return varlink_reply(vlink, NULL); + } + + r = varlink_verify_polkit_async( + vlink, + manager->bus, + "org.freedesktop.network1.set-persistent-storage", + /* details= */ NULL, + &manager->polkit_registry); + if (r <= 0) + return r; + + if (ready) { + _cleanup_close_ int fd = -EBADF; + + fd = varlink_take_fd(vlink, 0); + if (fd < 0) + return log_warning_errno(fd, "Failed to take file descriptor of the persistent storage: %m"); + + close_and_replace(manager->persistent_storage_fd, fd); + } else + manager->persistent_storage_fd = safe_close(manager->persistent_storage_fd); + + manager_toggle_dhcp4_server_state(manager, ready); + + return varlink_reply(vlink, NULL); +} + +static int on_connect(VarlinkServer *s, Varlink *vlink, void *userdata) { + int r; + + assert(vlink); + + r = varlink_set_allow_fd_passing_input(vlink, true); + if (r < 0) + return log_warning_errno(r, "Failed to allow receiving file descriptor through varlink: %m"); + + return 0; +} + +int manager_connect_varlink(Manager *m) { + _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; + int r; + + assert(m); + + if (m->varlink_server) + return 0; + + r = varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA); + if (r < 0) + return log_error_errno(r, "Failed to allocate varlink server object: %m"); + + varlink_server_set_userdata(s, m); + + (void) varlink_server_set_description(s, "varlink-api-network"); + + r = varlink_server_add_interface(s, &vl_interface_io_systemd_Network); + if (r < 0) + return log_error_errno(r, "Failed to add Network interface to varlink server: %m"); + + r = varlink_server_bind_method_many( + s, + "io.systemd.Network.GetStates", vl_method_get_states, + "io.systemd.Network.GetNamespaceId", vl_method_get_namespace_id, + "io.systemd.Network.GetLLDPNeighbors", vl_method_get_lldp_neighbors, + "io.systemd.Network.SetPersistentStorage", vl_method_set_persistent_storage); + if (r < 0) + return log_error_errno(r, "Failed to register varlink methods: %m"); + + r = varlink_server_listen_address(s, "/run/systemd/netif/io.systemd.Network", 0666); + if (r < 0) + return log_error_errno(r, "Failed to bind to varlink socket: %m"); + + r = varlink_server_attach_event(s, m->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); + + r = varlink_server_bind_connect(s, on_connect); + if (r < 0) + return log_error_errno(r, "Failed to set on-connect callback for varlink: %m"); + + m->varlink_server = TAKE_PTR(s); + return 0; +} + +void manager_varlink_done(Manager *m) { + assert(m); + + m->varlink_server = varlink_server_unref(m->varlink_server); + (void) unlink("/run/systemd/netif/io.systemd.Network"); +} diff --git a/src/network/networkd-manager-varlink.h b/src/network/networkd-manager-varlink.h new file mode 100644 index 0000000..46078a8 --- /dev/null +++ b/src/network/networkd-manager-varlink.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "networkd-manager.h" + +int manager_connect_varlink(Manager *m); +void manager_varlink_done(Manager *m); diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index c09dcfb..4ec4550 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -23,6 +23,7 @@ #include "device-private.h" #include "device-util.h" #include "dns-domain.h" +#include "env-util.h" #include "fd-util.h" #include "fileio.h" #include "firewall-util.h" @@ -36,8 +37,9 @@ #include "networkd-dhcp-server-bus.h" #include "networkd-dhcp6.h" #include "networkd-link-bus.h" -#include "networkd-manager-bus.h" #include "networkd-manager.h" +#include "networkd-manager-bus.h" +#include "networkd-manager-varlink.h" #include "networkd-neighbor.h" #include "networkd-network-bus.h" #include "networkd-nexthop.h" @@ -163,7 +165,6 @@ static int manager_connect_bus(Manager *m) { static int manager_process_uevent(sd_device_monitor *monitor, sd_device *device, void *userdata) { Manager *m = ASSERT_PTR(userdata); sd_device_action_t action; - const char *s; int r; assert(device); @@ -172,20 +173,12 @@ static int manager_process_uevent(sd_device_monitor *monitor, sd_device *device, if (r < 0) return log_device_warning_errno(device, r, "Failed to get udev action, ignoring: %m"); - r = sd_device_get_subsystem(device, &s); - if (r < 0) - return log_device_warning_errno(device, r, "Failed to get subsystem, ignoring: %m"); - - if (streq(s, "net")) + if (device_in_subsystem(device, "net")) r = manager_udev_process_link(m, device, action); - else if (streq(s, "ieee80211")) + else if (device_in_subsystem(device, "ieee80211")) r = manager_udev_process_wiphy(m, device, action); - else if (streq(s, "rfkill")) + else if (device_in_subsystem(device, "rfkill")) r = manager_udev_process_rfkill(m, device, action); - else { - log_device_debug(device, "Received device with unexpected subsystem \"%s\", ignoring.", s); - return 0; - } if (r < 0) log_device_warning_errno(device, r, "Failed to process \"%s\" uevent, ignoring: %m", device_action_to_string(action)); @@ -429,24 +422,13 @@ static int manager_connect_rtnl(Manager *m, int fd) { return manager_setup_rtnl_filter(m); } -static int manager_dirty_handler(sd_event_source *s, void *userdata) { - Manager *m = ASSERT_PTR(userdata); - Link *link; - int r; - - if (m->dirty) { - r = manager_save(m); - if (r < 0) - log_warning_errno(r, "Failed to update state file %s, ignoring: %m", m->state_file); - } - - SET_FOREACH(link, m->dirty_links) { - r = link_save_and_clean(link); - if (r < 0) - log_link_warning_errno(link, r, "Failed to update link state file %s, ignoring: %m", link->state_file); - } +static int manager_post_handler(sd_event_source *s, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); - return 1; + (void) manager_process_remove_requests(manager); + (void) manager_process_requests(manager); + (void) manager_clean_all(manager); + return 0; } static int signal_terminate_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { @@ -472,7 +454,7 @@ static int signal_restart_callback(sd_event_source *s, const struct signalfd_sig static int signal_reload_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { Manager *m = ASSERT_PTR(userdata); - manager_reload(m); + (void) manager_reload(m, /* message = */ NULL); return 0; } @@ -522,11 +504,7 @@ int manager_setup(Manager *m) { if (r < 0) log_debug_errno(r, "Failed allocate memory pressure event source, ignoring: %m"); - r = sd_event_add_post(m->event, NULL, manager_dirty_handler, m); - if (r < 0) - return r; - - r = sd_event_add_post(m->event, NULL, manager_process_requests, m); + r = sd_event_add_post(m->event, NULL, manager_post_handler, m); if (r < 0) return r; @@ -545,6 +523,10 @@ int manager_setup(Manager *m) { if (m->test_mode) return 0; + r = manager_connect_varlink(m); + if (r < 0) + return r; + r = manager_connect_bus(m); if (r < 0) return r; @@ -576,6 +558,29 @@ int manager_setup(Manager *m) { return 0; } +static int persistent_storage_open(void) { + _cleanup_close_ int fd = -EBADF; + int r; + + r = getenv_bool("SYSTEMD_NETWORK_PERSISTENT_STORAGE_READY"); + if (r < 0 && r != -ENXIO) + return log_debug_errno(r, "Failed to parse $SYSTEMD_NETWORK_PERSISTENT_STORAGE_READY environment variable, ignoring: %m"); + if (r <= 0) + return -EBADF; + + fd = open("/var/lib/systemd/network/", O_CLOEXEC | O_DIRECTORY); + if (fd < 0) + return log_debug_errno(errno, "Failed to open /var/lib/systemd/network/, ignoring: %m"); + + r = fd_is_read_only_fs(fd); + if (r < 0) + return log_debug_errno(r, "Failed to check if /var/lib/systemd/network/ is writable: %m"); + if (r > 0) + return log_debug_errno(SYNTHETIC_ERRNO(EROFS), "The directory /var/lib/systemd/network/ is on read-only filesystem."); + + return TAKE_FD(fd); +} + int manager_new(Manager **ret, bool test_mode) { _cleanup_(manager_freep) Manager *m = NULL; @@ -591,10 +596,17 @@ int manager_new(Manager **ret, bool test_mode) { .online_state = _LINK_ONLINE_STATE_INVALID, .manage_foreign_routes = true, .manage_foreign_rules = true, + .manage_foreign_nexthops = true, .ethtool_fd = -EBADF, + .persistent_storage_fd = persistent_storage_open(), + .dhcp_use_domains = _USE_DOMAINS_INVALID, + .dhcp6_use_domains = _USE_DOMAINS_INVALID, + .ndisc_use_domains = _USE_DOMAINS_INVALID, .dhcp_duid.type = DUID_TYPE_EN, .dhcp6_duid.type = DUID_TYPE_EN, .duid_product_uuid.type = DUID_TYPE_UUID, + .dhcp_server_persist_leases = true, + .ip_forwarding = { -1, -1, }, }; *ret = TAKE_PTR(m); @@ -613,6 +625,7 @@ Manager* manager_free(Manager *m) { (void) link_stop_engines(link, true); m->request_queue = ordered_set_free(m->request_queue); + m->remove_request_queue = ordered_set_free(m->remove_request_queue); m->dirty_links = set_free_with_destructor(m->dirty_links, link_unref); m->new_wlan_ifindices = set_free(m->new_wlan_ifindices); @@ -648,21 +661,23 @@ Manager* manager_free(Manager *m) { * set_free() must be called after the above sd_netlink_unref(). */ m->routes = set_free(m->routes); - m->nexthops = set_free(m->nexthops); m->nexthops_by_id = hashmap_free(m->nexthops_by_id); + m->nexthop_ids = set_free(m->nexthop_ids); sd_event_source_unref(m->speed_meter_event_source); sd_event_unref(m->event); sd_device_monitor_unref(m->device_monitor); - bus_verify_polkit_async_registry_free(m->polkit_registry); + manager_varlink_done(m); + hashmap_free(m->polkit_registry); sd_bus_flush_close_unref(m->bus); free(m->dynamic_timezone); free(m->dynamic_hostname); safe_close(m->ethtool_fd); + safe_close(m->persistent_storage_fd); m->fw_ctx = fw_ctx_free(m->fw_ctx); @@ -675,6 +690,8 @@ int manager_start(Manager *m) { assert(m); + manager_set_sysctl(m); + r = manager_start_speed_meter(m); if (r < 0) return log_error_errno(r, "Failed to initialize speed meter: %m"); @@ -708,7 +725,15 @@ int manager_load_config(Manager *m) { if (r < 0) return r; - return manager_build_dhcp_pd_subnet_ids(m); + r = manager_build_dhcp_pd_subnet_ids(m); + if (r < 0) + return r; + + r = manager_build_nexthop_ids(m); + if (r < 0) + return r; + + return 0; } int manager_enumerate_internal( @@ -752,6 +777,20 @@ static int manager_enumerate_links(Manager *m) { if (r < 0) return r; + r = manager_enumerate_internal(m, m->rtnl, req, manager_rtnl_process_link); + if (r < 0) + return r; + + req = sd_netlink_message_unref(req); + + r = sd_rtnl_message_new_link(m->rtnl, &req, RTM_GETLINK, 0); + if (r < 0) + return r; + + r = sd_rtnl_message_link_set_family(req, AF_BRIDGE); + if (r < 0) + return r; + return manager_enumerate_internal(m, m->rtnl, req, manager_rtnl_process_link); } @@ -853,6 +892,9 @@ static int manager_enumerate_nexthop(Manager *m) { assert(m); assert(m->rtnl); + if (!m->manage_foreign_nexthops) + return 0; + r = sd_rtnl_message_new_nexthop(m->rtnl, &req, RTM_GETNEXTHOP, 0, 0); if (r < 0) return r; @@ -1076,16 +1118,13 @@ int manager_set_timezone(Manager *m, const char *tz) { return 0; } -int manager_reload(Manager *m) { +int manager_reload(Manager *m, sd_bus_message *message) { Link *link; int r; assert(m); - (void) sd_notifyf(/* unset= */ false, - "RELOADING=1\n" - "STATUS=Reloading configuration...\n" - "MONOTONIC_USEC=" USEC_FMT, now(CLOCK_MONOTONIC)); + (void) notify_reloading(); r = netdev_load(m, /* reload= */ true); if (r < 0) @@ -1096,9 +1135,14 @@ int manager_reload(Manager *m) { goto finish; HASHMAP_FOREACH(link, m->links_by_index) { - r = link_reconfigure(link, /* force = */ false); - if (r < 0) - goto finish; + if (message) + r = link_reconfigure_on_bus_method_reload(link, message); + else + r = link_reconfigure(link, /* force = */ false); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to reconfigure the interface: %m"); + link_enter_failed(link); + } } r = 0; diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index fbef528..c14a98f 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -8,7 +8,7 @@ #include "sd-netlink.h" #include "sd-resolve.h" -#include "dhcp-identifier.h" +#include "dhcp-duid-internal.h" #include "firewall-util.h" #include "hashmap.h" #include "networkd-link.h" @@ -17,6 +17,7 @@ #include "ordered-set.h" #include "set.h" #include "time-util.h" +#include "varlink.h" struct Manager { sd_netlink *rtnl; @@ -25,9 +26,11 @@ struct Manager { sd_event *event; sd_resolve *resolve; sd_bus *bus; + VarlinkServer *varlink_server; sd_device_monitor *device_monitor; Hashmap *polkit_registry; int ethtool_fd; + int persistent_storage_fd; KeepConfiguration keep_configuration; IPv6PrivacyExtensions ipv6_privacy_extensions; @@ -38,6 +41,8 @@ struct Manager { bool restarting; bool manage_foreign_routes; bool manage_foreign_rules; + bool manage_foreign_nexthops; + bool dhcp_server_persist_leases; Set *dirty_links; Set *new_wlan_ifindices; @@ -59,6 +64,11 @@ struct Manager { OrderedSet *address_pools; Set *dhcp_pd_subnet_ids; + UseDomains use_domains; /* default for all protocols */ + UseDomains dhcp_use_domains; + UseDomains dhcp6_use_domains; + UseDomains ndisc_use_domains; + DUID dhcp_duid; DUID dhcp6_duid; DUID duid_product_uuid; @@ -72,9 +82,7 @@ struct Manager { /* Manage nexthops by id. */ Hashmap *nexthops_by_id; - - /* Manager stores nexthops without RTA_OIF attribute. */ - Set *nexthops; + Set *nexthop_ids; /* requested IDs in .network files */ /* Manager stores routes without RTA_OIF attribute. */ unsigned route_remove_messages; @@ -99,9 +107,16 @@ struct Manager { FirewallContext *fw_ctx; + bool request_queued; OrderedSet *request_queue; + OrderedSet *remove_request_queue; Hashmap *tuntap_fds_by_name; + + unsigned reloading; + + /* sysctl */ + int ip_forwarding[2]; }; int manager_new(Manager **ret, bool test_mode); @@ -122,6 +137,6 @@ int manager_enumerate(Manager *m); int manager_set_hostname(Manager *m, const char *hostname); int manager_set_timezone(Manager *m, const char *timezone); -int manager_reload(Manager *m); +int manager_reload(Manager *m, sd_bus_message *message); DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 840ccb1..7cafe1f 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -12,6 +12,7 @@ #include "event-util.h" #include "missing_network.h" +#include "ndisc-router-internal.h" #include "networkd-address-generation.h" #include "networkd-address.h" #include "networkd-dhcp6.h" @@ -20,6 +21,7 @@ #include "networkd-queue.h" #include "networkd-route.h" #include "networkd-state-file.h" +#include "networkd-sysctl.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -34,7 +36,9 @@ * Not sure if the threshold is high enough. Let's adjust later if not. */ #define NDISC_PREF64_MAX 64U -bool link_ipv6_accept_ra_enabled(Link *link) { +static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t timestamp_usec); + +bool link_ndisc_enabled(Link *link) { assert(link); if (!socket_ipv6_is_supported()) @@ -52,24 +56,33 @@ bool link_ipv6_accept_ra_enabled(Link *link) { if (!link_may_have_ipv6ll(link, /* check_multicast = */ true)) return false; - assert(link->network->ipv6_accept_ra >= 0); - return link->network->ipv6_accept_ra; + /* Honor explicitly specified value. */ + if (link->network->ndisc >= 0) + return link->network->ndisc; + + /* Disable if RADV is enabled. */ + if (link_radv_enabled(link)) + return false; + + /* Accept RAs if IPv6 forwarding is disabled, and ignore RAs if IPv6 forwarding is enabled. */ + int t = link_get_ip_forwarding(link, AF_INET6); + if (t >= 0) + return !t; + + /* Otherwise, defaults to true. */ + return true; } -void network_adjust_ipv6_accept_ra(Network *network) { +void network_adjust_ndisc(Network *network) { assert(network); if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) { - if (network->ipv6_accept_ra > 0) + if (network->ndisc > 0) log_warning("%s: IPv6AcceptRA= is enabled but IPv6 link-local addressing is disabled or not supported. " "Disabling IPv6AcceptRA=.", network->filename); - network->ipv6_accept_ra = false; + network->ndisc = false; } - if (network->ipv6_accept_ra < 0) - /* default to accept RA if ip_forward is disabled and ignore RA if ip_forward is enabled */ - network->ipv6_accept_ra = !FLAGS_SET(network->ip_forward, ADDRESS_FAMILY_IPV6); - /* When RouterAllowList=, PrefixAllowList= or RouteAllowList= are specified, then * RouterDenyList=, PrefixDenyList= or RouteDenyList= are ignored, respectively. */ if (!set_isempty(network->ndisc_allow_listed_router)) @@ -139,7 +152,7 @@ static int ndisc_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request assert(link); - r = route_configure_handler_internal(rtnl, m, link, "Could not set NDisc route"); + r = route_configure_handler_internal(rtnl, m, link, route, "Could not set NDisc route"); if (r <= 0) return r; @@ -159,66 +172,84 @@ static void ndisc_set_route_priority(Link *link, Route *route) { switch (route->pref) { case SD_NDISC_PREFERENCE_LOW: - route->priority = link->network->ipv6_accept_ra_route_metric_low; + route->priority = link->network->ndisc_route_metric_low; break; case SD_NDISC_PREFERENCE_MEDIUM: - route->priority = link->network->ipv6_accept_ra_route_metric_medium; + route->priority = link->network->ndisc_route_metric_medium; break; case SD_NDISC_PREFERENCE_HIGH: - route->priority = link->network->ipv6_accept_ra_route_metric_high; + route->priority = link->network->ndisc_route_metric_high; break; default: assert_not_reached(); } } -static int ndisc_request_route(Route *in, Link *link, sd_ndisc_router *rt) { - _cleanup_(route_freep) Route *route = in; - struct in6_addr router; - uint8_t hop_limit = 0; - uint32_t mtu = 0; - bool is_new; +static int ndisc_request_route(Route *route, Link *link) { int r; assert(route); assert(link); + assert(link->manager); assert(link->network); - assert(rt); - r = sd_ndisc_router_get_address(rt, &router); + route->source = NETWORK_CONFIG_SOURCE_NDISC; + + if (!route->table_set) + route->table = link_get_ndisc_route_table(link); + + r = route_metric_set(&route->metric, RTAX_QUICKACK, link->network->ndisc_quickack); if (r < 0) return r; - if (link->network->ipv6_accept_ra_use_mtu) { - r = sd_ndisc_router_get_mtu(rt, &mtu); - if (r < 0 && r != -ENODATA) - return log_link_warning_errno(link, r, "Failed to get default router MTU from RA: %m"); - } + r = route_adjust_nexthops(route, link); + if (r < 0) + return r; + + uint8_t pref, pref_original = route->pref; + FOREACH_ARGUMENT(pref, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH) { + Route *existing; + Request *req; + + /* If the preference is specified by the user config (that is, for semi-static routes), + * rather than RA, then only search conflicting routes that have the same preference. */ + if (route->pref_set && pref != pref_original) + continue; - if (link->network->ipv6_accept_ra_use_hop_limit) { - r = sd_ndisc_router_get_hop_limit(rt, &hop_limit); - if (r < 0 && r != -ENODATA) - return log_link_warning_errno(link, r, "Failed to get default router hop limit from RA: %m"); + route->pref = pref; + ndisc_set_route_priority(link, route); + + /* Note, here do not call route_remove_and_cancel() with 'route' directly, otherwise + * existing route(s) may be removed needlessly. */ + + if (route_get(link->manager, route, &existing) >= 0) { + /* Found an existing route that may conflict with this route. */ + if (!route_can_update(existing, route)) { + log_link_debug(link, "Found an existing route that conflicts with new route based on a received RA, removing."); + r = route_remove_and_cancel(existing, link->manager); + if (r < 0) + return r; + } + } + + if (route_get_request(link->manager, route, &req) >= 0) { + existing = ASSERT_PTR(req->userdata); + if (!route_can_update(existing, route)) { + log_link_debug(link, "Found a pending route request that conflicts with new request based on a received RA, cancelling."); + r = route_remove_and_cancel(existing, link->manager); + if (r < 0) + return r; + } + } } - route->source = NETWORK_CONFIG_SOURCE_NDISC; - route->provider.in6 = router; - if (!route->table_set) - route->table = link_get_ipv6_accept_ra_route_table(link); + /* The preference (and priority) may be changed in the above loop. Restore it. */ + route->pref = pref_original; ndisc_set_route_priority(link, route); - if (!route->protocol_set) - route->protocol = RTPROT_RA; - if (route->quickack < 0) - route->quickack = link->network->ipv6_accept_ra_quickack; - if (route->mtu == 0) - route->mtu = mtu; - if (route->hop_limit == 0) - route->hop_limit = hop_limit; - is_new = route_get(NULL, link, route, NULL) < 0; + bool is_new = route_get(link->manager, route, NULL) < 0; - r = link_request_route(link, TAKE_PTR(route), true, &link->ndisc_messages, - ndisc_route_handler, NULL); + r = link_request_route(link, route, &link->ndisc_messages, ndisc_route_handler); if (r < 0) return r; if (r > 0 && is_new) @@ -227,6 +258,56 @@ static int ndisc_request_route(Route *in, Link *link, sd_ndisc_router *rt) { return 0; } +static int ndisc_request_router_route(Route *route, Link *link, sd_ndisc_router *rt) { + int r; + + assert(route); + assert(link); + assert(rt); + + r = sd_ndisc_router_get_sender_address(rt, &route->provider.in6); + if (r < 0) + return r; + + if (!route->protocol_set) + route->protocol = RTPROT_RA; + + return ndisc_request_route(route, link); +} + +static int ndisc_remove_route(Route *route, Link *link) { + int r; + + assert(route); + assert(link); + assert(link->manager); + + ndisc_set_route_priority(link, route); + + if (!route->table_set) + route->table = link_get_ndisc_route_table(link); + + r = route_adjust_nexthops(route, link); + if (r < 0) + return r; + + if (route->pref_set) { + ndisc_set_route_priority(link, route); + return route_remove_and_cancel(route, link->manager); + } + + uint8_t pref; + FOREACH_ARGUMENT(pref, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH) { + route->pref = pref; + ndisc_set_route_priority(link, route); + r = route_remove_and_cancel(route, link->manager); + if (r < 0) + return r; + } + + return 0; +} + static int ndisc_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, Address *address) { int r; @@ -243,28 +324,39 @@ static int ndisc_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Reques return 1; } -static int ndisc_request_address(Address *in, Link *link, sd_ndisc_router *rt) { - _cleanup_(address_freep) Address *address = in; - struct in6_addr router; +static int ndisc_request_address(Address *address, Link *link) { bool is_new; int r; assert(address); assert(link); - assert(rt); - - r = sd_ndisc_router_get_address(rt, &router); - if (r < 0) - return r; address->source = NETWORK_CONFIG_SOURCE_NDISC; - address->provider.in6 = router; r = free_and_strdup_warn(&address->netlabel, link->network->ndisc_netlabel); if (r < 0) return r; - is_new = address_get(link, address, NULL) < 0; + Address *existing; + if (address_get_harder(link, address, &existing) < 0) + is_new = true; + else if (address_can_update(existing, address)) + is_new = false; + else if (existing->source == NETWORK_CONFIG_SOURCE_DHCP6) { + /* SLAAC address is preferred over DHCPv6 address. */ + log_link_debug(link, "Conflicting DHCPv6 address %s exists, removing.", + IN_ADDR_PREFIX_TO_STRING(existing->family, &existing->in_addr, existing->prefixlen)); + r = address_remove(existing, link); + if (r < 0) + return r; + + is_new = true; + } else { + /* Conflicting static address is configured?? */ + log_link_debug(link, "Conflicting address %s exists, ignoring request.", + IN_ADDR_PREFIX_TO_STRING(existing->family, &existing->in_addr, existing->prefixlen)); + return 0; + } r = link_request_address(link, address, &link->ndisc_messages, ndisc_address_handler, NULL); @@ -276,17 +368,397 @@ static int ndisc_request_address(Address *in, Link *link, sd_ndisc_router *rt) { return 0; } +int ndisc_reconfigure_address(Address *address, Link *link) { + int r; + + assert(address); + assert(address->source == NETWORK_CONFIG_SOURCE_NDISC); + assert(link); + + r = regenerate_address(address, link); + if (r <= 0) + return r; + + r = ndisc_request_address(address, link); + if (r < 0) + return r; + + if (!link->ndisc_configured) + link_set_state(link, LINK_STATE_CONFIGURING); + + link_check_ready(link); + return 0; +} + +static int ndisc_redirect_route_new(sd_ndisc_redirect *rd, Route **ret) { + _cleanup_(route_unrefp) Route *route = NULL; + struct in6_addr gateway, destination; + int r; + + assert(rd); + assert(ret); + + r = sd_ndisc_redirect_get_target_address(rd, &gateway); + if (r < 0) + return r; + + r = sd_ndisc_redirect_get_destination_address(rd, &destination); + if (r < 0) + return r; + + r = route_new(&route); + if (r < 0) + return r; + + route->family = AF_INET6; + if (!in6_addr_equal(&gateway, &destination)) { + route->nexthop.gw.in6 = gateway; + route->nexthop.family = AF_INET6; + } + route->dst.in6 = destination; + route->dst_prefixlen = 128; + route->protocol = RTPROT_REDIRECT; + + r = sd_ndisc_redirect_get_sender_address(rd, &route->provider.in6); + if (r < 0) + return r; + + *ret = TAKE_PTR(route); + return 0; +} + +static int ndisc_remove_redirect_route(Link *link, sd_ndisc_redirect *rd) { + _cleanup_(route_unrefp) Route *route = NULL; + int r; + + assert(link); + assert(rd); + + r = ndisc_redirect_route_new(rd, &route); + if (r < 0) + return r; + + return ndisc_remove_route(route, link); +} + +static void ndisc_redirect_hash_func(const sd_ndisc_redirect *x, struct siphash *state) { + struct in6_addr dest = {}; + + assert(x); + assert(state); + + (void) sd_ndisc_redirect_get_destination_address((sd_ndisc_redirect*) x, &dest); + + siphash24_compress_typesafe(dest, state); +} + +static int ndisc_redirect_compare_func(const sd_ndisc_redirect *x, const sd_ndisc_redirect *y) { + struct in6_addr dest_x = {}, dest_y = {}; + + assert(x); + assert(y); + + (void) sd_ndisc_redirect_get_destination_address((sd_ndisc_redirect*) x, &dest_x); + (void) sd_ndisc_redirect_get_destination_address((sd_ndisc_redirect*) y, &dest_y); + + return memcmp(&dest_x, &dest_y, sizeof(dest_x)); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + ndisc_redirect_hash_ops, + sd_ndisc_redirect, + ndisc_redirect_hash_func, + ndisc_redirect_compare_func, + sd_ndisc_redirect_unref); + +static int ndisc_redirect_equal(sd_ndisc_redirect *x, sd_ndisc_redirect *y) { + struct in6_addr a, b; + int r; + + assert(x); + assert(y); + + r = sd_ndisc_redirect_get_destination_address(x, &a); + if (r < 0) + return r; + + r = sd_ndisc_redirect_get_destination_address(y, &b); + if (r < 0) + return r; + + if (!in6_addr_equal(&a, &b)) + return false; + + r = sd_ndisc_redirect_get_target_address(x, &a); + if (r < 0) + return r; + + r = sd_ndisc_redirect_get_target_address(y, &b); + if (r < 0) + return r; + + return in6_addr_equal(&a, &b); +} + +static int ndisc_redirect_drop_conflict(Link *link, sd_ndisc_redirect *rd) { + _cleanup_(sd_ndisc_redirect_unrefp) sd_ndisc_redirect *existing = NULL; + int r; + + assert(link); + assert(rd); + + existing = set_remove(link->ndisc_redirects, rd); + if (!existing) + return 0; + + r = ndisc_redirect_equal(rd, existing); + if (r != 0) + return r; + + return ndisc_remove_redirect_route(link, existing); +} + +static int ndisc_redirect_verify_sender(Link *link, sd_ndisc_redirect *rd) { + int r; + + assert(link); + assert(rd); + + /* RFC 4861 section 8.1 + * The IP source address of the Redirect is the same as the current first-hop router for the specified + * ICMP Destination Address. */ + + struct in6_addr sender; + r = sd_ndisc_redirect_get_sender_address(rd, &sender); + if (r < 0) + return r; + + /* We will reuse the sender's router lifetime as the lifetime of the redirect route. Hence, if we + * have not remembered an RA from the sender, refuse the Redirect message. */ + sd_ndisc_router *router = hashmap_get(link->ndisc_routers_by_sender, &sender); + if (!router) + return false; + + sd_ndisc_redirect *existing = set_get(link->ndisc_redirects, rd); + if (existing) { + struct in6_addr target, dest; + + /* If we have received Redirect message for the host, the sender must be the previous target. */ + + r = sd_ndisc_redirect_get_target_address(existing, &target); + if (r < 0) + return r; + + if (in6_addr_equal(&sender, &target)) + return true; + + /* If the existing redirect route is on-link, that is, the destination and target address are + * equivalent, then also accept Redirect message from the current default router. This is not + * mentioned by the RFC, but without this, we cannot update on-link redirect route. */ + r = sd_ndisc_redirect_get_destination_address(existing, &dest); + if (r < 0) + return r; + + if (!in6_addr_equal(&dest, &target)) + return false; + } + + /* Check if the sender is one of the known router with highest priority. */ + uint8_t preference; + r = sd_ndisc_router_get_preference(router, &preference); + if (r < 0) + return r; + + if (preference == SD_NDISC_PREFERENCE_HIGH) + return true; + + sd_ndisc_router *rt; + HASHMAP_FOREACH(rt, link->ndisc_routers_by_sender) { + if (rt == router) + continue; + + uint8_t pref; + if (sd_ndisc_router_get_preference(rt, &pref) < 0) + continue; + + if (pref == SD_NDISC_PREFERENCE_HIGH || + (pref == SD_NDISC_PREFERENCE_MEDIUM && preference == SD_NDISC_PREFERENCE_LOW)) + return false; + } + + return true; +} + +static int ndisc_redirect_handler(Link *link, sd_ndisc_redirect *rd) { + int r; + + assert(link); + assert(link->network); + assert(rd); + + if (!link->network->ndisc_use_redirect) + return 0; + + usec_t now_usec; + r = sd_event_now(link->manager->event, CLOCK_BOOTTIME, &now_usec); + if (r < 0) + return r; + + r = ndisc_drop_outdated(link, /* router = */ NULL, now_usec); + if (r < 0) + return r; + + r = ndisc_redirect_verify_sender(link, rd); + if (r <= 0) + return r; + + /* First, drop conflicting redirect route, if exists. */ + r = ndisc_redirect_drop_conflict(link, rd); + if (r < 0) + return r; + + /* Then, remember the received message. */ + r = set_ensure_put(&link->ndisc_redirects, &ndisc_redirect_hash_ops, rd); + if (r < 0) + return r; + + sd_ndisc_redirect_ref(rd); + + /* Finally, request the corresponding route. */ + _cleanup_(route_unrefp) Route *route = NULL; + r = ndisc_redirect_route_new(rd, &route); + if (r < 0) + return r; + + sd_ndisc_router *rt = hashmap_get(link->ndisc_routers_by_sender, &route->provider.in6); + if (!rt) + return -EADDRNOTAVAIL; + + r = sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &route->lifetime_usec); + if (r < 0) + return r; + + return ndisc_request_route(route, link); +} + +static int ndisc_drop_redirect(Link *link, const struct in6_addr *router) { + int r, ret = 0; + + assert(link); + + sd_ndisc_redirect *rd; + SET_FOREACH(rd, link->ndisc_redirects) { + if (router) { + struct in6_addr a; + + if (!(sd_ndisc_redirect_get_sender_address(rd, &a) >= 0 && in6_addr_equal(&a, router)) && + !(sd_ndisc_redirect_get_target_address(rd, &a) >= 0 && in6_addr_equal(&a, router))) + continue; + } + + r = ndisc_remove_redirect_route(link, rd); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to remove redirect route, ignoring: %m")); + + sd_ndisc_redirect_unref(set_remove(link->ndisc_redirects, rd)); + } + + return ret; +} + +static int ndisc_update_redirect_sender(Link *link, const struct in6_addr *original_address, const struct in6_addr *current_address) { + int r; + + assert(link); + assert(original_address); + assert(current_address); + + sd_ndisc_redirect *rd; + SET_FOREACH(rd, link->ndisc_redirects) { + struct in6_addr sender; + + r = sd_ndisc_redirect_get_sender_address(rd, &sender); + if (r < 0) + return r; + + if (!in6_addr_equal(&sender, original_address)) + continue; + + r = sd_ndisc_redirect_set_sender_address(rd, current_address); + if (r < 0) + return r; + } + + return 0; +} + +static int ndisc_router_drop_default(Link *link, sd_ndisc_router *rt) { + _cleanup_(route_unrefp) Route *route = NULL; + struct in6_addr gateway; + int r; + + assert(link); + assert(link->network); + assert(rt); + + r = sd_ndisc_router_get_sender_address(rt, &gateway); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); + + r = route_new(&route); + if (r < 0) + return log_oom(); + + route->family = AF_INET6; + route->nexthop.family = AF_INET6; + route->nexthop.gw.in6 = gateway; + + r = ndisc_remove_route(route, link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to remove the default gateway configured by RA: %m"); + + Route *route_gw; + HASHMAP_FOREACH(route_gw, link->network->routes_by_section) { + _cleanup_(route_unrefp) Route *tmp = NULL; + + if (!route_gw->gateway_from_dhcp_or_ra) + continue; + + if (route_gw->nexthop.family != AF_INET6) + continue; + + r = route_dup(route_gw, NULL, &tmp); + if (r < 0) + return r; + + tmp->nexthop.gw.in6 = gateway; + + r = ndisc_remove_route(tmp, link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not remove semi-static gateway: %m"); + } + + return 0; +} + static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { usec_t lifetime_usec; struct in6_addr gateway; - unsigned preference; + uint8_t preference; int r; assert(link); assert(link->network); assert(rt); - if (!link->network->ipv6_accept_ra_use_gateway && + /* If the router lifetime is zero, the router should not be used as the default gateway. */ + r = sd_ndisc_router_get_lifetime(rt, NULL); + if (r < 0) + return r; + if (r == 0) + return ndisc_router_drop_default(link, rt); + + if (!link->network->ndisc_use_gateway && hashmap_isempty(link->network->routes_by_section)) return 0; @@ -294,23 +766,16 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { if (r < 0) return log_link_warning_errno(link, r, "Failed to get gateway lifetime from RA: %m"); - r = sd_ndisc_router_get_address(rt, &gateway); + r = sd_ndisc_router_get_sender_address(rt, &gateway); if (r < 0) return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m"); - if (link_get_ipv6_address(link, &gateway, 0, NULL) >= 0) { - if (DEBUG_LOGGING) - log_link_debug(link, "No NDisc route added, gateway %s matches local address", - IN6_ADDR_TO_STRING(&gateway)); - return 0; - } - r = sd_ndisc_router_get_preference(rt, &preference); if (r < 0) - return log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m"); + return log_link_warning_errno(link, r, "Failed to get router preference from RA: %m"); - if (link->network->ipv6_accept_ra_use_gateway) { - _cleanup_(route_freep) Route *route = NULL; + if (link->network->ndisc_use_gateway) { + _cleanup_(route_unrefp) Route *route = NULL; r = route_new(&route); if (r < 0) @@ -318,35 +783,35 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { route->family = AF_INET6; route->pref = preference; - route->gw_family = AF_INET6; - route->gw.in6 = gateway; + route->nexthop.family = AF_INET6; + route->nexthop.gw.in6 = gateway; route->lifetime_usec = lifetime_usec; - r = ndisc_request_route(TAKE_PTR(route), link, rt); + r = ndisc_request_router_route(route, link, rt); if (r < 0) return log_link_warning_errno(link, r, "Could not request default route: %m"); } Route *route_gw; HASHMAP_FOREACH(route_gw, link->network->routes_by_section) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; if (!route_gw->gateway_from_dhcp_or_ra) continue; - if (route_gw->gw_family != AF_INET6) + if (route_gw->nexthop.family != AF_INET6) continue; - r = route_dup(route_gw, &route); + r = route_dup(route_gw, NULL, &route); if (r < 0) return r; - route->gw.in6 = gateway; + route->nexthop.gw.in6 = gateway; if (!route->pref_set) route->pref = preference; route->lifetime_usec = lifetime_usec; - r = ndisc_request_route(TAKE_PTR(route), link, rt); + r = ndisc_request_router_route(route, link, rt); if (r < 0) return log_link_warning_errno(link, r, "Could not request gateway: %m"); } @@ -354,54 +819,310 @@ static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { return 0; } -static int ndisc_router_process_icmp6_ratelimit(Link *link, sd_ndisc_router *rt) { - usec_t icmp6_ratelimit, msec; +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + ndisc_router_hash_ops, + struct in6_addr, + in6_addr_hash_func, + in6_addr_compare_func, + sd_ndisc_router, + sd_ndisc_router_unref); + +static int ndisc_update_router_address(Link *link, const struct in6_addr *original_address, const struct in6_addr *current_address) { + _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL; + int r; + + assert(link); + assert(original_address); + assert(current_address); + + rt = hashmap_remove(link->ndisc_routers_by_sender, original_address); + if (!rt) + return 0; + + /* If we already received an RA from the new address, then forget the RA from the old address. */ + if (hashmap_contains(link->ndisc_routers_by_sender, current_address)) + return 0; + + /* Otherwise, update the sender address of the previously received RA. */ + r = sd_ndisc_router_set_sender_address(rt, current_address); + if (r < 0) + return r; + + r = hashmap_put(link->ndisc_routers_by_sender, &rt->packet->sender_address, rt); + if (r < 0) + return r; + + TAKE_PTR(rt); + return 0; +} + +static int ndisc_drop_router_one(Link *link, sd_ndisc_router *rt, usec_t timestamp_usec) { + usec_t lifetime_usec; + int r; + + assert(link); + assert(rt); + assert(rt->packet); + + r = sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec); + if (r < 0) + return r; + + if (lifetime_usec > timestamp_usec) + return 0; + + r = ndisc_drop_redirect(link, &rt->packet->sender_address); + + sd_ndisc_router_unref(hashmap_remove(link->ndisc_routers_by_sender, &rt->packet->sender_address)); + + return r; +} + +static int ndisc_drop_routers(Link *link, const struct in6_addr *router, usec_t timestamp_usec) { + sd_ndisc_router *rt; + int ret = 0; + + assert(link); + + if (router) { + rt = hashmap_get(link->ndisc_routers_by_sender, router); + if (!rt) + return 0; + + return ndisc_drop_router_one(link, rt, timestamp_usec); + } + + HASHMAP_FOREACH_KEY(rt, router, link->ndisc_routers_by_sender) + RET_GATHER(ret, ndisc_drop_router_one(link, rt, timestamp_usec)); + + return ret; +} + +static int ndisc_remember_router(Link *link, sd_ndisc_router *rt) { + int r; + + assert(link); + assert(rt); + assert(rt->packet); + + sd_ndisc_router_unref(hashmap_remove(link->ndisc_routers_by_sender, &rt->packet->sender_address)); + + /* Remember RAs with non-zero lifetime. */ + r = sd_ndisc_router_get_lifetime(rt, NULL); + if (r <= 0) + return r; + + r = hashmap_ensure_put(&link->ndisc_routers_by_sender, &ndisc_router_hash_ops, &rt->packet->sender_address, rt); + if (r < 0) + return r; + + sd_ndisc_router_ref(rt); + return 0; +} + +static int ndisc_router_process_reachable_time(Link *link, sd_ndisc_router *rt) { + usec_t reachable_time, msec; int r; assert(link); assert(link->network); assert(rt); - if (!link->network->ipv6_accept_ra_use_icmp6_ratelimit) + if (!link->network->ndisc_use_reachable_time) + return 0; + + r = sd_ndisc_router_get_reachable_time(rt, &reachable_time); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get reachable time from RA: %m"); + + /* 0 is the unspecified value and must not be set (see RFC4861, 6.3.4) */ + if (!timestamp_is_set(reachable_time)) + return 0; + + msec = DIV_ROUND_UP(reachable_time, USEC_PER_MSEC); + if (msec <= 0 || msec > UINT32_MAX) { + log_link_debug(link, "Failed to get reachable time from RA - out of range (%"PRIu64"), ignoring", msec); + return 0; + } + + /* Set the reachable time for Neighbor Solicitations. */ + r = sysctl_write_ip_neighbor_property_uint32(AF_INET6, link->ifname, "base_reachable_time_ms", (uint32_t) msec); + if (r < 0) + log_link_warning_errno(link, r, "Failed to apply neighbor reachable time (%"PRIu64"), ignoring: %m", msec); + + return 0; +} + +static int ndisc_router_process_retransmission_time(Link *link, sd_ndisc_router *rt) { + usec_t retrans_time, msec; + int r; + + assert(link); + assert(link->network); + assert(rt); + + if (!link->network->ndisc_use_retransmission_time) + return 0; + + r = sd_ndisc_router_get_retransmission_time(rt, &retrans_time); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get retransmission time from RA: %m"); + + /* 0 is the unspecified value and must not be set (see RFC4861, 6.3.4) */ + if (!timestamp_is_set(retrans_time)) return 0; - r = sd_ndisc_router_get_icmp6_ratelimit(rt, &icmp6_ratelimit); - if (r < 0) { - log_link_debug(link, "Failed to get ICMP6 ratelimit from RA, ignoring: %m"); + msec = DIV_ROUND_UP(retrans_time, USEC_PER_MSEC); + if (msec <= 0 || msec > UINT32_MAX) { + log_link_debug(link, "Failed to get retransmission time from RA - out of range (%"PRIu64"), ignoring", msec); return 0; } - /* We do not allow 0 here. */ - if (!timestamp_is_set(icmp6_ratelimit)) + /* Set the retransmission time for Neighbor Solicitations. */ + r = sysctl_write_ip_neighbor_property_uint32(AF_INET6, link->ifname, "retrans_time_ms", (uint32_t) msec); + if (r < 0) + log_link_warning_errno(link, r, "Failed to apply neighbor retransmission time (%"PRIu64"), ignoring: %m", msec); + + return 0; +} + +static int ndisc_router_process_hop_limit(Link *link, sd_ndisc_router *rt) { + uint8_t hop_limit; + int r; + + assert(link); + assert(link->network); + assert(rt); + + if (!link->network->ndisc_use_hop_limit) + return 0; + + r = sd_ndisc_router_get_hop_limit(rt, &hop_limit); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get hop limit from RA: %m"); + + /* 0 is the unspecified value and must not be set (see RFC4861, 6.3.4): + * + * A Router Advertisement field (e.g., Cur Hop Limit, Reachable Time, and Retrans Timer) may contain + * a value denoting that it is unspecified. In such cases, the parameter should be ignored and the + * host should continue using whatever value it is already using. In particular, a host MUST NOT + * interpret the unspecified value as meaning change back to the default value that was in use before + * the first Router Advertisement was received. + * + * If the received Cur Hop Limit value is non-zero, the host SHOULD set + * its CurHopLimit variable to the received value.*/ + if (hop_limit <= 0) + return 0; + + r = sysctl_write_ip_property_uint32(AF_INET6, link->ifname, "hop_limit", (uint32_t) hop_limit); + if (r < 0) + log_link_warning_errno(link, r, "Failed to apply hop_limit (%u), ignoring: %m", hop_limit); + + return 0; +} + +static int ndisc_router_process_mtu(Link *link, sd_ndisc_router *rt) { + uint32_t mtu; + int r; + + assert(link); + assert(link->network); + assert(rt); + + if (!link->network->ndisc_use_mtu) return 0; - msec = DIV_ROUND_UP(icmp6_ratelimit, USEC_PER_MSEC); - if (msec <= 0 || msec > INT_MAX) + r = sd_ndisc_router_get_mtu(rt, &mtu); + if (r == -ENODATA) return 0; + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get MTU from RA: %m"); + + link->ndisc_mtu = mtu; - /* Limit the maximal rates for sending ICMPv6 packets. 0 to disable any limiting, otherwise the - * minimal space between responses in milliseconds. Default: 1000. */ - r = sysctl_write_ip_property_int(AF_INET6, NULL, "icmp/ratelimit", (int) msec); + r = link_set_ipv6_mtu(link, LOG_DEBUG); if (r < 0) - log_link_warning_errno(link, r, "Failed to apply ICMP6 ratelimit, ignoring: %m"); + log_link_warning_errno(link, r, "Failed to apply IPv6 MTU (%"PRIu32"), ignoring: %m", mtu); return 0; } +static int ndisc_address_set_lifetime(Address *address, Link *link, sd_ndisc_router *rt) { + Address *existing; + usec_t t; + int r; + + assert(address); + assert(link); + assert(rt); + + /* This is mostly based on RFC 4862 section 5.5.3 (e). However, the definition of 'RemainingLifetime' + * is ambiguous, and there is no clear explanation when the address is not assigned yet. If we assume + * that 'RemainingLifetime' is zero in that case, then IPv6 Core Conformance test [v6LC.3.2.5 Part C] + * fails. So, in such case, we skip the conditions about 'RemainingLifetime'. */ + + r = sd_ndisc_router_prefix_get_valid_lifetime_timestamp(rt, CLOCK_BOOTTIME, &address->lifetime_valid_usec); + if (r < 0) + return r; + + r = sd_ndisc_router_prefix_get_preferred_lifetime_timestamp(rt, CLOCK_BOOTTIME, &address->lifetime_preferred_usec); + if (r < 0) + return r; + + /* RFC 4862 section 5.5.3 (e) + * 1. If the received Valid Lifetime is greater than 2 hours or greater than RemainingLifetime, + * set the valid lifetime of the corresponding address to the advertised Valid Lifetime. */ + r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &t); + if (r < 0) + return r; + + if (t > 2 * USEC_PER_HOUR) + return 0; + + if (address_get(link, address, &existing) < 0 || existing->source != NETWORK_CONFIG_SOURCE_NDISC) + return 0; + + if (address->lifetime_valid_usec > existing->lifetime_valid_usec) + return 0; + + /* 2. If RemainingLifetime is less than or equal to 2 hours, ignore the Prefix Information option + * with regards to the valid lifetime, unless the Router Advertisement from which this option was + * obtained has been authenticated (e.g., via Secure Neighbor Discovery [RFC3971]). If the Router + * Advertisement was authenticated, the valid lifetime of the corresponding address should be set + * to the Valid Lifetime in the received option. + * + * Currently, authentication is not supported. So check the lifetime of the existing address. */ + r = sd_ndisc_router_get_timestamp(rt, CLOCK_BOOTTIME, &t); + if (r < 0) + return r; + + if (existing->lifetime_valid_usec <= usec_add(t, 2 * USEC_PER_HOUR)) { + address->lifetime_valid_usec = existing->lifetime_valid_usec; + return 0; + } + + /* 3. Otherwise, reset the valid lifetime of the corresponding address to 2 hours. */ + address->lifetime_valid_usec = usec_add(t, 2 * USEC_PER_HOUR); + return 0; +} + static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) { usec_t lifetime_valid_usec, lifetime_preferred_usec; - _cleanup_set_free_ Set *addresses = NULL; - struct in6_addr prefix, *a; - unsigned prefixlen; + struct in6_addr prefix, router; + uint8_t prefixlen; int r; assert(link); assert(link->network); assert(rt); - if (!link->network->ipv6_accept_ra_use_autonomous_prefix) + if (!link->network->ndisc_use_autonomous_prefix) return 0; + r = sd_ndisc_router_get_sender_address(rt, &router); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get router address: %m"); + r = sd_ndisc_router_prefix_get_address(rt, &prefix); if (r < 0) return log_link_warning_errno(link, r, "Failed to get prefix address: %m"); @@ -417,37 +1138,48 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r return 0; } - r = sd_ndisc_router_prefix_get_valid_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_valid_usec); + r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime_valid_usec); if (r < 0) return log_link_warning_errno(link, r, "Failed to get prefix valid lifetime: %m"); - r = sd_ndisc_router_prefix_get_preferred_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_preferred_usec); + r = sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime_preferred_usec); if (r < 0) return log_link_warning_errno(link, r, "Failed to get prefix preferred lifetime: %m"); - /* The preferred lifetime is never greater than the valid lifetime */ + /* RFC 4862 section 5.5.3 (c) + * If the preferred lifetime is greater than the valid lifetime, silently ignore the Prefix + * Information option. */ if (lifetime_preferred_usec > lifetime_valid_usec) return 0; - r = ndisc_generate_addresses(link, &prefix, prefixlen, &addresses); + _cleanup_hashmap_free_ Hashmap *tokens_by_address = NULL; + r = ndisc_generate_addresses(link, &prefix, prefixlen, &tokens_by_address); if (r < 0) return log_link_warning_errno(link, r, "Failed to generate SLAAC addresses: %m"); - SET_FOREACH(a, addresses) { - _cleanup_(address_freep) Address *address = NULL; + IPv6Token *token; + struct in6_addr *a; + HASHMAP_FOREACH_KEY(token, a, tokens_by_address) { + _cleanup_(address_unrefp) Address *address = NULL; r = address_new(&address); if (r < 0) return log_oom(); + address->provider.in6 = router; address->family = AF_INET6; address->in_addr.in6 = *a; address->prefixlen = prefixlen; address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR; - address->lifetime_valid_usec = lifetime_valid_usec; - address->lifetime_preferred_usec = lifetime_preferred_usec; + address->token = ipv6_token_ref(token); - r = ndisc_request_address(TAKE_PTR(address), link, rt); + r = ndisc_address_set_lifetime(address, link, rt); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to set lifetime of SLAAC address: %m"); + + assert(address->lifetime_preferred_usec <= address->lifetime_valid_usec); + + r = ndisc_request_address(address, link); if (r < 0) return log_link_warning_errno(link, r, "Could not request SLAAC address: %m"); } @@ -456,8 +1188,8 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r } static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { - _cleanup_(route_freep) Route *route = NULL; - unsigned prefixlen, preference; + _cleanup_(route_unrefp) Route *route = NULL; + uint8_t prefixlen, preference; usec_t lifetime_usec; struct in6_addr prefix; int r; @@ -466,7 +1198,7 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { assert(link->network); assert(rt); - if (!link->network->ipv6_accept_ra_use_onlink_prefix) + if (!link->network->ndisc_use_onlink_prefix) return 0; r = sd_ndisc_router_prefix_get_valid_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec); @@ -484,7 +1216,7 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { /* Prefix Information option does not have preference, hence we use the 'main' preference here */ r = sd_ndisc_router_get_preference(rt, &preference); if (r < 0) - log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m"); + return log_link_warning_errno(link, r, "Failed to get router preference from RA: %m"); r = route_new(&route); if (r < 0) @@ -496,17 +1228,30 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { route->pref = preference; route->lifetime_usec = lifetime_usec; - r = ndisc_request_route(TAKE_PTR(route), link, rt); - if (r < 0) - return log_link_warning_errno(link, r, "Could not request prefix route: %m"); + /* RFC 4861 section 6.3.4: + * - If the prefix is not already present in the Prefix List, and the Prefix Information option's + * Valid Lifetime field is non-zero, create a new entry for the prefix and initialize its + * invalidation timer to the Valid Lifetime value in the Prefix Information option. + * + * - If the prefix is already present in the host's Prefix List as the result of a previously + * received advertisement, reset its invalidation timer to the Valid Lifetime value in the Prefix + * Information option. If the new Lifetime value is zero, time-out the prefix immediately. */ + if (lifetime_usec == 0) { + r = ndisc_remove_route(route, link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to remove prefix route: %m"); + } else { + r = ndisc_request_router_route(route, link, rt); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to request prefix route: %m"); + } return 0; } static int ndisc_router_process_prefix(Link *link, sd_ndisc_router *rt) { - unsigned prefixlen; + uint8_t flags, prefixlen; struct in6_addr a; - uint8_t flags; int r; assert(link); @@ -521,7 +1266,7 @@ static int ndisc_router_process_prefix(Link *link, sd_ndisc_router *rt) { * A router SHOULD NOT send a prefix option for the link-local prefix and a host SHOULD ignore such * a prefix option. */ if (in6_addr_is_link_local(&a)) { - log_link_debug(link, "Received link-local prefix, ignoring autonomous prefix."); + log_link_debug(link, "Received link-local prefix, ignoring prefix."); return 0; } @@ -558,15 +1303,15 @@ static int ndisc_router_process_prefix(Link *link, sd_ndisc_router *rt) { } static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { - _cleanup_(route_freep) Route *route = NULL; - unsigned preference, prefixlen; + _cleanup_(route_unrefp) Route *route = NULL; + uint8_t preference, prefixlen; struct in6_addr gateway, dst; usec_t lifetime_usec; int r; assert(link); - if (!link->network->ipv6_accept_ra_use_route_prefix) + if (!link->network->ndisc_use_route_prefix) return 0; r = sd_ndisc_router_route_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec); @@ -581,11 +1326,6 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { if (r < 0) return log_link_warning_errno(link, r, "Failed to get route prefix length: %m"); - if (in6_addr_is_null(&dst) && prefixlen == 0) { - log_link_debug(link, "Route prefix is ::/0, ignoring"); - return 0; - } - if (in6_prefix_is_filtered(&dst, prefixlen, link->network->ndisc_allow_listed_route_prefix, link->network->ndisc_deny_listed_route_prefix)) { @@ -598,7 +1338,7 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { return 0; } - r = sd_ndisc_router_get_address(rt, &gateway); + r = sd_ndisc_router_get_sender_address(rt, &gateway); if (r < 0) return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m"); @@ -610,12 +1350,12 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { } r = sd_ndisc_router_route_get_preference(rt, &preference); - if (r == -ENOTSUP) { + if (r == -EOPNOTSUPP) { log_link_debug_errno(link, r, "Received route prefix with unsupported preference, ignoring: %m"); return 0; } if (r < 0) - return log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m"); + return log_link_warning_errno(link, r, "Failed to get router preference from RA: %m"); r = route_new(&route); if (r < 0) @@ -623,21 +1363,27 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { route->family = AF_INET6; route->pref = preference; - route->gw.in6 = gateway; - route->gw_family = AF_INET6; + route->nexthop.gw.in6 = gateway; + route->nexthop.family = AF_INET6; route->dst.in6 = dst; route->dst_prefixlen = prefixlen; route->lifetime_usec = lifetime_usec; - r = ndisc_request_route(TAKE_PTR(route), link, rt); - if (r < 0) - return log_link_warning_errno(link, r, "Could not request additional route: %m"); + if (lifetime_usec != 0) { + r = ndisc_request_router_route(route, link, rt); + if (r < 0) + return log_link_warning_errno(link, r, "Could not request additional route: %m"); + } else { + r = ndisc_remove_route(route, link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not remove additional route with zero lifetime: %m"); + } return 0; } static void ndisc_rdnss_hash_func(const NDiscRDNSS *x, struct siphash *state) { - siphash24_compress(&x->address, sizeof(x->address), state); + siphash24_compress_typesafe(x->address, state); } static int ndisc_rdnss_compare_func(const NDiscRDNSS *a, const NDiscRDNSS *b) { @@ -662,10 +1408,10 @@ static int ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) { assert(link->network); assert(rt); - if (!link->network->ipv6_accept_ra_use_dns) + if (!link_get_use_dns(link, NETWORK_CONFIG_SOURCE_NDISC)) return 0; - r = sd_ndisc_router_get_address(rt, &router); + r = sd_ndisc_router_get_sender_address(rt, &router); if (r < 0) return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); @@ -744,7 +1490,7 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( free); static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) { - _cleanup_strv_free_ char **l = NULL; + char **l; usec_t lifetime_usec; struct in6_addr router; bool updated = false, logged_about_too_many = false; @@ -754,10 +1500,10 @@ static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) { assert(link->network); assert(rt); - if (link->network->ipv6_accept_ra_use_domains == DHCP_USE_DOMAINS_NO) + if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_NDISC) <= 0) return 0; - r = sd_ndisc_router_get_address(rt, &router); + r = sd_ndisc_router_get_sender_address(rt, &router); if (r < 0) return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); @@ -848,21 +1594,20 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( static int ndisc_router_process_captive_portal(Link *link, sd_ndisc_router *rt) { _cleanup_(ndisc_captive_portal_freep) NDiscCaptivePortal *new_entry = NULL; _cleanup_free_ char *captive_portal = NULL; + const char *uri; usec_t lifetime_usec; NDiscCaptivePortal *exist; struct in6_addr router; - const char *uri; - size_t len; int r; assert(link); assert(link->network); assert(rt); - if (!link->network->ipv6_accept_ra_use_captive_portal) + if (!link->network->ndisc_use_captive_portal) return 0; - r = sd_ndisc_router_get_address(rt, &router); + r = sd_ndisc_router_get_sender_address(rt, &router); if (r < 0) return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); @@ -873,31 +1618,25 @@ static int ndisc_router_process_captive_portal(Link *link, sd_ndisc_router *rt) if (r < 0) return log_link_warning_errno(link, r, "Failed to get lifetime of RA message: %m"); - r = sd_ndisc_router_captive_portal_get_uri(rt, &uri, &len); + r = sd_ndisc_router_get_captive_portal(rt, &uri); if (r < 0) return log_link_warning_errno(link, r, "Failed to get captive portal from RA: %m"); - if (len == 0) - return log_link_warning_errno(link, SYNTHETIC_ERRNO(EBADMSG), "Received empty captive portal, ignoring."); - - r = make_cstring(uri, len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &captive_portal); - if (r < 0) - return log_link_warning_errno(link, r, "Failed to convert captive portal URI: %m"); - - if (!in_charset(captive_portal, URI_VALID)) - return log_link_warning_errno(link, SYNTHETIC_ERRNO(EBADMSG), "Received invalid captive portal, ignoring."); + captive_portal = strdup(uri); + if (!captive_portal) + return log_oom(); if (lifetime_usec == 0) { /* Drop the portal with zero lifetime. */ ndisc_captive_portal_free(set_remove(link->ndisc_captive_portals, - &(NDiscCaptivePortal) { + &(const NDiscCaptivePortal) { .captive_portal = captive_portal, })); return 0; } exist = set_get(link->ndisc_captive_portals, - &(NDiscCaptivePortal) { + &(const NDiscCaptivePortal) { .captive_portal = captive_portal, }); if (exist) { @@ -943,8 +1682,8 @@ static int ndisc_router_process_captive_portal(Link *link, sd_ndisc_router *rt) static void ndisc_pref64_hash_func(const NDiscPREF64 *x, struct siphash *state) { assert(x); - siphash24_compress(&x->prefix_len, sizeof(x->prefix_len), state); - siphash24_compress(&x->prefix, sizeof(x->prefix), state); + siphash24_compress_typesafe(x->prefix_len, state); + siphash24_compress_typesafe(x->prefix, state); } static int ndisc_pref64_compare_func(const NDiscPREF64 *a, const NDiscPREF64 *b) { @@ -971,7 +1710,7 @@ static int ndisc_router_process_pref64(Link *link, sd_ndisc_router *rt) { _cleanup_free_ NDiscPREF64 *new_entry = NULL; usec_t lifetime_usec; struct in6_addr a, router; - unsigned prefix_len; + uint8_t prefix_len; NDiscPREF64 *exist; int r; @@ -979,10 +1718,10 @@ static int ndisc_router_process_pref64(Link *link, sd_ndisc_router *rt) { assert(link->network); assert(rt); - if (!link->network->ipv6_accept_ra_use_pref64) + if (!link->network->ndisc_use_pref64) return 0; - r = sd_ndisc_router_get_address(rt, &router); + r = sd_ndisc_router_get_sender_address(rt, &router); if (r < 0) return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); @@ -1102,7 +1841,7 @@ static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) { } } -static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { +static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t timestamp_usec) { bool updated = false; NDiscDNSSL *dnssl; NDiscRDNSS *rdnss; @@ -1110,9 +1849,10 @@ static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { NDiscPREF64 *p64; Address *address; Route *route; - int r = 0, k; + int r, ret = 0; assert(link); + assert(link->manager); /* If an address or friends is already assigned, but not valid anymore, then refuse to update it, * and let's immediately remove it. @@ -1120,58 +1860,86 @@ static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { * valid lifetimes to improve the reaction of SLAAC to renumbering events. * See draft-ietf-6man-slaac-renum-02, section 4.2. */ - SET_FOREACH(route, link->routes) { + r = ndisc_drop_routers(link, router, timestamp_usec); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to drop outdated default router, ignoring: %m")); + + SET_FOREACH(route, link->manager->routes) { if (route->source != NETWORK_CONFIG_SOURCE_NDISC) continue; - if (route->lifetime_usec >= timestamp_usec) + if (route->nexthop.ifindex != link->ifindex) + continue; + + if (route->protocol == RTPROT_REDIRECT) + continue; /* redirect route will be dropped by ndisc_drop_redirect(). */ + + if (route->lifetime_usec > timestamp_usec) continue; /* the route is still valid */ - k = route_remove_and_drop(route); - if (k < 0) - r = log_link_warning_errno(link, k, "Failed to remove outdated SLAAC route, ignoring: %m"); + if (router && !in6_addr_equal(&route->provider.in6, router)) + continue; + + r = route_remove_and_cancel(route, link->manager); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to remove outdated SLAAC route, ignoring: %m")); } SET_FOREACH(address, link->addresses) { if (address->source != NETWORK_CONFIG_SOURCE_NDISC) continue; - if (address->lifetime_valid_usec >= timestamp_usec) + if (address->lifetime_valid_usec > timestamp_usec) continue; /* the address is still valid */ - k = address_remove_and_drop(address); - if (k < 0) - r = log_link_warning_errno(link, k, "Failed to remove outdated SLAAC address, ignoring: %m"); + if (router && !in6_addr_equal(&address->provider.in6, router)) + continue; + + r = address_remove_and_cancel(address, link); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to remove outdated SLAAC address, ignoring: %m")); } SET_FOREACH(rdnss, link->ndisc_rdnss) { - if (rdnss->lifetime_usec >= timestamp_usec) + if (rdnss->lifetime_usec > timestamp_usec) continue; /* the DNS server is still valid */ + if (router && !in6_addr_equal(&rdnss->router, router)) + continue; + free(set_remove(link->ndisc_rdnss, rdnss)); updated = true; } SET_FOREACH(dnssl, link->ndisc_dnssl) { - if (dnssl->lifetime_usec >= timestamp_usec) + if (dnssl->lifetime_usec > timestamp_usec) continue; /* the DNS domain is still valid */ + if (router && !in6_addr_equal(&dnssl->router, router)) + continue; + free(set_remove(link->ndisc_dnssl, dnssl)); updated = true; } SET_FOREACH(cp, link->ndisc_captive_portals) { - if (cp->lifetime_usec >= timestamp_usec) + if (cp->lifetime_usec > timestamp_usec) continue; /* the captive portal is still valid */ + if (router && !in6_addr_equal(&cp->router, router)) + continue; + ndisc_captive_portal_free(set_remove(link->ndisc_captive_portals, cp)); updated = true; } SET_FOREACH(p64, link->ndisc_pref64) { - if (p64->lifetime_usec >= timestamp_usec) + if (p64->lifetime_usec > timestamp_usec) continue; /* the pref64 prefix is still valid */ + if (router && !in6_addr_equal(&p64->router, router)) + continue; + free(set_remove(link->ndisc_pref64, p64)); /* The pref64 prefix is not exported through the state file, hence it is not necessary to set * the 'updated' flag. */ @@ -1180,7 +1948,7 @@ static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { if (updated) link_dirty(link); - return r; + return ret; } static int ndisc_setup_expire(Link *link); @@ -1193,7 +1961,7 @@ static int ndisc_expire_handler(sd_event_source *s, uint64_t usec, void *userdat assert_se(sd_event_now(link->manager->event, CLOCK_BOOTTIME, &now_usec) >= 0); - (void) ndisc_drop_outdated(link, now_usec); + (void) ndisc_drop_outdated(link, /* router = */ NULL, now_usec); (void) ndisc_setup_expire(link); return 0; } @@ -1211,10 +1979,23 @@ static int ndisc_setup_expire(Link *link) { assert(link); assert(link->manager); - SET_FOREACH(route, link->routes) { + sd_ndisc_router *rt; + HASHMAP_FOREACH(rt, link->ndisc_routers_by_sender) { + usec_t t; + + if (sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &t) < 0) + continue; + + lifetime_usec = MIN(lifetime_usec, t); + } + + SET_FOREACH(route, link->manager->routes) { if (route->source != NETWORK_CONFIG_SOURCE_NDISC) continue; + if (route->nexthop.ifindex != link->ifindex) + continue; + if (!route_exists(route)) continue; @@ -1260,7 +2041,13 @@ static int ndisc_start_dhcp6_client(Link *link, sd_ndisc_router *rt) { assert(link); assert(link->network); - switch (link->network->ipv6_accept_ra_start_dhcp6_client) { + /* Do not start DHCPv6 client if the router lifetime is zero, as the message sent as a signal of + * that the router is e.g. shutting down, revoked, etc,. */ + r = sd_ndisc_router_get_lifetime(rt, NULL); + if (r <= 0) + return r; + + switch (link->network->ndisc_start_dhcp6_client) { case IPV6_ACCEPT_RA_START_DHCP6_CLIENT_NO: return 0; @@ -1307,7 +2094,7 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { assert(link->manager); assert(rt); - r = sd_ndisc_router_get_address(rt, &router); + r = sd_ndisc_router_get_sender_address(rt, &router); if (r == -ENODATA) { log_link_debug(link, "Received RA without router address, ignoring."); return 0; @@ -1333,7 +2120,11 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { if (r < 0) return r; - r = ndisc_drop_outdated(link, timestamp_usec); + r = ndisc_drop_outdated(link, /* router = */ NULL, timestamp_usec); + if (r < 0) + return r; + + r = ndisc_remember_router(link, rt); if (r < 0) return r; @@ -1345,7 +2136,19 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { if (r < 0) return r; - r = ndisc_router_process_icmp6_ratelimit(link, rt); + r = ndisc_router_process_reachable_time(link, rt); + if (r < 0) + return r; + + r = ndisc_router_process_retransmission_time(link, rt); + if (r < 0) + return r; + + r = ndisc_router_process_hop_limit(link, rt); + if (r < 0) + return r; + + r = ndisc_router_process_mtu(link, rt); if (r < 0) return r; @@ -1357,6 +2160,9 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { if (r < 0) return r; + if (sd_ndisc_router_get_lifetime(rt, NULL) <= 0) + (void) ndisc_drop_redirect(link, &router); + if (link->ndisc_messages == 0) link->ndisc_configured = true; else @@ -1369,7 +2175,142 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { return 0; } -static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router *rt, void *userdata) { +static int ndisc_neighbor_handle_non_router_message(Link *link, sd_ndisc_neighbor *na) { + struct in6_addr address; + int r; + + assert(link); + assert(na); + + /* Received Neighbor Advertisement message without Router flag. The node might have been a router, + * and now it is not. Let's drop all configurations based on RAs sent from the node. */ + + r = sd_ndisc_neighbor_get_target_address(na, &address); + if (r == -ENODATA) + return 0; + if (r < 0) + return r; + + (void) ndisc_drop_outdated(link, /* router = */ &address, /* timestamp_usec = */ USEC_INFINITY); + (void) ndisc_drop_redirect(link, &address); + + return 0; +} + +static int ndisc_neighbor_handle_router_message(Link *link, sd_ndisc_neighbor *na) { + struct in6_addr current_address, original_address; + int r; + + assert(link); + assert(link->manager); + assert(na); + + /* Received Neighbor Advertisement message with Router flag. If the router address is changed, update + * the provider field of configurations. */ + + r = sd_ndisc_neighbor_get_sender_address(na, ¤t_address); + if (r == -ENODATA) + return 0; + if (r < 0) + return r; + + r = sd_ndisc_neighbor_get_target_address(na, &original_address); + if (r == -ENODATA) + return 0; + if (r < 0) + return r; + + if (in6_addr_equal(¤t_address, &original_address)) + return 0; /* the router address is not changed */ + + r = ndisc_update_router_address(link, &original_address, ¤t_address); + if (r < 0) + return r; + + r = ndisc_update_redirect_sender(link, &original_address, ¤t_address); + if (r < 0) + return r; + + Route *route; + SET_FOREACH(route, link->manager->routes) { + if (route->source != NETWORK_CONFIG_SOURCE_NDISC) + continue; + + if (route->nexthop.ifindex != link->ifindex) + continue; + + if (!in6_addr_equal(&route->provider.in6, &original_address)) + continue; + + route->provider.in6 = current_address; + } + + Address *address; + SET_FOREACH(address, link->addresses) { + if (address->source != NETWORK_CONFIG_SOURCE_NDISC) + continue; + + if (!in6_addr_equal(&address->provider.in6, &original_address)) + continue; + + address->provider.in6 = current_address; + } + + NDiscRDNSS *rdnss; + SET_FOREACH(rdnss, link->ndisc_rdnss) { + if (!in6_addr_equal(&rdnss->router, &original_address)) + continue; + + rdnss->router = current_address; + } + + NDiscDNSSL *dnssl; + SET_FOREACH(dnssl, link->ndisc_dnssl) { + if (!in6_addr_equal(&dnssl->router, &original_address)) + continue; + + dnssl->router = current_address; + } + + NDiscCaptivePortal *cp; + SET_FOREACH(cp, link->ndisc_captive_portals) { + if (!in6_addr_equal(&cp->router, &original_address)) + continue; + + cp->router = current_address; + } + + NDiscPREF64 *p64; + SET_FOREACH(p64, link->ndisc_pref64) { + if (!in6_addr_equal(&p64->router, &original_address)) + continue; + + p64->router = current_address; + } + + return 0; +} + +static int ndisc_neighbor_handler(Link *link, sd_ndisc_neighbor *na) { + int r; + + assert(link); + assert(na); + + r = sd_ndisc_neighbor_is_router(na); + if (r < 0) + return r; + if (r == 0) + r = ndisc_neighbor_handle_non_router_message(link, na); + else + r = ndisc_neighbor_handle_router_message(link, na); + if (r < 0) + return r; + + return 0; +} + +static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event_t event, void *message, void *userdata) { Link *link = ASSERT_PTR(userdata); int r; @@ -1379,13 +2320,30 @@ static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router switch (event) { case SD_NDISC_EVENT_ROUTER: - r = ndisc_router_handler(link, rt); + r = ndisc_router_handler(link, ASSERT_PTR(message)); + if (r < 0 && r != -EBADMSG) { + link_enter_failed(link); + return; + } + break; + + case SD_NDISC_EVENT_NEIGHBOR: + r = ndisc_neighbor_handler(link, ASSERT_PTR(message)); if (r < 0 && r != -EBADMSG) { link_enter_failed(link); return; } break; + case SD_NDISC_EVENT_REDIRECT: + r = ndisc_redirect_handler(link, ASSERT_PTR(message)); + if (r < 0 && r != -EBADMSG) { + log_link_warning_errno(link, r, "Failed to process Redirect message: %m"); + link_enter_failed(link); + return; + } + break; + case SD_NDISC_EVENT_TIMEOUT: log_link_debug(link, "NDisc handler get timeout event"); if (link->ndisc_messages == 0) { @@ -1393,8 +2351,9 @@ static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router link_check_ready(link); } break; + default: - assert_not_reached(); + log_link_debug(link, "Received unsupported NDisc event, ignoring."); } } @@ -1403,7 +2362,7 @@ static int ndisc_configure(Link *link) { assert(link); - if (!link_ipv6_accept_ra_enabled(link)) + if (!link_ndisc_enabled(link)) return 0; if (link->ndisc) @@ -1448,6 +2407,10 @@ int ndisc_start(Link *link) { if (in6_addr_is_null(&link->ipv6ll_address)) return 0; + r = sd_ndisc_set_link_local_address(link->ndisc, &link->ipv6ll_address); + if (r < 0) + return r; + log_link_debug(link, "Discovering IPv6 routers"); r = sd_ndisc_start(link->ndisc); @@ -1483,7 +2446,7 @@ int link_request_ndisc(Link *link) { assert(link); - if (!link_ipv6_accept_ra_enabled(link)) + if (!link_ndisc_enabled(link)) return 0; if (link->ndisc) @@ -1505,27 +2468,29 @@ int ndisc_stop(Link *link) { return sd_ndisc_stop(link->ndisc); } - void ndisc_flush(Link *link) { assert(link); - /* Remove all RDNSS, DNSSL, and Captive Portal entries, without exception. */ + /* Remove all addresses, routes, RDNSS, DNSSL, and Captive Portal entries, without exception. */ + (void) ndisc_drop_outdated(link, /* router = */ NULL, /* timestamp_usec = */ USEC_INFINITY); + (void) ndisc_drop_redirect(link, /* router = */ NULL); + link->ndisc_routers_by_sender = hashmap_free(link->ndisc_routers_by_sender); link->ndisc_rdnss = set_free(link->ndisc_rdnss); link->ndisc_dnssl = set_free(link->ndisc_dnssl); link->ndisc_captive_portals = set_free(link->ndisc_captive_portals); link->ndisc_pref64 = set_free(link->ndisc_pref64); + link->ndisc_redirects = set_free(link->ndisc_redirects); + link->ndisc_mtu = 0; } -static const char* const ipv6_accept_ra_start_dhcp6_client_table[_IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX] = { +static const char* const ndisc_start_dhcp6_client_table[_IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX] = { [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_NO] = "no", [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_ALWAYS] = "always", [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES] = "yes", }; -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(ipv6_accept_ra_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client, IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES); +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(ndisc_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client, IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES); -DEFINE_CONFIG_PARSE_ENUM(config_parse_ipv6_accept_ra_use_domains, dhcp_use_domains, DHCPUseDomains, - "Failed to parse UseDomains= setting"); -DEFINE_CONFIG_PARSE_ENUM(config_parse_ipv6_accept_ra_start_dhcp6_client, ipv6_accept_ra_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client, +DEFINE_CONFIG_PARSE_ENUM(config_parse_ndisc_start_dhcp6_client, ndisc_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client, "Failed to parse DHCPv6Client= setting"); diff --git a/src/network/networkd-ndisc.h b/src/network/networkd-ndisc.h index a463f42..6094881 100644 --- a/src/network/networkd-ndisc.h +++ b/src/network/networkd-ndisc.h @@ -4,6 +4,7 @@ #include "conf-parser.h" #include "time-util.h" +typedef struct Address Address; typedef struct Link Link; typedef struct Network Network; @@ -52,15 +53,15 @@ static inline char* NDISC_DNSSL_DOMAIN(const NDiscDNSSL *n) { return ((char*) n) + ALIGN(sizeof(NDiscDNSSL)); } -bool link_ipv6_accept_ra_enabled(Link *link); +bool link_ndisc_enabled(Link *link); -void network_adjust_ipv6_accept_ra(Network *network); +void network_adjust_ndisc(Network *network); int ndisc_start(Link *link); int ndisc_stop(Link *link); void ndisc_flush(Link *link); int link_request_ndisc(Link *link); +int ndisc_reconfigure_address(Address *address, Link *link); -CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_accept_ra_start_dhcp6_client); -CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_accept_ra_use_domains); +CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_start_dhcp6_client); diff --git a/src/network/networkd-neighbor.c b/src/network/networkd-neighbor.c index 8321831..6b81950 100644 --- a/src/network/networkd-neighbor.c +++ b/src/network/networkd-neighbor.c @@ -10,28 +10,87 @@ #include "networkd-queue.h" #include "set.h" -Neighbor *neighbor_free(Neighbor *neighbor) { - if (!neighbor) - return NULL; +static Neighbor* neighbor_detach_impl(Neighbor *neighbor) { + assert(neighbor); + assert(!neighbor->link || !neighbor->network); if (neighbor->network) { assert(neighbor->section); ordered_hashmap_remove(neighbor->network->neighbors_by_section, neighbor->section); + neighbor->network = NULL; + return neighbor; } - config_section_free(neighbor->section); - - if (neighbor->link) + if (neighbor->link) { set_remove(neighbor->link->neighbors, neighbor); + neighbor->link = NULL; + return neighbor; + } + + return NULL; +} + +static void neighbor_detach(Neighbor *neighbor) { + neighbor_unref(neighbor_detach_impl(neighbor)); +} + +static Neighbor* neighbor_free(Neighbor *neighbor) { + if (!neighbor) + return NULL; + + neighbor_detach_impl(neighbor); + config_section_free(neighbor->section); return mfree(neighbor); } -DEFINE_SECTION_CLEANUP_FUNCTIONS(Neighbor, neighbor_free); +DEFINE_TRIVIAL_REF_UNREF_FUNC(Neighbor, neighbor, neighbor_free); +DEFINE_SECTION_CLEANUP_FUNCTIONS(Neighbor, neighbor_unref); + +static void neighbor_hash_func(const Neighbor *neighbor, struct siphash *state); +static int neighbor_compare_func(const Neighbor *a, const Neighbor *b); + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + neighbor_hash_ops_detach, + Neighbor, + neighbor_hash_func, + neighbor_compare_func, + neighbor_detach); + +DEFINE_PRIVATE_HASH_OPS( + neighbor_hash_ops, + Neighbor, + neighbor_hash_func, + neighbor_compare_func); + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + neighbor_section_hash_ops, + ConfigSection, + config_section_hash_func, + config_section_compare_func, + Neighbor, + neighbor_detach); + +static int neighbor_new(Neighbor **ret) { + Neighbor *neighbor; + + assert(ret); + + neighbor = new(Neighbor, 1); + if (!neighbor) + return -ENOMEM; + + *neighbor = (Neighbor) { + .n_ref = 1, + }; + + *ret = TAKE_PTR(neighbor); + return 0; +} static int neighbor_new_static(Network *network, const char *filename, unsigned section_line, Neighbor **ret) { _cleanup_(config_section_freep) ConfigSection *n = NULL; - _cleanup_(neighbor_freep) Neighbor *neighbor = NULL; + _cleanup_(neighbor_unrefp) Neighbor *neighbor = NULL; int r; assert(network); @@ -49,18 +108,15 @@ static int neighbor_new_static(Network *network, const char *filename, unsigned return 0; } - neighbor = new(Neighbor, 1); - if (!neighbor) - return -ENOMEM; + r = neighbor_new(&neighbor); + if (r < 0) + return r; - *neighbor = (Neighbor) { - .network = network, - .family = AF_UNSPEC, - .section = TAKE_PTR(n), - .source = NETWORK_CONFIG_SOURCE_STATIC, - }; + neighbor->network = network; + neighbor->section = TAKE_PTR(n); + neighbor->source = NETWORK_CONFIG_SOURCE_STATIC; - r = ordered_hashmap_ensure_put(&network->neighbors_by_section, &config_section_hash_ops, neighbor->section, neighbor); + r = ordered_hashmap_ensure_put(&network->neighbors_by_section, &neighbor_section_hash_ops, neighbor->section, neighbor); if (r < 0) return r; @@ -69,7 +125,7 @@ static int neighbor_new_static(Network *network, const char *filename, unsigned } static int neighbor_dup(const Neighbor *neighbor, Neighbor **ret) { - _cleanup_(neighbor_freep) Neighbor *dest = NULL; + _cleanup_(neighbor_unrefp) Neighbor *dest = NULL; assert(neighbor); assert(ret); @@ -78,7 +134,8 @@ static int neighbor_dup(const Neighbor *neighbor, Neighbor **ret) { if (!dest) return -ENOMEM; - /* Unset all pointers */ + /* Clear the reference counter and all pointers */ + dest->n_ref = 1; dest->link = NULL; dest->network = NULL; dest->section = NULL; @@ -90,7 +147,7 @@ static int neighbor_dup(const Neighbor *neighbor, Neighbor **ret) { static void neighbor_hash_func(const Neighbor *neighbor, struct siphash *state) { assert(neighbor); - siphash24_compress(&neighbor->family, sizeof(neighbor->family), state); + siphash24_compress_typesafe(neighbor->family, state); if (!IN_SET(neighbor->family, AF_INET, AF_INET6)) /* treat any other address family as AF_UNSPEC */ @@ -98,7 +155,7 @@ static void neighbor_hash_func(const Neighbor *neighbor, struct siphash *state) /* Equality of neighbors are given by the destination address. * See neigh_lookup() in the kernel. */ - siphash24_compress(&neighbor->in_addr, FAMILY_ADDRESS_SIZE(neighbor->family), state); + in_addr_hash_func(&neighbor->in_addr, neighbor->family, state); } static int neighbor_compare_func(const Neighbor *a, const Neighbor *b) { @@ -115,19 +172,6 @@ static int neighbor_compare_func(const Neighbor *a, const Neighbor *b) { return memcmp(&a->in_addr, &b->in_addr, FAMILY_ADDRESS_SIZE(a->family)); } -DEFINE_PRIVATE_HASH_OPS( - neighbor_hash_ops, - Neighbor, - neighbor_hash_func, - neighbor_compare_func); - -DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( - neighbor_hash_ops_free, - Neighbor, - neighbor_hash_func, - neighbor_compare_func, - neighbor_free); - static int neighbor_get_request(Link *link, const Neighbor *neighbor, Request **ret) { Request *req; @@ -152,7 +196,7 @@ static int neighbor_get_request(Link *link, const Neighbor *neighbor, Request ** return 0; } -static int neighbor_get(Link *link, const Neighbor *in, Neighbor **ret) { +int neighbor_get(Link *link, const Neighbor *in, Neighbor **ret) { Neighbor *existing; assert(link); @@ -167,19 +211,21 @@ static int neighbor_get(Link *link, const Neighbor *in, Neighbor **ret) { return 0; } -static int neighbor_add(Link *link, Neighbor *neighbor) { +static int neighbor_attach(Link *link, Neighbor *neighbor) { int r; assert(link); assert(neighbor); + assert(!neighbor->link); - r = set_ensure_put(&link->neighbors, &neighbor_hash_ops_free, neighbor); + r = set_ensure_put(&link->neighbors, &neighbor_hash_ops_detach, neighbor); if (r < 0) return r; if (r == 0) return -EEXIST; neighbor->link = link; + neighbor_ref(neighbor); return 0; } @@ -279,7 +325,7 @@ static int static_neighbor_configure_handler(sd_netlink *rtnl, sd_netlink_messag } static int link_request_neighbor(Link *link, const Neighbor *neighbor) { - _cleanup_(neighbor_freep) Neighbor *tmp = NULL; + _cleanup_(neighbor_unrefp) Neighbor *tmp = NULL; Neighbor *existing = NULL; int r; @@ -308,7 +354,7 @@ static int link_request_neighbor(Link *link, const Neighbor *neighbor) { log_neighbor_debug(tmp, "Requesting", link); r = link_queue_request_safe(link, REQUEST_TYPE_NEIGHBOR, tmp, - neighbor_free, + neighbor_unref, neighbor_hash_func, neighbor_compare_func, neighbor_process_request, @@ -353,35 +399,51 @@ int link_request_static_neighbors(Link *link) { return 0; } -static int neighbor_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { +static int neighbor_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, RemoveRequest *rreq) { int r; assert(m); - assert(link); + assert(rreq); - if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) - return 1; + Link *link = ASSERT_PTR(rreq->link); + Neighbor *neighbor = ASSERT_PTR(rreq->userdata); + + if (link->state == LINK_STATE_LINGER) + return 0; r = sd_netlink_message_get_errno(m); - if (r < 0 && r != -ESRCH) + if (r < 0) { /* Neighbor may not exist because it already got deleted, ignore that. */ - log_link_message_warning_errno(link, m, r, "Could not remove neighbor"); + log_link_message_full_errno(link, m, + (r == -ESRCH || !neighbor->link) ? LOG_DEBUG : LOG_WARNING, + r, "Could not remove neighbor"); + + if (neighbor->link) { + /* If the neighbor cannot be removed, then assume the neighbor is already removed. */ + log_neighbor_debug(neighbor, "Forgetting", link); + + Request *req; + if (neighbor_get_request(link, neighbor, &req) >= 0) + neighbor_enter_removed(req->userdata); + + neighbor_detach(neighbor); + } + } return 1; } -static int neighbor_remove(Neighbor *neighbor) { +int neighbor_remove(Neighbor *neighbor, Link *link) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - Request *req; - Link *link; int r; assert(neighbor); - assert(neighbor->link); - assert(neighbor->link->manager); - assert(neighbor->link->manager->rtnl); + assert(link); + assert(link->manager); + assert(link->manager->rtnl); - link = neighbor->link; + /* If the neighbor is remembered, then use the remembered object. */ + (void) neighbor_get(link, neighbor, &neighbor); log_neighbor_debug(neighbor, "Removing", link); @@ -394,17 +456,11 @@ static int neighbor_remove(Neighbor *neighbor) { if (r < 0) return log_link_error_errno(link, r, "Could not append NDA_DST attribute: %m"); - r = netlink_call_async(link->manager->rtnl, NULL, m, neighbor_remove_handler, - link_netlink_destroy_callback, link); + r = link_remove_request_add(link, neighbor, neighbor, link->manager->rtnl, m, neighbor_remove_handler); if (r < 0) - return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); - - link_ref(link); + return log_link_error_errno(link, r, "Could not queue rtnetlink message: %m"); neighbor_enter_removing(neighbor); - if (neighbor_get_request(neighbor->link, neighbor, &req) >= 0) - neighbor_enter_removing(req->userdata); - return 0; } @@ -440,13 +496,13 @@ int link_drop_foreign_neighbors(Link *link) { if (!neighbor_is_marked(neighbor)) continue; - RET_GATHER(r, neighbor_remove(neighbor)); + RET_GATHER(r, neighbor_remove(neighbor, link)); } return r; } -int link_drop_managed_neighbors(Link *link) { +int link_drop_static_neighbors(Link *link) { Neighbor *neighbor; int r = 0; @@ -454,14 +510,14 @@ int link_drop_managed_neighbors(Link *link) { SET_FOREACH(neighbor, link->neighbors) { /* Do not touch nexthops managed by kernel or other tools. */ - if (neighbor->source == NETWORK_CONFIG_SOURCE_FOREIGN) + if (neighbor->source != NETWORK_CONFIG_SOURCE_STATIC) continue; /* Ignore neighbors not assigned yet or already removing. */ if (!neighbor_exists(neighbor)) continue; - RET_GATHER(r, neighbor_remove(neighbor)); + RET_GATHER(r, neighbor_remove(neighbor, link)); } return r; @@ -477,7 +533,7 @@ void link_foreignize_neighbors(Link *link) { } int manager_rtnl_process_neighbor(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { - _cleanup_(neighbor_freep) Neighbor *tmp = NULL; + _cleanup_(neighbor_unrefp) Neighbor *tmp = NULL; Neighbor *neighbor = NULL; Request *req = NULL; uint16_t type, state; @@ -510,10 +566,9 @@ int manager_rtnl_process_neighbor(sd_netlink *rtnl, sd_netlink_message *message, if (r < 0) { log_warning_errno(r, "rtnl: received neighbor message with invalid state, ignoring: %m"); return 0; - } else if (!FLAGS_SET(state, NUD_PERMANENT)) { - log_debug("rtnl: received non-static neighbor, ignoring."); + } else if (!FLAGS_SET(state, NUD_PERMANENT)) + /* Currently, we are interested in only static neighbors. */ return 0; - } r = sd_rtnl_message_neigh_get_ifindex(message, &ifindex); if (r < 0) { @@ -525,15 +580,13 @@ int manager_rtnl_process_neighbor(sd_netlink *rtnl, sd_netlink_message *message, } r = link_get_by_index(m, ifindex, &link); - if (r < 0) { + if (r < 0) /* when enumerating we might be out of sync, but we will get the neighbor again. Also, * kernel sends messages about neighbors after a link is removed. So, just ignore it. */ - log_debug("rtnl: received neighbor for link '%d' we don't know about, ignoring.", ifindex); return 0; - } - tmp = new0(Neighbor, 1); - if (!tmp) + r = neighbor_new(&tmp); + if (r < 0) return log_oom(); /* First, retrieve the fundamental information about the neighbor. */ @@ -541,7 +594,10 @@ int manager_rtnl_process_neighbor(sd_netlink *rtnl, sd_netlink_message *message, if (r < 0) { log_link_warning(link, "rtnl: received neighbor message without family, ignoring."); return 0; - } else if (!IN_SET(tmp->family, AF_INET, AF_INET6)) { + } + if (tmp->family == AF_BRIDGE) /* Currently, we do not support it. */ + return 0; + if (!IN_SET(tmp->family, AF_INET, AF_INET6)) { log_link_debug(link, "rtnl: received neighbor message with invalid family '%i', ignoring.", tmp->family); return 0; } @@ -560,7 +616,7 @@ int manager_rtnl_process_neighbor(sd_netlink *rtnl, sd_netlink_message *message, if (neighbor) { neighbor_enter_removed(neighbor); log_neighbor_debug(neighbor, "Forgetting removed", link); - neighbor_free(neighbor); + neighbor_detach(neighbor); } else log_neighbor_debug(tmp, "Kernel removed unknown", link); @@ -572,12 +628,12 @@ int manager_rtnl_process_neighbor(sd_netlink *rtnl, sd_netlink_message *message, /* If we did not know the neighbor, then save it. */ if (!neighbor) { - r = neighbor_add(link, tmp); + r = neighbor_attach(link, tmp); if (r < 0) { log_link_warning_errno(link, r, "Failed to save received neighbor, ignoring: %m"); return 0; } - neighbor = TAKE_PTR(tmp); + neighbor = tmp; is_new = true; } @@ -638,9 +694,9 @@ int network_drop_invalid_neighbors(Network *network) { Neighbor *dup; if (neighbor_section_verify(neighbor) < 0) { - /* Drop invalid [Neighbor] sections. Note that neighbor_free() will drop the + /* Drop invalid [Neighbor] sections. Note that neighbor_detach() will drop the * neighbor from neighbors_by_section. */ - neighbor_free(neighbor); + neighbor_detach(neighbor); continue; } @@ -648,17 +704,17 @@ int network_drop_invalid_neighbors(Network *network) { dup = set_remove(neighbors, neighbor); if (dup) { log_warning("%s: Duplicated neighbor settings for %s is specified at line %u and %u, " - "dropping the address setting specified at line %u.", + "dropping the neighbor setting specified at line %u.", dup->section->filename, IN_ADDR_TO_STRING(neighbor->family, &neighbor->in_addr), neighbor->section->line, dup->section->line, dup->section->line); - /* neighbor_free() will drop the address from neighbors_by_section. */ - neighbor_free(dup); + /* neighbor_detach() will drop the neighbor from neighbors_by_section. */ + neighbor_detach(dup); } - /* Use neighbor_hash_ops, instead of neighbor_hash_ops_free. Otherwise, the Neighbor objects - * will be freed. */ + /* Use neighbor_hash_ops, instead of neighbor_hash_ops_detach. Otherwise, the Neighbor objects + * will be detached. */ r = set_ensure_put(&neighbors, &neighbor_hash_ops, neighbor); if (r < 0) return log_oom(); @@ -681,7 +737,7 @@ int config_parse_neighbor_address( void *data, void *userdata) { - _cleanup_(neighbor_free_or_set_invalidp) Neighbor *n = NULL; + _cleanup_(neighbor_unref_or_set_invalidp) Neighbor *n = NULL; Network *network = ASSERT_PTR(userdata); int r; @@ -724,7 +780,7 @@ int config_parse_neighbor_lladdr( void *data, void *userdata) { - _cleanup_(neighbor_free_or_set_invalidp) Neighbor *n = NULL; + _cleanup_(neighbor_unref_or_set_invalidp) Neighbor *n = NULL; Network *network = ASSERT_PTR(userdata); int r; diff --git a/src/network/networkd-neighbor.h b/src/network/networkd-neighbor.h index 683a310..7917930 100644 --- a/src/network/networkd-neighbor.h +++ b/src/network/networkd-neighbor.h @@ -21,16 +21,22 @@ typedef struct Neighbor { NetworkConfigSource source; NetworkConfigState state; + unsigned n_ref; + int family; union in_addr_union in_addr; struct hw_addr_data ll_addr; } Neighbor; -Neighbor *neighbor_free(Neighbor *neighbor); +Neighbor* neighbor_ref(Neighbor *neighbor); +Neighbor* neighbor_unref(Neighbor *neighbor); + +int neighbor_get(Link *link, const Neighbor *in, Neighbor **ret); +int neighbor_remove(Neighbor *neighbor, Link *link); int network_drop_invalid_neighbors(Network *network); -int link_drop_managed_neighbors(Link *link); +int link_drop_static_neighbors(Link *link); int link_drop_foreign_neighbors(Link *link); void link_foreignize_neighbors(Link *link); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index a6593a0..23e25ff 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -3,7 +3,9 @@ #if __GNUC__ >= 7 _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") #endif +#include <netinet/icmp6.h> #include <stddef.h> + #include "conf-parser.h" #include "in-addr-prefix-util.h" #include "netem.h" @@ -20,6 +22,7 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") #include "networkd-dhcp-server.h" #include "networkd-dhcp4.h" #include "networkd-dhcp6.h" +#include "networkd-dns.h" #include "networkd-ipv4ll.h" #include "networkd-ipv6-proxy-ndp.h" #include "networkd-ipv6ll.h" @@ -29,6 +32,7 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") #include "networkd-network.h" #include "networkd-neighbor.h" #include "networkd-nexthop.h" +#include "networkd-ntp.h" #include "networkd-radv.h" #include "networkd-route.h" #include "networkd-routing-policy-rule.h" @@ -116,6 +120,7 @@ Network.EmitLLDP, config_parse_lldp_multicast_mode, Network.Address, config_parse_address, 0, 0 Network.Gateway, config_parse_gateway, 0, 0 Network.Domains, config_parse_domains, 0, 0 +Network.UseDomains, config_parse_use_domains, 0, offsetof(Network, use_domains) Network.DNS, config_parse_dns, 0, 0 Network.DNSDefaultRoute, config_parse_tristate, 0, offsetof(Network, dns_default_route) Network.LLMNR, config_parse_resolve_support, 0, offsetof(Network, llmnr) @@ -124,13 +129,16 @@ Network.DNSOverTLS, config_parse_dns_over_tls_mode, Network.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Network, dnssec_mode) Network.DNSSECNegativeTrustAnchors, config_parse_dnssec_negative_trust_anchors, 0, offsetof(Network, dnssec_negative_trust_anchors) Network.NTP, config_parse_ntp, 0, offsetof(Network, ntp) -Network.IPForward, config_parse_address_family_with_kernel, 0, offsetof(Network, ip_forward) +Network.IPForward, config_parse_ip_forward_deprecated, 0, 0 +Network.IPv4Forwarding, config_parse_tristate, 0, offsetof(Network, ip_forwarding[0]) +Network.IPv6Forwarding, config_parse_tristate, 0, offsetof(Network, ip_forwarding[1]) Network.IPMasquerade, config_parse_ip_masquerade, 0, offsetof(Network, ip_masquerade) Network.IPv6PrivacyExtensions, config_parse_ipv6_privacy_extensions, 0, offsetof(Network, ipv6_privacy_extensions) -Network.IPv6AcceptRA, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra) -Network.IPv6AcceptRouterAdvertisements, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra) +Network.IPv6AcceptRA, config_parse_tristate, 0, offsetof(Network, ndisc) +Network.IPv6AcceptRouterAdvertisements, config_parse_tristate, 0, offsetof(Network, ndisc) Network.IPv6DuplicateAddressDetection, config_parse_int, 0, offsetof(Network, ipv6_dad_transmits) Network.IPv6HopLimit, config_parse_uint8, 0, offsetof(Network, ipv6_hop_limit) +Network.IPv6RetransmissionTimeSec, config_parse_sec, 0, offsetof(Network, ipv6_retransmission_time) Network.IPv6ProxyNDP, config_parse_tristate, 0, offsetof(Network, ipv6_proxy_ndp) Network.IPv6MTUBytes, config_parse_mtu, AF_INET6, offsetof(Network, ipv6_mtu) Network.IPv4AcceptLocal, config_parse_tristate, 0, offsetof(Network, ipv4_accept_local) @@ -138,6 +146,7 @@ Network.IPv4RouteLocalnet, config_parse_tristate, Network.ActiveSlave, config_parse_bool, 0, offsetof(Network, active_slave) Network.PrimarySlave, config_parse_bool, 0, offsetof(Network, primary_slave) Network.IPv4ProxyARP, config_parse_tristate, 0, offsetof(Network, proxy_arp) +Network.IPv4ProxyARPPrivateVLAN, config_parse_tristate, 0, offsetof(Network, proxy_arp_pvlan) Network.ProxyARP, config_parse_tristate, 0, offsetof(Network, proxy_arp) Network.IPv6ProxyNDPAddress, config_parse_ipv6_proxy_ndp_address, 0, 0 Network.IPv4ReversePathFilter, config_parse_ip_reverse_path_filter, 0, offsetof(Network, ipv4_rp_filter) @@ -179,6 +188,7 @@ RoutingPolicyRule.IPProtocol, config_parse_routing_policy_rule_ip RoutingPolicyRule.SourcePort, config_parse_routing_policy_rule_port_range, 0, 0 RoutingPolicyRule.DestinationPort, config_parse_routing_policy_rule_port_range, 0, 0 RoutingPolicyRule.InvertRule, config_parse_routing_policy_rule_invert, 0, 0 +RoutingPolicyRule.L3MasterDevice, config_parse_routing_policy_rule_l3mdev, 0, 0 RoutingPolicyRule.Family, config_parse_routing_policy_rule_family, 0, 0 RoutingPolicyRule.User, config_parse_routing_policy_rule_uid_range, 0, 0 RoutingPolicyRule.SuppressInterfaceGroup, config_parse_routing_policy_rule_suppress_ifgroup, 0, 0 @@ -191,23 +201,23 @@ Route.Metric, config_parse_route_priority, Route.Scope, config_parse_route_scope, 0, 0 Route.PreferredSource, config_parse_preferred_src, 0, 0 Route.Table, config_parse_route_table, 0, 0 -Route.MTUBytes, config_parse_route_mtu, AF_UNSPEC, 0 -Route.GatewayOnLink, config_parse_route_boolean, 0, 0 -Route.GatewayOnlink, config_parse_route_boolean, 0, 0 +Route.GatewayOnLink, config_parse_route_gateway_onlink, 0, 0 +Route.GatewayOnlink, config_parse_route_gateway_onlink, 0, 0 Route.IPv6Preference, config_parse_ipv6_route_preference, 0, 0 Route.Protocol, config_parse_route_protocol, 0, 0 Route.Type, config_parse_route_type, 0, 0 -Route.TCPRetransmissionTimeoutSec, config_parse_route_tcp_rto, 0, 0 -Route.HopLimit, config_parse_route_hop_limit, 0, 0 -Route.InitialCongestionWindow, config_parse_route_tcp_window, 0, 0 -Route.InitialAdvertisedReceiveWindow, config_parse_route_tcp_window, 0, 0 -Route.TCPAdvertisedMaximumSegmentSize, config_parse_tcp_advmss, 0, 0 -Route.TCPCongestionControlAlgorithm, config_parse_tcp_congestion, 0, 0 -Route.QuickAck, config_parse_route_boolean, 0, 0 -Route.FastOpenNoCookie, config_parse_route_boolean, 0, 0 -Route.TTLPropagate, config_parse_route_boolean, 0, 0 Route.MultiPathRoute, config_parse_multipath_route, 0, 0 Route.NextHop, config_parse_route_nexthop, 0, 0 +Route.MTUBytes, config_parse_route_metric_mtu, RTAX_MTU, 0 +Route.TCPAdvertisedMaximumSegmentSize, config_parse_route_metric_advmss, RTAX_ADVMSS, 0 +Route.HopLimit, config_parse_route_metric_hop_limit, RTAX_HOPLIMIT, 0 +Route.InitialCongestionWindow, config_parse_route_metric_tcp_window, RTAX_INITCWND, 0 +Route.TCPRetransmissionTimeoutSec, config_parse_route_metric_tcp_rto, RTAX_RTO_MIN, 0 +Route.InitialAdvertisedReceiveWindow, config_parse_route_metric_tcp_window, RTAX_INITRWND, 0 +Route.QuickAck, config_parse_route_metric_boolean, RTAX_QUICKACK, 0 +Route.TCPCongestionControlAlgorithm, config_parse_route_metric_tcp_congestion, RTAX_CC_ALGO, 0 +Route.FastOpenNoCookie, config_parse_route_metric_boolean, RTAX_FASTOPEN_NO_COOKIE, 0 +Route.TTLPropagate, config_parse_warn_compat, DISABLED_LEGACY, 0 NextHop.Id, config_parse_nexthop_id, 0, 0 NextHop.Gateway, config_parse_nexthop_gateway, 0, 0 NextHop.Family, config_parse_nexthop_family, 0, 0 @@ -216,15 +226,15 @@ NextHop.Blackhole, config_parse_nexthop_blackhole, NextHop.Group, config_parse_nexthop_group, 0, 0 DHCPv4.RequestAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_request_address) DHCPv4.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier) -DHCPv4.UseDNS, config_parse_dhcp_use_dns, AF_INET, 0 +DHCPv4.UseDNS, config_parse_tristate, 0, offsetof(Network, dhcp_use_dns) DHCPv4.RoutesToDNS, config_parse_bool, 0, offsetof(Network, dhcp_routes_to_dns) -DHCPv4.UseNTP, config_parse_dhcp_use_ntp, AF_INET, 0 +DHCPv4.UseNTP, config_parse_tristate, 0, offsetof(Network, dhcp_use_ntp) DHCPv4.RoutesToNTP, config_parse_bool, 0, offsetof(Network, dhcp_routes_to_ntp) DHCPv4.UseSIP, config_parse_bool, 0, offsetof(Network, dhcp_use_sip) DHCPv4.UseCaptivePortal, config_parse_bool, 0, offsetof(Network, dhcp_use_captive_portal) DHCPv4.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu) DHCPv4.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname) -DHCPv4.UseDomains, config_parse_dhcp_use_domains, AF_INET, 0 +DHCPv4.UseDomains, config_parse_use_domains, 0, offsetof(Network, dhcp_use_domains) DHCPv4.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_use_routes) DHCPv4.UseGateway, config_parse_tristate, 0, offsetof(Network, dhcp_use_gateway) DHCPv4.QuickAck, config_parse_bool, 0, offsetof(Network, dhcp_quickack) @@ -245,6 +255,7 @@ DHCPv4.RouteMetric, config_parse_dhcp_route_metric, DHCPv4.RouteTable, config_parse_dhcp_or_ra_route_table, AF_INET, 0 DHCPv4.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone) DHCPv4.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port) +DHCPv4.ServerPort, config_parse_uint16, 0, offsetof(Network, dhcp_port) DHCPv4.SendRelease, config_parse_bool, 0, offsetof(Network, dhcp_send_release) DHCPv4.SendDecline, config_parse_bool, 0, offsetof(Network, dhcp_send_decline) DHCPv4.DenyList, config_parse_in_addr_prefixes, AF_INET, offsetof(Network, dhcp_deny_listed_ip) @@ -264,10 +275,10 @@ DHCPv4.NFTSet, config_parse_nft_set, DHCPv4.RapidCommit, config_parse_tristate, 0, offsetof(Network, dhcp_use_rapid_commit) DHCPv6.UseAddress, config_parse_bool, 0, offsetof(Network, dhcp6_use_address) DHCPv6.UseDelegatedPrefix, config_parse_bool, 0, offsetof(Network, dhcp6_use_pd_prefix) -DHCPv6.UseDNS, config_parse_dhcp_use_dns, AF_INET6, 0 +DHCPv6.UseDNS, config_parse_tristate, 0, offsetof(Network, dhcp6_use_dns) DHCPv6.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp6_use_hostname) -DHCPv6.UseDomains, config_parse_dhcp_use_domains, AF_INET6, 0 -DHCPv6.UseNTP, config_parse_dhcp_use_ntp, AF_INET6, 0 +DHCPv6.UseDomains, config_parse_use_domains, 0, offsetof(Network, dhcp6_use_domains) +DHCPv6.UseNTP, config_parse_tristate, 0, offsetof(Network, dhcp6_use_ntp) DHCPv6.UseCaptivePortal, config_parse_bool, 0, offsetof(Network, dhcp6_use_captive_portal) DHCPv6.MUDURL, config_parse_mud_url, 0, offsetof(Network, dhcp6_mudurl) DHCPv6.SendHostname, config_parse_dhcp_send_hostname, AF_INET6, 0 @@ -286,21 +297,23 @@ DHCPv6.RapidCommit, config_parse_bool, DHCPv6.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp6_netlabel) DHCPv6.SendRelease, config_parse_bool, 0, offsetof(Network, dhcp6_send_release) DHCPv6.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, dhcp6_nft_set_context) -IPv6AcceptRA.UseGateway, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_gateway) -IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_route_prefix) -IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_autonomous_prefix) -IPv6AcceptRA.UseOnLinkPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_onlink_prefix) -IPv6AcceptRA.UsePREF64, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_pref64) -IPv6AcceptRA.UseDNS, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_dns) -IPv6AcceptRA.UseDomains, config_parse_ipv6_accept_ra_use_domains, 0, offsetof(Network, ipv6_accept_ra_use_domains) -IPv6AcceptRA.UseMTU, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_mtu) -IPv6AcceptRA.UseHopLimit, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_hop_limit) -IPv6AcceptRA.UseICMP6RateLimit, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_icmp6_ratelimit) -IPv6AcceptRA.DHCPv6Client, config_parse_ipv6_accept_ra_start_dhcp6_client, 0, offsetof(Network, ipv6_accept_ra_start_dhcp6_client) +IPv6AcceptRA.UseRedirect, config_parse_bool, 0, offsetof(Network, ndisc_use_redirect) +IPv6AcceptRA.UseGateway, config_parse_bool, 0, offsetof(Network, ndisc_use_gateway) +IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ndisc_use_route_prefix) +IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ndisc_use_autonomous_prefix) +IPv6AcceptRA.UseOnLinkPrefix, config_parse_bool, 0, offsetof(Network, ndisc_use_onlink_prefix) +IPv6AcceptRA.UsePREF64, config_parse_bool, 0, offsetof(Network, ndisc_use_pref64) +IPv6AcceptRA.UseDNS, config_parse_tristate, 0, offsetof(Network, ndisc_use_dns) +IPv6AcceptRA.UseDomains, config_parse_use_domains, 0, offsetof(Network, ndisc_use_domains) +IPv6AcceptRA.UseMTU, config_parse_bool, 0, offsetof(Network, ndisc_use_mtu) +IPv6AcceptRA.UseHopLimit, config_parse_bool, 0, offsetof(Network, ndisc_use_hop_limit) +IPv6AcceptRA.UseReachableTime, config_parse_bool, 0, offsetof(Network, ndisc_use_reachable_time) +IPv6AcceptRA.UseRetransmissionTime, config_parse_bool, 0, offsetof(Network, ndisc_use_retransmission_time) +IPv6AcceptRA.DHCPv6Client, config_parse_ndisc_start_dhcp6_client, 0, offsetof(Network, ndisc_start_dhcp6_client) IPv6AcceptRA.RouteTable, config_parse_dhcp_or_ra_route_table, AF_INET6, 0 -IPv6AcceptRA.RouteMetric, config_parse_ipv6_accept_ra_route_metric, 0, 0 -IPv6AcceptRA.QuickAck, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_quickack) -IPv6AcceptRA.UseCaptivePortal, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_captive_portal) +IPv6AcceptRA.RouteMetric, config_parse_ndisc_route_metric, 0, 0 +IPv6AcceptRA.QuickAck, config_parse_bool, 0, offsetof(Network, ndisc_quickack) +IPv6AcceptRA.UseCaptivePortal, config_parse_bool, 0, offsetof(Network, ndisc_use_captive_portal) IPv6AcceptRA.RouterAllowList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_allow_listed_router) IPv6AcceptRA.RouterDenyList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_deny_listed_router) IPv6AcceptRA.PrefixAllowList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_allow_listed_prefix) @@ -343,6 +356,7 @@ DHCPServer.BootServerAddress, config_parse_in_addr_non_null, DHCPServer.BootServerName, config_parse_dns_name, 0, offsetof(Network, dhcp_server_boot_server_name) DHCPServer.BootFilename, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, dhcp_server_boot_filename) DHCPServer.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp_server_rapid_commit) +DHCPServer.PersistLeases, config_parse_tristate, 0, offsetof(Network, dhcp_server_persist_leases) DHCPServerStaticLease.Address, config_parse_dhcp_static_lease_address, 0, 0 DHCPServerStaticLease.MACAddress, config_parse_dhcp_static_lease_hwaddr, 0, 0 Bridge.Cost, config_parse_uint32, 0, offsetof(Network, cost) @@ -368,9 +382,9 @@ BridgeFDB.AssociatedWith, config_parse_fdb_ntf_flags, BridgeFDB.OutgoingInterface, config_parse_fdb_interface, 0, 0 BridgeMDB.MulticastGroupAddress, config_parse_mdb_group_address, 0, 0 BridgeMDB.VLANId, config_parse_mdb_vlan_id, 0, 0 -BridgeVLAN.PVID, config_parse_brvlan_pvid, 0, 0 -BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0 -BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0 +BridgeVLAN.PVID, config_parse_bridge_vlan_id, 0, offsetof(Network, bridge_vlan_pvid) +BridgeVLAN.VLAN, config_parse_bridge_vlan_id_range, 0, offsetof(Network, bridge_vlan_bitmap) +BridgeVLAN.EgressUntagged, config_parse_bridge_vlan_id_range, 0, offsetof(Network, bridge_vlan_untagged_bitmap) DHCPPrefixDelegation.UplinkInterface, config_parse_uplink, 0, 0 DHCPPrefixDelegation.SubnetId, config_parse_dhcp_pd_subnet_id, 0, offsetof(Network, dhcp_pd_subnet_id) DHCPPrefixDelegation.Announce, config_parse_bool, 0, offsetof(Network, dhcp_pd_announce) @@ -381,7 +395,8 @@ DHCPPrefixDelegation.RouteMetric, config_parse_uint32, DHCPPrefixDelegation.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_pd_netlabel) DHCPPrefixDelegation.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, dhcp_pd_nft_set_context) IPv6SendRA.RouterLifetimeSec, config_parse_router_lifetime, 0, offsetof(Network, router_lifetime_usec) -IPv6SendRA.RetransmitSec, config_parse_router_retransmit, 0, offsetof(Network, router_retransmit_usec) +IPv6SendRA.ReachableTimeSec, config_parse_router_uint32_msec_usec, 0, offsetof(Network, router_reachable_usec) +IPv6SendRA.RetransmitSec, config_parse_router_uint32_msec_usec, 0, offsetof(Network, router_retransmit_usec) IPv6SendRA.Managed, config_parse_bool, 0, offsetof(Network, router_managed) IPv6SendRA.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information) IPv6SendRA.RouterPreference, config_parse_router_preference, 0, 0 @@ -396,8 +411,8 @@ IPv6SendRA.HomeAgent, config_parse_bool, IPv6SendRA.HomeAgentLifetimeSec, config_parse_router_home_agent_lifetime, 0, offsetof(Network, home_agent_lifetime_usec) IPv6SendRA.HomeAgentPreference, config_parse_uint16, 0, offsetof(Network, router_home_agent_preference) IPv6Prefix.Prefix, config_parse_prefix, 0, 0 -IPv6Prefix.OnLink, config_parse_prefix_boolean, 0, 0 -IPv6Prefix.AddressAutoconfiguration, config_parse_prefix_boolean, 0, 0 +IPv6Prefix.OnLink, config_parse_prefix_boolean, ND_OPT_PI_FLAG_ONLINK, 0 +IPv6Prefix.AddressAutoconfiguration, config_parse_prefix_boolean, ND_OPT_PI_FLAG_AUTO, 0 IPv6Prefix.ValidLifetimeSec, config_parse_prefix_lifetime, 0, 0 IPv6Prefix.PreferredLifetimeSec, config_parse_prefix_lifetime, 0, 0 IPv6Prefix.Assign, config_parse_prefix_boolean, 0, 0 @@ -578,12 +593,12 @@ IPv6PrefixDelegation.Domains, config_parse_radv_search_domains, IPv6PrefixDelegation.DNSLifetimeSec, config_parse_sec, 0, offsetof(Network, router_dns_lifetime_usec) DHCPv4.BlackList, config_parse_in_addr_prefixes, AF_INET, offsetof(Network, dhcp_deny_listed_ip) DHCP.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier) -DHCP.UseDNS, config_parse_dhcp_use_dns, AF_UNSPEC, 0 -DHCP.UseNTP, config_parse_dhcp_use_ntp, AF_UNSPEC, 0 +DHCP.UseDNS, config_parse_tristate, 0, offsetof(Network, compat_dhcp_use_dns) +DHCP.UseNTP, config_parse_tristate, 0, offsetof(Network, compat_dhcp_use_ntp) DHCP.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu) DHCP.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname) -DHCP.UseDomains, config_parse_dhcp_use_domains, AF_UNSPEC, 0 -DHCP.UseDomainName, config_parse_dhcp_use_domains, AF_UNSPEC, 0 +DHCP.UseDomains, config_parse_use_domains, 0, offsetof(Network, compat_dhcp_use_domains) +DHCP.UseDomainName, config_parse_use_domains, 0, offsetof(Network, compat_dhcp_use_domains) DHCP.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_use_routes) DHCP.Anonymize, config_parse_bool, 0, offsetof(Network, dhcp_anonymize) DHCP.SendHostname, config_parse_dhcp_send_hostname, AF_UNSPEC, 0 @@ -601,9 +616,9 @@ DHCP.UseTimezone, config_parse_bool, DHCP.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port) DHCP.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp6_use_rapid_commit) DHCP.ForceDHCPv6PDOtherInformation, config_parse_warn_compat, DISABLED_LEGACY, 0 -DHCPv4.UseDomainName, config_parse_dhcp_use_domains, AF_INET, 0 +DHCPv4.UseDomainName, config_parse_use_domains, 0, offsetof(Network, dhcp_use_domains) DHCPv4.CriticalConnection, config_parse_tristate, 0, offsetof(Network, dhcp_critical) -DHCPv6.RouteMetric, config_parse_ipv6_accept_ra_route_metric, AF_INET6, 0 +DHCPv6.RouteMetric, config_parse_ndisc_route_metric, AF_INET6, 0 DHCPv6.ForceDHCPv6PDOtherInformation, config_parse_warn_compat, DISABLED_LEGACY, 0 DHCPv6PrefixDelegation.SubnetId, config_parse_dhcp_pd_subnet_id, 0, offsetof(Network, dhcp_pd_subnet_id) DHCPv6PrefixDelegation.Announce, config_parse_bool, 0, offsetof(Network, dhcp_pd_announce) @@ -613,6 +628,7 @@ DHCPv6PrefixDelegation.Token, config_parse_address_generation_typ DHCPv6PrefixDelegation.RouteMetric, config_parse_uint32, 0, offsetof(Network, dhcp_pd_route_metric) IPv6AcceptRA.DenyList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_deny_listed_prefix) IPv6AcceptRA.BlackList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_deny_listed_prefix) +IPv6AcceptRA.UseICMP6RateLimit, config_parse_warn_compat, DISABLED_LEGACY, 0 TrafficControlQueueingDiscipline.Parent, config_parse_qdisc_parent, _QDISC_KIND_INVALID, 0 TrafficControlQueueingDiscipline.NetworkEmulatorDelaySec, config_parse_network_emulator_delay, 0, 0 TrafficControlQueueingDiscipline.NetworkEmulatorDelayJitterSec, config_parse_network_emulator_delay, 0, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index dcd3e5a..8232db0 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> #include <netinet/in.h> #include <linux/netdevice.h> @@ -42,9 +43,6 @@ #include "strv.h" #include "tclass.h" -/* Let's assume that anything above this number is a user misconfiguration. */ -#define MAX_NTP_SERVERS 128U - static int network_resolve_netdev_one(Network *network, const char *name, NetDevKind kind, NetDev **ret) { const char *kind_string; NetDev *netdev; @@ -187,8 +185,8 @@ int network_verify(Network *network) { log_warning("%s: Cannot set routes when Bond= is specified, ignoring routes.", network->filename); - network->addresses_by_section = ordered_hashmap_free_with_destructor(network->addresses_by_section, address_free); - network->routes_by_section = hashmap_free_with_destructor(network->routes_by_section, route_free); + network->addresses_by_section = ordered_hashmap_free(network->addresses_by_section); + network->routes_by_section = hashmap_free(network->routes_by_section); } if (network->link_local < 0) { @@ -225,11 +223,8 @@ int network_verify(Network *network) { network->ipv6ll_address_gen_mode < 0) network->ipv6ll_address_gen_mode = IPV6_LINK_LOCAL_ADDRESSS_GEN_MODE_STABLE_PRIVACY; - /* IPMasquerade implies IPForward */ - network->ip_forward |= network->ip_masquerade; - network_adjust_ipv6_proxy_ndp(network); - network_adjust_ipv6_accept_ra(network); + network_adjust_ndisc(network); network_adjust_dhcp(network); network_adjust_radv(network); network_adjust_bridge_vlan(network); @@ -304,15 +299,15 @@ int network_verify(Network *network) { if (r < 0) return r; /* network_drop_invalid_addresses() logs internally. */ network_drop_invalid_routes(network); - network_drop_invalid_nexthops(network); + r = network_drop_invalid_nexthops(network); + if (r < 0) + return r; network_drop_invalid_bridge_fdb_entries(network); network_drop_invalid_bridge_mdb_entries(network); r = network_drop_invalid_neighbors(network); if (r < 0) return r; network_drop_invalid_address_labels(network); - network_drop_invalid_prefixes(network); - network_drop_invalid_route_prefixes(network); network_drop_invalid_routing_policy_rules(network); network_drop_invalid_qdisc(network); network_drop_invalid_tclass(network); @@ -370,7 +365,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .n_ref = 1, .required_for_online = -1, - .required_operstate_for_online = LINK_OPERSTATE_RANGE_DEFAULT, + .required_operstate_for_online = LINK_OPERSTATE_RANGE_INVALID, .activation_policy = _ACTIVATION_POLICY_INVALID, .group = -1, .arp = -1, @@ -380,14 +375,21 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .keep_configuration = manager->keep_configuration, + .use_domains = _USE_DOMAINS_INVALID, + + .compat_dhcp_use_domains = _USE_DOMAINS_INVALID, + .compat_dhcp_use_dns = -1, + .compat_dhcp_use_ntp = -1, + .dhcp_duid.type = _DUID_TYPE_INVALID, .dhcp_critical = -1, - .dhcp_use_ntp = true, + .dhcp_use_ntp = -1, .dhcp_routes_to_ntp = true, .dhcp_use_sip = true, .dhcp_use_captive_portal = true, - .dhcp_use_dns = true, + .dhcp_use_dns = -1, .dhcp_routes_to_dns = true, + .dhcp_use_domains = _USE_DOMAINS_INVALID, .dhcp_use_hostname = true, .dhcp_use_routes = true, .dhcp_use_gateway = -1, @@ -403,9 +405,10 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp6_use_address = true, .dhcp6_use_pd_prefix = true, - .dhcp6_use_dns = true, + .dhcp6_use_dns = -1, + .dhcp6_use_domains = _USE_DOMAINS_INVALID, .dhcp6_use_hostname = true, - .dhcp6_use_ntp = true, + .dhcp6_use_ntp = -1, .dhcp6_use_captive_portal = true, .dhcp6_use_rapid_commit = true, .dhcp6_send_hostname = true, @@ -427,6 +430,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp_server_emit_router = true, .dhcp_server_emit_timezone = true, .dhcp_server_rapid_commit = true, + .dhcp_server_persist_leases = -1, .router_lifetime_usec = RADV_DEFAULT_ROUTER_LIFETIME_USEC, .router_dns_lifetime_usec = RADV_DEFAULT_VALID_LIFETIME_USEC, @@ -448,6 +452,8 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .priority = LINK_BRIDGE_PORT_PRIORITY_INVALID, .multicast_router = _MULTICAST_ROUTER_INVALID, + .bridge_vlan_pvid = BRIDGE_VLAN_KEEP_PVID, + .lldp_mode = LLDP_MODE_ROUTERS_ONLY, .lldp_multicast_mode = _SD_LLDP_MULTICAST_MODE_INVALID, @@ -461,29 +467,34 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .link_local = _ADDRESS_FAMILY_INVALID, .ipv6ll_address_gen_mode = _IPV6_LINK_LOCAL_ADDRESS_GEN_MODE_INVALID, + .ip_forwarding = { -1, -1, }, .ipv4_accept_local = -1, .ipv4_route_localnet = -1, .ipv6_privacy_extensions = _IPV6_PRIVACY_EXTENSIONS_INVALID, .ipv6_dad_transmits = -1, .ipv6_proxy_ndp = -1, .proxy_arp = -1, + .proxy_arp_pvlan = -1, .ipv4_rp_filter = _IP_REVERSE_PATH_FILTER_INVALID, - .ipv6_accept_ra = -1, - .ipv6_accept_ra_use_dns = true, - .ipv6_accept_ra_use_gateway = true, - .ipv6_accept_ra_use_captive_portal = true, - .ipv6_accept_ra_use_route_prefix = true, - .ipv6_accept_ra_use_autonomous_prefix = true, - .ipv6_accept_ra_use_onlink_prefix = true, - .ipv6_accept_ra_use_mtu = true, - .ipv6_accept_ra_use_hop_limit = true, - .ipv6_accept_ra_use_icmp6_ratelimit = true, - .ipv6_accept_ra_route_table = RT_TABLE_MAIN, - .ipv6_accept_ra_route_metric_high = IPV6RA_ROUTE_METRIC_HIGH, - .ipv6_accept_ra_route_metric_medium = IPV6RA_ROUTE_METRIC_MEDIUM, - .ipv6_accept_ra_route_metric_low = IPV6RA_ROUTE_METRIC_LOW, - .ipv6_accept_ra_start_dhcp6_client = IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES, + .ndisc = -1, + .ndisc_use_redirect = true, + .ndisc_use_dns = -1, + .ndisc_use_gateway = true, + .ndisc_use_captive_portal = true, + .ndisc_use_route_prefix = true, + .ndisc_use_autonomous_prefix = true, + .ndisc_use_onlink_prefix = true, + .ndisc_use_mtu = true, + .ndisc_use_hop_limit = true, + .ndisc_use_reachable_time = true, + .ndisc_use_retransmission_time = true, + .ndisc_use_domains = _USE_DOMAINS_INVALID, + .ndisc_route_table = RT_TABLE_MAIN, + .ndisc_route_metric_high = IPV6RA_ROUTE_METRIC_HIGH, + .ndisc_route_metric_medium = IPV6RA_ROUTE_METRIC_MEDIUM, + .ndisc_route_metric_low = IPV6RA_ROUTE_METRIC_LOW, + .ndisc_start_dhcp6_client = IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES, .can_termination = -1, @@ -630,7 +641,15 @@ int network_reload(Manager *manager) { ordered_hashmap_free_with_destructor(manager->networks, network_unref); manager->networks = new_networks; - return manager_build_dhcp_pd_subnet_ids(manager); + r = manager_build_dhcp_pd_subnet_ids(manager); + if (r < 0) + return r; + + r = manager_build_nexthop_ids(manager); + if (r < 0) + return r; + + return 0; failure: ordered_hashmap_free_with_destructor(new_networks, network_unref); @@ -767,16 +786,16 @@ static Network *network_free(Network *network) { /* static configs */ set_free_free(network->ipv6_proxy_ndp_addresses); - ordered_hashmap_free_with_destructor(network->addresses_by_section, address_free); - hashmap_free_with_destructor(network->routes_by_section, route_free); - hashmap_free_with_destructor(network->nexthops_by_section, nexthop_free); + ordered_hashmap_free(network->addresses_by_section); + hashmap_free(network->routes_by_section); + ordered_hashmap_free(network->nexthops_by_section); hashmap_free_with_destructor(network->bridge_fdb_entries_by_section, bridge_fdb_free); hashmap_free_with_destructor(network->bridge_mdb_entries_by_section, bridge_mdb_free); - ordered_hashmap_free_with_destructor(network->neighbors_by_section, neighbor_free); + ordered_hashmap_free(network->neighbors_by_section); hashmap_free_with_destructor(network->address_labels_by_section, address_label_free); hashmap_free_with_destructor(network->prefixes_by_section, prefix_free); hashmap_free_with_destructor(network->route_prefixes_by_section, route_prefix_free); - hashmap_free_with_destructor(network->pref64_prefixes_by_section, pref64_prefix_free); + hashmap_free_with_destructor(network->pref64_prefixes_by_section, prefix64_free); hashmap_free_with_destructor(network->rules_by_section, routing_policy_rule_free); hashmap_free_with_destructor(network->dhcp_static_leases_by_section, dhcp_static_lease_free); ordered_hashmap_free_with_destructor(network->sr_iov_by_section, sr_iov_free); @@ -905,288 +924,6 @@ int config_parse_stacked_netdev( return 0; } -int config_parse_domains( - 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 *n = ASSERT_PTR(userdata); - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - n->search_domains = ordered_set_free(n->search_domains); - n->route_domains = ordered_set_free(n->route_domains); - return 0; - } - - for (const char *p = rvalue;;) { - _cleanup_free_ char *w = NULL, *normalized = NULL; - const char *domain; - bool is_route; - - r = extract_first_word(&p, &w, NULL, 0); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to extract search or route domain, ignoring: %s", rvalue); - return 0; - } - if (r == 0) - return 0; - - is_route = w[0] == '~'; - domain = is_route ? w + 1 : w; - - if (dns_name_is_root(domain) || streq(domain, "*")) { - /* If the root domain appears as is, or the special token "*" is found, we'll - * consider this as routing domain, unconditionally. */ - is_route = true; - domain = "."; /* make sure we don't allow empty strings, thus write the root - * domain as "." */ - } else { - r = dns_name_normalize(domain, 0, &normalized); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "'%s' is not a valid domain name, ignoring.", domain); - continue; - } - - domain = normalized; - - if (is_localhost(domain)) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "'localhost' domain may not be configured as search or route domain, ignoring assignment: %s", - domain); - continue; - } - } - - OrderedSet **set = is_route ? &n->route_domains : &n->search_domains; - r = ordered_set_put_strdup(set, domain); - if (r == -EEXIST) - continue; - if (r < 0) - return log_oom(); - } -} - -int config_parse_timezone( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - char **tz = ASSERT_PTR(data); - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - *tz = mfree(*tz); - return 0; - } - - r = verify_timezone(rvalue, LOG_WARNING); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Timezone is not valid, ignoring assignment: %s", rvalue); - return 0; - } - - return free_and_strdup_warn(tz, rvalue); -} - -int config_parse_dns( - 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 *n = ASSERT_PTR(userdata); - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - for (unsigned i = 0; i < n->n_dns; i++) - in_addr_full_free(n->dns[i]); - n->dns = mfree(n->dns); - n->n_dns = 0; - return 0; - } - - for (const char *p = rvalue;;) { - _cleanup_(in_addr_full_freep) struct in_addr_full *dns = NULL; - _cleanup_free_ char *w = NULL; - struct in_addr_full **m; - - r = extract_first_word(&p, &w, NULL, 0); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Invalid syntax, ignoring: %s", rvalue); - return 0; - } - if (r == 0) - return 0; - - r = in_addr_full_new_from_string(w, &dns); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to parse dns server address, ignoring: %s", w); - continue; - } - - if (IN_SET(dns->port, 53, 853)) - dns->port = 0; - - m = reallocarray(n->dns, n->n_dns + 1, sizeof(struct in_addr_full*)); - if (!m) - return log_oom(); - - m[n->n_dns++] = TAKE_PTR(dns); - n->dns = m; - } -} - -int config_parse_dnssec_negative_trust_anchors( - 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) { - - Set **nta = ASSERT_PTR(data); - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - *nta = set_free_free(*nta); - return 0; - } - - for (const char *p = rvalue;;) { - _cleanup_free_ char *w = NULL; - - r = extract_first_word(&p, &w, NULL, 0); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to extract negative trust anchor domain, ignoring: %s", rvalue); - return 0; - } - if (r == 0) - return 0; - - r = dns_name_is_valid(w); - if (r <= 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "%s is not a valid domain name, ignoring.", w); - continue; - } - - r = set_ensure_consume(nta, &dns_name_hash_ops, TAKE_PTR(w)); - if (r < 0) - return log_oom(); - } -} - -int config_parse_ntp( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - char ***l = ASSERT_PTR(data); - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - *l = strv_free(*l); - return 0; - } - - for (const char *p = rvalue;;) { - _cleanup_free_ char *w = NULL; - - r = extract_first_word(&p, &w, NULL, 0); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to extract NTP server name, ignoring: %s", rvalue); - return 0; - } - if (r == 0) - return 0; - - r = dns_name_is_valid_or_address(w); - if (r <= 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "%s is not a valid domain name or IP address, ignoring.", w); - continue; - } - - if (strv_length(*l) > MAX_NTP_SERVERS) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "More than %u NTP servers specified, ignoring \"%s\" and any subsequent entries.", - MAX_NTP_SERVERS, w); - return 0; - } - - r = strv_consume(l, TAKE_PTR(w)); - if (r < 0) - return log_oom(); - } -} - int config_parse_required_for_online( const char *unit, const char *filename, @@ -1200,8 +937,6 @@ int config_parse_required_for_online( void *userdata) { Network *network = ASSERT_PTR(userdata); - LinkOperationalStateRange range; - bool required = true; int r; assert(filename); @@ -1210,11 +945,11 @@ int config_parse_required_for_online( if (isempty(rvalue)) { network->required_for_online = -1; - network->required_operstate_for_online = LINK_OPERSTATE_RANGE_DEFAULT; + network->required_operstate_for_online = LINK_OPERSTATE_RANGE_INVALID; return 0; } - r = parse_operational_state_range(rvalue, &range); + r = parse_operational_state_range(rvalue, &network->required_operstate_for_online); if (r < 0) { r = parse_boolean(rvalue); if (r < 0) { @@ -1224,13 +959,12 @@ int config_parse_required_for_online( return 0; } - required = r; - range = LINK_OPERSTATE_RANGE_DEFAULT; + network->required_for_online = r; + network->required_operstate_for_online = LINK_OPERSTATE_RANGE_DEFAULT; + return 0; } - network->required_for_online = required; - network->required_operstate_for_online = range; - + network->required_for_online = true; return 0; } diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 03131b7..92d367e 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -20,6 +20,7 @@ #include "networkd-dhcp-common.h" #include "networkd-dhcp4.h" #include "networkd-dhcp6.h" +#include "networkd-dns.h" #include "networkd-ipv6ll.h" #include "networkd-lldp-rx.h" #include "networkd-ndisc.h" @@ -112,6 +113,14 @@ struct Network { bool default_route_on_device; AddressFamily ip_masquerade; + /* Protocol independent settings */ + UseDomains use_domains; + + /* For backward compatibility, only applied to DHCPv4 and DHCPv6. */ + UseDomains compat_dhcp_use_domains; + int compat_dhcp_use_dns; + int compat_dhcp_use_ntp; + /* DHCP Client Support */ AddressFamily dhcp; struct in_addr dhcp_request_address; @@ -132,6 +141,7 @@ struct Network { usec_t dhcp_fallback_lease_lifetime_usec; uint32_t dhcp_route_mtu; uint16_t dhcp_client_port; + uint16_t dhcp_port; int dhcp_critical; int dhcp_ip_service_type; int dhcp_socket_priority; @@ -142,11 +152,9 @@ struct Network { int dhcp_broadcast; int dhcp_ipv6_only_mode; int dhcp_use_rapid_commit; - bool dhcp_use_dns; - bool dhcp_use_dns_set; + int dhcp_use_dns; bool dhcp_routes_to_dns; - bool dhcp_use_ntp; - bool dhcp_use_ntp_set; + int dhcp_use_ntp; bool dhcp_routes_to_ntp; bool dhcp_use_sip; bool dhcp_use_captive_portal; @@ -161,8 +169,7 @@ struct Network { bool dhcp_use_6rd; bool dhcp_send_release; bool dhcp_send_decline; - DHCPUseDomains dhcp_use_domains; - bool dhcp_use_domains_set; + UseDomains dhcp_use_domains; Set *dhcp_deny_listed_ip; Set *dhcp_allow_listed_ip; Set *dhcp_request_options; @@ -176,15 +183,12 @@ struct Network { bool dhcp6_use_pd_prefix; bool dhcp6_send_hostname; bool dhcp6_send_hostname_set; - bool dhcp6_use_dns; - bool dhcp6_use_dns_set; + int dhcp6_use_dns; bool dhcp6_use_hostname; - bool dhcp6_use_ntp; - bool dhcp6_use_ntp_set; + int dhcp6_use_ntp; bool dhcp6_use_captive_portal; bool dhcp6_use_rapid_commit; - DHCPUseDomains dhcp6_use_domains; - bool dhcp6_use_domains_set; + UseDomains dhcp6_use_domains; uint32_t dhcp6_iaid; bool dhcp6_iaid_set; bool dhcp6_iaid_set_explicitly; @@ -229,6 +233,7 @@ struct Network { char *dhcp_server_boot_filename; usec_t dhcp_server_ipv6_only_preferred_usec; bool dhcp_server_rapid_commit; + int dhcp_server_persist_leases; /* link-local addressing support */ AddressFamily link_local; @@ -241,6 +246,7 @@ struct Network { RADVPrefixDelegation router_prefix_delegation; usec_t router_lifetime_usec; uint8_t router_preference; + usec_t router_reachable_usec; usec_t router_retransmit_usec; uint8_t router_hop_limit; bool router_managed; @@ -289,10 +295,9 @@ struct Network { MulticastRouter multicast_router; /* Bridge VLAN */ - bool use_br_vlan; - uint16_t pvid; - uint32_t br_vid_bitmap[BRIDGE_VLAN_BITMAP_LEN]; - uint32_t br_untagged_bitmap[BRIDGE_VLAN_BITMAP_LEN]; + uint16_t bridge_vlan_pvid; + uint32_t bridge_vlan_bitmap[BRIDGE_VLAN_BITMAP_LEN]; + uint32_t bridge_vlan_untagged_bitmap[BRIDGE_VLAN_BITMAP_LEN]; /* CAN support */ uint32_t can_bitrate; @@ -320,41 +325,45 @@ struct Network { int ipoib_umcast; /* sysctl settings */ - AddressFamily ip_forward; + int ip_forwarding[2]; int ipv4_accept_local; int ipv4_route_localnet; int ipv6_dad_transmits; uint8_t ipv6_hop_limit; + usec_t ipv6_retransmission_time; int proxy_arp; + int proxy_arp_pvlan; uint32_t ipv6_mtu; IPv6PrivacyExtensions ipv6_privacy_extensions; IPReversePathFilter ipv4_rp_filter; int ipv6_proxy_ndp; Set *ipv6_proxy_ndp_addresses; - /* IPv6 accept RA */ - int ipv6_accept_ra; - bool ipv6_accept_ra_use_dns; - bool ipv6_accept_ra_use_gateway; - bool ipv6_accept_ra_use_route_prefix; - bool ipv6_accept_ra_use_autonomous_prefix; - bool ipv6_accept_ra_use_onlink_prefix; - bool ipv6_accept_ra_use_mtu; - bool ipv6_accept_ra_use_hop_limit; - bool ipv6_accept_ra_use_icmp6_ratelimit; - bool ipv6_accept_ra_quickack; - bool ipv6_accept_ra_use_captive_portal; - bool ipv6_accept_ra_use_pref64; + /* NDisc support */ + int ndisc; + bool ndisc_use_redirect; + int ndisc_use_dns; + bool ndisc_use_gateway; + bool ndisc_use_route_prefix; + bool ndisc_use_autonomous_prefix; + bool ndisc_use_onlink_prefix; + bool ndisc_use_mtu; + bool ndisc_use_hop_limit; + bool ndisc_use_reachable_time; + bool ndisc_use_retransmission_time; + bool ndisc_quickack; + bool ndisc_use_captive_portal; + bool ndisc_use_pref64; bool active_slave; bool primary_slave; - DHCPUseDomains ipv6_accept_ra_use_domains; - IPv6AcceptRAStartDHCP6Client ipv6_accept_ra_start_dhcp6_client; - uint32_t ipv6_accept_ra_route_table; - bool ipv6_accept_ra_route_table_set; - uint32_t ipv6_accept_ra_route_metric_high; - uint32_t ipv6_accept_ra_route_metric_medium; - uint32_t ipv6_accept_ra_route_metric_low; - bool ipv6_accept_ra_route_metric_set; + UseDomains ndisc_use_domains; + IPv6AcceptRAStartDHCP6Client ndisc_start_dhcp6_client; + uint32_t ndisc_route_table; + bool ndisc_route_table_set; + uint32_t ndisc_route_metric_high; + uint32_t ndisc_route_metric_medium; + uint32_t ndisc_route_metric_low; + bool ndisc_route_metric_set; Set *ndisc_deny_listed_router; Set *ndisc_allow_listed_router; Set *ndisc_deny_listed_prefix; @@ -372,7 +381,7 @@ struct Network { OrderedHashmap *addresses_by_section; Hashmap *routes_by_section; - Hashmap *nexthops_by_section; + OrderedHashmap *nexthops_by_section; Hashmap *bridge_fdb_entries_by_section; Hashmap *bridge_mdb_entries_by_section; OrderedHashmap *neighbors_by_section; @@ -419,11 +428,6 @@ bool network_has_static_ipv6_configurations(Network *network); CONFIG_PARSER_PROTOTYPE(config_parse_stacked_netdev); CONFIG_PARSER_PROTOTYPE(config_parse_tunnel); -CONFIG_PARSER_PROTOTYPE(config_parse_domains); -CONFIG_PARSER_PROTOTYPE(config_parse_dns); -CONFIG_PARSER_PROTOTYPE(config_parse_timezone); -CONFIG_PARSER_PROTOTYPE(config_parse_dnssec_negative_trust_anchors); -CONFIG_PARSER_PROTOTYPE(config_parse_ntp); CONFIG_PARSER_PROTOTYPE(config_parse_required_for_online); CONFIG_PARSER_PROTOTYPE(config_parse_required_family_for_online); CONFIG_PARSER_PROTOTYPE(config_parse_keep_configuration); diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c index e2ded28..1b44ef3 100644 --- a/src/network/networkd-nexthop.c +++ b/src/network/networkd-nexthop.c @@ -2,6 +2,7 @@ * Copyright © 2019 VMware, Inc. */ +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> #include <linux/nexthop.h> @@ -12,53 +13,125 @@ #include "networkd-network.h" #include "networkd-nexthop.h" #include "networkd-queue.h" +#include "networkd-route.h" #include "networkd-route-util.h" #include "parse-util.h" #include "set.h" #include "stdio-util.h" #include "string-util.h" -NextHop *nexthop_free(NextHop *nexthop) { - if (!nexthop) - return NULL; +static void nexthop_detach_from_group_members(NextHop *nexthop) { + assert(nexthop); + assert(nexthop->manager); + assert(nexthop->id > 0); - if (nexthop->network) { - assert(nexthop->section); - hashmap_remove(nexthop->network->nexthops_by_section, nexthop->section); + struct nexthop_grp *nhg; + HASHMAP_FOREACH(nhg, nexthop->group) { + NextHop *nh; + + if (nexthop_get_by_id(nexthop->manager, nhg->id, &nh) < 0) + continue; + + set_remove(nh->nexthops, UINT32_TO_PTR(nexthop->id)); } +} - config_section_free(nexthop->section); +static void nexthop_attach_to_group_members(NextHop *nexthop) { + int r; - if (nexthop->link) { - set_remove(nexthop->link->nexthops, nexthop); + assert(nexthop); + assert(nexthop->manager); + assert(nexthop->id > 0); + + struct nexthop_grp *nhg; + HASHMAP_FOREACH(nhg, nexthop->group) { + NextHop *nh; + + r = nexthop_get_by_id(nexthop->manager, nhg->id, &nh); + if (r < 0) { + if (nexthop->manager->manage_foreign_nexthops) + log_debug_errno(r, "Nexthop (id=%"PRIu32") has unknown group member (%"PRIu32"), ignoring.", + nexthop->id, nhg->id); + continue; + } + + r = set_ensure_put(&nh->nexthops, NULL, UINT32_TO_PTR(nexthop->id)); + if (r < 0) + log_debug_errno(r, "Failed to save nexthop ID (%"PRIu32") to group member (%"PRIu32"), ignoring: %m", + nexthop->id, nhg->id); + } +} + +static NextHop* nexthop_detach_impl(NextHop *nexthop) { + assert(nexthop); + assert(!nexthop->manager || !nexthop->network); - if (nexthop->link->manager && nexthop->id > 0) - hashmap_remove(nexthop->link->manager->nexthops_by_id, UINT32_TO_PTR(nexthop->id)); + if (nexthop->network) { + assert(nexthop->section); + ordered_hashmap_remove(nexthop->network->nexthops_by_section, nexthop->section); + nexthop->network = NULL; + return nexthop; } if (nexthop->manager) { - set_remove(nexthop->manager->nexthops, nexthop); + assert(nexthop->id > 0); - if (nexthop->id > 0) - hashmap_remove(nexthop->manager->nexthops_by_id, UINT32_TO_PTR(nexthop->id)); + nexthop_detach_from_group_members(nexthop); + + hashmap_remove(nexthop->manager->nexthops_by_id, UINT32_TO_PTR(nexthop->id)); + nexthop->manager = NULL; + return nexthop; } + return NULL; +} + +static void nexthop_detach(NextHop *nexthop) { + nexthop_unref(nexthop_detach_impl(nexthop)); +} + +static NextHop* nexthop_free(NextHop *nexthop) { + if (!nexthop) + return NULL; + + nexthop_detach_impl(nexthop); + + config_section_free(nexthop->section); hashmap_free_free(nexthop->group); + set_free(nexthop->nexthops); + set_free(nexthop->routes); return mfree(nexthop); } -DEFINE_SECTION_CLEANUP_FUNCTIONS(NextHop, nexthop_free); +DEFINE_TRIVIAL_REF_UNREF_FUNC(NextHop, nexthop, nexthop_free); +DEFINE_SECTION_CLEANUP_FUNCTIONS(NextHop, nexthop_unref); + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + nexthop_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + NextHop, + nexthop_detach); + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + nexthop_section_hash_ops, + ConfigSection, + config_section_hash_func, + config_section_compare_func, + NextHop, + nexthop_detach); static int nexthop_new(NextHop **ret) { - _cleanup_(nexthop_freep) NextHop *nexthop = NULL; + _cleanup_(nexthop_unrefp) NextHop *nexthop = NULL; nexthop = new(NextHop, 1); if (!nexthop) return -ENOMEM; *nexthop = (NextHop) { - .family = AF_UNSPEC, + .n_ref = 1, .onlink = -1, }; @@ -69,7 +142,7 @@ static int nexthop_new(NextHop **ret) { static int nexthop_new_static(Network *network, const char *filename, unsigned section_line, NextHop **ret) { _cleanup_(config_section_freep) ConfigSection *n = NULL; - _cleanup_(nexthop_freep) NextHop *nexthop = NULL; + _cleanup_(nexthop_unrefp) NextHop *nexthop = NULL; int r; assert(network); @@ -81,7 +154,7 @@ static int nexthop_new_static(Network *network, const char *filename, unsigned s if (r < 0) return r; - nexthop = hashmap_get(network->nexthops_by_section, n); + nexthop = ordered_hashmap_get(network->nexthops_by_section, n); if (nexthop) { *ret = TAKE_PTR(nexthop); return 0; @@ -96,7 +169,7 @@ static int nexthop_new_static(Network *network, const char *filename, unsigned s nexthop->section = TAKE_PTR(n); nexthop->source = NETWORK_CONFIG_SOURCE_STATIC; - r = hashmap_ensure_put(&network->nexthops_by_section, &config_section_hash_ops, nexthop->section, nexthop); + r = ordered_hashmap_ensure_put(&network->nexthops_by_section, &nexthop_section_hash_ops, nexthop->section, nexthop); if (r < 0) return r; @@ -106,35 +179,54 @@ static int nexthop_new_static(Network *network, const char *filename, unsigned s static void nexthop_hash_func(const NextHop *nexthop, struct siphash *state) { assert(nexthop); + assert(state); - siphash24_compress(&nexthop->protocol, sizeof(nexthop->protocol), state); - siphash24_compress(&nexthop->id, sizeof(nexthop->id), state); - siphash24_compress(&nexthop->blackhole, sizeof(nexthop->blackhole), state); - siphash24_compress(&nexthop->family, sizeof(nexthop->family), state); + siphash24_compress_typesafe(nexthop->id, state); +} - switch (nexthop->family) { - case AF_INET: - case AF_INET6: - siphash24_compress(&nexthop->gw, FAMILY_ADDRESS_SIZE(nexthop->family), state); +static int nexthop_compare_func(const NextHop *a, const NextHop *b) { + assert(a); + assert(b); - break; - default: - /* treat any other address family as AF_UNSPEC */ - break; - } + return CMP(a->id, b->id); } -static int nexthop_compare_func(const NextHop *a, const NextHop *b) { +static int nexthop_compare_full(const NextHop *a, const NextHop *b) { int r; + assert(a); + assert(b); + + /* This compares detailed configs, except for ID and ifindex. */ + r = CMP(a->protocol, b->protocol); if (r != 0) return r; - r = CMP(a->id, b->id); + r = CMP(a->flags, b->flags); + if (r != 0) + return r; + + r = CMP(hashmap_size(a->group), hashmap_size(b->group)); if (r != 0) return r; + if (!hashmap_isempty(a->group)) { + struct nexthop_grp *ga; + + HASHMAP_FOREACH(ga, a->group) { + struct nexthop_grp *gb; + + gb = hashmap_get(b->group, UINT32_TO_PTR(ga->id)); + if (!gb) + return CMP(ga, gb); + + r = CMP(ga->weight, gb->weight); + if (r != 0) + return r; + } + } + r = CMP(a->blackhole, b->blackhole); if (r != 0) return r; @@ -143,31 +235,17 @@ static int nexthop_compare_func(const NextHop *a, const NextHop *b) { if (r != 0) return r; - if (IN_SET(a->family, AF_INET, AF_INET6)) - return memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family)); + if (IN_SET(a->family, AF_INET, AF_INET6)) { + r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + } return 0; } -DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( - nexthop_hash_ops, - NextHop, - nexthop_hash_func, - nexthop_compare_func, - nexthop_free); - -static bool nexthop_equal(const NextHop *a, const NextHop *b) { - if (a == b) - return true; - - if (!a || !b) - return false; - - return nexthop_compare_func(a, b) == 0; -} - static int nexthop_dup(const NextHop *src, NextHop **ret) { - _cleanup_(nexthop_freep) NextHop *dest = NULL; + _cleanup_(nexthop_unrefp) NextHop *dest = NULL; struct nexthop_grp *nhg; int r; @@ -178,9 +256,9 @@ static int nexthop_dup(const NextHop *src, NextHop **ret) { if (!dest) return -ENOMEM; - /* unset all pointers */ + /* clear the reference counter and all pointers */ + dest->n_ref = 1; dest->manager = NULL; - dest->link = NULL; dest->network = NULL; dest->section = NULL; dest->group = NULL; @@ -203,7 +281,12 @@ static int nexthop_dup(const NextHop *src, NextHop **ret) { return 0; } -int manager_get_nexthop_by_id(Manager *manager, uint32_t id, NextHop **ret) { +static bool nexthop_bound_to_link(const NextHop *nexthop) { + assert(nexthop); + return !nexthop->blackhole && hashmap_isempty(nexthop->group); +} + +int nexthop_get_by_id(Manager *manager, uint32_t id, NextHop **ret) { NextHop *nh; assert(manager); @@ -220,143 +303,175 @@ int manager_get_nexthop_by_id(Manager *manager, uint32_t id, NextHop **ret) { return 0; } -static bool nexthop_owned_by_link(const NextHop *nexthop) { - return !nexthop->blackhole && hashmap_isempty(nexthop->group); -} - -static int nexthop_get(Manager *manager, Link *link, NextHop *in, NextHop **ret) { +static int nexthop_get(Link *link, const NextHop *in, NextHop **ret) { NextHop *nexthop; - Set *nexthops; + int ifindex; + assert(link); + assert(link->manager); assert(in); - if (nexthop_owned_by_link(in)) { - if (!link) - return -ENOENT; + if (in->id > 0) + return nexthop_get_by_id(link->manager, in->id, ret); - nexthops = link->nexthops; - } else { - if (!manager) - return -ENOENT; + /* If ManageForeignNextHops=no, nexthop with id == 0 should be already filtered by + * nexthop_section_verify(). */ + assert(link->manager->manage_foreign_nexthops); - nexthops = manager->nexthops; - } + ifindex = nexthop_bound_to_link(in) ? link->ifindex : 0; + + HASHMAP_FOREACH(nexthop, link->manager->nexthops_by_id) { + if (nexthop->ifindex != ifindex) + continue; + if (nexthop_compare_full(nexthop, in) != 0) + continue; + + /* Even if the configuration matches, it may be configured with another [NextHop] section + * that has an explicit ID. If so, the assigned nexthop is not the one we are looking for. */ + if (set_contains(link->manager->nexthop_ids, UINT32_TO_PTR(nexthop->id))) + continue; - nexthop = set_get(nexthops, in); - if (nexthop) { if (ret) *ret = nexthop; return 0; } - if (in->id > 0) + return -ENOENT; +} + +static int nexthop_get_request_by_id(Manager *manager, uint32_t id, Request **ret) { + Request *req; + + assert(manager); + + if (id == 0) + return -EINVAL; + + req = ordered_set_get( + manager->request_queue, + &(Request) { + .type = REQUEST_TYPE_NEXTHOP, + .userdata = (void*) &(const NextHop) { .id = id }, + .hash_func = (hash_func_t) nexthop_hash_func, + .compare_func = (compare_func_t) nexthop_compare_func, + }); + if (!req) return -ENOENT; - /* Also find nexthop configured without ID. */ - SET_FOREACH(nexthop, nexthops) { - uint32_t id; - bool found; + if (ret) + *ret = req; + return 0; +} - id = nexthop->id; - nexthop->id = 0; - found = nexthop_equal(nexthop, in); - nexthop->id = id; +static int nexthop_get_request(Link *link, const NextHop *in, Request **ret) { + Request *req; + int ifindex; + + assert(link); + assert(link->manager); + assert(in); + + if (in->id > 0) + return nexthop_get_request_by_id(link->manager, in->id, ret); - if (!found) + /* If ManageForeignNextHops=no, nexthop with id == 0 should be already filtered by + * nexthop_section_verify(). */ + assert(link->manager->manage_foreign_nexthops); + + ifindex = nexthop_bound_to_link(in) ? link->ifindex : 0; + + ORDERED_SET_FOREACH(req, link->manager->request_queue) { + if (req->type != REQUEST_TYPE_NEXTHOP) + continue; + + NextHop *nexthop = ASSERT_PTR(req->userdata); + if (nexthop->ifindex != ifindex) + continue; + if (nexthop_compare_full(nexthop, in) != 0) + continue; + + /* Even if the configuration matches, it may be requested by another [NextHop] section + * that has an explicit ID. If so, the request is not the one we are looking for. */ + if (set_contains(link->manager->nexthop_ids, UINT32_TO_PTR(nexthop->id))) continue; if (ret) - *ret = nexthop; + *ret = req; return 0; } return -ENOENT; } -static int nexthop_add(Manager *manager, Link *link, NextHop *nexthop) { +static int nexthop_add_new(Manager *manager, uint32_t id, NextHop **ret) { + _cleanup_(nexthop_unrefp) NextHop *nexthop = NULL; int r; - assert(nexthop); - assert(nexthop->id > 0); - - if (nexthop_owned_by_link(nexthop)) { - assert(link); + assert(manager); + assert(id > 0); - r = set_ensure_put(&link->nexthops, &nexthop_hash_ops, nexthop); - if (r < 0) - return r; - if (r == 0) - return -EEXIST; + r = nexthop_new(&nexthop); + if (r < 0) + return r; - nexthop->link = link; + nexthop->id = id; - manager = link->manager; - } else { - assert(manager); + r = hashmap_ensure_put(&manager->nexthops_by_id, &nexthop_hash_ops, UINT32_TO_PTR(nexthop->id), nexthop); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; - r = set_ensure_put(&manager->nexthops, &nexthop_hash_ops, nexthop); - if (r < 0) - return r; - if (r == 0) - return -EEXIST; + nexthop->manager = manager; - nexthop->manager = manager; - } + if (ret) + *ret = nexthop; - return hashmap_ensure_put(&manager->nexthops_by_id, NULL, UINT32_TO_PTR(nexthop->id), nexthop); + TAKE_PTR(nexthop); + return 0; } static int nexthop_acquire_id(Manager *manager, NextHop *nexthop) { - _cleanup_set_free_ Set *ids = NULL; - Network *network; - uint32_t id; - int r; - assert(manager); assert(nexthop); if (nexthop->id > 0) return 0; - /* Find the lowest unused ID. */ - - ORDERED_HASHMAP_FOREACH(network, manager->networks) { - NextHop *tmp; - - HASHMAP_FOREACH(tmp, network->nexthops_by_section) { - if (tmp->id == 0) - continue; + /* If ManageForeignNextHops=no, nexthop with id == 0 should be already filtered by + * nexthop_section_verify(). */ + assert(manager->manage_foreign_nexthops); - r = set_ensure_put(&ids, NULL, UINT32_TO_PTR(tmp->id)); - if (r < 0) - return r; - } - } + /* Find the lowest unused ID. */ - for (id = 1; id < UINT32_MAX; id++) { - if (manager_get_nexthop_by_id(manager, id, NULL) >= 0) + for (uint32_t id = 1; id < UINT32_MAX; id++) { + if (nexthop_get_by_id(manager, id, NULL) >= 0) continue; - if (set_contains(ids, UINT32_TO_PTR(id))) + if (nexthop_get_request_by_id(manager, id, NULL) >= 0) continue; - break; + if (set_contains(manager->nexthop_ids, UINT32_TO_PTR(id))) + continue; + + nexthop->id = id; + return 0; } - nexthop->id = id; - return 0; + return -EBUSY; } -static void log_nexthop_debug(const NextHop *nexthop, const char *str, const Link *link) { +static void log_nexthop_debug(const NextHop *nexthop, const char *str, Manager *manager) { _cleanup_free_ char *state = NULL, *group = NULL, *flags = NULL; struct nexthop_grp *nhg; + Link *link = NULL; assert(nexthop); assert(str); - - /* link may be NULL. */ + assert(manager); if (!DEBUG_LOGGING) return; + (void) link_get_by_index(manager, nexthop->ifindex, &link); (void) network_config_state_to_string_alloc(nexthop->state, &state); (void) route_flags_to_string_alloc(nexthop->flags, &flags); @@ -370,42 +485,81 @@ static void log_nexthop_debug(const NextHop *nexthop, const char *str, const Lin yes_no(nexthop->blackhole), strna(group), strna(flags)); } -static int nexthop_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { +static int nexthop_remove_dependents(NextHop *nexthop, Manager *manager) { + int r = 0; + + assert(nexthop); + assert(manager); + + /* If a nexthop is removed, the kernel silently removes nexthops and routes that depend on the + * removed nexthop. Let's remove them for safety (though, they are already removed in the kernel, + * hence that should fail), and forget them. */ + + void *id; + SET_FOREACH(id, nexthop->nexthops) { + NextHop *nh; + + if (nexthop_get_by_id(manager, PTR_TO_UINT32(id), &nh) < 0) + continue; + + RET_GATHER(r, nexthop_remove(nh, manager)); + } + + Route *route; + SET_FOREACH(route, nexthop->routes) + RET_GATHER(r, route_remove(route, manager)); + + return r; +} + +static int nexthop_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, RemoveRequest *rreq) { int r; assert(m); + assert(rreq); - /* link may be NULL. */ - - if (link && IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) - return 1; + Manager *manager = ASSERT_PTR(rreq->manager); + NextHop *nexthop = ASSERT_PTR(rreq->userdata); r = sd_netlink_message_get_errno(m); - if (r < 0 && r != -ENOENT) - log_link_message_warning_errno(link, m, r, "Could not drop nexthop, ignoring"); + if (r < 0) { + log_message_full_errno(m, + (r == -ENOENT || !nexthop->manager) ? LOG_DEBUG : LOG_WARNING, + r, "Could not drop nexthop, ignoring"); + + (void) nexthop_remove_dependents(nexthop, manager); + + if (nexthop->manager) { + /* If the nexthop cannot be removed, then assume the nexthop is already removed. */ + log_nexthop_debug(nexthop, "Forgetting", manager); + + Request *req; + if (nexthop_get_request_by_id(manager, nexthop->id, &req) >= 0) + nexthop_enter_removed(req->userdata); + + nexthop_detach(nexthop); + } + } return 1; } -static int nexthop_remove(NextHop *nexthop) { +int nexthop_remove(NextHop *nexthop, Manager *manager) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - Manager *manager; - Link *link; + Link *link = NULL; int r; assert(nexthop); - assert(nexthop->manager || (nexthop->link && nexthop->link->manager)); + assert(nexthop->id > 0); + assert(manager); - /* link may be NULL. */ - link = nexthop->link; - manager = nexthop->manager ?: nexthop->link->manager; + /* If the nexthop is remembered, then use the remembered object. */ + (void) nexthop_get_by_id(manager, PTR_TO_UINT32(nexthop->id), &nexthop); - if (nexthop->id == 0) { - log_link_debug(link, "Cannot remove nexthop without valid ID, ignoring."); - return 0; - } + /* link may be NULL. */ + (void) link_get_by_index(manager, nexthop->ifindex, &link); - log_nexthop_debug(nexthop, "Removing", link); + log_nexthop_debug(nexthop, "Removing", manager); r = sd_rtnl_message_new_nexthop(manager->rtnl, &m, RTM_DELNEXTHOP, AF_UNSPEC, RTPROT_UNSPEC); if (r < 0) @@ -415,12 +569,9 @@ static int nexthop_remove(NextHop *nexthop) { if (r < 0) return log_link_error_errno(link, r, "Could not append NHA_ID attribute: %m"); - r = netlink_call_async(manager->rtnl, NULL, m, nexthop_remove_handler, - link ? link_netlink_destroy_callback : NULL, link); + r = manager_remove_request_add(manager, nexthop, nexthop, manager->rtnl, m, nexthop_remove_handler); if (r < 0) - return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); - - link_ref(link); /* link may be NULL, link_ref() is OK with that */ + return log_link_error_errno(link, r, "Could not queue rtnetlink message: %m"); nexthop_enter_removing(nexthop); return 0; @@ -431,6 +582,7 @@ static int nexthop_configure(NextHop *nexthop, Link *link, Request *req) { int r; assert(nexthop); + assert(nexthop->id > 0); assert(IN_SET(nexthop->family, AF_UNSPEC, AF_INET, AF_INET6)); assert(link); assert(link->manager); @@ -438,17 +590,15 @@ static int nexthop_configure(NextHop *nexthop, Link *link, Request *req) { assert(link->ifindex > 0); assert(req); - log_nexthop_debug(nexthop, "Configuring", link); + log_nexthop_debug(nexthop, "Configuring", link->manager); r = sd_rtnl_message_new_nexthop(link->manager->rtnl, &m, RTM_NEWNEXTHOP, nexthop->family, nexthop->protocol); if (r < 0) return r; - if (nexthop->id > 0) { - r = sd_netlink_message_append_u32(m, NHA_ID, nexthop->id); - if (r < 0) - return r; - } + r = sd_netlink_message_append_u32(m, NHA_ID, nexthop->id); + if (r < 0) + return r; if (!hashmap_isempty(nexthop->group)) { _cleanup_free_ struct nexthop_grp *group = NULL; @@ -471,7 +621,9 @@ static int nexthop_configure(NextHop *nexthop, Link *link, Request *req) { if (r < 0) return r; } else { - r = sd_netlink_message_append_u32(m, NHA_OIF, link->ifindex); + assert(nexthop->ifindex == link->ifindex); + + r = sd_netlink_message_append_u32(m, NHA_OIF, nexthop->ifindex); if (r < 0) return r; @@ -511,16 +663,49 @@ static int static_nexthop_handler(sd_netlink *rtnl, sd_netlink_message *m, Reque return 1; } +int nexthop_is_ready(Manager *manager, uint32_t id, NextHop **ret) { + NextHop *nexthop; + + assert(manager); + + if (id == 0) + return -EINVAL; + + if (nexthop_get_request_by_id(manager, id, NULL) >= 0) + goto not_ready; + + if (nexthop_get_by_id(manager, id, &nexthop) < 0) + goto not_ready; + + if (!nexthop_exists(nexthop)) + goto not_ready; + + if (ret) + *ret = nexthop; + + return true; + +not_ready: + if (ret) + *ret = NULL; + + return false; +} + static bool nexthop_is_ready_to_configure(Link *link, const NextHop *nexthop) { struct nexthop_grp *nhg; + int r; assert(link); assert(nexthop); + assert(nexthop->id > 0); if (!link_is_ready_to_configure(link, false)) return false; - if (nexthop_owned_by_link(nexthop)) { + if (nexthop_bound_to_link(nexthop)) { + assert(nexthop->ifindex == link->ifindex); + /* TODO: fdb nexthop does not require IFF_UP. The conditions below needs to be updated * when fdb nexthop support is added. See rtm_to_nh_config() in net/ipv4/nexthop.c of * kernel. */ @@ -532,34 +717,21 @@ static bool nexthop_is_ready_to_configure(Link *link, const NextHop *nexthop) { /* All group members must be configured first. */ HASHMAP_FOREACH(nhg, nexthop->group) { - NextHop *g; - - if (manager_get_nexthop_by_id(link->manager, nhg->id, &g) < 0) - return false; - - if (!nexthop_exists(g)) - return false; - } - - if (nexthop->id == 0) { - Request *req; - - ORDERED_SET_FOREACH(req, link->manager->request_queue) { - if (req->type != REQUEST_TYPE_NEXTHOP) - continue; - if (((NextHop*) req->userdata)->id != 0) - return false; /* first configure nexthop with id. */ - } + r = nexthop_is_ready(link->manager, nhg->id, NULL); + if (r <= 0) + return r; } return gateway_is_ready(link, FLAGS_SET(nexthop->flags, RTNH_F_ONLINK), nexthop->family, &nexthop->gw); } static int nexthop_process_request(Request *req, Link *link, NextHop *nexthop) { + NextHop *existing; int r; assert(req); assert(link); + assert(link->manager); assert(nexthop); if (!nexthop_is_ready_to_configure(link, nexthop)) @@ -570,39 +742,49 @@ static int nexthop_process_request(Request *req, Link *link, NextHop *nexthop) { return log_link_warning_errno(link, r, "Failed to configure nexthop"); nexthop_enter_configuring(nexthop); + if (nexthop_get_by_id(link->manager, nexthop->id, &existing) >= 0) + nexthop_enter_configuring(existing); + return 1; } -static int link_request_nexthop(Link *link, NextHop *nexthop) { - NextHop *existing; +static int link_request_nexthop(Link *link, const NextHop *nexthop) { + _cleanup_(nexthop_unrefp) NextHop *tmp = NULL; + NextHop *existing = NULL; int r; assert(link); + assert(link->manager); assert(nexthop); assert(nexthop->source != NETWORK_CONFIG_SOURCE_FOREIGN); - if (nexthop_get(link->manager, link, nexthop, &existing) < 0) { - _cleanup_(nexthop_freep) NextHop *tmp = NULL; + if (nexthop_get_request(link, nexthop, NULL) >= 0) + return 0; /* already requested, skipping. */ - r = nexthop_dup(nexthop, &tmp); - if (r < 0) - return r; + r = nexthop_dup(nexthop, &tmp); + if (r < 0) + return r; + if (nexthop_get(link, nexthop, &existing) < 0) { r = nexthop_acquire_id(link->manager, tmp); if (r < 0) return r; + } else { + /* Copy ID */ + assert(tmp->id == 0 || tmp->id == existing->id); + tmp->id = existing->id; - r = nexthop_add(link->manager, link, tmp); - if (r < 0) - return r; + /* Copy state for logging below. */ + tmp->state = existing->state; + } - existing = TAKE_PTR(tmp); - } else - existing->source = nexthop->source; + if (nexthop_bound_to_link(tmp)) + tmp->ifindex = link->ifindex; - log_nexthop_debug(existing, "Requesting", link); + log_nexthop_debug(tmp, "Requesting", link->manager); r = link_queue_request_safe(link, REQUEST_TYPE_NEXTHOP, - existing, NULL, + tmp, + nexthop_unref, nexthop_hash_func, nexthop_compare_func, nexthop_process_request, @@ -612,7 +794,11 @@ static int link_request_nexthop(Link *link, NextHop *nexthop) { if (r <= 0) return r; - nexthop_enter_requesting(existing); + nexthop_enter_requesting(tmp); + if (existing) + nexthop_enter_requesting(existing); + + TAKE_PTR(tmp); return 1; } @@ -625,7 +811,7 @@ int link_request_static_nexthops(Link *link, bool only_ipv4) { link->static_nexthops_configured = false; - HASHMAP_FOREACH(nh, link->network->nexthops_by_section) { + ORDERED_HASHMAP_FOREACH(nh, link->network->nexthops_by_section) { if (only_ipv4 && nh->family != AF_INET) continue; @@ -645,162 +831,182 @@ int link_request_static_nexthops(Link *link, bool only_ipv4) { return 0; } -static void manager_mark_nexthops(Manager *manager, bool foreign, const Link *except) { +static bool nexthop_can_update(const NextHop *assigned_nexthop, const NextHop *requested_nexthop) { + assert(assigned_nexthop); + assert(assigned_nexthop->manager); + assert(requested_nexthop); + assert(requested_nexthop->network); + + /* A group nexthop cannot be replaced with a non-group nexthop, and vice versa. + * See replace_nexthop_grp() and replace_nexthop_single() in net/ipv4/nexthop.c of the kernel. */ + if (hashmap_isempty(assigned_nexthop->group) != hashmap_isempty(requested_nexthop->group)) + return false; + + /* There are several more conditions if we can replace a group nexthop, e.g. hash threshold and + * resilience. But, currently we do not support to modify that. Let's add checks for them in the + * future when we support to configure them.*/ + + /* When a nexthop is replaced with a blackhole nexthop, and a group nexthop has multiple nexthops + * including this nexthop, then the kernel refuses to replace the existing nexthop. + * So, here, for simplicity, let's unconditionally refuse to replace a non-blackhole nexthop with + * a blackhole nexthop. See replace_nexthop() in net/ipv4/nexthop.c of the kernel. */ + if (!assigned_nexthop->blackhole && requested_nexthop->blackhole) + return false; + + return true; +} + +static void link_mark_nexthops(Link *link, bool foreign) { NextHop *nexthop; - Link *link; + Link *other; - assert(manager); + assert(link); + assert(link->manager); /* First, mark all nexthops. */ - SET_FOREACH(nexthop, manager->nexthops) { + HASHMAP_FOREACH(nexthop, link->manager->nexthops_by_id) { /* do not touch nexthop created by the kernel */ if (nexthop->protocol == RTPROT_KERNEL) continue; /* When 'foreign' is true, mark only foreign nexthops, and vice versa. */ - if (foreign != (nexthop->source == NETWORK_CONFIG_SOURCE_FOREIGN)) + if (nexthop->source != (foreign ? NETWORK_CONFIG_SOURCE_FOREIGN : NETWORK_CONFIG_SOURCE_STATIC)) continue; /* Ignore nexthops not assigned yet or already removed. */ if (!nexthop_exists(nexthop)) continue; + /* Ignore nexthops bound to other links. */ + if (nexthop->ifindex > 0 && nexthop->ifindex != link->ifindex) + continue; + nexthop_mark(nexthop); } /* Then, unmark all nexthops requested by active links. */ - HASHMAP_FOREACH(link, manager->links_by_index) { - if (link == except) + HASHMAP_FOREACH(other, link->manager->links_by_index) { + if (!foreign && other == link) continue; - if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) + if (!IN_SET(other->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) continue; - HASHMAP_FOREACH(nexthop, link->network->nexthops_by_section) { + ORDERED_HASHMAP_FOREACH(nexthop, other->network->nexthops_by_section) { NextHop *existing; - if (nexthop_get(manager, NULL, nexthop, &existing) >= 0) - nexthop_unmark(existing); + if (nexthop_get(other, nexthop, &existing) < 0) + continue; + + if (!nexthop_can_update(existing, nexthop)) + continue; + + /* Found matching static configuration. Keep the existing nexthop. */ + nexthop_unmark(existing); } } } -static int manager_drop_marked_nexthops(Manager *manager) { +int link_drop_nexthops(Link *link, bool foreign) { NextHop *nexthop; int r = 0; - assert(manager); + assert(link); + assert(link->manager); - SET_FOREACH(nexthop, manager->nexthops) { + link_mark_nexthops(link, foreign); + + HASHMAP_FOREACH(nexthop, link->manager->nexthops_by_id) { if (!nexthop_is_marked(nexthop)) continue; - RET_GATHER(r, nexthop_remove(nexthop)); + RET_GATHER(r, nexthop_remove(nexthop, link->manager)); } return r; } -int link_drop_foreign_nexthops(Link *link) { +void link_foreignize_nexthops(Link *link) { NextHop *nexthop; - int r = 0; assert(link); assert(link->manager); - assert(link->network); - /* First, mark all nexthops. */ - SET_FOREACH(nexthop, link->nexthops) { - /* do not touch nexthop created by the kernel */ - if (nexthop->protocol == RTPROT_KERNEL) - continue; - - /* Do not remove nexthops we configured. */ - if (nexthop->source != NETWORK_CONFIG_SOURCE_FOREIGN) - continue; + link_mark_nexthops(link, /* foreign = */ false); - /* Ignore nexthops not assigned yet or already removed. */ - if (!nexthop_exists(nexthop)) + HASHMAP_FOREACH(nexthop, link->manager->nexthops_by_id) { + if (!nexthop_is_marked(nexthop)) continue; - nexthop_mark(nexthop); + nexthop->source = NETWORK_CONFIG_SOURCE_FOREIGN; } +} - /* Then, unmark all nexthops requested by active links. */ - HASHMAP_FOREACH(nexthop, link->network->nexthops_by_section) { - NextHop *existing; - - if (nexthop_get(NULL, link, nexthop, &existing) >= 0) - nexthop_unmark(existing); - } +static int nexthop_update_group(NextHop *nexthop, sd_netlink_message *message) { + _cleanup_hashmap_free_free_ Hashmap *h = NULL; + _cleanup_free_ struct nexthop_grp *group = NULL; + size_t size = 0, n_group; + int r; - /* Finally, remove all marked rules. */ - SET_FOREACH(nexthop, link->nexthops) { - if (!nexthop_is_marked(nexthop)) - continue; + assert(nexthop); + assert(message); - RET_GATHER(r, nexthop_remove(nexthop)); - } + r = sd_netlink_message_read_data(message, NHA_GROUP, &size, (void**) &group); + if (r < 0 && r != -ENODATA) + return log_debug_errno(r, "rtnl: could not get NHA_GROUP attribute, ignoring: %m"); - manager_mark_nexthops(link->manager, /* foreign = */ true, NULL); + nexthop_detach_from_group_members(nexthop); - return RET_GATHER(r, manager_drop_marked_nexthops(link->manager)); -} + if (size % sizeof(struct nexthop_grp) != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "rtnl: received nexthop message with invalid nexthop group size, ignoring."); -int link_drop_managed_nexthops(Link *link) { - NextHop *nexthop; - int r = 0; + if ((uintptr_t) group % alignof(struct nexthop_grp) != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "rtnl: received nexthop message with invalid alignment, ignoring."); - assert(link); - assert(link->manager); + n_group = size / sizeof(struct nexthop_grp); + for (size_t i = 0; i < n_group; i++) { + _cleanup_free_ struct nexthop_grp *nhg = NULL; - SET_FOREACH(nexthop, link->nexthops) { - /* do not touch nexthop created by the kernel */ - if (nexthop->protocol == RTPROT_KERNEL) + if (group[i].id == 0) { + log_debug("rtnl: received nexthop message with invalid ID in group, ignoring."); continue; + } - /* Do not touch addresses managed by kernel or other tools. */ - if (nexthop->source == NETWORK_CONFIG_SOURCE_FOREIGN) + if (group[i].weight > 254) { + log_debug("rtnl: received nexthop message with invalid weight in group, ignoring."); continue; + } - /* Ignore nexthops not assigned yet or already removing. */ - if (!nexthop_exists(nexthop)) - continue; + nhg = newdup(struct nexthop_grp, group + i, 1); + if (!nhg) + return log_oom(); - RET_GATHER(r, nexthop_remove(nexthop)); + r = hashmap_ensure_put(&h, NULL, UINT32_TO_PTR(nhg->id), nhg); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_debug_errno(r, "Failed to store nexthop group, ignoring: %m"); + continue; + } + if (r > 0) + TAKE_PTR(nhg); } - manager_mark_nexthops(link->manager, /* foreign = */ false, link); - - return RET_GATHER(r, manager_drop_marked_nexthops(link->manager)); -} - -void link_foreignize_nexthops(Link *link) { - NextHop *nexthop; - - assert(link); - - SET_FOREACH(nexthop, link->nexthops) - nexthop->source = NETWORK_CONFIG_SOURCE_FOREIGN; - - manager_mark_nexthops(link->manager, /* foreign = */ false, link); - - SET_FOREACH(nexthop, link->manager->nexthops) { - if (!nexthop_is_marked(nexthop)) - continue; + hashmap_free_free(nexthop->group); + nexthop->group = TAKE_PTR(h); - nexthop->source = NETWORK_CONFIG_SOURCE_FOREIGN; - } + nexthop_attach_to_group_members(nexthop); + return 0; } int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { - _cleanup_(nexthop_freep) NextHop *tmp = NULL; - _cleanup_free_ void *raw_group = NULL; - NextHop *nexthop = NULL; - size_t raw_group_size; - uint32_t ifindex; uint16_t type; - Link *link = NULL; + uint32_t id, ifindex; + NextHop *nexthop = NULL; + Request *req = NULL; + bool is_new = false; int r; assert(rtnl); @@ -824,163 +1030,102 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, return 0; } - r = sd_netlink_message_read_u32(message, NHA_OIF, &ifindex); - if (r < 0 && r != -ENODATA) { - log_warning_errno(r, "rtnl: could not get NHA_OIF attribute, ignoring: %m"); + r = sd_netlink_message_read_u32(message, NHA_ID, &id); + if (r == -ENODATA) { + log_warning_errno(r, "rtnl: received nexthop message without NHA_ID attribute, ignoring: %m"); return 0; - } else if (r >= 0) { - if (ifindex <= 0) { - log_warning("rtnl: received nexthop message with invalid ifindex %"PRIu32", ignoring.", ifindex); - return 0; - } - - r = link_get_by_index(m, ifindex, &link); - if (r < 0) { - if (!m->enumerating) - log_warning("rtnl: received nexthop message for link (%"PRIu32") we do not know about, ignoring", ifindex); - return 0; - } - } - - r = nexthop_new(&tmp); - if (r < 0) - return log_oom(); - - r = sd_rtnl_message_get_family(message, &tmp->family); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: could not get nexthop family, ignoring: %m"); + } else if (r < 0) { + log_warning_errno(r, "rtnl: could not get NHA_ID attribute, ignoring: %m"); return 0; - } else if (!IN_SET(tmp->family, AF_UNSPEC, AF_INET, AF_INET6)) { - log_link_debug(link, "rtnl: received nexthop message with invalid family %d, ignoring.", tmp->family); + } else if (id == 0) { + log_warning("rtnl: received nexthop message with invalid nexthop ID, ignoring: %m"); return 0; } - r = sd_rtnl_message_nexthop_get_protocol(message, &tmp->protocol); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: could not get nexthop protocol, ignoring: %m"); - return 0; - } + (void) nexthop_get_by_id(m, id, &nexthop); + (void) nexthop_get_request_by_id(m, id, &req); - r = sd_rtnl_message_nexthop_get_flags(message, &tmp->flags); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: could not get nexthop flags, ignoring: %m"); - return 0; - } + if (type == RTM_DELNEXTHOP) { + if (nexthop) { + nexthop_enter_removed(nexthop); + log_nexthop_debug(nexthop, "Forgetting removed", m); + (void) nexthop_remove_dependents(nexthop, m); + nexthop_detach(nexthop); + } else + log_nexthop_debug(&(const NextHop) { .id = id }, "Kernel removed unknown", m); + + if (req) + nexthop_enter_removed(req->userdata); - r = sd_netlink_message_read_data(message, NHA_GROUP, &raw_group_size, &raw_group); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: could not get NHA_GROUP attribute, ignoring: %m"); return 0; - } else if (r >= 0) { - struct nexthop_grp *group = raw_group; - size_t n_group; + } - if (raw_group_size == 0 || raw_group_size % sizeof(struct nexthop_grp) != 0) { - log_link_warning(link, "rtnl: received nexthop message with invalid nexthop group size, ignoring."); + /* If we did not know the nexthop, then save it. */ + if (!nexthop) { + r = nexthop_add_new(m, id, &nexthop); + if (r < 0) { + log_warning_errno(r, "Failed to add received nexthop, ignoring: %m"); return 0; } - assert((uintptr_t) group % alignof(struct nexthop_grp) == 0); + is_new = true; + } - n_group = raw_group_size / sizeof(struct nexthop_grp); - for (size_t i = 0; i < n_group; i++) { - _cleanup_free_ struct nexthop_grp *nhg = NULL; + /* Also update information that cannot be obtained through netlink notification. */ + if (req && req->waiting_reply) { + NextHop *n = ASSERT_PTR(req->userdata); - if (group[i].id == 0) { - log_link_warning(link, "rtnl: received nexthop message with invalid ID in group, ignoring."); - return 0; - } - if (group[i].weight > 254) { - log_link_warning(link, "rtnl: received nexthop message with invalid weight in group, ignoring."); - return 0; - } + nexthop->source = n->source; + } - nhg = newdup(struct nexthop_grp, group + i, 1); - if (!nhg) - return log_oom(); + r = sd_rtnl_message_get_family(message, &nexthop->family); + if (r < 0) + log_debug_errno(r, "rtnl: could not get nexthop family, ignoring: %m"); - r = hashmap_ensure_put(&tmp->group, NULL, UINT32_TO_PTR(nhg->id), nhg); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_link_warning_errno(link, r, "Failed to store nexthop group, ignoring: %m"); - return 0; - } - if (r > 0) - TAKE_PTR(nhg); - } - } + r = sd_rtnl_message_nexthop_get_protocol(message, &nexthop->protocol); + if (r < 0) + log_debug_errno(r, "rtnl: could not get nexthop protocol, ignoring: %m"); - if (tmp->family != AF_UNSPEC) { - r = netlink_message_read_in_addr_union(message, NHA_GATEWAY, tmp->family, &tmp->gw); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: could not get NHA_GATEWAY attribute, ignoring: %m"); - return 0; - } + r = sd_rtnl_message_nexthop_get_flags(message, &nexthop->flags); + if (r < 0) + log_debug_errno(r, "rtnl: could not get nexthop flags, ignoring: %m"); + + (void) nexthop_update_group(nexthop, message); + + if (nexthop->family != AF_UNSPEC) { + r = netlink_message_read_in_addr_union(message, NHA_GATEWAY, nexthop->family, &nexthop->gw); + if (r == -ENODATA) + nexthop->gw = IN_ADDR_NULL; + else if (r < 0) + log_debug_errno(r, "rtnl: could not get NHA_GATEWAY attribute, ignoring: %m"); } r = sd_netlink_message_has_flag(message, NHA_BLACKHOLE); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: could not get NHA_BLACKHOLE attribute, ignoring: %m"); - return 0; - } - tmp->blackhole = r; + if (r < 0) + log_debug_errno(r, "rtnl: could not get NHA_BLACKHOLE attribute, ignoring: %m"); + else + nexthop->blackhole = r; - r = sd_netlink_message_read_u32(message, NHA_ID, &tmp->id); - if (r == -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received nexthop message without NHA_ID attribute, ignoring: %m"); - return 0; - } else if (r < 0) { - log_link_warning_errno(link, r, "rtnl: could not get NHA_ID attribute, ignoring: %m"); - return 0; - } else if (tmp->id == 0) { - log_link_warning(link, "rtnl: received nexthop message with invalid nexthop ID, ignoring: %m"); - return 0; - } + r = sd_netlink_message_read_u32(message, NHA_OIF, &ifindex); + if (r == -ENODATA) + nexthop->ifindex = 0; + else if (r < 0) + log_debug_errno(r, "rtnl: could not get NHA_OIF attribute, ignoring: %m"); + else if (ifindex > INT32_MAX) + log_debug_errno(r, "rtnl: received invalid NHA_OIF attribute, ignoring: %m"); + else + nexthop->ifindex = (int) ifindex; /* All blackhole or group nexthops are managed by Manager. Note that the linux kernel does not * set NHA_OID attribute when NHA_BLACKHOLE or NHA_GROUP is set. Just for safety. */ - if (!nexthop_owned_by_link(tmp)) - link = NULL; - - (void) nexthop_get(m, link, tmp, &nexthop); - - switch (type) { - case RTM_NEWNEXTHOP: - if (nexthop) { - nexthop->flags = tmp->flags; - nexthop_enter_configured(nexthop); - log_nexthop_debug(tmp, "Received remembered", link); - } else { - nexthop_enter_configured(tmp); - log_nexthop_debug(tmp, "Remembering", link); - - r = nexthop_add(m, link, tmp); - if (r < 0) { - log_link_warning_errno(link, r, "Could not remember foreign nexthop, ignoring: %m"); - return 0; - } - - TAKE_PTR(tmp); - } + if (!nexthop_bound_to_link(nexthop)) + nexthop->ifindex = 0; - break; - case RTM_DELNEXTHOP: - if (nexthop) { - nexthop_enter_removed(nexthop); - if (nexthop->state == 0) { - log_nexthop_debug(nexthop, "Forgetting", link); - nexthop_free(nexthop); - } else - log_nexthop_debug(nexthop, "Removed", link); - } else - log_nexthop_debug(tmp, "Kernel removed unknown", link); - break; - - default: - assert_not_reached(); - } + nexthop_enter_configured(nexthop); + if (req) + nexthop_enter_configured(req->userdata); + log_nexthop_debug(nexthop, is_new ? "Remembering" : "Received remembered", m); return 1; } @@ -988,6 +1133,13 @@ static int nexthop_section_verify(NextHop *nh) { if (section_is_invalid(nh->section)) return -EINVAL; + if (!nh->network->manager->manage_foreign_nexthops && nh->id == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: [NextHop] section without specifying Id= is not supported " + "if ManageForeignNextHops=no is set in networkd.conf. " + "Ignoring [NextHop] section from line %u.", + nh->section->filename, nh->section->line); + if (!hashmap_isempty(nh->group)) { if (in_addr_is_set(nh->family, &nh->gw)) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), @@ -1001,20 +1153,34 @@ static int nexthop_section_verify(NextHop *nh) { "Ignoring [NextHop] section from line %u.", nh->section->filename, nh->section->line); - if (nh->blackhole && in_addr_is_set(nh->family, &nh->gw)) + if (nh->blackhole) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "%s: nexthop group cannot be a blackhole. " "Ignoring [NextHop] section from line %u.", nh->section->filename, nh->section->line); + + if (nh->onlink > 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: nexthop group cannot have on-link flag. " + "Ignoring [NextHop] section from line %u.", + nh->section->filename, nh->section->line); } else if (nh->family == AF_UNSPEC) /* When neither Family=, Gateway=, nor Group= is specified, assume IPv4. */ nh->family = AF_INET; - if (nh->blackhole && in_addr_is_set(nh->family, &nh->gw)) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: blackhole nexthop cannot have gateway address. " - "Ignoring [NextHop] section from line %u.", - nh->section->filename, nh->section->line); + if (nh->blackhole) { + if (in_addr_is_set(nh->family, &nh->gw)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: blackhole nexthop cannot have gateway address. " + "Ignoring [NextHop] section from line %u.", + nh->section->filename, nh->section->line); + + if (nh->onlink > 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: blackhole nexthop cannot have on-link flag. " + "Ignoring [NextHop] section from line %u.", + nh->section->filename, nh->section->line); + } if (nh->onlink < 0 && in_addr_is_set(nh->family, &nh->gw) && ordered_hashmap_isempty(nh->network->addresses_by_section)) { @@ -1032,14 +1198,68 @@ static int nexthop_section_verify(NextHop *nh) { return 0; } -void network_drop_invalid_nexthops(Network *network) { +int network_drop_invalid_nexthops(Network *network) { + _cleanup_hashmap_free_ Hashmap *nexthops = NULL; NextHop *nh; + int r; assert(network); - HASHMAP_FOREACH(nh, network->nexthops_by_section) - if (nexthop_section_verify(nh) < 0) - nexthop_free(nh); + ORDERED_HASHMAP_FOREACH(nh, network->nexthops_by_section) { + if (nexthop_section_verify(nh) < 0) { + nexthop_detach(nh); + continue; + } + + if (nh->id == 0) + continue; + + /* Always use the setting specified later. So, remove the previously assigned setting. */ + NextHop *dup = hashmap_remove(nexthops, UINT32_TO_PTR(nh->id)); + if (dup) { + log_warning("%s: Duplicated nexthop settings for ID %"PRIu32" is specified at line %u and %u, " + "dropping the nexthop setting specified at line %u.", + dup->section->filename, + nh->id, nh->section->line, + dup->section->line, dup->section->line); + /* nexthop_detach() will drop the nexthop from nexthops_by_section. */ + nexthop_detach(dup); + } + + r = hashmap_ensure_put(&nexthops, NULL, UINT32_TO_PTR(nh->id), nh); + if (r < 0) + return log_oom(); + assert(r > 0); + } + + return 0; +} + +int manager_build_nexthop_ids(Manager *manager) { + Network *network; + int r; + + assert(manager); + + if (!manager->manage_foreign_nexthops) + return 0; + + manager->nexthop_ids = set_free(manager->nexthop_ids); + + ORDERED_HASHMAP_FOREACH(network, manager->networks) { + NextHop *nh; + + ORDERED_HASHMAP_FOREACH(nh, network->nexthops_by_section) { + if (nh->id == 0) + continue; + + r = set_ensure_put(&manager->nexthop_ids, NULL, UINT32_TO_PTR(nh->id)); + if (r < 0) + return r; + } + } + + return 0; } int config_parse_nexthop_id( @@ -1054,7 +1274,7 @@ int config_parse_nexthop_id( void *data, void *userdata) { - _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; + _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL; Network *network = userdata; uint32_t id; int r; @@ -1104,7 +1324,7 @@ int config_parse_nexthop_gateway( void *data, void *userdata) { - _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; + _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL; Network *network = userdata; int r; @@ -1149,7 +1369,7 @@ int config_parse_nexthop_family( void *data, void *userdata) { - _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; + _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL; Network *network = userdata; AddressFamily a; int r; @@ -1215,7 +1435,7 @@ int config_parse_nexthop_onlink( void *data, void *userdata) { - _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; + _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL; Network *network = userdata; int r; @@ -1252,7 +1472,7 @@ int config_parse_nexthop_blackhole( void *data, void *userdata) { - _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; + _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL; Network *network = userdata; int r; @@ -1291,7 +1511,7 @@ int config_parse_nexthop_group( void *data, void *userdata) { - _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; + _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL; Network *network = userdata; int r; diff --git a/src/network/networkd-nexthop.h b/src/network/networkd-nexthop.h index 6f2aa6f..fbe330a 100644 --- a/src/network/networkd-nexthop.h +++ b/src/network/networkd-nexthop.h @@ -20,34 +20,54 @@ typedef struct Network Network; typedef struct NextHop { Network *network; Manager *manager; - Link *link; ConfigSection *section; NetworkConfigSource source; NetworkConfigState state; - uint8_t protocol; + unsigned n_ref; - uint32_t id; - bool blackhole; + /* struct nhmsg */ int family; - union in_addr_union gw; + uint8_t protocol; uint8_t flags; - int onlink; /* Only used in conf parser and nexthop_section_verify(). */ - Hashmap *group; + + /* attributes */ + uint32_t id; /* NHA_ID */ + Hashmap *group; /* NHA_GROUP */ + bool blackhole; /* NHA_BLACKHOLE */ + int ifindex; /* NHA_OIF */ + union in_addr_union gw; /* NHA_GATEWAY */ + + /* Only used in conf parser and nexthop_section_verify(). */ + int onlink; + + /* For managing routes and nexthops that depend on this nexthop. */ + Set *nexthops; + Set *routes; } NextHop; -NextHop *nexthop_free(NextHop *nexthop); +NextHop* nexthop_ref(NextHop *nexthop); +NextHop* nexthop_unref(NextHop *nexthop); + +int nexthop_remove(NextHop *nexthop, Manager *manager); -void network_drop_invalid_nexthops(Network *network); +int network_drop_invalid_nexthops(Network *network); -int link_drop_managed_nexthops(Link *link); -int link_drop_foreign_nexthops(Link *link); +int link_drop_nexthops(Link *link, bool foreign); +static inline int link_drop_foreign_nexthops(Link *link) { + return link_drop_nexthops(link, /* foreign = */ true); +} +static inline int link_drop_static_nexthops(Link *link) { + return link_drop_nexthops(link, /* foreign = */ false); +} void link_foreignize_nexthops(Link *link); int link_request_static_nexthops(Link *link, bool only_ipv4); -int manager_get_nexthop_by_id(Manager *manager, uint32_t id, NextHop **ret); +int nexthop_get_by_id(Manager *manager, uint32_t id, NextHop **ret); +int nexthop_is_ready(Manager *manager, uint32_t id, NextHop **ret); int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, Manager *m); +int manager_build_nexthop_ids(Manager *manager); DEFINE_NETWORK_CONFIG_STATE_FUNCTIONS(NextHop, nexthop); diff --git a/src/network/networkd-ntp.c b/src/network/networkd-ntp.c new file mode 100644 index 0000000..e764fea --- /dev/null +++ b/src/network/networkd-ntp.c @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "dns-domain.h" +#include "networkd-network.h" +#include "networkd-ntp.h" +#include "parse-util.h" +#include "strv.h" + +/* Let's assume that anything above this number is a user misconfiguration. */ +#define MAX_NTP_SERVERS 128U + +bool link_get_use_ntp(Link *link, NetworkConfigSource proto) { + int n, c; + + assert(link); + + if (!link->network) + return false; + + switch (proto) { + case NETWORK_CONFIG_SOURCE_DHCP4: + n = link->network->dhcp_use_ntp; + c = link->network->compat_dhcp_use_ntp; + break; + case NETWORK_CONFIG_SOURCE_DHCP6: + n = link->network->dhcp6_use_ntp; + c = link->network->compat_dhcp_use_ntp; + break; + default: + assert_not_reached(); + } + + /* If per-network and per-protocol setting is specified, use it. */ + if (n >= 0) + return n; + + /* If compat setting is specified, use it. */ + if (c >= 0) + return c; + + /* Otherwise, defaults to yes. */ + return true; +} + +int config_parse_ntp( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char ***l = ASSERT_PTR(data); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *l = strv_free(*l); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to extract NTP server name, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + r = dns_name_is_valid_or_address(w); + if (r <= 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "%s is not a valid domain name or IP address, ignoring.", w); + continue; + } + + if (strv_length(*l) > MAX_NTP_SERVERS) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "More than %u NTP servers specified, ignoring \"%s\" and any subsequent entries.", + MAX_NTP_SERVERS, w); + return 0; + } + + r = strv_consume(l, TAKE_PTR(w)); + if (r < 0) + return log_oom(); + } +} diff --git a/src/network/networkd-ntp.h b/src/network/networkd-ntp.h new file mode 100644 index 0000000..44e7678 --- /dev/null +++ b/src/network/networkd-ntp.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "conf-parser.h" +#include "networkd-util.h" + +typedef struct Link Link; + +bool link_get_use_ntp(Link *link, NetworkConfigSource proto); + +CONFIG_PARSER_PROTOTYPE(config_parse_ntp); diff --git a/src/network/networkd-queue.c b/src/network/networkd-queue.c index 1128987..98c629f 100644 --- a/src/network/networkd-queue.c +++ b/src/network/networkd-queue.c @@ -9,14 +9,28 @@ #define REPLY_CALLBACK_COUNT_THRESHOLD 128 +static Request* request_detach_impl(Request *req) { + assert(req); + + if (!req->manager) + return NULL; + + ordered_set_remove(req->manager->request_queue, req); + req->manager = NULL; + return req; +} + +void request_detach(Request *req) { + request_unref(request_detach_impl(req)); +} + static Request *request_free(Request *req) { if (!req) return NULL; /* To prevent from triggering assertions in the hash and compare functions, remove this request * from the set before freeing userdata below. */ - if (req->manager) - ordered_set_remove(req->manager->request_queue, req); + request_detach_impl(req); if (req->free_func) req->free_func(req->userdata); @@ -31,26 +45,10 @@ static Request *request_free(Request *req) { DEFINE_TRIVIAL_REF_UNREF_FUNC(Request, request, request_free); -void request_detach(Manager *manager, Request *req) { - assert(manager); - - if (!req) - return; - - req = ordered_set_remove(manager->request_queue, req); - if (!req) - return; - - req->manager = NULL; - request_unref(req); -} - static void request_destroy_callback(Request *req) { assert(req); - if (req->manager) - request_detach(req->manager, req); - + request_detach(req); request_unref(req); } @@ -58,14 +56,16 @@ static void request_hash_func(const Request *req, struct siphash *state) { assert(req); assert(state); - siphash24_compress_boolean(req->link, state); - if (req->link) - siphash24_compress(&req->link->ifindex, sizeof(req->link->ifindex), state); + siphash24_compress_typesafe(req->type, state); - siphash24_compress(&req->type, sizeof(req->type), state); + if (!IN_SET(req->type, REQUEST_TYPE_NEXTHOP, REQUEST_TYPE_ROUTE)) { + siphash24_compress_boolean(req->link, state); + if (req->link) + siphash24_compress_typesafe(req->link->ifindex, state); + } - siphash24_compress(&req->hash_func, sizeof(req->hash_func), state); - siphash24_compress(&req->compare_func, sizeof(req->compare_func), state); + siphash24_compress_typesafe(req->hash_func, state); + siphash24_compress_typesafe(req->compare_func, state); if (req->hash_func) req->hash_func(req->userdata, state); @@ -77,19 +77,21 @@ static int request_compare_func(const struct Request *a, const struct Request *b assert(a); assert(b); - r = CMP(!!a->link, !!b->link); + r = CMP(a->type, b->type); if (r != 0) return r; - if (a->link) { - r = CMP(a->link->ifindex, b->link->ifindex); + if (!IN_SET(a->type, REQUEST_TYPE_NEXTHOP, REQUEST_TYPE_ROUTE)) { + r = CMP(!!a->link, !!b->link); if (r != 0) return r; - } - r = CMP(a->type, b->type); - if (r != 0) - return r; + if (a->link) { + r = CMP(a->link->ifindex, b->link->ifindex); + if (r != 0) + return r; + } + } r = CMP(PTR_TO_UINT64(a->hash_func), PTR_TO_UINT64(b->hash_func)); if (r != 0) @@ -110,7 +112,7 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( Request, request_hash_func, request_compare_func, - request_unref); + request_detach); static int request_new( Manager *manager, @@ -164,6 +166,10 @@ static int request_new( if (req->counter) (*req->counter)++; + /* If this is called in the ORDERED_SET_FOREACH() loop of manager_process_requests(), we need to + * exit from the loop, due to the limitation of the iteration on OrderedSet. */ + manager->request_queued = true; + if (ret) *ret = req; @@ -210,52 +216,70 @@ int link_queue_request_full( process, counter, netlink_handler, ret); } -int manager_process_requests(sd_event_source *s, void *userdata) { - Manager *manager = ASSERT_PTR(userdata); - int r; +int link_requeue_request(Link *link, Request *req, void *userdata, Request **ret) { + assert(link); + assert(req); - for (;;) { - bool processed = false; - Request *req; + return link_queue_request_full( + link, + req->type, + userdata, + req->free_func, + req->hash_func, + req->compare_func, + req->process, + req->counter, + req->netlink_handler, + ret); +} - ORDERED_SET_FOREACH(req, manager->request_queue) { - _cleanup_(link_unrefp) Link *link = link_ref(req->link); +int manager_process_requests(Manager *manager) { + Request *req; + int r; - assert(req->process); + assert(manager); - if (req->waiting_reply) - continue; /* Waiting for netlink reply. */ + /* Process only when no remove request is queued. */ + if (!ordered_set_isempty(manager->remove_request_queue)) + return 0; - /* Typically, requests send netlink message asynchronously. If there are many requests - * queued, then this event may make reply callback queue in sd-netlink full. */ - if (netlink_get_reply_callback_count(manager->rtnl) >= REPLY_CALLBACK_COUNT_THRESHOLD || - netlink_get_reply_callback_count(manager->genl) >= REPLY_CALLBACK_COUNT_THRESHOLD || - fw_ctx_get_reply_callback_count(manager->fw_ctx) >= REPLY_CALLBACK_COUNT_THRESHOLD) - return 0; + manager->request_queued = false; - r = req->process(req, link, req->userdata); - if (r == 0) - continue; + ORDERED_SET_FOREACH(req, manager->request_queue) { + if (req->waiting_reply) + continue; /* Already processed, and waiting for netlink reply. */ - processed = true; + /* Typically, requests send netlink message asynchronously. If there are many requests + * queued, then this event may make reply callback queue in sd-netlink full. */ + if (netlink_get_reply_callback_count(manager->rtnl) >= REPLY_CALLBACK_COUNT_THRESHOLD || + netlink_get_reply_callback_count(manager->genl) >= REPLY_CALLBACK_COUNT_THRESHOLD || + fw_ctx_get_reply_callback_count(manager->fw_ctx) >= REPLY_CALLBACK_COUNT_THRESHOLD) + break; - /* If the request sends netlink message, e.g. for Address or so, the Request object - * is referenced by the netlink slot, and will be detached later by its destroy callback. - * Otherwise, e.g. for DHCP client or so, detach the request from queue now. */ - if (!req->waiting_reply) - request_detach(manager, req); + /* Avoid the request and link freed by req->process() and request_detach(). */ + _unused_ _cleanup_(request_unrefp) Request *req_unref = request_ref(req); + _cleanup_(link_unrefp) Link *link = link_ref(req->link); - if (r < 0 && link) { + assert(req->process); + r = req->process(req, link, req->userdata); + if (r < 0) { + request_detach(req); + + if (link) { link_enter_failed(link); - /* link_enter_failed() may remove multiple requests, - * hence we need to exit from the loop. */ + /* link_enter_failed() may detach multiple requests from the queue. + * Hence, we need to exit from the loop. */ break; } } + if (r > 0 && !req->waiting_reply) + /* If the request sends netlink message, e.g. for Address or so, the Request object is + * referenced by the netlink slot, and will be detached later by its destroy callback. + * Otherwise, e.g. for DHCP client or so, detach the request from queue now. */ + request_detach(req); - /* When at least one request is processed, then another request may be ready now. */ - if (!processed) - break; + if (manager->request_queued) + break; /* New request is queued. Exit from the loop. */ } return 0; @@ -316,7 +340,8 @@ static const char *const request_type_table[_REQUEST_TYPE_MAX] = { [REQUEST_TYPE_SET_LINK_ADDRESS_GENERATION_MODE] = "IPv6LL address generation mode", [REQUEST_TYPE_SET_LINK_BOND] = "bond configurations", [REQUEST_TYPE_SET_LINK_BRIDGE] = "bridge configurations", - [REQUEST_TYPE_SET_LINK_BRIDGE_VLAN] = "bridge VLAN configurations", + [REQUEST_TYPE_SET_LINK_BRIDGE_VLAN] = "bridge VLAN configurations (step 1)", + [REQUEST_TYPE_DEL_LINK_BRIDGE_VLAN] = "bridge VLAN configurations (step 2)", [REQUEST_TYPE_SET_LINK_CAN] = "CAN interface configurations", [REQUEST_TYPE_SET_LINK_FLAGS] = "link flags", [REQUEST_TYPE_SET_LINK_GROUP] = "interface group", @@ -331,3 +356,110 @@ static const char *const request_type_table[_REQUEST_TYPE_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP_TO_STRING(request_type, RequestType); + +static RemoveRequest* remove_request_free(RemoveRequest *req) { + if (!req) + return NULL; + + if (req->manager) + ordered_set_remove(req->manager->remove_request_queue, req); + + if (req->unref_func) + req->unref_func(req->userdata); + + link_unref(req->link); + sd_netlink_unref(req->netlink); + sd_netlink_message_unref(req->message); + + return mfree(req); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(RemoveRequest*, remove_request_free); +DEFINE_TRIVIAL_DESTRUCTOR(remove_request_destroy_callback, RemoveRequest, remove_request_free); +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + remove_request_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + remove_request_free); + +int remove_request_add( + Manager *manager, + Link *link, + void *userdata, + mfree_func_t unref_func, + sd_netlink *netlink, + sd_netlink_message *message, + remove_request_netlink_handler_t netlink_handler) { + + _cleanup_(remove_request_freep) RemoveRequest *req = NULL; + int r; + + assert(manager); + assert(userdata); + assert(netlink); + assert(message); + + req = new(RemoveRequest, 1); + if (!req) + return -ENOMEM; + + *req = (RemoveRequest) { + .link = link_ref(link), /* link may be NULL, but link_ref() handles it gracefully. */ + .userdata = userdata, + .netlink = sd_netlink_ref(netlink), + .message = sd_netlink_message_ref(message), + .netlink_handler = netlink_handler, + }; + + r = ordered_set_ensure_put(&manager->remove_request_queue, &remove_request_hash_ops, req); + if (r < 0) + return r; + assert(r > 0); + + req->manager = manager; + req->unref_func = unref_func; + + TAKE_PTR(req); + return 0; +} + +int manager_process_remove_requests(Manager *manager) { + RemoveRequest *req; + int r; + + assert(manager); + + while ((req = ordered_set_first(manager->remove_request_queue))) { + + /* Do not make the reply callback queue in sd-netlink full. */ + if (netlink_get_reply_callback_count(req->netlink) >= REPLY_CALLBACK_COUNT_THRESHOLD) + return 0; + + r = netlink_call_async( + req->netlink, NULL, req->message, + req->netlink_handler, + remove_request_destroy_callback, + req); + if (r < 0) { + _cleanup_(link_unrefp) Link *link = link_ref(req->link); + + log_link_warning_errno(link, r, "Failed to call netlink message: %m"); + + /* First free the request. */ + remove_request_free(req); + + /* Then, make the link enter the failed state. */ + if (link) + link_enter_failed(link); + + } else { + /* On success, netlink needs to be unref()ed. Otherwise, the netlink and remove + * request may not freed on shutting down. */ + req->netlink = sd_netlink_unref(req->netlink); + ordered_set_remove(manager->remove_request_queue, req); + } + } + + return 0; +} diff --git a/src/network/networkd-queue.h b/src/network/networkd-queue.h index e58d1be..e35cd73 100644 --- a/src/network/networkd-queue.h +++ b/src/network/networkd-queue.h @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "sd-event.h" #include "sd-netlink.h" #include "alloc-util.h" @@ -37,6 +36,7 @@ typedef enum RequestType { REQUEST_TYPE_SET_LINK_BOND, /* Setting bond configs. */ REQUEST_TYPE_SET_LINK_BRIDGE, /* Setting bridge configs. */ REQUEST_TYPE_SET_LINK_BRIDGE_VLAN, /* Setting bridge VLAN configs. */ + REQUEST_TYPE_DEL_LINK_BRIDGE_VLAN, /* Removing bridge VLAN configs. */ REQUEST_TYPE_SET_LINK_CAN, /* Setting CAN interface configs. */ REQUEST_TYPE_SET_LINK_FLAGS, /* Setting IFF_NOARP or friends. */ REQUEST_TYPE_SET_LINK_GROUP, /* Setting interface group. */ @@ -88,7 +88,7 @@ Request *request_ref(Request *req); Request *request_unref(Request *req); DEFINE_TRIVIAL_CLEANUP_FUNC(Request*, request_unref); -void request_detach(Manager *manager, Request *req); +void request_detach(Request *req); int netdev_queue_request( NetDev *netdev, @@ -107,6 +107,8 @@ int link_queue_request_full( request_netlink_handler_t netlink_handler, Request **ret); +int link_requeue_request(Link *link, Request *req, void *userdata, Request **ret); + static inline int link_queue_request( Link *link, RequestType type, @@ -135,7 +137,56 @@ static inline int link_queue_request( ret); \ }) -int manager_process_requests(sd_event_source *s, void *userdata); +int manager_process_requests(Manager *manager); int request_call_netlink_async(sd_netlink *nl, sd_netlink_message *m, Request *req); const char* request_type_to_string(RequestType t) _const_; + +typedef struct RemoveRequest RemoveRequest; +typedef int (*remove_request_netlink_handler_t)(sd_netlink *nl, sd_netlink_message *m, RemoveRequest *req); + +struct RemoveRequest { + Manager *manager; + Link *link; + void *userdata; /* e.g. Address */ + mfree_func_t unref_func; /* e.g. address_unref() */ + sd_netlink *netlink; + sd_netlink_message *message; + remove_request_netlink_handler_t netlink_handler; +}; + +int remove_request_add( + Manager *manager, + Link *link, + void *userdata, /* This is unref()ed when the call failed. */ + mfree_func_t unref_func, + sd_netlink *netlink, + sd_netlink_message *message, + remove_request_netlink_handler_t netlink_handler); + +#define _remove_request_add(manager, link, data, name, nl, m, handler) \ + ({ \ + typeof(*data) *_data = (data); \ + int _r; \ + \ + _r = remove_request_add(manager, link, _data, \ + (mfree_func_t) name##_unref, \ + nl, m, handler); \ + if (_r >= 0) \ + name##_ref(_data); \ + _r; \ + }) + + +#define link_remove_request_add(link, data, name, nl, m, handler) \ + ({ \ + Link *_link = (link); \ + \ + _remove_request_add(_link->manager, _link, data, name, \ + nl, m, handler); \ + }) + +#define manager_remove_request_add(manager, data, name, nl, m, handler) \ + _remove_request_add(manager, NULL, data, name, nl, m, handler) + +int manager_process_remove_requests(Manager *manager); diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c index fc36a00..652e784 100644 --- a/src/network/networkd-radv.c +++ b/src/network/networkd-radv.c @@ -7,6 +7,7 @@ #include <arpa/inet.h> #include "dns-domain.h" +#include "ndisc-router-internal.h" #include "networkd-address-generation.h" #include "networkd-address.h" #include "networkd-dhcp-prefix-delegation.h" @@ -22,36 +23,6 @@ #include "string-table.h" #include "strv.h" -void network_adjust_radv(Network *network) { - assert(network); - - /* After this function is called, network->router_prefix_delegation can be treated as a boolean. */ - - if (network->dhcp_pd < 0) - /* For backward compatibility. */ - network->dhcp_pd = FLAGS_SET(network->router_prefix_delegation, RADV_PREFIX_DELEGATION_DHCP6); - - if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) { - if (network->router_prefix_delegation != RADV_PREFIX_DELEGATION_NONE) - log_warning("%s: IPv6PrefixDelegation= is enabled but IPv6 link-local addressing is disabled. " - "Disabling IPv6PrefixDelegation=.", network->filename); - - network->router_prefix_delegation = RADV_PREFIX_DELEGATION_NONE; - } - - if (network->router_prefix_delegation == RADV_PREFIX_DELEGATION_NONE) { - network->n_router_dns = 0; - network->router_dns = mfree(network->router_dns); - network->router_search_domains = ordered_set_free(network->router_search_domains); - } - - if (!FLAGS_SET(network->router_prefix_delegation, RADV_PREFIX_DELEGATION_STATIC)) { - network->prefixes_by_section = hashmap_free_with_destructor(network->prefixes_by_section, prefix_free); - network->route_prefixes_by_section = hashmap_free_with_destructor(network->route_prefixes_by_section, route_prefix_free); - network->pref64_prefixes_by_section = hashmap_free_with_destructor(network->pref64_prefixes_by_section, pref64_prefix_free); - } -} - bool link_radv_enabled(Link *link) { assert(link); @@ -64,7 +35,7 @@ bool link_radv_enabled(Link *link) { return link->network->router_prefix_delegation; } -Prefix *prefix_free(Prefix *prefix) { +Prefix* prefix_free(Prefix *prefix) { if (!prefix) return NULL; @@ -109,10 +80,11 @@ static int prefix_new_static(Network *network, const char *filename, unsigned se .network = network, .section = TAKE_PTR(n), - .preferred_lifetime = RADV_DEFAULT_PREFERRED_LIFETIME_USEC, - .valid_lifetime = RADV_DEFAULT_VALID_LIFETIME_USEC, - .onlink = true, - .address_auto_configuration = true, + .prefix.flags = ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO, + .prefix.valid_lifetime = RADV_DEFAULT_VALID_LIFETIME_USEC, + .prefix.preferred_lifetime = RADV_DEFAULT_PREFERRED_LIFETIME_USEC, + .prefix.valid_until = USEC_INFINITY, + .prefix.preferred_until = USEC_INFINITY, }; r = hashmap_ensure_put(&network->prefixes_by_section, &config_section_hash_ops, prefix->section, prefix); @@ -123,7 +95,7 @@ static int prefix_new_static(Network *network, const char *filename, unsigned se return 0; } -RoutePrefix *route_prefix_free(RoutePrefix *prefix) { +RoutePrefix* route_prefix_free(RoutePrefix *prefix) { if (!prefix) return NULL; @@ -167,7 +139,8 @@ static int route_prefix_new_static(Network *network, const char *filename, unsig .network = network, .section = TAKE_PTR(n), - .lifetime = RADV_DEFAULT_VALID_LIFETIME_USEC, + .route.lifetime = RADV_DEFAULT_VALID_LIFETIME_USEC, + .route.valid_until = USEC_INFINITY, }; r = hashmap_ensure_put(&network->route_prefixes_by_section, &config_section_hash_ops, prefix->section, prefix); @@ -178,7 +151,7 @@ static int route_prefix_new_static(Network *network, const char *filename, unsig return 0; } -pref64Prefix *pref64_prefix_free(pref64Prefix *prefix) { +Prefix64* prefix64_free(Prefix64 *prefix) { if (!prefix) return NULL; @@ -192,11 +165,11 @@ pref64Prefix *pref64_prefix_free(pref64Prefix *prefix) { return mfree(prefix); } -DEFINE_SECTION_CLEANUP_FUNCTIONS(pref64Prefix, pref64_prefix_free); +DEFINE_SECTION_CLEANUP_FUNCTIONS(Prefix64, prefix64_free); -static int pref64_prefix_new_static(Network *network, const char *filename, unsigned section_line, pref64Prefix **ret) { +static int prefix64_new_static(Network *network, const char *filename, unsigned section_line, Prefix64 **ret) { _cleanup_(config_section_freep) ConfigSection *n = NULL; - _cleanup_(pref64_prefix_freep) pref64Prefix *prefix = NULL; + _cleanup_(prefix64_freep) Prefix64 *prefix = NULL; int r; assert(network); @@ -214,15 +187,16 @@ static int pref64_prefix_new_static(Network *network, const char *filename, unsi return 0; } - prefix = new(pref64Prefix, 1); + prefix = new(Prefix64, 1); if (!prefix) return -ENOMEM; - *prefix = (pref64Prefix) { + *prefix = (Prefix64) { .network = network, .section = TAKE_PTR(n), - .lifetime = RADV_PREF64_DEFAULT_LIFETIME_USEC, + .prefix64.lifetime = RADV_PREF64_DEFAULT_LIFETIME_USEC, + .prefix64.valid_until = USEC_INFINITY, }; r = hashmap_ensure_put(&network->pref64_prefixes_by_section, &config_section_hash_ops, prefix->section, prefix); @@ -243,22 +217,22 @@ int link_request_radv_addresses(Link *link) { return 0; HASHMAP_FOREACH(p, link->network->prefixes_by_section) { - _cleanup_set_free_ Set *addresses = NULL; - struct in6_addr *a; - if (!p->assign) continue; /* radv_generate_addresses() below requires the prefix length <= 64. */ - if (p->prefixlen > 64) + if (p->prefix.prefixlen > 64) continue; - r = radv_generate_addresses(link, p->tokens, &p->prefix, p->prefixlen, &addresses); + _cleanup_hashmap_free_ Hashmap *tokens_by_address = NULL; + r = radv_generate_addresses(link, p->tokens, &p->prefix.address, p->prefix.prefixlen, &tokens_by_address); if (r < 0) return r; - SET_FOREACH(a, addresses) { - _cleanup_(address_freep) Address *address = NULL; + IPv6Token *token; + struct in6_addr *a; + HASHMAP_FOREACH_KEY(token, a, tokens_by_address) { + _cleanup_(address_unrefp) Address *address = NULL; r = address_new(&address); if (r < 0) @@ -267,8 +241,9 @@ int link_request_radv_addresses(Link *link) { address->source = NETWORK_CONFIG_SOURCE_STATIC; address->family = AF_INET6; address->in_addr.in6 = *a; - address->prefixlen = p->prefixlen; + address->prefixlen = p->prefix.prefixlen; address->route_metric = p->route_metric; + address->token = ipv6_token_ref(token); r = link_request_static_address(link, address); if (r < 0) @@ -279,6 +254,29 @@ int link_request_radv_addresses(Link *link) { return 0; } +int link_reconfigure_radv_address(Address *address, Link *link) { + int r; + + assert(address); + assert(address->source == NETWORK_CONFIG_SOURCE_STATIC); + assert(link); + + r = regenerate_address(address, link); + if (r <= 0) + return r; + + r = link_request_static_address(link, address); + if (r < 0) + return r; + + if (link->static_address_messages != 0) { + link->static_addresses_configured = false; + link_set_state(link, LINK_STATE_CONFIGURING); + } + + return 0; +} + static int radv_set_prefix(Link *link, Prefix *prefix) { _cleanup_(sd_radv_prefix_unrefp) sd_radv_prefix *p = NULL; int r; @@ -291,23 +289,23 @@ static int radv_set_prefix(Link *link, Prefix *prefix) { if (r < 0) return r; - r = sd_radv_prefix_set_prefix(p, &prefix->prefix, prefix->prefixlen); + r = sd_radv_prefix_set_prefix(p, &prefix->prefix.address, prefix->prefix.prefixlen); if (r < 0) return r; - r = sd_radv_prefix_set_preferred_lifetime(p, prefix->preferred_lifetime, USEC_INFINITY); + r = sd_radv_prefix_set_preferred_lifetime(p, prefix->prefix.preferred_lifetime, prefix->prefix.preferred_until); if (r < 0) return r; - r = sd_radv_prefix_set_valid_lifetime(p, prefix->valid_lifetime, USEC_INFINITY); + r = sd_radv_prefix_set_valid_lifetime(p, prefix->prefix.valid_lifetime, prefix->prefix.valid_until); if (r < 0) return r; - r = sd_radv_prefix_set_onlink(p, prefix->onlink); + r = sd_radv_prefix_set_onlink(p, FLAGS_SET(prefix->prefix.flags, ND_OPT_PI_FLAG_ONLINK)); if (r < 0) return r; - r = sd_radv_prefix_set_address_autoconfiguration(p, prefix->address_auto_configuration); + r = sd_radv_prefix_set_address_autoconfiguration(p, FLAGS_SET(prefix->prefix.flags, ND_OPT_PI_FLAG_AUTO)); if (r < 0) return r; @@ -326,18 +324,18 @@ static int radv_set_route_prefix(Link *link, RoutePrefix *prefix) { if (r < 0) return r; - r = sd_radv_route_prefix_set_prefix(p, &prefix->prefix, prefix->prefixlen); + r = sd_radv_route_prefix_set_prefix(p, &prefix->route.address, prefix->route.prefixlen); if (r < 0) return r; - r = sd_radv_route_prefix_set_lifetime(p, prefix->lifetime, USEC_INFINITY); + r = sd_radv_route_prefix_set_lifetime(p, prefix->route.lifetime, prefix->route.valid_until); if (r < 0) return r; return sd_radv_add_route_prefix(link->radv, p); } -static int radv_set_pref64_prefix(Link *link, pref64Prefix *prefix) { +static int radv_set_pref64_prefix(Link *link, Prefix64 *prefix) { _cleanup_(sd_radv_pref64_prefix_unrefp) sd_radv_pref64_prefix *p = NULL; int r; @@ -349,7 +347,7 @@ static int radv_set_pref64_prefix(Link *link, pref64Prefix *prefix) { if (r < 0) return r; - r = sd_radv_pref64_prefix_set_prefix(p, &prefix->prefix, prefix->prefixlen, prefix->lifetime); + r = sd_radv_pref64_prefix_set_prefix(p, &prefix->prefix64.prefix, prefix->prefix64.prefixlen, prefix->prefix64.lifetime); if (r < 0) return r; @@ -502,9 +500,6 @@ static int radv_find_uplink(Link *link, Link **ret) { static int radv_configure(Link *link) { Link *uplink = NULL; - RoutePrefix *q; - pref64Prefix *n; - Prefix *p; int r; assert(link); @@ -547,30 +542,33 @@ static int radv_configure(Link *link) { 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; - } + r = sd_radv_set_preference(link->radv, link->network->router_preference); + if (r < 0) + return r; - if (link->network->router_retransmit_usec > 0) { - r = sd_radv_set_retransmit(link->radv, link->network->router_retransmit_usec); - if (r < 0) - return r; - } + r = sd_radv_set_reachable_time(link->radv, link->network->router_reachable_usec); + if (r < 0) + return r; + r = sd_radv_set_retransmit(link->radv, link->network->router_retransmit_usec); + if (r < 0) + return r; + + Prefix *p; HASHMAP_FOREACH(p, link->network->prefixes_by_section) { r = radv_set_prefix(link, p); if (r < 0 && r != -EEXIST) return r; } + RoutePrefix *q; HASHMAP_FOREACH(q, link->network->route_prefixes_by_section) { r = radv_set_route_prefix(link, q); if (r < 0 && r != -EEXIST) return r; } + Prefix64 *n; HASHMAP_FOREACH(n, link->network->pref64_prefixes_by_section) { r = radv_set_pref64_prefix(link, n); if (r < 0 && r != -EEXIST) @@ -603,9 +601,6 @@ static int radv_configure(Link *link) { } int radv_update_mac(Link *link) { - bool restart; - int r; - assert(link); if (!link->radv) @@ -614,23 +609,7 @@ int radv_update_mac(Link *link) { if (link->hw_addr.length != ETH_ALEN) return 0; - restart = sd_radv_is_running(link->radv); - - r = sd_radv_stop(link->radv); - if (r < 0) - return r; - - r = sd_radv_set_mac(link->radv, &link->hw_addr.ether); - if (r < 0) - return r; - - if (restart) { - r = sd_radv_start(link->radv); - if (r < 0) - return r; - } - - return 0; + return sd_radv_set_mac(link->radv, &link->hw_addr.ether); } static int radv_is_ready_to_configure(Link *link) { @@ -745,6 +724,10 @@ int radv_start(Link *link) { return log_link_debug_errno(link, r, "Failed to request DHCP delegated subnet prefix: %m"); } + r = sd_radv_set_link_local_address(link->radv, &link->ipv6ll_address); + if (r < 0) + return r; + log_link_debug(link, "Starting IPv6 Router Advertisements"); return sd_radv_start(link->radv); } @@ -781,9 +764,18 @@ int radv_add_prefix( return r; r = sd_radv_add_prefix(link->radv, p); - if (r < 0 && r != -EEXIST) + if (r == -EEXIST) + return 0; + if (r < 0) return r; + if (sd_radv_is_running(link->radv)) { + /* Announce updated prefixe now. */ + r = sd_radv_send(link->radv); + if (r < 0) + return r; + } + return 0; } @@ -793,94 +785,112 @@ static int prefix_section_verify(Prefix *p) { if (section_is_invalid(p->section)) return -EINVAL; - if (in6_addr_is_null(&p->prefix)) + if (in6_addr_is_null(&p->prefix.address)) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "%s: [IPv6Prefix] section without Prefix= field configured, " "or specified prefix is the null address. " "Ignoring [IPv6Prefix] section from line %u.", p->section->filename, p->section->line); - if (p->prefixlen < 3 || p->prefixlen > 128) + if (p->prefix.prefixlen < 3 || p->prefix.prefixlen > 128) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "%s: Invalid prefix length %u is specified in [IPv6Prefix] section. " "Valid range is 3…128. Ignoring [IPv6Prefix] section from line %u.", - p->section->filename, p->prefixlen, p->section->line); + p->section->filename, p->prefix.prefixlen, p->section->line); - if (p->prefixlen > 64) { + if (p->prefix.prefixlen > 64) { log_info("%s:%u: Unusual prefix length %u (> 64) is specified in [IPv6Prefix] section from line %s%s.", p->section->filename, p->section->line, - p->prefixlen, + p->prefix.prefixlen, p->assign ? ", refusing to assign an address in " : "", - p->assign ? IN6_ADDR_PREFIX_TO_STRING(&p->prefix, p->prefixlen) : ""); + p->assign ? IN6_ADDR_PREFIX_TO_STRING(&p->prefix.address, p->prefix.prefixlen) : ""); p->assign = false; } - if (p->valid_lifetime == 0) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: The valid lifetime of prefix cannot be zero. " - "Ignoring [IPv6Prefix] section from line %u.", - p->section->filename, p->section->line); - - if (p->preferred_lifetime > p->valid_lifetime) + if (p->prefix.preferred_lifetime > p->prefix.valid_lifetime) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "%s: The preferred lifetime %s is longer than the valid lifetime %s. " "Ignoring [IPv6Prefix] section from line %u.", p->section->filename, - FORMAT_TIMESPAN(p->preferred_lifetime, USEC_PER_SEC), - FORMAT_TIMESPAN(p->valid_lifetime, USEC_PER_SEC), + FORMAT_TIMESPAN(p->prefix.preferred_lifetime, USEC_PER_SEC), + FORMAT_TIMESPAN(p->prefix.valid_lifetime, USEC_PER_SEC), p->section->line); return 0; } -void network_drop_invalid_prefixes(Network *network) { - Prefix *p; - - assert(network); - - HASHMAP_FOREACH(p, network->prefixes_by_section) - if (prefix_section_verify(p) < 0) - prefix_free(p); -} - static int route_prefix_section_verify(RoutePrefix *p) { if (section_is_invalid(p->section)) return -EINVAL; - if (p->prefixlen > 128) + if (p->route.prefixlen > 128) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "%s: Invalid prefix length %u is specified in [IPv6RoutePrefix] section. " "Valid range is 0…128. Ignoring [IPv6RoutePrefix] section from line %u.", - p->section->filename, p->prefixlen, p->section->line); - - if (p->lifetime == 0) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: The lifetime of route cannot be zero. " - "Ignoring [IPv6RoutePrefix] section from line %u.", - p->section->filename, p->section->line); + p->section->filename, p->route.prefixlen, p->section->line); return 0; } -void network_drop_invalid_route_prefixes(Network *network) { - RoutePrefix *p; - +void network_adjust_radv(Network *network) { assert(network); - HASHMAP_FOREACH(p, network->route_prefixes_by_section) - if (route_prefix_section_verify(p) < 0) - route_prefix_free(p); -} + /* After this function is called, network->router_prefix_delegation can be treated as a boolean. */ -void network_drop_invalid_pref64_prefixes(Network *network) { - pref64Prefix *p; + if (network->dhcp_pd < 0) + /* For backward compatibility. */ + network->dhcp_pd = FLAGS_SET(network->router_prefix_delegation, RADV_PREFIX_DELEGATION_DHCP6); - assert(network); + if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) { + if (network->router_prefix_delegation != RADV_PREFIX_DELEGATION_NONE) + log_warning("%s: IPv6PrefixDelegation= is enabled but IPv6 link-local addressing is disabled. " + "Disabling IPv6PrefixDelegation=.", network->filename); + + network->router_prefix_delegation = RADV_PREFIX_DELEGATION_NONE; + } - HASHMAP_FOREACH(p, network->pref64_prefixes_by_section) - if (section_is_invalid(p->section)) - pref64_prefix_free(p); + if (network->router_prefix_delegation == RADV_PREFIX_DELEGATION_NONE) { + network->n_router_dns = 0; + network->router_dns = mfree(network->router_dns); + network->router_search_domains = ordered_set_free(network->router_search_domains); + } + + if (!FLAGS_SET(network->router_prefix_delegation, RADV_PREFIX_DELEGATION_STATIC)) { + network->prefixes_by_section = hashmap_free_with_destructor(network->prefixes_by_section, prefix_free); + network->route_prefixes_by_section = hashmap_free_with_destructor(network->route_prefixes_by_section, route_prefix_free); + network->pref64_prefixes_by_section = hashmap_free_with_destructor(network->pref64_prefixes_by_section, prefix64_free); + } + + if (!network->router_prefix_delegation) + return; + + /* Below, let's verify router settings, if enabled. */ + + if (network->router_lifetime_usec == 0 && network->router_preference != SD_NDISC_PREFERENCE_MEDIUM) + /* RFC 4191, Section 2.2, + * If the Router Lifetime is zero, the preference value MUST be set to (00) by the sender. + * + * Note, radv_send_router() gracefully handle that. So, it is not necessary to refuse, but + * let's warn about that. */ + log_notice("%s: RouterPreference=%s specified with RouterLifetimeSec=0, ignoring RouterPreference= setting.", + network->filename, ndisc_router_preference_to_string(network->router_preference)); + + Prefix *prefix; + HASHMAP_FOREACH(prefix, network->prefixes_by_section) + if (prefix_section_verify(prefix) < 0) + prefix_free(prefix); + + + RoutePrefix *route; + HASHMAP_FOREACH(route, network->route_prefixes_by_section) + if (route_prefix_section_verify(route) < 0) + route_prefix_free(route); + + Prefix64 *pref64; + HASHMAP_FOREACH(pref64, network->pref64_prefixes_by_section) + if (section_is_invalid(pref64->section)) + prefix64_free(pref64); } int config_parse_prefix( @@ -909,15 +919,15 @@ int config_parse_prefix( if (r < 0) return log_oom(); - r = in_addr_prefix_from_string(rvalue, AF_INET6, &a, &p->prefixlen); + r = in_addr_prefix_from_string(rvalue, AF_INET6, &a, &p->prefix.prefixlen); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Prefix is invalid, ignoring assignment: %s", rvalue); return 0; } - (void) in6_addr_mask(&a.in6, p->prefixlen); - p->prefix = a.in6; + (void) in6_addr_mask(&a.in6, p->prefix.prefixlen); + p->prefix.address = a.in6; TAKE_PTR(p); return 0; @@ -955,14 +965,12 @@ int config_parse_prefix_boolean( return 0; } - if (streq(lvalue, "OnLink")) - p->onlink = r; - else if (streq(lvalue, "AddressAutoconfiguration")) - p->address_auto_configuration = r; - else if (streq(lvalue, "Assign")) + if (ltype != 0) + SET_FLAG(p->prefix.flags, ltype, r); + else { + assert(streq(lvalue, "Assign")); p->assign = r; - else - assert_not_reached(); + } TAKE_PTR(p); return 0; @@ -1008,9 +1016,9 @@ int config_parse_prefix_lifetime( } if (streq(lvalue, "PreferredLifetimeSec")) - p->preferred_lifetime = usec; + p->prefix.preferred_lifetime = usec; else if (streq(lvalue, "ValidLifetimeSec")) - p->valid_lifetime = usec; + p->prefix.valid_lifetime = usec; else assert_not_reached(); @@ -1115,15 +1123,15 @@ int config_parse_route_prefix( if (r < 0) return log_oom(); - r = in_addr_prefix_from_string(rvalue, AF_INET6, &a, &p->prefixlen); + r = in_addr_prefix_from_string(rvalue, AF_INET6, &a, &p->route.prefixlen); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Route prefix is invalid, ignoring assignment: %s", rvalue); return 0; } - (void) in6_addr_mask(&a.in6, p->prefixlen); - p->prefix = a.in6; + (void) in6_addr_mask(&a.in6, p->route.prefixlen); + p->route.address = a.in6; TAKE_PTR(p); return 0; @@ -1168,7 +1176,7 @@ int config_parse_route_prefix_lifetime( return 0; } - p->lifetime = usec; + p->route.lifetime = usec; TAKE_PTR(p); return 0; @@ -1186,7 +1194,7 @@ int config_parse_pref64_prefix( void *data, void *userdata) { - _cleanup_(pref64_prefix_free_or_set_invalidp) pref64Prefix *p = NULL; + _cleanup_(prefix64_free_or_set_invalidp) Prefix64 *p = NULL; Network *network = ASSERT_PTR(userdata); union in_addr_union a; uint8_t prefixlen; @@ -1197,7 +1205,7 @@ int config_parse_pref64_prefix( assert(lvalue); assert(rvalue); - r = pref64_prefix_new_static(network, filename, section_line, &p); + r = prefix64_new_static(network, filename, section_line, &p); if (r < 0) return log_oom(); @@ -1214,9 +1222,9 @@ int config_parse_pref64_prefix( return 0; } - (void) in6_addr_mask(&a.in6,prefixlen); - p->prefix = a.in6; - p->prefixlen = prefixlen; + (void) in6_addr_mask(&a.in6, prefixlen); + p->prefix64.prefix = a.in6; + p->prefix64.prefixlen = prefixlen; TAKE_PTR(p); return 0; @@ -1234,7 +1242,7 @@ int config_parse_pref64_prefix_lifetime( void *data, void *userdata) { - _cleanup_(pref64_prefix_free_or_set_invalidp) pref64Prefix *p = NULL; + _cleanup_(prefix64_free_or_set_invalidp) Prefix64 *p = NULL; Network *network = ASSERT_PTR(userdata); usec_t usec; int r; @@ -1244,7 +1252,7 @@ int config_parse_pref64_prefix_lifetime( assert(lvalue); assert(rvalue); - r = pref64_prefix_new_static(network, filename, section_line, &p); + r = prefix64_new_static(network, filename, section_line, &p); if (r < 0) return log_oom(); @@ -1261,7 +1269,7 @@ int config_parse_pref64_prefix_lifetime( return 0; } - p->lifetime = usec; + p->prefix64.lifetime = usec; TAKE_PTR(p); return 0; @@ -1499,7 +1507,7 @@ int config_parse_router_lifetime( return 0; } -int config_parse_router_retransmit( +int config_parse_router_uint32_msec_usec( const char *unit, const char *filename, unsigned line, @@ -1511,7 +1519,7 @@ int config_parse_router_retransmit( void *data, void *userdata) { - usec_t usec, *router_retransmit_usec = ASSERT_PTR(data); + usec_t usec, *router_usec = ASSERT_PTR(data); int r; assert(filename); @@ -1520,7 +1528,7 @@ int config_parse_router_retransmit( assert(rvalue); if (isempty(rvalue)) { - *router_retransmit_usec = 0; + *router_usec = 0; return 0; } @@ -1532,13 +1540,13 @@ int config_parse_router_retransmit( } if (usec != USEC_INFINITY && - usec > RADV_MAX_RETRANSMIT_USEC) { + usec > RADV_MAX_UINT32_MSEC_USEC) { log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid [%s] %s=, ignoring assignment: %s", section, lvalue, rvalue); return 0; } - *router_retransmit_usec = usec; + *router_usec = usec; return 0; } diff --git a/src/network/networkd-radv.h b/src/network/networkd-radv.h index 48677b5..ccdd656 100644 --- a/src/network/networkd-radv.h +++ b/src/network/networkd-radv.h @@ -12,6 +12,7 @@ #include "in-addr-util.h" #include "conf-parser.h" +#include "ndisc-option.h" #include "networkd-util.h" typedef struct Link Link; @@ -30,13 +31,7 @@ typedef struct Prefix { Network *network; ConfigSection *section; - struct in6_addr prefix; - uint8_t prefixlen; - usec_t preferred_lifetime; - usec_t valid_lifetime; - - bool onlink; - bool address_auto_configuration; + sd_ndisc_prefix prefix; bool assign; uint32_t route_metric; @@ -47,30 +42,24 @@ typedef struct RoutePrefix { Network *network; ConfigSection *section; - struct in6_addr prefix; - uint8_t prefixlen; - usec_t lifetime; + sd_ndisc_route route; } RoutePrefix; -typedef struct pref64Prefix { +typedef struct Prefix64 { Network *network; ConfigSection *section; - struct in6_addr prefix; - uint8_t prefixlen; - usec_t lifetime; -} pref64Prefix; + sd_ndisc_prefix64 prefix64; +} Prefix64; -Prefix *prefix_free(Prefix *prefix); -RoutePrefix *route_prefix_free(RoutePrefix *prefix); -pref64Prefix *pref64_prefix_free(pref64Prefix *prefix); +Prefix* prefix_free(Prefix *prefix); +RoutePrefix* route_prefix_free(RoutePrefix *prefix); +Prefix64* prefix64_free(Prefix64 *prefix); -void network_drop_invalid_prefixes(Network *network); -void network_drop_invalid_route_prefixes(Network *network); -void network_drop_invalid_pref64_prefixes(Network *network); void network_adjust_radv(Network *network); int link_request_radv_addresses(Link *link); +int link_reconfigure_radv_address(Address *address, Link *link); bool link_radv_enabled(Link *link); int radv_start(Link *link); @@ -85,7 +74,7 @@ RADVPrefixDelegation radv_prefix_delegation_from_string(const char *s) _pure_; CONFIG_PARSER_PROTOTYPE(config_parse_router_prefix_delegation); CONFIG_PARSER_PROTOTYPE(config_parse_router_lifetime); -CONFIG_PARSER_PROTOTYPE(config_parse_router_retransmit); +CONFIG_PARSER_PROTOTYPE(config_parse_router_uint32_msec_usec); CONFIG_PARSER_PROTOTYPE(config_parse_router_preference); CONFIG_PARSER_PROTOTYPE(config_parse_prefix); CONFIG_PARSER_PROTOTYPE(config_parse_prefix_boolean); diff --git a/src/network/networkd-route-metric.c b/src/network/networkd-route-metric.c new file mode 100644 index 0000000..31a2bde --- /dev/null +++ b/src/network/networkd-route-metric.c @@ -0,0 +1,483 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "netlink-util.h" +#include "networkd-route.h" +#include "networkd-route-metric.h" +#include "parse-util.h" +#include "string-util.h" + +void route_metric_done(RouteMetric *metric) { + assert(metric); + + free(metric->metrics); + free(metric->metrics_set); + free(metric->tcp_congestion_control_algo); +} + +int route_metric_copy(const RouteMetric *src, RouteMetric *dest) { + assert(src); + assert(dest); + + dest->n_metrics = src->n_metrics; + if (src->n_metrics > 0) { + assert(src->n_metrics != 1); + + dest->metrics = newdup(uint32_t, src->metrics, src->n_metrics); + if (!dest->metrics) + return -ENOMEM; + } else + dest->metrics = NULL; + + dest->n_metrics_set = src->n_metrics_set; + if (src->n_metrics_set > 0) { + assert(src->n_metrics_set != 1); + + dest->metrics_set = newdup(bool, src->metrics_set, src->n_metrics_set); + if (!dest->metrics_set) + return -ENOMEM; + } else + dest->metrics_set = NULL; + + return strdup_to(&dest->tcp_congestion_control_algo, src->tcp_congestion_control_algo); +} + +void route_metric_hash_func(const RouteMetric *metric, struct siphash *state) { + assert(metric); + + siphash24_compress_typesafe(metric->n_metrics, state); + siphash24_compress_safe(metric->metrics, sizeof(uint32_t) * metric->n_metrics, state); + siphash24_compress_string(metric->tcp_congestion_control_algo, state); +} + +int route_metric_compare_func(const RouteMetric *a, const RouteMetric *b) { + int r; + + assert(a); + assert(b); + + r = memcmp_nn(a->metrics, a->n_metrics * sizeof(uint32_t), b->metrics, b->n_metrics * sizeof(uint32_t)); + if (r != 0) + return r; + + return strcmp_ptr(a->tcp_congestion_control_algo, b->tcp_congestion_control_algo); +} + +bool route_metric_can_update(const RouteMetric *a, const RouteMetric *b, bool expiration_by_kernel) { + assert(a); + assert(b); + + /* If the kernel has expiration timer for the route, then only MTU can be updated. */ + + if (!expiration_by_kernel) + return route_metric_compare_func(a, b) == 0; + + if (a->n_metrics != b->n_metrics) + return false; + + for (size_t i = 1; i < a->n_metrics; i++) { + if (i != RTAX_MTU) + continue; + if (a->metrics[i] != b->metrics[i]) + return false; + } + + return streq_ptr(a->tcp_congestion_control_algo, b->tcp_congestion_control_algo); +} + +int route_metric_set_full(RouteMetric *metric, uint16_t attr, uint32_t value, bool force) { + assert(metric); + + if (force) { + if (!GREEDY_REALLOC0(metric->metrics_set, attr + 1)) + return -ENOMEM; + + metric->metrics_set[attr] = true; + metric->n_metrics_set = MAX(metric->n_metrics_set, (size_t) (attr + 1)); + } else { + /* Do not override the values specified in conf parsers. */ + if (metric->n_metrics_set > attr && metric->metrics_set[attr]) + return 0; + } + + if (value != 0) { + if (!GREEDY_REALLOC0(metric->metrics, attr + 1)) + return -ENOMEM; + + metric->metrics[attr] = value; + metric->n_metrics = MAX(metric->n_metrics, (size_t) (attr + 1)); + return 0; + } + + if (metric->n_metrics <= attr) + return 0; + + metric->metrics[attr] = 0; + + for (size_t i = metric->n_metrics; i > 0; i--) + if (metric->metrics[i-1] != 0) { + metric->n_metrics = i; + return 0; + } + + metric->n_metrics = 0; + return 0; +} + +static void route_metric_unset(RouteMetric *metric, uint16_t attr) { + assert(metric); + + if (metric->n_metrics_set > attr) + metric->metrics_set[attr] = false; + + assert_se(route_metric_set_full(metric, attr, 0, /* force = */ false) >= 0); +} + +uint32_t route_metric_get(const RouteMetric *metric, uint16_t attr) { + assert(metric); + + if (metric->n_metrics <= attr) + return 0; + + return metric->metrics[attr]; +} + +int route_metric_set_netlink_message(const RouteMetric *metric, sd_netlink_message *m) { + int r; + + assert(metric); + assert(m); + + if (metric->n_metrics <= 0 && isempty(metric->tcp_congestion_control_algo)) + return 0; + + r = sd_netlink_message_open_container(m, RTA_METRICS); + if (r < 0) + return r; + + for (size_t i = 0; i < metric->n_metrics; i++) { + if (i == RTAX_CC_ALGO) + continue; + + if (metric->metrics[i] == 0) + continue; + + r = sd_netlink_message_append_u32(m, i, metric->metrics[i]); + if (r < 0) + return r; + } + + if (!isempty(metric->tcp_congestion_control_algo)) { + r = sd_netlink_message_append_string(m, RTAX_CC_ALGO, metric->tcp_congestion_control_algo); + if (r < 0) + return r; + } + + r = sd_netlink_message_close_container(m); + if (r < 0) + return r; + + return 0; +} + +int route_metric_read_netlink_message(RouteMetric *metric, sd_netlink_message *m) { + _cleanup_free_ void *data = NULL; + size_t len; + int r; + + assert(metric); + assert(m); + + r = sd_netlink_message_read_data(m, RTA_METRICS, &len, &data); + if (r == -ENODATA) + return 0; + if (r < 0) + return log_warning_errno(r, "rtnl: Could not read RTA_METRICS attribute, ignoring: %m"); + + for (struct rtattr *rta = data; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { + size_t rta_type = RTA_TYPE(rta); + + if (rta_type == RTAX_CC_ALGO) { + char *p = memdup_suffix0(RTA_DATA(rta), RTA_PAYLOAD(rta)); + if (!p) + return log_oom(); + + free_and_replace(metric->tcp_congestion_control_algo, p); + + } else { + if (RTA_PAYLOAD(rta) != sizeof(uint32_t)) + continue; + + r = route_metric_set(metric, rta_type, *(uint32_t*) RTA_DATA(rta)); + if (r < 0) + return log_oom(); + } + } + + return 0; +} + +static int config_parse_route_metric_advmss_impl( + 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) { + + uint32_t *val = ASSERT_PTR(data); + uint64_t u; + int r; + + assert(rvalue); + + r = parse_size(rvalue, 1024, &u); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + if (u == 0 || u > UINT32_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + *val = (uint32_t) u; + return 1; +} + +static int config_parse_route_metric_hop_limit_impl( + 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) { + + uint32_t k, *val = ASSERT_PTR(data); + int r; + + assert(rvalue); + + r = safe_atou32(rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + if (k == 0 || k > 255) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + *val = k; + return 1; +} + +int config_parse_tcp_window( + 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) { + + uint32_t k, *val = ASSERT_PTR(data); + int r; + + assert(rvalue); + + r = safe_atou32(rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + if (k == 0 || k >= 1024) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + *val = k; + return 1; +} + +static int config_parse_route_metric_tcp_rto_impl( + 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) { + + uint32_t *val = ASSERT_PTR(data); + usec_t usec; + int r; + + assert(rvalue); + + r = parse_sec(rvalue, &usec); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + if (!timestamp_is_set(usec) || + DIV_ROUND_UP(usec, USEC_PER_MSEC) > UINT32_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + *val = (uint32_t) DIV_ROUND_UP(usec, USEC_PER_MSEC); + return 1; +} + +static int config_parse_route_metric_boolean_impl( + 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) { + + uint32_t *val = ASSERT_PTR(data); + int r; + + assert(rvalue); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + *val = r; + return 1; +} + +#define DEFINE_CONFIG_PARSE_ROUTE_METRIC(name, parser) \ + int config_parse_route_metric_##name( \ + 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 = ASSERT_PTR(userdata); \ + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; \ + uint16_t attr_type = ltype; \ + int r; \ + \ + assert(filename); \ + assert(section); \ + assert(lvalue); \ + assert(rvalue); \ + \ + r = route_new_static(network, filename, section_line, &route); \ + if (r == -ENOMEM) \ + return log_oom(); \ + if (r < 0) { \ + log_syntax(unit, LOG_WARNING, filename, line, r, \ + "Failed to allocate route, ignoring assignment: %m"); \ + return 0; \ + } \ + \ + if (isempty(rvalue)) { \ + route_metric_unset(&route->metric, attr_type); \ + TAKE_PTR(route); \ + return 0; \ + } \ + \ + uint32_t k; \ + r = parser(unit, filename, line, section, section_line, \ + lvalue, /* ltype = */ 0, rvalue, \ + &k, userdata); \ + if (r <= 0) \ + return r; \ + \ + if (route_metric_set_full( \ + &route->metric, \ + attr_type, \ + k, \ + /* force = */ true) < 0) \ + return log_oom(); \ + \ + TAKE_PTR(route); \ + return 0; \ + } + +DEFINE_CONFIG_PARSE_ROUTE_METRIC(mtu, config_parse_mtu); +DEFINE_CONFIG_PARSE_ROUTE_METRIC(advmss, config_parse_route_metric_advmss_impl); +DEFINE_CONFIG_PARSE_ROUTE_METRIC(hop_limit, config_parse_route_metric_hop_limit_impl); +DEFINE_CONFIG_PARSE_ROUTE_METRIC(tcp_window, config_parse_tcp_window); +DEFINE_CONFIG_PARSE_ROUTE_METRIC(tcp_rto, config_parse_route_metric_tcp_rto_impl); +DEFINE_CONFIG_PARSE_ROUTE_METRIC(boolean, config_parse_route_metric_boolean_impl); + +int config_parse_route_metric_tcp_congestion( + 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 = ASSERT_PTR(userdata); + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; + int r; + + assert(filename); + assert(rvalue); + + r = route_new_static(network, filename, section_line, &route); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + r = config_parse_string(unit, filename, line, section, section_line, lvalue, 0, + rvalue, &route->metric.tcp_congestion_control_algo, userdata); + if (r <= 0) + return r; + + TAKE_PTR(route); + return 0; +} diff --git a/src/network/networkd-route-metric.h b/src/network/networkd-route-metric.h new file mode 100644 index 0000000..212f907 --- /dev/null +++ b/src/network/networkd-route-metric.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <inttypes.h> +#include <stdbool.h> + +#include "sd-netlink.h" + +#include "conf-parser.h" +#include "hash-funcs.h" + +typedef struct RouteMetric { + size_t n_metrics; /* maximum metric attr type with non-zero value */ + uint32_t *metrics; /* RTAX_*, except for RTAX_CC_ALGO */ + + size_t n_metrics_set; + bool *metrics_set; /* used by conf parsers */ + + char *tcp_congestion_control_algo; /* RTAX_CC_ALGO */ +} RouteMetric; + +#define ROUTE_METRIC_NULL ((const RouteMetric) {}) + +void route_metric_done(RouteMetric *metric); +int route_metric_copy(const RouteMetric *src, RouteMetric *dest); + +void route_metric_hash_func(const RouteMetric *metric, struct siphash *state); +int route_metric_compare_func(const RouteMetric *a, const RouteMetric *b); +bool route_metric_can_update(const RouteMetric *a, const RouteMetric *b, bool expiration_by_kernel); + +int route_metric_set_full(RouteMetric *metric, uint16_t attr, uint32_t value, bool force); +static inline int route_metric_set(RouteMetric *metric, uint16_t attr, uint32_t value) { + return route_metric_set_full(metric, attr, value, false); +} +uint32_t route_metric_get(const RouteMetric *metric, uint16_t attr); + +int route_metric_set_netlink_message(const RouteMetric *metric, sd_netlink_message *m); +int route_metric_read_netlink_message(RouteMetric *metric, sd_netlink_message *message); + +CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_mtu); +CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_advmss); +CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_hop_limit); +CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_tcp_window); +CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_tcp_rto); +CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_boolean); +CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_tcp_congestion); +CONFIG_PARSER_PROTOTYPE(config_parse_tcp_window); diff --git a/src/network/networkd-route-nexthop.c b/src/network/networkd-route-nexthop.c new file mode 100644 index 0000000..11215c3 --- /dev/null +++ b/src/network/networkd-route-nexthop.c @@ -0,0 +1,1225 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <linux/nexthop.h> + +#include "alloc-util.h" +#include "extract-word.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "networkd-nexthop.h" +#include "networkd-route.h" +#include "networkd-route-nexthop.h" +#include "networkd-route-util.h" +#include "parse-util.h" +#include "string-util.h" + +void route_detach_from_nexthop(Route *route) { + NextHop *nh; + + assert(route); + assert(route->manager); + + if (route->nexthop_id == 0) + return; + + if (nexthop_get_by_id(route->manager, route->nexthop_id, &nh) < 0) + return; + + route_unref(set_remove(nh->routes, route)); +} + +void route_attach_to_nexthop(Route *route) { + NextHop *nh; + int r; + + assert(route); + assert(route->manager); + + if (route->nexthop_id == 0) + return; + + r = nexthop_get_by_id(route->manager, route->nexthop_id, &nh); + if (r < 0) { + if (route->manager->manage_foreign_nexthops) + log_debug_errno(r, "Route has unknown nexthop ID (%"PRIu32"), ignoring.", + route->nexthop_id); + return; + } + + r = set_ensure_put(&nh->routes, &route_hash_ops_unref, route); + if (r < 0) + return (void) log_debug_errno(r, "Failed to save route to nexthop, ignoring: %m"); + if (r == 0) + return (void) log_debug("Duplicated route assigned to nexthop, ignoring."); + + route_ref(route); +} + +static void route_nexthop_done(RouteNextHop *nh) { + assert(nh); + + free(nh->ifname); +} + +RouteNextHop* route_nexthop_free(RouteNextHop *nh) { + if (!nh) + return NULL; + + route_nexthop_done(nh); + + return mfree(nh); +} + +void route_nexthops_done(Route *route) { + assert(route); + + route_nexthop_done(&route->nexthop); + ordered_set_free(route->nexthops); +} + +static void route_nexthop_hash_func_full(const RouteNextHop *nh, struct siphash *state, bool with_weight) { + assert(nh); + assert(state); + + /* See nh_comp() in net/ipv4/fib_semantics.c of the kernel. */ + + siphash24_compress_typesafe(nh->family, state); + if (!IN_SET(nh->family, AF_INET, AF_INET6)) + return; + + in_addr_hash_func(&nh->gw, nh->family, state); + if (with_weight) + siphash24_compress_typesafe(nh->weight, state); + siphash24_compress_typesafe(nh->ifindex, state); + if (nh->ifindex == 0) + siphash24_compress_string(nh->ifname, state); /* For Network or Request object. */ +} + +static int route_nexthop_compare_func_full(const RouteNextHop *a, const RouteNextHop *b, bool with_weight) { + int r; + + assert(a); + assert(b); + + r = CMP(a->family, b->family); + if (r != 0) + return r; + + if (!IN_SET(a->family, AF_INET, AF_INET6)) + return 0; + + r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + + if (with_weight) { + r = CMP(a->weight, b->weight); + if (r != 0) + return r; + } + + r = CMP(a->ifindex, b->ifindex); + if (r != 0) + return r; + + if (a->ifindex == 0) { + r = strcmp_ptr(a->ifname, b->ifname); + if (r != 0) + return r; + } + + return 0; +} + +static void route_nexthop_hash_func(const RouteNextHop *nh, struct siphash *state) { + route_nexthop_hash_func_full(nh, state, /* with_weight = */ true); +} + +static int route_nexthop_compare_func(const RouteNextHop *a, const RouteNextHop *b) { + return route_nexthop_compare_func_full(a, b, /* with_weight = */ true); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + route_nexthop_hash_ops, + RouteNextHop, + route_nexthop_hash_func, + route_nexthop_compare_func, + route_nexthop_free); + +static size_t route_n_nexthops(const Route *route) { + if (route->nexthop_id != 0 || route_type_is_reject(route)) + return 0; + + if (ordered_set_isempty(route->nexthops)) + return 1; + + return ordered_set_size(route->nexthops); +} + +void route_nexthops_hash_func(const Route *route, struct siphash *state) { + assert(route); + + size_t nhs = route_n_nexthops(route); + siphash24_compress_typesafe(nhs, state); + + switch (nhs) { + case 0: + siphash24_compress_typesafe(route->nexthop_id, state); + return; + + case 1: + route_nexthop_hash_func_full(&route->nexthop, state, /* with_weight = */ false); + return; + + default: { + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) + route_nexthop_hash_func(nh, state); + }} +} + +int route_nexthops_compare_func(const Route *a, const Route *b) { + int r; + + assert(a); + assert(b); + + size_t a_nhs = route_n_nexthops(a); + size_t b_nhs = route_n_nexthops(b); + r = CMP(a_nhs, b_nhs); + if (r != 0) + return r; + + switch (a_nhs) { + case 0: + return CMP(a->nexthop_id, b->nexthop_id); + + case 1: + return route_nexthop_compare_func_full(&a->nexthop, &b->nexthop, /* with_weight = */ false); + + default: { + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, a->nexthops) { + r = CMP(nh, (RouteNextHop*) ordered_set_get(a->nexthops, nh)); + if (r != 0) + return r; + } + return 0; + }} +} + +static int route_nexthop_copy(const RouteNextHop *src, RouteNextHop *dest) { + assert(src); + assert(dest); + + *dest = *src; + + /* unset pointer copied in the above. */ + dest->ifname = NULL; + + return strdup_to(&dest->ifname, src->ifindex > 0 ? NULL : src->ifname); +} + +static int route_nexthop_dup(const RouteNextHop *src, RouteNextHop **ret) { + _cleanup_(route_nexthop_freep) RouteNextHop *dest = NULL; + int r; + + assert(src); + assert(ret); + + dest = new(RouteNextHop, 1); + if (!dest) + return -ENOMEM; + + r = route_nexthop_copy(src, dest); + if (r < 0) + return r; + + *ret = TAKE_PTR(dest); + return 0; +} + +int route_nexthops_copy(const Route *src, const RouteNextHop *nh, Route *dest) { + int r; + + assert(src); + assert(dest); + + if (src->nexthop_id != 0 || route_type_is_reject(src)) + return 0; + + if (nh) + return route_nexthop_copy(nh, &dest->nexthop); + + if (ordered_set_isempty(src->nexthops)) + return route_nexthop_copy(&src->nexthop, &dest->nexthop); + + ORDERED_SET_FOREACH(nh, src->nexthops) { + _cleanup_(route_nexthop_freep) RouteNextHop *nh_dup = NULL; + + r = route_nexthop_dup(nh, &nh_dup); + if (r < 0) + return r; + + r = ordered_set_ensure_put(&dest->nexthops, &route_nexthop_hash_ops, nh_dup); + if (r < 0) + return r; + assert(r > 0); + + TAKE_PTR(nh_dup); + } + + return 0; +} + +static bool multipath_routes_needs_adjust(const Route *route) { + assert(route); + + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) + if (route->nexthop.ifindex == 0) + return true; + + return false; +} + +bool route_nexthops_needs_adjust(const Route *route) { + assert(route); + + if (route->nexthop_id != 0) + /* At this stage, the nexthop may not be configured, or may be under reconfiguring. + * Hence, we cannot know if the nexthop is blackhole or not. */ + return route->type != RTN_BLACKHOLE; + + if (route_type_is_reject(route)) + return false; + + if (ordered_set_isempty(route->nexthops)) + return route->nexthop.ifindex == 0; + + return multipath_routes_needs_adjust(route); +} + +static bool route_nexthop_set_ifindex(RouteNextHop *nh, Link *link) { + assert(nh); + assert(link); + assert(link->manager); + + if (nh->ifindex > 0) { + nh->ifname = mfree(nh->ifname); + return false; + } + + /* If an interface name is specified, use it. Otherwise, use the interface that requests this route. */ + if (nh->ifname && link_get_by_name(link->manager, nh->ifname, &link) < 0) + return false; + + nh->ifindex = link->ifindex; + nh->ifname = mfree(nh->ifname); + return true; /* updated */ +} + +int route_adjust_nexthops(Route *route, Link *link) { + int r; + + assert(route); + assert(link); + assert(link->manager); + + /* When an IPv4 route has nexthop id and the nexthop type is blackhole, even though kernel sends + * RTM_NEWROUTE netlink message with blackhole type, kernel's internal route type fib_rt_info::type + * may not be blackhole. Thus, we cannot know the internal value. Moreover, on route removal, the + * matching is done with the hidden value if we set non-zero type in RTM_DELROUTE message. So, + * here let's set route type to BLACKHOLE when the nexthop is blackhole. */ + if (route->nexthop_id != 0) { + NextHop *nexthop; + + r = nexthop_is_ready(link->manager, route->nexthop_id, &nexthop); + if (r <= 0) + return r; /* r == 0 means the nexthop is under (re-)configuring. + * We cannot use the currently remembered information. */ + + if (!nexthop->blackhole) + return false; + + if (route->type == RTN_BLACKHOLE) + return false; + + route->type = RTN_BLACKHOLE; + return true; /* updated */ + } + + if (route_type_is_reject(route)) + return false; + + if (ordered_set_isempty(route->nexthops)) + return route_nexthop_set_ifindex(&route->nexthop, link); + + if (!multipath_routes_needs_adjust(route)) + return false; + + _cleanup_ordered_set_free_ OrderedSet *nexthops = NULL; + for (;;) { + _cleanup_(route_nexthop_freep) RouteNextHop *nh = NULL; + + nh = ordered_set_steal_first(route->nexthops); + if (!nh) + break; + + (void) route_nexthop_set_ifindex(nh, link); + + r = ordered_set_ensure_put(&nexthops, &route_nexthop_hash_ops, nh); + if (r == -EEXIST) + continue; /* Duplicated? Let's drop the nexthop. */ + if (r < 0) + return r; + assert(r > 0); + + TAKE_PTR(nh); + } + + ordered_set_free(route->nexthops); + route->nexthops = TAKE_PTR(nexthops); + return true; /* updated */ +} + +int route_nexthop_get_link(Manager *manager, const RouteNextHop *nh, Link **ret) { + assert(manager); + assert(nh); + + if (nh->ifindex > 0) + return link_get_by_index(manager, nh->ifindex, ret); + if (nh->ifname) + return link_get_by_name(manager, nh->ifname, ret); + + return -ENOENT; +} + +static bool route_nexthop_is_ready_to_configure(const RouteNextHop *nh, Manager *manager, bool onlink) { + Link *link; + + assert(nh); + assert(manager); + + if (route_nexthop_get_link(manager, nh, &link) < 0) + return false; + + if (!link_is_ready_to_configure(link, /* allow_unmanaged = */ true)) + return false; + + /* If the interface is not managed by us, we request that the interface has carrier. + * That is, ConfigureWithoutCarrier=no is the default even for unamanaged interfaces. */ + if (!link->network && !link_has_carrier(link)) + return false; + + return gateway_is_ready(link, onlink, nh->family, &nh->gw); +} + +int route_nexthops_is_ready_to_configure(const Route *route, Manager *manager) { + int r; + + assert(route); + assert(manager); + + if (route->nexthop_id != 0) { + struct nexthop_grp *nhg; + NextHop *nh; + + r = nexthop_is_ready(manager, route->nexthop_id, &nh); + if (r <= 0) + return r; + + HASHMAP_FOREACH(nhg, nh->group) { + r = nexthop_is_ready(manager, nhg->id, NULL); + if (r <= 0) + return r; + } + + return true; + } + + if (route_type_is_reject(route)) + return true; + + if (ordered_set_isempty(route->nexthops)) + return route_nexthop_is_ready_to_configure(&route->nexthop, manager, FLAGS_SET(route->flags, RTNH_F_ONLINK)); + + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) + if (!route_nexthop_is_ready_to_configure(nh, manager, FLAGS_SET(route->flags, RTNH_F_ONLINK))) + return false; + + return true; +} + +int route_nexthops_to_string(const Route *route, char **ret) { + _cleanup_free_ char *buf = NULL; + int r; + + assert(route); + assert(ret); + + if (route->nexthop_id != 0) { + if (asprintf(&buf, "nexthop: %"PRIu32, route->nexthop_id) < 0) + return -ENOMEM; + + *ret = TAKE_PTR(buf); + return 0; + } + + if (route_type_is_reject(route)) { + buf = strdup("gw: n/a"); + if (!buf) + return -ENOMEM; + + *ret = TAKE_PTR(buf); + return 0; + } + + if (ordered_set_isempty(route->nexthops)) { + if (in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) + buf = strjoin("gw: ", IN_ADDR_TO_STRING(route->nexthop.family, &route->nexthop.gw)); + else if (route->gateway_from_dhcp_or_ra) { + if (route->nexthop.family == AF_INET) + buf = strdup("gw: _dhcp4"); + else if (route->nexthop.family == AF_INET6) + buf = strdup("gw: _ipv6ra"); + else + buf = strdup("gw: _dhcp"); + } else + buf = strdup("gw: n/a"); + if (!buf) + return -ENOMEM; + + *ret = TAKE_PTR(buf); + return 0; + } + + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) { + const char *s = in_addr_is_set(nh->family, &nh->gw) ? IN_ADDR_TO_STRING(nh->family, &nh->gw) : NULL; + + if (nh->ifindex > 0) + r = strextendf_with_separator(&buf, ",", "%s@%i:%"PRIu32, strempty(s), nh->ifindex, nh->weight + 1); + else if (nh->ifname) + r = strextendf_with_separator(&buf, ",", "%s@%s:%"PRIu32, strempty(s), nh->ifname, nh->weight + 1); + else + r = strextendf_with_separator(&buf, ",", "%s:%"PRIu32, strempty(s), nh->weight + 1); + if (r < 0) + return r; + } + + char *p = strjoin("gw: ", strna(buf)); + if (!p) + return -ENOMEM; + + *ret = p; + return 0; +} + +static int append_nexthop_one(const Route *route, const RouteNextHop *nh, struct rtattr **rta, size_t offset) { + struct rtnexthop *rtnh; + struct rtattr *new_rta; + int r; + + assert(route); + assert(IN_SET(route->family, AF_INET, AF_INET6)); + assert(nh); + assert(rta); + assert(*rta); + + new_rta = realloc(*rta, RTA_ALIGN((*rta)->rta_len) + RTA_SPACE(sizeof(struct rtnexthop))); + if (!new_rta) + return -ENOMEM; + *rta = new_rta; + + rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); + *rtnh = (struct rtnexthop) { + .rtnh_len = sizeof(*rtnh), + .rtnh_ifindex = nh->ifindex, + .rtnh_hops = nh->weight, + }; + + (*rta)->rta_len += sizeof(struct rtnexthop); + + if (nh->family == route->family) { + r = rtattr_append_attribute(rta, RTA_GATEWAY, &nh->gw, FAMILY_ADDRESS_SIZE(nh->family)); + if (r < 0) + goto clear; + + rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); + rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(nh->family)); + + } else if (nh->family == AF_INET6) { + assert(route->family == AF_INET); + + r = rtattr_append_attribute(rta, RTA_VIA, + &(RouteVia) { + .family = nh->family, + .address = nh->gw, + }, sizeof(RouteVia)); + if (r < 0) + goto clear; + + rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); + rtnh->rtnh_len += RTA_SPACE(sizeof(RouteVia)); + + } else if (nh->family == AF_INET) + assert_not_reached(); + + return 0; + +clear: + (*rta)->rta_len -= sizeof(struct rtnexthop); + return r; +} + +static int netlink_message_append_multipath_route(const Route *route, sd_netlink_message *message) { + _cleanup_free_ struct rtattr *rta = NULL; + size_t offset; + int r; + + assert(route); + assert(message); + + rta = new(struct rtattr, 1); + if (!rta) + return -ENOMEM; + + *rta = (struct rtattr) { + .rta_type = RTA_MULTIPATH, + .rta_len = RTA_LENGTH(0), + }; + offset = (uint8_t *) RTA_DATA(rta) - (uint8_t *) rta; + + if (ordered_set_isempty(route->nexthops)) { + r = append_nexthop_one(route, &route->nexthop, &rta, offset); + if (r < 0) + return r; + + } else { + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) { + struct rtnexthop *rtnh; + + r = append_nexthop_one(route, nh, &rta, offset); + if (r < 0) + return r; + + rtnh = (struct rtnexthop *)((uint8_t *) rta + offset); + offset = (uint8_t *) RTNH_NEXT(rtnh) - (uint8_t *) rta; + } + } + + return sd_netlink_message_append_data(message, RTA_MULTIPATH, RTA_DATA(rta), RTA_PAYLOAD(rta)); +} + +int route_nexthops_set_netlink_message(const Route *route, sd_netlink_message *message) { + int r; + + assert(route); + assert(IN_SET(route->family, AF_INET, AF_INET6)); + assert(message); + + if (route->nexthop_id != 0) + return sd_netlink_message_append_u32(message, RTA_NH_ID, route->nexthop_id); + + if (route_type_is_reject(route)) + return 0; + + /* We request IPv6 multipath routes separately. Even though, if weight is non-zero, we need to use + * RTA_MULTIPATH, as we have no way to specify the weight of the nexthop. */ + if (ordered_set_isempty(route->nexthops) && route->nexthop.weight == 0) { + + if (in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) { + if (route->nexthop.family == route->family) + r = netlink_message_append_in_addr_union(message, RTA_GATEWAY, route->nexthop.family, &route->nexthop.gw); + else { + assert(route->family == AF_INET); + r = sd_netlink_message_append_data(message, RTA_VIA, + &(const RouteVia) { + .family = route->nexthop.family, + .address = route->nexthop.gw, + }, sizeof(RouteVia)); + } + if (r < 0) + return r; + } + + assert(route->nexthop.ifindex > 0); + return sd_netlink_message_append_u32(message, RTA_OIF, route->nexthop.ifindex); + } + + return netlink_message_append_multipath_route(route, message); +} + +static int route_parse_nexthops(Route *route, const struct rtnexthop *rtnh, size_t size) { + _cleanup_ordered_set_free_ OrderedSet *nexthops = NULL; + int r; + + assert(route); + assert(IN_SET(route->family, AF_INET, AF_INET6)); + assert(rtnh); + + if (size < sizeof(struct rtnexthop)) + return -EBADMSG; + + for (; size >= sizeof(struct rtnexthop); ) { + _cleanup_(route_nexthop_freep) RouteNextHop *nh = NULL; + + if (NLMSG_ALIGN(rtnh->rtnh_len) > size) + return -EBADMSG; + + if (rtnh->rtnh_len < sizeof(struct rtnexthop)) + return -EBADMSG; + + nh = new(RouteNextHop, 1); + if (!nh) + return -ENOMEM; + + *nh = (RouteNextHop) { + .ifindex = rtnh->rtnh_ifindex, + .weight = rtnh->rtnh_hops, + }; + + if (rtnh->rtnh_len > sizeof(struct rtnexthop)) { + size_t len = rtnh->rtnh_len - sizeof(struct rtnexthop); + bool have_gw = false; + + for (struct rtattr *attr = RTNH_DATA(rtnh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) { + + switch (attr->rta_type) { + case RTA_GATEWAY: + if (have_gw) + return -EBADMSG; + + if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(route->family))) + return -EBADMSG; + + nh->family = route->family; + memcpy(&nh->gw, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(nh->family)); + have_gw = true; + break; + + case RTA_VIA: + if (have_gw) + return -EBADMSG; + + if (route->family != AF_INET) + return -EBADMSG; + + if (attr->rta_len != RTA_LENGTH(sizeof(RouteVia))) + return -EBADMSG; + + RouteVia *via = RTA_DATA(attr); + if (via->family != AF_INET6) + return -EBADMSG; + + nh->family = via->family; + nh->gw = via->address; + have_gw = true; + break; + } + } + } + + r = ordered_set_ensure_put(&nexthops, &route_nexthop_hash_ops, nh); + assert(r != 0); + if (r > 0) + TAKE_PTR(nh); + else if (r != -EEXIST) + return r; + + size -= NLMSG_ALIGN(rtnh->rtnh_len); + rtnh = RTNH_NEXT(rtnh); + } + + ordered_set_free(route->nexthops); + route->nexthops = TAKE_PTR(nexthops); + return 0; +} + +int route_nexthops_read_netlink_message(Route *route, sd_netlink_message *message) { + int r; + + assert(route); + assert(message); + + r = sd_netlink_message_read_u32(message, RTA_NH_ID, &route->nexthop_id); + if (r < 0 && r != -ENODATA) + return log_warning_errno(r, "rtnl: received route message with invalid nexthop id, ignoring: %m"); + + if (route->nexthop_id != 0 || route_type_is_reject(route)) + /* IPv6 routes with reject type are always assigned to the loopback interface. See kernel's + * fib6_nh_init() in net/ipv6/route.c. However, we'd like to make it consistent with IPv4 + * routes. Hence, skip reading of RTA_OIF. */ + return 0; + + uint32_t ifindex; + r = sd_netlink_message_read_u32(message, RTA_OIF, &ifindex); + if (r >= 0) + route->nexthop.ifindex = (int) ifindex; + else if (r != -ENODATA) + return log_warning_errno(r, "rtnl: could not get ifindex from route message, ignoring: %m"); + + if (route->nexthop.ifindex > 0) { + r = netlink_message_read_in_addr_union(message, RTA_GATEWAY, route->family, &route->nexthop.gw); + if (r >= 0) { + route->nexthop.family = route->family; + return 0; + } + if (r != -ENODATA) + return log_warning_errno(r, "rtnl: received route message without valid gateway, ignoring: %m"); + + if (route->family != AF_INET) + return 0; + + RouteVia via; + r = sd_netlink_message_read(message, RTA_VIA, sizeof(via), &via); + if (r >= 0) { + route->nexthop.family = via.family; + route->nexthop.gw = via.address; + return 0; + } + if (r != -ENODATA) + return log_warning_errno(r, "rtnl: received route message without valid gateway, ignoring: %m"); + + return 0; + } + + size_t rta_len; + _cleanup_free_ void *rta = NULL; + r = sd_netlink_message_read_data(message, RTA_MULTIPATH, &rta_len, &rta); + if (r == -ENODATA) + return 0; + if (r < 0) + return log_warning_errno(r, "rtnl: failed to read RTA_MULTIPATH attribute, ignoring: %m"); + + r = route_parse_nexthops(route, rta, rta_len); + if (r < 0) + return log_warning_errno(r, "rtnl: failed to parse RTA_MULTIPATH attribute, ignoring: %m"); + + return 0; +} + +int route_section_verify_nexthops(Route *route) { + assert(route); + assert(route->section); + + if (route->gateway_from_dhcp_or_ra) { + assert(route->network); + + if (route->nexthop.family == AF_UNSPEC) + /* When deprecated Gateway=_dhcp is set, then assume gateway family based on other settings. */ + switch (route->family) { + case AF_UNSPEC: + log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. " + "Please use \"_dhcp4\" or \"_ipv6ra\" instead. Assuming \"_dhcp4\".", + route->section->filename, route->section->line); + + route->nexthop.family = route->family = AF_INET; + break; + case AF_INET: + case AF_INET6: + log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. " + "Assuming \"%s\" based on Destination=, Source=, or PreferredSource= setting.", + route->section->filename, route->section->line, route->family == AF_INET ? "_dhcp4" : "_ipv6ra"); + + route->nexthop.family = route->family; + break; + default: + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Invalid route family. Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + } + + if (route->nexthop.family == AF_INET && !FLAGS_SET(route->network->dhcp, ADDRESS_FAMILY_IPV4)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Gateway=\"_dhcp4\" is specified but DHCPv4 client is disabled. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + + if (route->nexthop.family == AF_INET6 && route->network->ndisc == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Gateway=\"_ipv6ra\" is specified but IPv6AcceptRA= is disabled. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + } + + /* When only Gateway= is specified, assume the route family based on the Gateway address. */ + if (route->family == AF_UNSPEC) + route->family = route->nexthop.family; + + if (route->family == AF_UNSPEC) { + assert(route->section); + + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Route section without Gateway=, Destination=, Source=, " + "or PreferredSource= field configured. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + } + + if (route->gateway_onlink < 0 && in_addr_is_set(route->nexthop.family, &route->nexthop.gw) && + route->network && ordered_hashmap_isempty(route->network->addresses_by_section)) { + /* If no address is configured, in most cases the gateway cannot be reachable. + * TODO: we may need to improve the condition above. */ + log_warning("%s: Gateway= without static address configured. " + "Enabling GatewayOnLink= option.", + route->section->filename); + route->gateway_onlink = true; + } + + if (route->gateway_onlink >= 0) + SET_FLAG(route->flags, RTNH_F_ONLINK, route->gateway_onlink); + + if (route->family == AF_INET6) { + if (route->nexthop.family == AF_INET) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: IPv4 gateway is configured for IPv6 route. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) + if (nh->family == AF_INET) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: IPv4 multipath route is specified for IPv6 route. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + } + + if (route->nexthop_id != 0 && + (route->gateway_from_dhcp_or_ra || + in_addr_is_set(route->nexthop.family, &route->nexthop.gw) || + !ordered_set_isempty(route->nexthops))) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: NextHopId= cannot be specified with Gateway= or MultiPathRoute=. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + + if (route_type_is_reject(route) && + (route->gateway_from_dhcp_or_ra || + in_addr_is_set(route->nexthop.family, &route->nexthop.gw) || + !ordered_set_isempty(route->nexthops))) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: reject type route cannot be specified with Gateway= or MultiPathRoute=. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + + if ((route->gateway_from_dhcp_or_ra || + in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) && + !ordered_set_isempty(route->nexthops)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Gateway= cannot be specified with MultiPathRoute=. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + + if (ordered_set_size(route->nexthops) == 1) { + _cleanup_(route_nexthop_freep) RouteNextHop *nh = ordered_set_steal_first(route->nexthops); + + route_nexthop_done(&route->nexthop); + route->nexthop = TAKE_STRUCT(*nh); + + assert(ordered_set_isempty(route->nexthops)); + route->nexthops = ordered_set_free(route->nexthops); + } + + return 0; +} + +int config_parse_gateway( + 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_(route_unref_or_set_invalidp) Route *route = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(section, "Network")) { + /* we are not in an Route section, so use line number instead */ + r = route_new_static(network, filename, line, &route); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + } else { + r = route_new_static(network, filename, section_line, &route); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + if (isempty(rvalue)) { + route->gateway_from_dhcp_or_ra = false; + route->nexthop.family = AF_UNSPEC; + route->nexthop.gw = IN_ADDR_NULL; + TAKE_PTR(route); + return 0; + } + + if (streq(rvalue, "_dhcp")) { + route->gateway_from_dhcp_or_ra = true; + route->nexthop.family = AF_UNSPEC; + route->nexthop.gw = IN_ADDR_NULL; + TAKE_PTR(route); + return 0; + } + + if (streq(rvalue, "_dhcp4")) { + route->gateway_from_dhcp_or_ra = true; + route->nexthop.family = AF_INET; + route->nexthop.gw = IN_ADDR_NULL; + TAKE_PTR(route); + return 0; + } + + if (streq(rvalue, "_ipv6ra")) { + route->gateway_from_dhcp_or_ra = true; + route->nexthop.family = AF_INET6; + route->nexthop.gw = IN_ADDR_NULL; + TAKE_PTR(route); + return 0; + } + } + + r = in_addr_from_string_auto(rvalue, &route->nexthop.family, &route->nexthop.gw); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); + return 0; + } + + route->gateway_from_dhcp_or_ra = false; + TAKE_PTR(route); + return 0; +} + +int config_parse_route_gateway_onlink( + 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_(route_unref_or_set_invalidp) Route *route = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &route); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + r = config_parse_tristate(unit, filename, line, section, section_line, lvalue, ltype, rvalue, + &route->gateway_onlink, network); + if (r <= 0) + return r; + + TAKE_PTR(route); + return 0; +} + +int config_parse_route_nexthop( + 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_(route_unref_or_set_invalidp) Route *route = NULL; + uint32_t id; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &route); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + if (isempty(rvalue)) { + route->nexthop_id = 0; + TAKE_PTR(route); + return 0; + } + + r = safe_atou32(rvalue, &id); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse nexthop ID, ignoring assignment: %s", rvalue); + return 0; + } + if (id == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid nexthop ID, ignoring assignment: %s", rvalue); + return 0; + } + + route->nexthop_id = id; + TAKE_PTR(route); + return 0; +} + +int config_parse_multipath_route( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(route_nexthop_freep) RouteNextHop *nh = NULL; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; + _cleanup_free_ char *word = NULL; + Network *network = userdata; + const char *p; + char *dev; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &route); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + if (isempty(rvalue)) { + route->nexthops = ordered_set_free(route->nexthops); + TAKE_PTR(route); + return 0; + } + + nh = new0(RouteNextHop, 1); + if (!nh) + return log_oom(); + + p = rvalue; + r = extract_first_word(&p, &word, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r <= 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid multipath route option, ignoring assignment: %s", rvalue); + return 0; + } + + dev = strchr(word, '@'); + if (dev) { + *dev++ = '\0'; + + r = parse_ifindex(dev); + if (r > 0) + nh->ifindex = r; + else { + if (!ifname_valid_full(dev, IFNAME_VALID_ALTERNATIVE)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid interface name '%s' in %s=, ignoring: %s", dev, lvalue, rvalue); + return 0; + } + + nh->ifname = strdup(dev); + if (!nh->ifname) + return log_oom(); + } + } + + r = in_addr_from_string_auto(word, &nh->family, &nh->gw); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid multipath route gateway '%s', ignoring assignment: %m", rvalue); + return 0; + } + + if (!isempty(p)) { + r = safe_atou32(p, &nh->weight); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid multipath route weight, ignoring assignment: %s", p); + return 0; + } + /* ip command takes weight in the range 1…255, while kernel takes the value in the + * range 0…254. MultiPathRoute= setting also takes weight in the same range which ip + * command uses, then networkd decreases by one and stores it to match the range which + * kernel uses. */ + if (nh->weight == 0 || nh->weight > 256) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid multipath route weight, ignoring assignment: %s", p); + return 0; + } + nh->weight--; + } + + r = ordered_set_ensure_put(&route->nexthops, &route_nexthop_hash_ops, nh); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store multipath route, ignoring assignment: %m"); + return 0; + } + + TAKE_PTR(nh); + TAKE_PTR(route); + return 0; +} diff --git a/src/network/networkd-route-nexthop.h b/src/network/networkd-route-nexthop.h new file mode 100644 index 0000000..f9a1478 --- /dev/null +++ b/src/network/networkd-route-nexthop.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include <inttypes.h> +#include <stdbool.h> + +#include "sd-netlink.h" + +#include "conf-parser.h" +#include "in-addr-util.h" +#include "macro.h" +#include "siphash24.h" + +typedef struct Link Link; +typedef struct Manager Manager; +typedef struct Route Route; + +typedef struct RouteNextHop { + int family; /* used in RTA_VIA (IPv4 only) */ + union in_addr_union gw; /* RTA_GATEWAY or RTA_VIA (IPv4 only) */ + uint32_t weight; /* rtnh_hops */ + int ifindex; /* RTA_OIF(u32) or rtnh_ifindex */ + char *ifname; /* only used by Route object owned by Network object */ + /* unsupported attributes: RTA_FLOW (IPv4 only), RTA_ENCAP_TYPE, RTA_ENCAP. */ +} RouteNextHop; + +#define ROUTE_NEXTHOP_NULL ((const RouteNextHop) {}) + +void route_detach_from_nexthop(Route *route); +void route_attach_to_nexthop(Route *route); + +RouteNextHop* route_nexthop_free(RouteNextHop *nh); +DEFINE_TRIVIAL_CLEANUP_FUNC(RouteNextHop*, route_nexthop_free); + +void route_nexthops_done(Route *route); + +void route_nexthops_hash_func(const Route *route, struct siphash *state); +int route_nexthops_compare_func(const Route *a, const Route *b); + +int route_nexthops_copy(const Route *src, const RouteNextHop *nh, Route *dest); +bool route_nexthops_needs_adjust(const Route *route); +int route_adjust_nexthops(Route *route, Link *link); + +int route_nexthop_get_link(Manager *manager, const RouteNextHop *nh, Link **ret); +int route_nexthops_is_ready_to_configure(const Route *route, Manager *manager); + +int route_nexthops_to_string(const Route *route, char **ret); + +int route_nexthops_set_netlink_message(const Route *route, sd_netlink_message *message); +int route_nexthops_read_netlink_message(Route *route, sd_netlink_message *message); + +int route_section_verify_nexthops(Route *route); + +CONFIG_PARSER_PROTOTYPE(config_parse_gateway); +CONFIG_PARSER_PROTOTYPE(config_parse_route_gateway_onlink); +CONFIG_PARSER_PROTOTYPE(config_parse_route_nexthop); +CONFIG_PARSER_PROTOTYPE(config_parse_multipath_route); diff --git a/src/network/networkd-route-util.c b/src/network/networkd-route-util.c index d49a0b9..b7e07f4 100644 --- a/src/network/networkd-route-util.c +++ b/src/network/networkd-route-util.c @@ -39,6 +39,12 @@ unsigned routes_max(void) { return cached; } +bool route_type_is_reject(const Route *route) { + assert(route); + + return IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW); +} + static bool route_lifetime_is_valid(const Route *route) { assert(route); @@ -52,8 +58,11 @@ bool link_find_default_gateway(Link *link, int family, Route **gw) { Route *route; assert(link); + assert(link->manager); - SET_FOREACH(route, link->routes) { + SET_FOREACH(route, link->manager->routes) { + if (route->nexthop.ifindex != link->ifindex) + continue; if (!route_exists(route)) continue; if (family != AF_UNSPEC && route->family != family) @@ -68,7 +77,7 @@ bool link_find_default_gateway(Link *link, int family, Route **gw) { continue; if (route->scope != RT_SCOPE_UNIVERSE) continue; - if (!in_addr_is_set(route->gw_family, &route->gw)) + if (!in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) continue; /* Found a default gateway. */ @@ -77,7 +86,7 @@ bool link_find_default_gateway(Link *link, int family, Route **gw) { /* If we have already found another gw, then let's compare their weight and priority. */ if (*gw) { - if (route->gw_weight > (*gw)->gw_weight) + if (route->nexthop.weight > (*gw)->nexthop.weight) continue; if (route->priority >= (*gw)->priority) continue; @@ -113,12 +122,7 @@ int manager_find_uplink(Manager *m, int family, Link *exclude, Link **ret) { if (!gw) return -ENOENT; - if (ret) { - assert(gw->link); - *ret = gw->link; - } - - return 0; + return link_get_by_index(m, gw->nexthop.ifindex, ret); } bool gateway_is_ready(Link *link, bool onlink, int family, const union in_addr_union *gw) { @@ -137,7 +141,9 @@ bool gateway_is_ready(Link *link, bool onlink, int family, const union in_addr_u if (family == AF_INET6 && in6_addr_is_link_local(&gw->in6)) return true; - SET_FOREACH(route, link->routes) { + SET_FOREACH(route, link->manager->routes) { + if (route->nexthop.ifindex != link->ifindex) + continue; if (!route_exists(route)) continue; if (!route_lifetime_is_valid(route)) @@ -181,10 +187,14 @@ static int link_address_is_reachable_internal( Route *route, *found = NULL; assert(link); + assert(link->manager); assert(IN_SET(family, AF_INET, AF_INET6)); assert(address); - SET_FOREACH(route, link->routes) { + SET_FOREACH(route, link->manager->routes) { + if (route->nexthop.ifindex != link->ifindex) + continue; + if (!route_exists(route)) continue; @@ -298,7 +308,11 @@ int manager_address_is_reachable( return 0; } - r = link_get_address(found->link, found->family, &found->prefsrc, 0, &a); + r = link_get_by_index(manager, found->nexthop.ifindex, &link); + if (r < 0) + return r; + + r = link_get_address(link, found->family, &found->prefsrc, 0, &a); if (r < 0) return r; diff --git a/src/network/networkd-route-util.h b/src/network/networkd-route-util.h index f326888..eba823a 100644 --- a/src/network/networkd-route-util.h +++ b/src/network/networkd-route-util.h @@ -13,6 +13,8 @@ typedef struct Route Route; unsigned routes_max(void); +bool route_type_is_reject(const Route *route); + bool link_find_default_gateway(Link *link, int family, Route **gw); static inline bool link_has_default_gateway(Link *link, int family) { return link_find_default_gateway(link, family, NULL); diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index eb502ae..d596fd8 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include <linux/icmpv6.h> #include <linux/ipv6_route.h> #include <linux/nexthop.h> @@ -21,133 +20,105 @@ #include "vrf.h" #include "wireguard.h" -int route_new(Route **ret) { - _cleanup_(route_freep) Route *route = NULL; - - route = new(Route, 1); - if (!route) - return -ENOMEM; - - *route = (Route) { - .family = AF_UNSPEC, - .scope = RT_SCOPE_UNIVERSE, - .protocol = RTPROT_UNSPEC, - .type = RTN_UNICAST, - .table = RT_TABLE_MAIN, - .lifetime_usec = USEC_INFINITY, - .quickack = -1, - .fast_open_no_cookie = -1, - .gateway_onlink = -1, - .ttl_propagate = -1, - }; - - *ret = TAKE_PTR(route); - - return 0; -} - -static int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret) { - _cleanup_(config_section_freep) ConfigSection *n = NULL; - _cleanup_(route_freep) Route *route = NULL; - int r; - - assert(network); - assert(ret); - assert(filename); - assert(section_line > 0); - - r = config_section_new(filename, section_line, &n); - if (r < 0) - return r; +static Route* route_detach_impl(Route *route) { + assert(route); + assert(!!route->network + !!route->manager + !!route->wireguard <= 1); - route = hashmap_get(network->routes_by_section, n); - if (route) { - *ret = TAKE_PTR(route); - return 0; + if (route->network) { + assert(route->section); + hashmap_remove(route->network->routes_by_section, route->section); + route->network = NULL; + return route; } - if (hashmap_size(network->routes_by_section) >= routes_max()) - return -E2BIG; - - r = route_new(&route); - if (r < 0) - return r; + if (route->manager) { + route_detach_from_nexthop(route); + set_remove(route->manager->routes, route); + route->manager = NULL; + return route; + } - route->protocol = RTPROT_STATIC; - route->network = network; - route->section = TAKE_PTR(n); - route->source = NETWORK_CONFIG_SOURCE_STATIC; + if (route->wireguard) { + set_remove(route->wireguard->routes, route); + route->wireguard = NULL; + return route; + } - r = hashmap_ensure_put(&network->routes_by_section, &config_section_hash_ops, route->section, route); - if (r < 0) - return r; + return NULL; +} - *ret = TAKE_PTR(route); - return 0; +static void route_detach(Route *route) { + route_unref(route_detach_impl(route)); } -Route *route_free(Route *route) { +static Route* route_free(Route *route) { if (!route) return NULL; - if (route->network) { - assert(route->section); - hashmap_remove(route->network->routes_by_section, route->section); - } + route_detach_impl(route); config_section_free(route->section); - - if (route->link) - set_remove(route->link->routes, route); - - if (route->manager) - set_remove(route->manager->routes, route); - - ordered_set_free_with_destructor(route->multipath_routes, multipath_route_free); - + route_nexthops_done(route); + route_metric_done(&route->metric); sd_event_source_disable_unref(route->expire); - free(route->tcp_congestion_control_algo); - return mfree(route); } +DEFINE_TRIVIAL_REF_UNREF_FUNC(Route, route, route_free); + static void route_hash_func(const Route *route, struct siphash *state) { assert(route); - siphash24_compress(&route->family, sizeof(route->family), state); + siphash24_compress_typesafe(route->family, state); switch (route->family) { case AF_INET: - case AF_INET6: - siphash24_compress(&route->dst_prefixlen, sizeof(route->dst_prefixlen), state); - siphash24_compress(&route->dst, FAMILY_ADDRESS_SIZE(route->family), state); - - siphash24_compress(&route->src_prefixlen, sizeof(route->src_prefixlen), state); - siphash24_compress(&route->src, FAMILY_ADDRESS_SIZE(route->family), state); - - siphash24_compress(&route->gw_family, sizeof(route->gw_family), state); - if (IN_SET(route->gw_family, AF_INET, AF_INET6)) { - siphash24_compress(&route->gw, FAMILY_ADDRESS_SIZE(route->gw_family), state); - siphash24_compress(&route->gw_weight, sizeof(route->gw_weight), state); - } + /* First, the table, destination prefix, priority, and tos (dscp), are used to find routes. + * See fib_table_insert(), fib_find_node(), and fib_find_alias() in net/ipv4/fib_trie.c of the kernel. */ + siphash24_compress_typesafe(route->table, state); + in_addr_hash_func(&route->dst, route->family, state); + siphash24_compress_typesafe(route->dst_prefixlen, state); + siphash24_compress_typesafe(route->priority, state); + siphash24_compress_typesafe(route->tos, state); + + /* Then, protocol, scope, type, flags, prefsrc, metrics (RTAX_* attributes), and nexthops (gateways) + * are used to find routes. See fib_find_info() in net/ipv4/fib_semantics.c of the kernel. */ + siphash24_compress_typesafe(route->protocol, state); + siphash24_compress_typesafe(route->scope, state); + siphash24_compress_typesafe(route->type, state); + unsigned flags = route->flags & ~RTNH_COMPARE_MASK; + siphash24_compress_typesafe(flags, state); + in_addr_hash_func(&route->prefsrc, route->family, state); + + /* nexthops (id, number of nexthops, nexthop) */ + route_nexthops_hash_func(route, state); + + /* metrics */ + route_metric_hash_func(&route->metric, state); + break; - siphash24_compress(&route->prefsrc, FAMILY_ADDRESS_SIZE(route->family), state); + case AF_INET6: + /* First, table and destination prefix are used for classifying routes. + * See fib6_add() and fib6_add_1() in net/ipv6/ip6_fib.c of the kernel. */ + siphash24_compress_typesafe(route->table, state); + in_addr_hash_func(&route->dst, route->family, state); + siphash24_compress_typesafe(route->dst_prefixlen, state); - siphash24_compress(&route->tos, sizeof(route->tos), state); - siphash24_compress(&route->priority, sizeof(route->priority), state); - siphash24_compress(&route->table, sizeof(route->table), state); - siphash24_compress(&route->protocol, sizeof(route->protocol), state); - siphash24_compress(&route->scope, sizeof(route->scope), state); - siphash24_compress(&route->type, sizeof(route->type), state); + /* Then, source prefix is used. See fib6_add(). */ + in_addr_hash_func(&route->src, route->family, state); + siphash24_compress_typesafe(route->src_prefixlen, state); - siphash24_compress(&route->initcwnd, sizeof(route->initcwnd), state); - siphash24_compress(&route->initrwnd, sizeof(route->initrwnd), state); + /* See fib6_add_rt2node(). */ + siphash24_compress_typesafe(route->priority, state); - siphash24_compress(&route->advmss, sizeof(route->advmss), state); - siphash24_compress(&route->nexthop_id, sizeof(route->nexthop_id), state); + /* See rt6_duplicate_nexthop() in include/net/ip6_route.h of the kernel. + * Here, we hash nexthop in a similar way as the one for IPv4. */ + route_nexthops_hash_func(route, state); + /* Unlike IPv4 routes, metrics are not taken into account. */ break; + default: /* treat any other address family as AF_UNSPEC */ break; @@ -163,8 +134,7 @@ static int route_compare_func(const Route *a, const Route *b) { switch (a->family) { case AF_INET: - case AF_INET6: - r = CMP(a->dst_prefixlen, b->dst_prefixlen); + r = CMP(a->table, b->table); if (r != 0) return r; @@ -172,73 +142,71 @@ static int route_compare_func(const Route *a, const Route *b) { if (r != 0) return r; - r = CMP(a->src_prefixlen, b->src_prefixlen); + r = CMP(a->dst_prefixlen, b->dst_prefixlen); if (r != 0) return r; - r = memcmp(&a->src, &b->src, FAMILY_ADDRESS_SIZE(a->family)); + r = CMP(a->priority, b->priority); if (r != 0) return r; - r = CMP(a->gw_family, b->gw_family); + r = CMP(a->tos, b->tos); if (r != 0) return r; - if (IN_SET(a->gw_family, AF_INET, AF_INET6)) { - r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family)); - if (r != 0) - return r; - - r = CMP(a->gw_weight, b->gw_weight); - if (r != 0) - return r; - } + r = CMP(a->protocol, b->protocol); + if (r != 0) + return r; - r = memcmp(&a->prefsrc, &b->prefsrc, FAMILY_ADDRESS_SIZE(a->family)); + r = CMP(a->scope, b->scope); if (r != 0) return r; - r = CMP(a->tos, b->tos); + r = CMP(a->type, b->type); if (r != 0) return r; - r = CMP(a->priority, b->priority); + r = CMP(a->flags & ~RTNH_COMPARE_MASK, b->flags & ~RTNH_COMPARE_MASK); if (r != 0) return r; - r = CMP(a->table, b->table); + r = memcmp(&a->prefsrc, &b->prefsrc, FAMILY_ADDRESS_SIZE(a->family)); if (r != 0) return r; - r = CMP(a->protocol, b->protocol); + r = route_nexthops_compare_func(a, b); if (r != 0) return r; - r = CMP(a->scope, b->scope); + return route_metric_compare_func(&a->metric, &b->metric); + + case AF_INET6: + r = CMP(a->table, b->table); if (r != 0) return r; - r = CMP(a->type, b->type); + r = memcmp(&a->dst, &b->dst, FAMILY_ADDRESS_SIZE(a->family)); if (r != 0) return r; - r = CMP(a->initcwnd, b->initcwnd); + r = CMP(a->dst_prefixlen, b->dst_prefixlen); if (r != 0) return r; - r = CMP(a->initrwnd, b->initrwnd); + r = memcmp(&a->src, &b->src, FAMILY_ADDRESS_SIZE(a->family)); if (r != 0) return r; - r = CMP(a->advmss, b->advmss); + r = CMP(a->src_prefixlen, b->src_prefixlen); if (r != 0) return r; - r = CMP(a->nexthop_id, b->nexthop_id); + r = CMP(a->priority, b->priority); if (r != 0) return r; - return 0; + return route_nexthops_compare_func(a, b); + default: /* treat any other address family as AF_UNSPEC */ return 0; @@ -250,317 +218,209 @@ DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( Route, route_hash_func, route_compare_func, - route_free); - -static bool route_type_is_reject(const Route *route) { - assert(route); - - return IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW); -} - -static bool route_needs_convert(const Route *route) { - assert(route); - - return route->nexthop_id > 0 || !ordered_set_isempty(route->multipath_routes); -} - -static int route_add(Manager *manager, Link *link, Route *route) { - int r; - - assert(route); - - if (route_type_is_reject(route)) { - assert(manager); - - r = set_ensure_put(&manager->routes, &route_hash_ops, route); - if (r < 0) - return r; - if (r == 0) - return -EEXIST; - - route->manager = manager; - } else { - assert(link); - - r = set_ensure_put(&link->routes, &route_hash_ops, route); - if (r < 0) - return r; - if (r == 0) - return -EEXIST; - - route->link = link; - } - - return 0; -} + route_detach); -int route_get(Manager *manager, Link *link, const Route *in, Route **ret) { - Route *route; - - assert(in); +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( + route_hash_ops_unref, + Route, + route_hash_func, + route_compare_func, + route_unref); - if (route_type_is_reject(in)) { - if (!manager) - return -ENOENT; +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + route_section_hash_ops, + ConfigSection, + config_section_hash_func, + config_section_compare_func, + Route, + route_detach); - route = set_get(manager->routes, in); - } else { - if (!link) - return -ENOENT; +int route_new(Route **ret) { + _cleanup_(route_unrefp) Route *route = NULL; - route = set_get(link->routes, in); - } + route = new(Route, 1); if (!route) - return -ENOENT; + return -ENOMEM; - if (ret) - *ret = route; + *route = (Route) { + .n_ref = 1, + .family = AF_UNSPEC, + .scope = RT_SCOPE_UNIVERSE, + .protocol = RTPROT_UNSPEC, + .type = RTN_UNICAST, + .table = RT_TABLE_MAIN, + .lifetime_usec = USEC_INFINITY, + .gateway_onlink = -1, + }; + + *ret = TAKE_PTR(route); return 0; } -int route_dup(const Route *src, Route **ret) { - _cleanup_(route_freep) Route *dest = NULL; +int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret) { + _cleanup_(config_section_freep) ConfigSection *n = NULL; + _cleanup_(route_unrefp) Route *route = NULL; int r; - /* This does not copy mulipath routes. */ - - assert(src); + assert(network); assert(ret); + assert(filename); + assert(section_line > 0); - dest = newdup(Route, src, 1); - if (!dest) - return -ENOMEM; - - /* Unset all pointers */ - dest->network = NULL; - dest->section = NULL; - dest->link = NULL; - dest->manager = NULL; - dest->multipath_routes = NULL; - dest->expire = NULL; - dest->tcp_congestion_control_algo = NULL; - - r = free_and_strdup(&dest->tcp_congestion_control_algo, src->tcp_congestion_control_algo); + r = config_section_new(filename, section_line, &n); if (r < 0) return r; - *ret = TAKE_PTR(dest); - return 0; -} - -static void route_apply_nexthop(Route *route, const NextHop *nh, uint8_t nh_weight) { - assert(route); - assert(nh); - assert(hashmap_isempty(nh->group)); + route = hashmap_get(network->routes_by_section, n); + if (route) { + *ret = TAKE_PTR(route); + return 0; + } - route->gw_family = nh->family; - route->gw = nh->gw; + if (hashmap_size(network->routes_by_section) >= routes_max()) + return -E2BIG; - if (nh_weight != UINT8_MAX) - route->gw_weight = nh_weight; + r = route_new(&route); + if (r < 0) + return r; - if (nh->blackhole) - route->type = RTN_BLACKHOLE; -} + route->protocol = RTPROT_STATIC; + route->network = network; + route->section = TAKE_PTR(n); + route->source = NETWORK_CONFIG_SOURCE_STATIC; -static void route_apply_multipath_route(Route *route, const MultipathRoute *m) { - assert(route); - assert(m); + r = hashmap_ensure_put(&network->routes_by_section, &route_section_hash_ops, route->section, route); + if (r < 0) + return r; - route->gw_family = m->gateway.family; - route->gw = m->gateway.address; - route->gw_weight = m->weight; + *ret = TAKE_PTR(route); + return 0; } -static int multipath_route_get_link(Manager *manager, const MultipathRoute *m, Link **ret) { +static int route_attach(Manager *manager, Route *route) { int r; assert(manager); - assert(m); - - if (m->ifname) { - r = link_get_by_name(manager, m->ifname, ret); - return r < 0 ? r : 1; + assert(route); + assert(!route->network); + assert(!route->wireguard); - } else if (m->ifindex > 0) { /* Always ignore ifindex if ifname is set. */ - r = link_get_by_index(manager, m->ifindex, ret); - return r < 0 ? r : 1; - } + r = set_ensure_put(&manager->routes, &route_hash_ops, route); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; - if (ret) - *ret = NULL; + route->manager = manager; return 0; } -typedef struct ConvertedRoutes { - size_t n; - Route **routes; - Link **links; -} ConvertedRoutes; - -static ConvertedRoutes *converted_routes_free(ConvertedRoutes *c) { - if (!c) - return NULL; - - for (size_t i = 0; i < c->n; i++) - route_free(c->routes[i]); - - free(c->routes); - free(c->links); - - return mfree(c); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(ConvertedRoutes*, converted_routes_free); - -static int converted_routes_new(size_t n, ConvertedRoutes **ret) { - _cleanup_(converted_routes_freep) ConvertedRoutes *c = NULL; - _cleanup_free_ Route **routes = NULL; - _cleanup_free_ Link **links = NULL; +int route_get(Manager *manager, const Route *route, Route **ret) { + Route *existing; - assert(n > 0); - assert(ret); - - routes = new0(Route*, n); - if (!routes) - return -ENOMEM; - - links = new0(Link*, n); - if (!links) - return -ENOMEM; + assert(manager); + assert(route); - c = new(ConvertedRoutes, 1); - if (!c) - return -ENOMEM; + existing = set_get(manager->routes, route); + if (!existing) + return -ENOENT; - *c = (ConvertedRoutes) { - .n = n, - .routes = TAKE_PTR(routes), - .links = TAKE_PTR(links), - }; + if (ret) + *ret = existing; - *ret = TAKE_PTR(c); return 0; } -static int route_convert(Manager *manager, const Route *route, ConvertedRoutes **ret) { - _cleanup_(converted_routes_freep) ConvertedRoutes *c = NULL; +static int route_get_link(Manager *manager, const Route *route, Link **ret) { int r; assert(manager); assert(route); - assert(ret); - if (!route_needs_convert(route)) { - *ret = NULL; - return 0; - } - - if (route->nexthop_id > 0) { - struct nexthop_grp *nhg; + if (route->nexthop_id != 0) { NextHop *nh; - r = manager_get_nexthop_by_id(manager, route->nexthop_id, &nh); + r = nexthop_get_by_id(manager, route->nexthop_id, &nh); if (r < 0) return r; - if (hashmap_isempty(nh->group)) { - r = converted_routes_new(1, &c); - if (r < 0) - return r; - - r = route_dup(route, &c->routes[0]); - if (r < 0) - return r; - - route_apply_nexthop(c->routes[0], nh, UINT8_MAX); - c->links[0] = nh->link; - - *ret = TAKE_PTR(c); - return 1; - } + return link_get_by_index(manager, nh->ifindex, ret); + } - r = converted_routes_new(hashmap_size(nh->group), &c); - if (r < 0) - return r; + return route_nexthop_get_link(manager, &route->nexthop, ret); +} - size_t i = 0; - HASHMAP_FOREACH(nhg, nh->group) { - NextHop *h; +int route_get_request(Manager *manager, const Route *route, Request **ret) { + Request *req; - r = manager_get_nexthop_by_id(manager, nhg->id, &h); - if (r < 0) - return r; + assert(manager); + assert(route); - r = route_dup(route, &c->routes[i]); - if (r < 0) - return r; + req = ordered_set_get(manager->request_queue, + &(const Request) { + .type = REQUEST_TYPE_ROUTE, + .userdata = (void*) route, + .hash_func = (hash_func_t) route_hash_func, + .compare_func = (compare_func_t) route_compare_func, + }); + if (!req) + return -ENOENT; - route_apply_nexthop(c->routes[i], h, nhg->weight); - c->links[i] = h->link; + if (ret) + *ret = req; + return 0; +} - i++; - } +int route_dup(const Route *src, const RouteNextHop *nh, Route **ret) { + _cleanup_(route_unrefp) Route *dest = NULL; + int r; - *ret = TAKE_PTR(c); - return 1; + assert(src); + assert(ret); - } + dest = newdup(Route, src, 1); + if (!dest) + return -ENOMEM; - assert(!ordered_set_isempty(route->multipath_routes)); + /* Unset number of reference and all pointers */ + dest->n_ref = 1; + dest->manager = NULL; + dest->network = NULL; + dest->wireguard = NULL; + dest->section = NULL; + dest->nexthop = ROUTE_NEXTHOP_NULL; + dest->nexthops = NULL; + dest->metric = ROUTE_METRIC_NULL; + dest->expire = NULL; - r = converted_routes_new(ordered_set_size(route->multipath_routes), &c); + r = route_nexthops_copy(src, nh, dest); if (r < 0) return r; - size_t i = 0; - MultipathRoute *m; - ORDERED_SET_FOREACH(m, route->multipath_routes) { - r = route_dup(route, &c->routes[i]); - if (r < 0) - return r; - - route_apply_multipath_route(c->routes[i], m); - - r = multipath_route_get_link(manager, m, &c->links[i]); - if (r < 0) - return r; - - i++; - } - - *ret = TAKE_PTR(c); - return 1; -} - -void link_mark_routes(Link *link, NetworkConfigSource source) { - Route *route; - - assert(link); - - SET_FOREACH(route, link->routes) { - if (route->source != source) - continue; + r = route_metric_copy(&src->metric, &dest->metric); + if (r < 0) + return r; - route_mark(route); - } + *ret = TAKE_PTR(dest); + return 0; } -static void log_route_debug(const Route *route, const char *str, const Link *link, const Manager *manager) { - _cleanup_free_ char *state = NULL, *gw_alloc = NULL, *prefsrc = NULL, +static void log_route_debug(const Route *route, const char *str, Manager *manager) { + _cleanup_free_ char *state = NULL, *nexthop = NULL, *prefsrc = NULL, *table = NULL, *scope = NULL, *proto = NULL, *flags = NULL; - const char *gw = NULL, *dst, *src; + const char *dst, *src; + Link *link = NULL; assert(route); assert(str); assert(manager); - /* link may be NULL. */ - if (!DEBUG_LOGGING) return; + (void) route_get_link(manager, route, &link); + (void) network_config_state_to_string_alloc(route->state, &state); dst = in_addr_is_set(route->family, &route->dst) || route->dst_prefixlen > 0 ? @@ -568,32 +428,8 @@ static void log_route_debug(const Route *route, const char *str, const Link *lin src = in_addr_is_set(route->family, &route->src) || route->src_prefixlen > 0 ? IN_ADDR_PREFIX_TO_STRING(route->family, &route->src, route->src_prefixlen) : NULL; - if (in_addr_is_set(route->gw_family, &route->gw)) { - (void) in_addr_to_string(route->gw_family, &route->gw, &gw_alloc); - gw = gw_alloc; - } else if (route->gateway_from_dhcp_or_ra) { - if (route->gw_family == AF_INET) - gw = "_dhcp4"; - else if (route->gw_family == AF_INET6) - gw = "_ipv6ra"; - } else { - MultipathRoute *m; - - ORDERED_SET_FOREACH(m, route->multipath_routes) { - _cleanup_free_ char *buf = NULL; - union in_addr_union a = m->gateway.address; - - (void) in_addr_to_string(m->gateway.family, &a, &buf); - (void) strextend_with_separator(&gw_alloc, ",", strna(buf)); - if (m->ifname) - (void) strextend(&gw_alloc, "@", m->ifname); - else if (m->ifindex > 0) - (void) strextendf(&gw_alloc, "@%i", m->ifindex); - /* See comments in config_parse_multipath_route(). */ - (void) strextendf(&gw_alloc, ":%"PRIu32, m->weight + 1); - } - gw = gw_alloc; - } + (void) route_nexthops_to_string(route, &nexthop); + if (in_addr_is_set(route->family, &route->prefsrc)) (void) in_addr_to_string(route->family, &route->prefsrc, &prefsrc); (void) route_scope_to_string_alloc(route->scope, &scope); @@ -602,426 +438,203 @@ static void log_route_debug(const Route *route, const char *str, const Link *lin (void) route_flags_to_string_alloc(route->flags, &flags); log_link_debug(link, - "%s %s route (%s): dst: %s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, " - "proto: %s, type: %s, nexthop: %"PRIu32", priority: %"PRIu32", flags: %s", + "%s %s route (%s): dst: %s, src: %s, %s, prefsrc: %s, " + "table: %s, priority: %"PRIu32", " + "proto: %s, scope: %s, type: %s, flags: %s", str, strna(network_config_source_to_string(route->source)), strna(state), - strna(dst), strna(src), strna(gw), strna(prefsrc), - strna(scope), strna(table), strna(proto), - strna(route_type_to_string(route->type)), - route->nexthop_id, route->priority, strna(flags)); + strna(dst), strna(src), strna(nexthop), strna(prefsrc), + strna(table), route->priority, + strna(proto), strna(scope), strna(route_type_to_string(route->type)), strna(flags)); } -static int route_set_netlink_message(const Route *route, sd_netlink_message *req, Link *link) { +static int route_set_netlink_message(const Route *route, sd_netlink_message *m) { int r; assert(route); - assert(req); - - /* link may be NULL */ - - if (in_addr_is_set(route->gw_family, &route->gw) && route->nexthop_id == 0) { - if (route->gw_family == route->family) { - r = netlink_message_append_in_addr_union(req, RTA_GATEWAY, route->gw_family, &route->gw); - if (r < 0) - return r; - } else { - RouteVia rtvia = { - .family = route->gw_family, - .address = route->gw, - }; - - r = sd_netlink_message_append_data(req, RTA_VIA, &rtvia, sizeof(rtvia)); - if (r < 0) - return r; - } - } + assert(m); + /* rtmsg header (and relevant attributes) */ if (route->dst_prefixlen > 0) { - r = netlink_message_append_in_addr_union(req, RTA_DST, route->family, &route->dst); + r = netlink_message_append_in_addr_union(m, RTA_DST, route->family, &route->dst); if (r < 0) return r; - r = sd_rtnl_message_route_set_dst_prefixlen(req, route->dst_prefixlen); + r = sd_rtnl_message_route_set_dst_prefixlen(m, route->dst_prefixlen); if (r < 0) return r; } if (route->src_prefixlen > 0) { - r = netlink_message_append_in_addr_union(req, RTA_SRC, route->family, &route->src); + r = netlink_message_append_in_addr_union(m, RTA_SRC, route->family, &route->src); if (r < 0) return r; - r = sd_rtnl_message_route_set_src_prefixlen(req, route->src_prefixlen); + r = sd_rtnl_message_route_set_src_prefixlen(m, route->src_prefixlen); if (r < 0) return r; } - if (in_addr_is_set(route->family, &route->prefsrc)) { - r = netlink_message_append_in_addr_union(req, RTA_PREFSRC, route->family, &route->prefsrc); - if (r < 0) - return r; - } + r = sd_rtnl_message_route_set_tos(m, route->tos); + if (r < 0) + return r; - r = sd_rtnl_message_route_set_scope(req, route->scope); + r = sd_rtnl_message_route_set_scope(m, route->scope); if (r < 0) return r; - r = sd_rtnl_message_route_set_flags(req, route->flags & RTNH_F_ONLINK); + r = sd_rtnl_message_route_set_type(m, route->type); + if (r < 0) + return r; + + r = sd_rtnl_message_route_set_flags(m, route->flags & ~RTNH_COMPARE_MASK); if (r < 0) return r; + /* attributes */ + r = sd_netlink_message_append_u32(m, RTA_PRIORITY, route->priority); + if (r < 0) + return r; + + if (in_addr_is_set(route->family, &route->prefsrc)) { + r = netlink_message_append_in_addr_union(m, RTA_PREFSRC, route->family, &route->prefsrc); + if (r < 0) + return r; + } + if (route->table < 256) { - r = sd_rtnl_message_route_set_table(req, route->table); + r = sd_rtnl_message_route_set_table(m, route->table); if (r < 0) return r; } else { - r = sd_rtnl_message_route_set_table(req, RT_TABLE_UNSPEC); + r = sd_rtnl_message_route_set_table(m, RT_TABLE_UNSPEC); if (r < 0) return r; /* Table attribute to allow more than 256. */ - r = sd_netlink_message_append_u32(req, RTA_TABLE, route->table); - if (r < 0) - return r; - } - - if (!route_type_is_reject(route) && - route->nexthop_id == 0 && - ordered_set_isempty(route->multipath_routes)) { - assert(link); /* Those routes must be attached to a specific link */ - - r = sd_netlink_message_append_u32(req, RTA_OIF, link->ifindex); + r = sd_netlink_message_append_u32(m, RTA_TABLE, route->table); if (r < 0) return r; } - if (route->nexthop_id > 0) { - r = sd_netlink_message_append_u32(req, RTA_NH_ID, route->nexthop_id); - if (r < 0) - return r; - } + r = sd_netlink_message_append_u8(m, RTA_PREF, route->pref); + if (r < 0) + return r; - r = sd_netlink_message_append_u8(req, RTA_PREF, route->pref); + /* nexthops */ + r = route_nexthops_set_netlink_message(route, m); if (r < 0) return r; - r = sd_netlink_message_append_u32(req, RTA_PRIORITY, route->priority); + /* metrics */ + r = route_metric_set_netlink_message(&route->metric, m); if (r < 0) return r; return 0; } -static int route_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { +static int route_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, RemoveRequest *rreq) { int r; assert(m); + assert(rreq); - /* link may be NULL. */ - - if (link && IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) - return 0; + Manager *manager = ASSERT_PTR(rreq->manager); + Route *route = ASSERT_PTR(rreq->userdata); r = sd_netlink_message_get_errno(m); - if (r < 0 && r != -ESRCH) - log_link_message_warning_errno(link, m, r, "Could not drop route, ignoring"); + if (r < 0) { + log_message_full_errno(m, + (r == -ESRCH || /* the route is already removed? */ + (r == -EINVAL && route->nexthop_id != 0) || /* The nexthop is already removed? */ + !route->manager) ? /* already detached? */ + LOG_DEBUG : LOG_WARNING, + r, "Could not drop route, ignoring"); + + if (route->manager) { + /* If the route cannot be removed, then assume the route is already removed. */ + log_route_debug(route, "Forgetting", manager); + + Request *req; + if (route_get_request(manager, route, &req) >= 0) + route_enter_removed(req->userdata); + + route_detach(route); + } + } return 1; } -int route_remove(Route *route) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; - unsigned char type; - Manager *manager; - Link *link; +int route_remove(Route *route, Manager *manager) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + Link *link = NULL; int r; assert(route); - assert(route->manager || (route->link && route->link->manager)); - assert(IN_SET(route->family, AF_INET, AF_INET6)); + assert(manager); - link = route->link; - manager = route->manager ?: link->manager; + /* If the route is remembered, then use the remembered object. */ + (void) route_get(manager, route, &route); - log_route_debug(route, "Removing", link, manager); + log_route_debug(route, "Removing", manager); - r = sd_rtnl_message_new_route(manager->rtnl, &req, - RTM_DELROUTE, route->family, - route->protocol); - if (r < 0) - return log_link_error_errno(link, r, "Could not create netlink message: %m"); - - if (route->family == AF_INET && route->nexthop_id > 0 && route->type == RTN_BLACKHOLE) - /* When IPv4 route has nexthop id and the nexthop type is blackhole, even though kernel - * sends RTM_NEWROUTE netlink message with blackhole type, kernel's internal route type - * fib_rt_info::type may not be blackhole. Thus, we cannot know the internal value. - * Moreover, on route removal, the matching is done with the hidden value if we set - * non-zero type in RTM_DELROUTE message. Note, sd_rtnl_message_new_route() sets - * RTN_UNICAST by default. So, we need to clear the type here. */ - type = RTN_UNSPEC; - else - type = route->type; + /* For logging. */ + (void) route_get_link(manager, route, &link); - r = sd_rtnl_message_route_set_type(req, type); + r = sd_rtnl_message_new_route(manager->rtnl, &m, RTM_DELROUTE, route->family, route->protocol); if (r < 0) - return log_link_error_errno(link, r, "Could not set route type: %m"); + return log_link_warning_errno(link, r, "Could not create netlink message: %m"); - r = route_set_netlink_message(route, req, link); + r = route_set_netlink_message(route, m); if (r < 0) - return log_error_errno(r, "Could not fill netlink message: %m"); + return log_link_warning_errno(link, r, "Could not fill netlink message: %m"); - r = netlink_call_async(manager->rtnl, NULL, req, route_remove_handler, - link ? link_netlink_destroy_callback : NULL, link); + r = manager_remove_request_add(manager, route, route, manager->rtnl, m, route_remove_handler); if (r < 0) - return log_link_error_errno(link, r, "Could not send netlink message: %m"); - - link_ref(link); + return log_link_warning_errno(link, r, "Could not queue rtnetlink message: %m"); route_enter_removing(route); return 0; } -int route_remove_and_drop(Route *route) { - if (!route) - return 0; - - route_cancel_request(route, NULL); - - if (route_exists(route)) - return route_remove(route); - - if (route->state == 0) - route_free(route); - - return 0; -} - -static void manager_mark_routes(Manager *manager, bool foreign, const Link *except) { - Route *route; - Link *link; - int r; - - assert(manager); - - /* First, mark all routes. */ - SET_FOREACH(route, manager->routes) { - /* Do not touch routes managed by the kernel. */ - if (route->protocol == RTPROT_KERNEL) - continue; - - /* When 'foreign' is true, mark only foreign routes, and vice versa. */ - if (foreign != (route->source == NETWORK_CONFIG_SOURCE_FOREIGN)) - continue; - - /* Do not touch dynamic routes. They will removed by dhcp_pd_prefix_lost() */ - if (IN_SET(route->source, NETWORK_CONFIG_SOURCE_DHCP4, NETWORK_CONFIG_SOURCE_DHCP6)) - continue; - - /* Ignore routes not assigned yet or already removed. */ - if (!route_exists(route)) - continue; - - route_mark(route); - } - - /* Then, unmark all routes requested by active links. */ - HASHMAP_FOREACH(link, manager->links_by_index) { - if (link == except) - continue; - - if (!link->network) - continue; - - if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) - continue; - - HASHMAP_FOREACH(route, link->network->routes_by_section) { - _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL; - Route *existing; - - r = route_convert(manager, route, &converted); - if (r < 0) - continue; - if (r == 0) { - if (route_get(manager, NULL, route, &existing) >= 0) - route_unmark(existing); - continue; - } - - for (size_t i = 0; i < converted->n; i++) - if (route_get(manager, NULL, converted->routes[i], &existing) >= 0) - route_unmark(existing); - } - } -} - -static int manager_drop_marked_routes(Manager *manager) { - Route *route; - int r = 0; - - assert(manager); - - SET_FOREACH(route, manager->routes) { - if (!route_is_marked(route)) - continue; - - RET_GATHER(r, route_remove(route)); - } - - return r; -} +int route_remove_and_cancel(Route *route, Manager *manager) { + _cleanup_(request_unrefp) Request *req = NULL; + bool waiting = false; -static bool route_by_kernel(const Route *route) { assert(route); + assert(manager); - if (route->protocol == RTPROT_KERNEL) - return true; - - /* The kernels older than a826b04303a40d52439aa141035fca5654ccaccd (v5.11) create the IPv6 - * multicast with RTPROT_BOOT. Do not touch it. */ - if (route->protocol == RTPROT_BOOT && - route->family == AF_INET6 && - route->dst_prefixlen == 8 && - in6_addr_equal(&route->dst.in6, & (struct in6_addr) {{{ 0xff,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 }}})) - return true; - - return false; -} - -static void link_unmark_wireguard_routes(Link *link) { - assert(link); - - if (!link->netdev || link->netdev->kind != NETDEV_KIND_WIREGUARD) - return; - - Route *route, *existing; - Wireguard *w = WIREGUARD(link->netdev); - - SET_FOREACH(route, w->routes) - if (route_get(NULL, link, route, &existing) >= 0) - route_unmark(existing); -} - -int link_drop_foreign_routes(Link *link) { - Route *route; - int r; - - assert(link); - assert(link->manager); - assert(link->network); - - SET_FOREACH(route, link->routes) { - /* do not touch routes managed by the kernel */ - if (route_by_kernel(route)) - continue; - - /* Do not remove routes we configured. */ - if (route->source != NETWORK_CONFIG_SOURCE_FOREIGN) - continue; - - /* Ignore routes not assigned yet or already removed. */ - if (!route_exists(route)) - continue; - - if (route->protocol == RTPROT_STATIC && - FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_STATIC)) - continue; - - if (route->protocol == RTPROT_DHCP && - FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) - continue; - - route_mark(route); - } - - HASHMAP_FOREACH(route, link->network->routes_by_section) { - _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL; - Route *existing; - - r = route_convert(link->manager, route, &converted); - if (r < 0) - continue; - if (r == 0) { - if (route_get(NULL, link, route, &existing) >= 0) - route_unmark(existing); - continue; - } - - for (size_t i = 0; i < converted->n; i++) - if (route_get(NULL, link, converted->routes[i], &existing) >= 0) - route_unmark(existing); - } - - link_unmark_wireguard_routes(link); - - r = 0; - SET_FOREACH(route, link->routes) { - if (!route_is_marked(route)) - continue; - - RET_GATHER(r, route_remove(route)); - } - - manager_mark_routes(link->manager, /* foreign = */ true, NULL); - - return RET_GATHER(r, manager_drop_marked_routes(link->manager)); -} - -int link_drop_managed_routes(Link *link) { - Route *route; - int r = 0; - - assert(link); - - SET_FOREACH(route, link->routes) { - /* do not touch routes managed by the kernel */ - if (route_by_kernel(route)) - continue; - - /* Do not touch routes managed by kernel or other tools. */ - if (route->source == NETWORK_CONFIG_SOURCE_FOREIGN) - continue; - - if (!route_exists(route)) - continue; + /* If the route is remembered by the manager, then use the remembered object. */ + (void) route_get(manager, route, &route); - RET_GATHER(r, route_remove(route)); + /* Cancel the request for the route. If the request is already called but we have not received the + * notification about the request, then explicitly remove the route. */ + if (route_get_request(manager, route, &req) >= 0) { + request_ref(req); /* avoid the request freed by request_detach() */ + waiting = req->waiting_reply; + request_detach(req); + route_cancel_requesting(route); } - manager_mark_routes(link->manager, /* foreign = */ false, link); + /* If we know that the route will come or already exists, remove it. */ + if (waiting || (route->manager && route_exists(route))) + return route_remove(route, manager); - return RET_GATHER(r, manager_drop_marked_routes(link->manager)); -} - -void link_foreignize_routes(Link *link) { - Route *route; - - assert(link); - - SET_FOREACH(route, link->routes) - route->source = NETWORK_CONFIG_SOURCE_FOREIGN; - - manager_mark_routes(link->manager, /* foreign = */ false, link); - - SET_FOREACH(route, link->manager->routes) { - if (!route_is_marked(route)) - continue; - - route->source = NETWORK_CONFIG_SOURCE_FOREIGN; - } + return 0; } static int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdata) { Route *route = ASSERT_PTR(userdata); - Link *link; int r; - assert(route->manager || (route->link && route->link->manager)); - - link = route->link; /* This may be NULL. */ + if (!route->manager) + return 0; /* already detached. */ - r = route_remove(route); + r = route_remove(route, route->manager); if (r < 0) { + Link *link = NULL; + (void) route_get_link(route->manager, route, &link); log_link_warning_errno(link, r, "Could not remove route: %m"); if (link) link_enter_failed(link); @@ -1031,124 +644,67 @@ static int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdat } static int route_setup_timer(Route *route, const struct rta_cacheinfo *cacheinfo) { - Manager *manager; int r; assert(route); - assert(route->manager || (route->link && route->link->manager)); - - manager = route->manager ?: route->link->manager; - - if (route->lifetime_usec == USEC_INFINITY) - return 0; if (cacheinfo && cacheinfo->rta_expires != 0) - /* Assume that non-zero rta_expires means kernel will handle the route expiration. */ + route->expiration_managed_by_kernel = true; + + if (route->lifetime_usec == USEC_INFINITY || /* We do not request expiration for the route. */ + route->expiration_managed_by_kernel) { /* We have received nonzero expiration previously. The expiration is managed by the kernel. */ + route->expire = sd_event_source_disable_unref(route->expire); return 0; + } + Manager *manager = ASSERT_PTR(route->manager); r = event_reset_time(manager->event, &route->expire, CLOCK_BOOTTIME, route->lifetime_usec, 0, route_expire_handler, route, 0, "route-expiration", true); - if (r < 0) - return r; + if (r < 0) { + Link *link = NULL; + (void) route_get_link(manager, route, &link); + return log_link_warning_errno(link, r, "Failed to configure expiration timer for route, ignoring: %m"); + } + log_route_debug(route, "Configured expiration timer for", manager); return 1; } -static int append_nexthop_one(const Link *link, const Route *route, const MultipathRoute *m, struct rtattr **rta, size_t offset) { - struct rtnexthop *rtnh; - struct rtattr *new_rta; +int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, Route *route, const char *error_msg) { int r; - assert(route); assert(m); - assert(rta); - assert(*rta); - - new_rta = realloc(*rta, RTA_ALIGN((*rta)->rta_len) + RTA_SPACE(sizeof(struct rtnexthop))); - if (!new_rta) - return -ENOMEM; - *rta = new_rta; - - rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); - *rtnh = (struct rtnexthop) { - .rtnh_len = sizeof(*rtnh), - .rtnh_ifindex = m->ifindex > 0 ? m->ifindex : link->ifindex, - .rtnh_hops = m->weight, - }; - - (*rta)->rta_len += sizeof(struct rtnexthop); - - if (route->family == m->gateway.family) { - r = rtattr_append_attribute(rta, RTA_GATEWAY, &m->gateway.address, FAMILY_ADDRESS_SIZE(m->gateway.family)); - if (r < 0) - goto clear; - rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); - rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(m->gateway.family)); - } else { - r = rtattr_append_attribute(rta, RTA_VIA, &m->gateway, FAMILY_ADDRESS_SIZE(m->gateway.family) + sizeof(m->gateway.family)); - if (r < 0) - goto clear; - rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); - rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(m->gateway.family) + sizeof(m->gateway.family)); - } - - return 0; - -clear: - (*rta)->rta_len -= sizeof(struct rtnexthop); - return r; -} - -static int append_nexthops(const Link *link, const Route *route, sd_netlink_message *req) { - _cleanup_free_ struct rtattr *rta = NULL; - struct rtnexthop *rtnh; - MultipathRoute *m; - size_t offset; - int r; - assert(link); + assert(link->manager); assert(route); - assert(req); - - if (ordered_set_isempty(route->multipath_routes)) - return 0; - - rta = new(struct rtattr, 1); - if (!rta) - return -ENOMEM; - - *rta = (struct rtattr) { - .rta_type = RTA_MULTIPATH, - .rta_len = RTA_LENGTH(0), - }; - offset = (uint8_t *) RTA_DATA(rta) - (uint8_t *) rta; - - ORDERED_SET_FOREACH(m, route->multipath_routes) { - r = append_nexthop_one(link, route, m, &rta, offset); - if (r < 0) - return r; - - rtnh = (struct rtnexthop *)((uint8_t *) rta + offset); - offset = (uint8_t *) RTNH_NEXT(rtnh) - (uint8_t *) rta; - } - - r = sd_netlink_message_append_data(req, RTA_MULTIPATH, RTA_DATA(rta), RTA_PAYLOAD(rta)); - if (r < 0) - return r; - - return 0; -} - -int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, const char *error_msg) { - int r; - - assert(m); - assert(link); assert(error_msg); r = sd_netlink_message_get_errno(m); - if (r < 0 && r != -EEXIST) { - log_link_message_warning_errno(link, m, r, "Could not set route"); + if (r == -EEXIST) { + Route *existing; + + if (route_get(link->manager, route, &existing) >= 0) { + /* When re-configuring an existing route, kernel does not send RTM_NEWROUTE + * notification, so we need to update the timer here. */ + existing->lifetime_usec = route->lifetime_usec; + (void) route_setup_timer(existing, NULL); + + /* This may be a bug in the kernel, but the MTU of an IPv6 route can be updated only + * when the route has an expiration timer managed by the kernel (not by us). + * See fib6_add_rt2node() in net/ipv6/ip6_fib.c of the kernel. */ + if (existing->family == AF_INET6 && + existing->expiration_managed_by_kernel) { + r = route_metric_set(&existing->metric, RTAX_MTU, route_metric_get(&route->metric, RTAX_MTU)); + if (r < 0) { + log_oom(); + link_enter_failed(link); + return 0; + } + } + } + + } else if (r < 0) { + log_link_message_warning_errno(link, m, r, error_msg); link_enter_failed(link); return 0; } @@ -1161,24 +717,17 @@ static int route_configure(const Route *route, uint32_t lifetime_sec, Link *link int r; assert(route); - assert(IN_SET(route->family, AF_INET, AF_INET6)); assert(link); assert(link->manager); - assert(link->manager->rtnl); - assert(link->ifindex > 0); assert(req); - log_route_debug(route, "Configuring", link, link->manager); + log_route_debug(route, "Configuring", link->manager); r = sd_rtnl_message_new_route(link->manager->rtnl, &m, RTM_NEWROUTE, route->family, route->protocol); if (r < 0) return r; - r = sd_rtnl_message_route_set_type(m, route->type); - if (r < 0) - return r; - - r = route_set_netlink_message(route, m, link); + r = route_set_netlink_message(route, m); if (r < 0) return r; @@ -1188,84 +737,56 @@ static int route_configure(const Route *route, uint32_t lifetime_sec, Link *link return r; } - if (route->ttl_propagate >= 0) { - r = sd_netlink_message_append_u8(m, RTA_TTL_PROPAGATE, route->ttl_propagate); - if (r < 0) - return r; - } - - r = sd_netlink_message_open_container(m, RTA_METRICS); - if (r < 0) - return r; + return request_call_netlink_async(link->manager->rtnl, m, req); +} - if (route->mtu > 0) { - r = sd_netlink_message_append_u32(m, RTAX_MTU, route->mtu); - if (r < 0) - return r; - } +static int route_requeue_request(Request *req, Link *link, const Route *route) { + _unused_ _cleanup_(request_unrefp) Request *req_unref = NULL; + _cleanup_(route_unrefp) Route *tmp = NULL; + int r; - if (route->initcwnd > 0) { - r = sd_netlink_message_append_u32(m, RTAX_INITCWND, route->initcwnd); - if (r < 0) - return r; - } + assert(req); + assert(link); + assert(link->manager); + assert(route); - if (route->initrwnd > 0) { - r = sd_netlink_message_append_u32(m, RTAX_INITRWND, route->initrwnd); - if (r < 0) - return r; - } + /* It is not possible to adjust the Route object owned by Request, as it is used as a key to manage + * Request objects in the queue. Hence, we need to re-request with the updated Route object. */ - if (route->quickack >= 0) { - r = sd_netlink_message_append_u32(m, RTAX_QUICKACK, route->quickack); - if (r < 0) - return r; - } + if (!route_nexthops_needs_adjust(route)) + return 0; /* The Route object does not need the adjustment. Continue with it. */ - if (route->fast_open_no_cookie >= 0) { - r = sd_netlink_message_append_u32(m, RTAX_FASTOPEN_NO_COOKIE, route->fast_open_no_cookie); - if (r < 0) - return r; - } + r = route_dup(route, NULL, &tmp); + if (r < 0) + return r; - if (route->advmss > 0) { - r = sd_netlink_message_append_u32(m, RTAX_ADVMSS, route->advmss); - if (r < 0) - return r; - } + r = route_adjust_nexthops(tmp, link); + if (r <= 0) + return r; - if (!isempty(route->tcp_congestion_control_algo)) { - r = sd_netlink_message_append_string(m, RTAX_CC_ALGO, route->tcp_congestion_control_algo); - if (r < 0) - return r; - } + if (route_compare_func(route, tmp) == 0 && route->type == tmp->type) + return 0; /* No effective change?? That's OK. */ - if (route->hop_limit > 0) { - r = sd_netlink_message_append_u32(m, RTAX_HOPLIMIT, route->hop_limit); - if (r < 0) - return r; - } + /* Avoid the request to be freed by request_detach(). */ + req_unref = request_ref(req); - if (route->tcp_rto_usec > 0) { - r = sd_netlink_message_append_u32(m, RTAX_RTO_MIN, DIV_ROUND_UP(route->tcp_rto_usec, USEC_PER_MSEC)); - if (r < 0) - return r; - } + /* Detach the request from the queue, to make not the new request is deduped. + * Why this is necessary? IPv6 routes with different type may be handled as the same, + * As commented in route_adjust_nexthops(), we need to configure the adjusted type, + * otherwise we cannot remove the route on reconfigure or so. If we request the new Route object + * without detaching the current request, the new request is deduped, and the route is configured + * with unmodified type. */ + request_detach(req); - r = sd_netlink_message_close_container(m); + /* Request the route with the adjusted Route object combined with the same other parameters. */ + r = link_requeue_request(link, req, tmp, NULL); if (r < 0) return r; + if (r == 0) + return 1; /* Already queued?? That's OK. Maybe, [Route] section is effectively duplicated. */ - if (!ordered_set_isempty(route->multipath_routes)) { - assert(route->nexthop_id == 0); - assert(!in_addr_is_set(route->gw_family, &route->gw)); - - r = append_nexthops(link, route, m); - if (r < 0) - return r; - } - - return request_call_netlink_async(link->manager->rtnl, m, req); + TAKE_PTR(tmp); + return 1; /* New request is queued. Finish to process this request. */ } static int route_is_ready_to_configure(const Route *route, Link *link) { @@ -1274,67 +795,20 @@ static int route_is_ready_to_configure(const Route *route, Link *link) { assert(route); assert(link); - if (!link_is_ready_to_configure(link, false)) - return false; - - if (set_size(link->routes) >= routes_max()) + if (!link_is_ready_to_configure(link, /* allow_unmanaged = */ false)) return false; - if (route->nexthop_id > 0) { - struct nexthop_grp *nhg; - NextHop *nh; - - if (manager_get_nexthop_by_id(link->manager, route->nexthop_id, &nh) < 0) - return false; - - if (!nexthop_exists(nh)) - return false; - - HASHMAP_FOREACH(nhg, nh->group) { - NextHop *g; - - if (manager_get_nexthop_by_id(link->manager, nhg->id, &g) < 0) - return false; - - if (!nexthop_exists(g)) - return false; - } - } - if (in_addr_is_set(route->family, &route->prefsrc) > 0) { - r = manager_has_address(link->manager, route->family, &route->prefsrc, route->family == AF_INET6); + r = manager_has_address(link->manager, route->family, &route->prefsrc); if (r <= 0) return r; } - if (!gateway_is_ready(link, FLAGS_SET(route->flags, RTNH_F_ONLINK), route->gw_family, &route->gw)) - return false; - - MultipathRoute *m; - ORDERED_SET_FOREACH(m, route->multipath_routes) { - union in_addr_union a = m->gateway.address; - Link *l = NULL; - - r = multipath_route_get_link(link->manager, m, &l); - if (r < 0) - return false; - if (r > 0) { - if (!link_is_ready_to_configure(l, /* allow_unmanaged = */ true) || - !link_has_carrier(l)) - return false; - - m->ifindex = l->ifindex; - } - - if (!gateway_is_ready(l ?: link, FLAGS_SET(route->flags, RTNH_F_ONLINK), m->gateway.family, &a)) - return false; - } - - return true; + return route_nexthops_is_ready_to_configure(route, link->manager); } static int route_process_request(Request *req, Link *link, Route *route) { - _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL; + Route *existing; int r; assert(req); @@ -1348,36 +822,6 @@ static int route_process_request(Request *req, Link *link, Route *route) { if (r == 0) return 0; - if (route_needs_convert(route)) { - r = route_convert(link->manager, route, &converted); - if (r < 0) - return log_link_warning_errno(link, r, "Failed to convert route: %m"); - - assert(r > 0); - assert(converted); - - for (size_t i = 0; i < converted->n; i++) { - Route *existing; - - if (route_get(link->manager, converted->links[i] ?: link, converted->routes[i], &existing) < 0) { - _cleanup_(route_freep) Route *tmp = NULL; - - r = route_dup(converted->routes[i], &tmp); - if (r < 0) - return log_oom(); - - r = route_add(link->manager, converted->links[i] ?: link, tmp); - if (r < 0) - return log_link_warning_errno(link, r, "Failed to add route: %m"); - - TAKE_PTR(tmp); - } else { - existing->source = converted->routes[i]->source; - existing->provider = converted->routes[i]->provider; - } - } - } - usec_t now_usec; assert_se(sd_event_now(link->manager->event, CLOCK_BOOTTIME, &now_usec) >= 0); uint32_t sec = usec_to_sec(route->lifetime_usec, now_usec); @@ -1385,117 +829,106 @@ static int route_process_request(Request *req, Link *link, Route *route) { log_link_debug(link, "Refuse to configure %s route with zero lifetime.", network_config_source_to_string(route->source)); - if (converted) - for (size_t i = 0; i < converted->n; i++) { - Route *existing; - - assert_se(route_get(link->manager, converted->links[i] ?: link, converted->routes[i], &existing) >= 0); - route_cancel_requesting(existing); - } - else - route_cancel_requesting(route); - + route_cancel_requesting(route); + if (route_get(link->manager, route, &existing) >= 0) + route_cancel_requesting(existing); return 1; } + r = route_requeue_request(req, link, route); + if (r != 0) + return r; + r = route_configure(route, sec, link, req); if (r < 0) return log_link_warning_errno(link, r, "Failed to configure route: %m"); - if (converted) - for (size_t i = 0; i < converted->n; i++) { - Route *existing; - - assert_se(route_get(link->manager, converted->links[i] ?: link, converted->routes[i], &existing) >= 0); - route_enter_configuring(existing); - } - else - route_enter_configuring(route); - + route_enter_configuring(route); + if (route_get(link->manager, route, &existing) >= 0) + route_enter_configuring(existing); return 1; } -int link_request_route( +static int link_request_route_one( Link *link, - Route *route, - bool consume_object, + const Route *route, + const RouteNextHop *nh, unsigned *message_counter, - route_netlink_handler_t netlink_handler, - Request **ret) { + route_netlink_handler_t netlink_handler) { + _cleanup_(route_unrefp) Route *tmp = NULL; Route *existing = NULL; int r; assert(link); assert(link->manager); assert(route); - assert(route->source != NETWORK_CONFIG_SOURCE_FOREIGN); - assert(!route_needs_convert(route)); - - (void) route_get(link->manager, link, route, &existing); - - if (route->lifetime_usec == 0) { - if (consume_object) - route_free(route); - - /* The requested route is outdated. Let's remove it. */ - return route_remove_and_drop(existing); - } - - if (!existing) { - _cleanup_(route_freep) Route *tmp = NULL; - if (consume_object) - tmp = route; - else { - r = route_dup(route, &tmp); - if (r < 0) - return r; - } - - r = route_add(link->manager, link, tmp); - if (r < 0) - return r; + r = route_dup(route, nh, &tmp); + if (r < 0) + return r; - existing = TAKE_PTR(tmp); - } else { - existing->source = route->source; - existing->provider = route->provider; - existing->lifetime_usec = route->lifetime_usec; - if (consume_object) - route_free(route); + r = route_adjust_nexthops(tmp, link); + if (r < 0) + return r; - if (existing->expire) { - /* When re-configuring an existing route, kernel does not send RTM_NEWROUTE - * message, so we need to update the timer here. */ - r = route_setup_timer(existing, NULL); - if (r < 0) - log_link_warning_errno(link, r, "Failed to update expiration timer for route, ignoring: %m"); - if (r > 0) - log_route_debug(existing, "Updated expiration timer for", link, link->manager); - } - } + if (route_get(link->manager, tmp, &existing) >= 0) + /* Copy state for logging below. */ + tmp->state = existing->state; - log_route_debug(existing, "Requesting", link, link->manager); + log_route_debug(tmp, "Requesting", link->manager); r = link_queue_request_safe(link, REQUEST_TYPE_ROUTE, - existing, NULL, + tmp, + route_unref, route_hash_func, route_compare_func, route_process_request, - message_counter, netlink_handler, ret); + message_counter, + netlink_handler, + NULL); if (r <= 0) return r; - route_enter_requesting(existing); + route_enter_requesting(tmp); + if (existing) + route_enter_requesting(existing); + + TAKE_PTR(tmp); return 1; } +int link_request_route( + Link *link, + const Route *route, + unsigned *message_counter, + route_netlink_handler_t netlink_handler) { + + int r; + + assert(link); + assert(link->manager); + assert(route); + assert(route->source != NETWORK_CONFIG_SOURCE_FOREIGN); + + if (route->family == AF_INET || route_type_is_reject(route) || ordered_set_isempty(route->nexthops)) + return link_request_route_one(link, route, NULL, message_counter, netlink_handler); + + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) { + r = link_request_route_one(link, route, nh, message_counter, netlink_handler); + if (r < 0) + return r; + } + + return 0; +} + static int static_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, Route *route) { int r; assert(link); - r = route_configure_handler_internal(rtnl, m, link, "Could not set route"); + r = route_configure_handler_internal(rtnl, m, link, route, "Could not set route"); if (r <= 0) return r; @@ -1508,22 +941,6 @@ static int static_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request return 1; } -static int link_request_static_route(Link *link, Route *route) { - assert(link); - assert(link->manager); - assert(route); - - if (!route_needs_convert(route)) - return link_request_route(link, route, false, &link->static_route_messages, - static_route_handler, NULL); - - log_route_debug(route, "Requesting", link, link->manager); - return link_queue_request_safe(link, REQUEST_TYPE_ROUTE, - route, NULL, route_hash_func, route_compare_func, - route_process_request, - &link->static_route_messages, static_route_handler, NULL); -} - static int link_request_wireguard_routes(Link *link, bool only_ipv4) { NetDev *netdev; Route *route; @@ -1543,7 +960,7 @@ static int link_request_wireguard_routes(Link *link, bool only_ipv4) { if (only_ipv4 && route->family != AF_INET) continue; - r = link_request_static_route(link, route); + r = link_request_route(link, route, &link->static_route_messages, static_route_handler); if (r < 0) return r; } @@ -1567,7 +984,7 @@ int link_request_static_routes(Link *link, bool only_ipv4) { if (only_ipv4 && route->family != AF_INET) continue; - r = link_request_static_route(link, route); + r = link_request_route(link, route, &link->static_route_messages, static_route_handler); if (r < 0) return r; } @@ -1587,95 +1004,79 @@ int link_request_static_routes(Link *link, bool only_ipv4) { return 0; } -void route_cancel_request(Route *route, Link *link) { - Request req; - - assert(route); - - link = route->link ?: link; - - assert(link); - - if (!route_is_requesting(route)) - return; - - req = (Request) { - .link = link, - .type = REQUEST_TYPE_ROUTE, - .userdata = route, - .hash_func = (hash_func_t) route_hash_func, - .compare_func = (compare_func_t) route_compare_func, - }; - - request_detach(link->manager, &req); - route_cancel_requesting(route); -} - static int process_route_one( Manager *manager, - Link *link, uint16_t type, - Route *in, + Route *tmp, const struct rta_cacheinfo *cacheinfo) { - _cleanup_(route_freep) Route *tmp = in; + Request *req = NULL; Route *route = NULL; - bool update_dhcp4; + Link *link = NULL; + bool is_new = false, update_dhcp4; int r; assert(manager); assert(tmp); assert(IN_SET(type, RTM_NEWROUTE, RTM_DELROUTE)); - /* link may be NULL. This consumes 'in'. */ + (void) route_get(manager, tmp, &route); + (void) route_get_request(manager, tmp, &req); + (void) route_get_link(manager, tmp, &link); update_dhcp4 = link && tmp->family == AF_INET6 && tmp->dst_prefixlen == 0; - (void) route_get(manager, link, tmp, &route); - switch (type) { case RTM_NEWROUTE: - if (route) { - route->flags = tmp->flags; - route_enter_configured(route); - log_route_debug(route, "Received remembered", link, manager); - - r = route_setup_timer(route, cacheinfo); - if (r < 0) - log_link_warning_errno(link, r, "Failed to configure expiration timer for route, ignoring: %m"); - if (r > 0) - log_route_debug(route, "Configured expiration timer for", link, manager); - - } else if (!manager->manage_foreign_routes) { - route_enter_configured(tmp); - log_route_debug(tmp, "Ignoring received", link, manager); - - } else { - /* A route appeared that we did not request */ - route_enter_configured(tmp); - log_route_debug(tmp, "Received new", link, manager); - r = route_add(manager, link, tmp); + if (!route) { + if (!manager->manage_foreign_routes && !(req && req->waiting_reply)) { + route_enter_configured(tmp); + log_route_debug(tmp, "Ignoring received", manager); + return 0; + } + + /* If we do not know the route, then save it. */ + r = route_attach(manager, tmp); if (r < 0) { log_link_warning_errno(link, r, "Failed to remember foreign route, ignoring: %m"); return 0; } - TAKE_PTR(tmp); + + route = route_ref(tmp); + is_new = true; + + } else + /* Update remembered route with the received notification. */ + route->nexthop.weight = tmp->nexthop.weight; + + /* Also update information that cannot be obtained through netlink notification. */ + if (req && req->waiting_reply) { + Route *rt = ASSERT_PTR(req->userdata); + + route->source = rt->source; + route->provider = rt->provider; + route->lifetime_usec = rt->lifetime_usec; } + route_enter_configured(route); + log_route_debug(route, is_new ? "Received new" : "Received remembered", manager); + + (void) route_setup_timer(route, cacheinfo); + break; case RTM_DELROUTE: if (route) { route_enter_removed(route); - if (route->state == 0) { - log_route_debug(route, "Forgetting", link, manager); - route_free(route); - } else - log_route_debug(route, "Removed", link, manager); + log_route_debug(route, "Forgetting removed", manager); + route_detach(route); } else log_route_debug(tmp, manager->manage_foreign_routes ? "Kernel removed unknown" : "Ignoring received", - link, manager); + manager); + + if (req) + route_enter_removed(req->userdata); break; @@ -1695,15 +1096,7 @@ static int process_route_one( } int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { - _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL; - _cleanup_(route_freep) Route *tmp = NULL; - _cleanup_free_ void *rta_multipath = NULL; - struct rta_cacheinfo cacheinfo; - bool has_cacheinfo; - Link *link = NULL; - uint32_t ifindex; - uint16_t type; - size_t rta_len; + _cleanup_(route_unrefp) Route *tmp = NULL; int r; assert(rtnl); @@ -1718,6 +1111,7 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma return 0; } + uint16_t type; r = sd_netlink_message_get_type(message, &type); if (r < 0) { log_warning_errno(r, "rtnl: could not get message type, ignoring: %m"); @@ -1727,115 +1121,84 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma return 0; } - r = sd_netlink_message_read_u32(message, RTA_OIF, &ifindex); - if (r < 0 && r != -ENODATA) { - log_warning_errno(r, "rtnl: could not get ifindex from route message, ignoring: %m"); - return 0; - } else if (r >= 0) { - if (ifindex <= 0) { - log_warning("rtnl: received route message with invalid ifindex %u, ignoring.", ifindex); - return 0; - } - - r = link_get_by_index(m, ifindex, &link); - if (r < 0) { - /* when enumerating we might be out of sync, but we will - * get the route again, so just ignore it */ - if (!m->enumerating) - log_warning("rtnl: received route message for link (%u) we do not know about, ignoring", ifindex); - return 0; - } - } - r = route_new(&tmp); if (r < 0) return log_oom(); + /* rtmsg header */ r = sd_rtnl_message_route_get_family(message, &tmp->family); if (r < 0) { - log_link_warning(link, "rtnl: received route message without family, ignoring"); + log_warning_errno(r, "rtnl: received route message without family, ignoring: %m"); return 0; } else if (!IN_SET(tmp->family, AF_INET, AF_INET6)) { - log_link_debug(link, "rtnl: received route message with invalid family '%i', ignoring", tmp->family); + log_debug("rtnl: received route message with invalid family '%i', ignoring.", tmp->family); return 0; } - r = sd_rtnl_message_route_get_protocol(message, &tmp->protocol); + r = sd_rtnl_message_route_get_dst_prefixlen(message, &tmp->dst_prefixlen); if (r < 0) { - log_warning_errno(r, "rtnl: received route message without route protocol, ignoring: %m"); + log_warning_errno(r, "rtnl: received route message with invalid destination prefixlen, ignoring: %m"); return 0; } - r = sd_rtnl_message_route_get_flags(message, &tmp->flags); + r = sd_rtnl_message_route_get_src_prefixlen(message, &tmp->src_prefixlen); if (r < 0) { - log_warning_errno(r, "rtnl: received route message without route flags, ignoring: %m"); + log_warning_errno(r, "rtnl: received route message with invalid source prefixlen, ignoring: %m"); return 0; } - r = netlink_message_read_in_addr_union(message, RTA_DST, tmp->family, &tmp->dst); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message without valid destination, ignoring: %m"); + r = sd_rtnl_message_route_get_tos(message, &tmp->tos); + if (r < 0) { + log_warning_errno(r, "rtnl: received route message with invalid tos, ignoring: %m"); return 0; } - r = netlink_message_read_in_addr_union(message, RTA_GATEWAY, tmp->family, &tmp->gw); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message without valid gateway, ignoring: %m"); + r = sd_rtnl_message_route_get_protocol(message, &tmp->protocol); + if (r < 0) { + log_warning_errno(r, "rtnl: received route message without route protocol, ignoring: %m"); return 0; - } else if (r >= 0) - tmp->gw_family = tmp->family; - else if (tmp->family == AF_INET) { - RouteVia via; - - r = sd_netlink_message_read(message, RTA_VIA, sizeof(via), &via); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message without valid gateway, ignoring: %m"); - return 0; - } else if (r >= 0) { - tmp->gw_family = via.family; - tmp->gw = via.address; - } } - r = netlink_message_read_in_addr_union(message, RTA_SRC, tmp->family, &tmp->src); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message without valid source, ignoring: %m"); + r = sd_rtnl_message_route_get_scope(message, &tmp->scope); + if (r < 0) { + log_warning_errno(r, "rtnl: received route message with invalid scope, ignoring: %m"); return 0; } - r = netlink_message_read_in_addr_union(message, RTA_PREFSRC, tmp->family, &tmp->prefsrc); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message without valid preferred source, ignoring: %m"); + r = sd_rtnl_message_route_get_type(message, &tmp->type); + if (r < 0) { + log_warning_errno(r, "rtnl: received route message with invalid type, ignoring: %m"); return 0; } - r = sd_rtnl_message_route_get_dst_prefixlen(message, &tmp->dst_prefixlen); + r = sd_rtnl_message_route_get_flags(message, &tmp->flags); if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid destination prefixlen, ignoring: %m"); + log_warning_errno(r, "rtnl: received route message without route flags, ignoring: %m"); return 0; } - r = sd_rtnl_message_route_get_src_prefixlen(message, &tmp->src_prefixlen); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid source prefixlen, ignoring: %m"); + /* attributes */ + r = netlink_message_read_in_addr_union(message, RTA_DST, tmp->family, &tmp->dst); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: received route message without valid destination, ignoring: %m"); return 0; } - r = sd_rtnl_message_route_get_scope(message, &tmp->scope); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid scope, ignoring: %m"); + r = netlink_message_read_in_addr_union(message, RTA_SRC, tmp->family, &tmp->src); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: received route message without valid source, ignoring: %m"); return 0; } - r = sd_rtnl_message_route_get_tos(message, &tmp->tos); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid tos, ignoring: %m"); + r = sd_netlink_message_read_u32(message, RTA_PRIORITY, &tmp->priority); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: received route message with invalid priority, ignoring: %m"); return 0; } - r = sd_rtnl_message_route_get_type(message, &tmp->type); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid type, ignoring: %m"); + r = netlink_message_read_in_addr_union(message, RTA_PREFSRC, tmp->family, &tmp->prefsrc); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: received route message without valid preferred source, ignoring: %m"); return 0; } @@ -1848,102 +1211,285 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma tmp->table = table; } if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid table, ignoring: %m"); + log_warning_errno(r, "rtnl: received route message with invalid table, ignoring: %m"); return 0; } - r = sd_netlink_message_read_u32(message, RTA_PRIORITY, &tmp->priority); + r = sd_netlink_message_read_u8(message, RTA_PREF, &tmp->pref); if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid priority, ignoring: %m"); + log_warning_errno(r, "rtnl: received route message with invalid preference, ignoring: %m"); return 0; } - r = sd_netlink_message_read_u32(message, RTA_NH_ID, &tmp->nexthop_id); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid nexthop id, ignoring: %m"); + /* nexthops */ + if (route_nexthops_read_netlink_message(tmp, message) < 0) + return 0; + + /* metrics */ + if (route_metric_read_netlink_message(&tmp->metric, message) < 0) return 0; - } - r = sd_netlink_message_enter_container(message, RTA_METRICS); + bool has_cacheinfo; + struct rta_cacheinfo cacheinfo; + r = sd_netlink_message_read(message, RTA_CACHEINFO, sizeof(cacheinfo), &cacheinfo); if (r < 0 && r != -ENODATA) { - log_link_error_errno(link, r, "rtnl: Could not enter RTA_METRICS container, ignoring: %m"); + log_warning_errno(r, "rtnl: failed to read RTA_CACHEINFO attribute, ignoring: %m"); return 0; } - if (r >= 0) { - r = sd_netlink_message_read_u32(message, RTAX_INITCWND, &tmp->initcwnd); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid initcwnd, ignoring: %m"); - return 0; - } + has_cacheinfo = r >= 0; - r = sd_netlink_message_read_u32(message, RTAX_INITRWND, &tmp->initrwnd); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid initrwnd, ignoring: %m"); - return 0; - } + if (tmp->family == AF_INET || ordered_set_isempty(tmp->nexthops)) + return process_route_one(m, type, tmp, has_cacheinfo ? &cacheinfo : NULL); - r = sd_netlink_message_read_u32(message, RTAX_ADVMSS, &tmp->advmss); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid advmss, ignoring: %m"); - return 0; - } + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, tmp->nexthops) { + _cleanup_(route_unrefp) Route *dup = NULL; - r = sd_netlink_message_exit_container(message); - if (r < 0) { - log_link_error_errno(link, r, "rtnl: Could not exit from RTA_METRICS container, ignoring: %m"); - return 0; + r = route_dup(tmp, nh, &dup); + if (r < 0) + return log_oom(); + + r = process_route_one(m, type, dup, has_cacheinfo ? &cacheinfo : NULL); + if (r < 0) + return r; + } + + return 1; +} + +void manager_mark_routes(Manager *manager, Link *link, NetworkConfigSource source) { + Route *route; + + assert(manager); + + SET_FOREACH(route, manager->routes) { + if (route->source != source) + continue; + + if (link) { + Link *route_link; + + if (route_get_link(manager, route, &route_link) < 0) + continue; + if (route_link != link) + continue; } + + route_mark(route); } +} - r = sd_netlink_message_read_data(message, RTA_MULTIPATH, &rta_len, &rta_multipath); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: failed to read RTA_MULTIPATH attribute, ignoring: %m"); +static bool route_by_kernel(const Route *route) { + assert(route); + + if (route->protocol == RTPROT_KERNEL) + return true; + + /* The kernels older than a826b04303a40d52439aa141035fca5654ccaccd (v5.11) create the IPv6 + * multicast with RTPROT_BOOT. Do not touch it. */ + if (route->protocol == RTPROT_BOOT && + route->family == AF_INET6 && + route->dst_prefixlen == 8 && + in6_addr_equal(&route->dst.in6, & (struct in6_addr) {{{ 0xff,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 }}})) + return true; + + return false; +} + +bool route_can_update(const Route *existing, const Route *requesting) { + assert(existing); + assert(requesting); + + if (route_compare_func(existing, requesting) != 0) + return false; + + switch (existing->family) { + case AF_INET: + if (existing->nexthop.weight != requesting->nexthop.weight) + return false; + return true; + + case AF_INET6: + if (existing->protocol != requesting->protocol) + return false; + if (existing->type != requesting->type) + return false; + if (existing->flags != requesting->flags) + return false; + if (!in6_addr_equal(&existing->prefsrc.in6, &requesting->prefsrc.in6)) + return false; + if (existing->pref != requesting->pref) + return false; + if (existing->expiration_managed_by_kernel && requesting->lifetime_usec == USEC_INFINITY) + return false; /* We cannot disable expiration timer in the kernel. */ + if (!route_metric_can_update(&existing->metric, &requesting->metric, existing->expiration_managed_by_kernel)) + return false; + if (existing->nexthop.weight != requesting->nexthop.weight) + return false; + return true; + + default: + assert_not_reached(); + } +} + +static int link_unmark_route(Link *link, const Route *route, const RouteNextHop *nh) { + _cleanup_(route_unrefp) Route *tmp = NULL; + Route *existing; + int r; + + assert(link); + assert(route); + + r = route_dup(route, nh, &tmp); + if (r < 0) + return r; + + r = route_adjust_nexthops(tmp, link); + if (r < 0) + return r; + + if (route_get(link->manager, tmp, &existing) < 0) return 0; - } else if (r >= 0) { - r = rtattr_read_nexthop(rta_multipath, rta_len, tmp->family, &tmp->multipath_routes); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: failed to parse RTA_MULTIPATH attribute, ignoring: %m"); - return 0; + + if (!route_can_update(existing, tmp)) + return 0; + + route_unmark(existing); + return 1; +} + +static int link_mark_routes(Link *link, bool foreign) { + Route *route; + Link *other; + int r; + + assert(link); + assert(link->manager); + + /* First, mark all routes. */ + SET_FOREACH(route, link->manager->routes) { + /* Do not touch routes managed by the kernel. */ + if (route_by_kernel(route)) + continue; + + /* When 'foreign' is true, mark only foreign routes, and vice versa. + * Note, do not touch dynamic routes. They will removed by when e.g. lease is lost. */ + if (route->source != (foreign ? NETWORK_CONFIG_SOURCE_FOREIGN : NETWORK_CONFIG_SOURCE_STATIC)) + continue; + + /* Ignore routes not assigned yet or already removed. */ + if (!route_exists(route)) + continue; + + if (link->network) { + if (route->protocol == RTPROT_STATIC && + FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_STATIC)) + continue; + + if (route->protocol == RTPROT_DHCP && + FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) + continue; } + + /* When we mark foreign routes, do not mark routes assigned to other interfaces. + * Otherwise, routes assigned to unmanaged interfaces will be dropped. + * Note, route_get_link() does not provide assigned link for routes with an unreachable type + * or IPv4 multipath routes. So, the current implementation does not support managing such + * routes by other daemon or so, unless ManageForeignRoutes=no. */ + if (foreign) { + Link *route_link; + + if (route_get_link(link->manager, route, &route_link) >= 0 && route_link != link) + continue; + } + + route_mark(route); } - r = sd_netlink_message_read(message, RTA_CACHEINFO, sizeof(cacheinfo), &cacheinfo); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: failed to read RTA_CACHEINFO attribute, ignoring: %m"); - return 0; + /* Then, unmark all routes requested by active links. */ + HASHMAP_FOREACH(other, link->manager->links_by_index) { + if (!foreign && other == link) + continue; + + if (!IN_SET(other->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) + continue; + + HASHMAP_FOREACH(route, other->network->routes_by_section) { + if (route->family == AF_INET || ordered_set_isempty(route->nexthops)) { + r = link_unmark_route(other, route, NULL); + if (r < 0) + return r; + + } else { + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) { + r = link_unmark_route(other, route, nh); + if (r < 0) + return r; + } + } + } + + /* Also unmark routes requested in .netdev file. */ + if (other->netdev && other->netdev->kind == NETDEV_KIND_WIREGUARD) { + Wireguard *w = WIREGUARD(other->netdev); + + SET_FOREACH(route, w->routes) { + r = link_unmark_route(other, route, NULL); + if (r < 0) + return r; + } + } } - has_cacheinfo = r >= 0; - /* IPv6 routes with reject type are always assigned to the loopback interface. See kernel's - * fib6_nh_init() in net/ipv6/route.c. However, we'd like to manage them by Manager. Hence, set - * link to NULL here. */ - if (route_type_is_reject(tmp)) - link = NULL; + return 0; +} - if (!route_needs_convert(tmp)) - return process_route_one(m, link, type, TAKE_PTR(tmp), has_cacheinfo ? &cacheinfo : NULL); +int link_drop_routes(Link *link, bool foreign) { + Route *route; + int r; - r = route_convert(m, tmp, &converted); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: failed to convert received route, ignoring: %m"); - return 0; + assert(link); + assert(link->manager); + + r = link_mark_routes(link, foreign); + if (r < 0) + return r; + + SET_FOREACH(route, link->manager->routes) { + if (!route_is_marked(route)) + continue; + + RET_GATHER(r, route_remove(route, link->manager)); } - assert(r > 0); - assert(converted); + return r; +} - for (size_t i = 0; i < converted->n; i++) - (void) process_route_one(m, - converted->links[i] ?: link, - type, - TAKE_PTR(converted->routes[i]), - has_cacheinfo ? &cacheinfo : NULL); +int link_foreignize_routes(Link *link) { + Route *route; + int r; - return 1; + assert(link); + assert(link->manager); + + r = link_mark_routes(link, /* foreign = */ false); + if (r < 0) + return r; + + SET_FOREACH(route, link->manager->routes) { + if (!route_is_marked(route)) + continue; + + route->source = NETWORK_CONFIG_SOURCE_FOREIGN; + } + + return 0; } int network_add_ipv4ll_route(Network *network) { - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; unsigned section_line; int r; @@ -1957,28 +1503,28 @@ int network_add_ipv4ll_route(Network *network) { return r; /* IPv4LLRoute= is in [Network] section. */ - r = route_new_static(network, network->filename, section_line, &n); + r = route_new_static(network, network->filename, section_line, &route); if (r < 0) return r; - r = in_addr_from_string(AF_INET, "169.254.0.0", &n->dst); + r = in_addr_from_string(AF_INET, "169.254.0.0", &route->dst); if (r < 0) return r; - n->family = AF_INET; - n->dst_prefixlen = 16; - n->scope = RT_SCOPE_LINK; - n->scope_set = true; - n->table_set = true; - n->priority = IPV4LL_ROUTE_METRIC; - n->protocol = RTPROT_STATIC; + route->family = AF_INET; + route->dst_prefixlen = 16; + route->scope = RT_SCOPE_LINK; + route->scope_set = true; + route->table_set = true; + route->priority = IPV4LL_ROUTE_METRIC; + route->protocol = RTPROT_STATIC; - TAKE_PTR(n); + TAKE_PTR(route); return 0; } int network_add_default_route_on_device(Network *network) { - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; unsigned section_line; int r; @@ -1992,99 +1538,16 @@ int network_add_default_route_on_device(Network *network) { return r; /* DefaultRouteOnDevice= is in [Network] section. */ - r = route_new_static(network, network->filename, section_line, &n); + r = route_new_static(network, network->filename, section_line, &route); if (r < 0) return r; - n->family = AF_INET; - n->scope = RT_SCOPE_LINK; - n->scope_set = true; - n->protocol = RTPROT_STATIC; - - TAKE_PTR(n); - return 0; -} - -int config_parse_gateway( - 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_(route_free_or_set_invalidp) Route *n = NULL; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - if (streq(section, "Network")) { - /* we are not in an Route section, so use line number instead */ - r = route_new_static(network, filename, line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - } else { - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - if (isempty(rvalue)) { - n->gateway_from_dhcp_or_ra = false; - n->gw_family = AF_UNSPEC; - n->gw = IN_ADDR_NULL; - TAKE_PTR(n); - return 0; - } - - if (streq(rvalue, "_dhcp")) { - n->gateway_from_dhcp_or_ra = true; - TAKE_PTR(n); - return 0; - } - - if (streq(rvalue, "_dhcp4")) { - n->gw_family = AF_INET; - n->gateway_from_dhcp_or_ra = true; - TAKE_PTR(n); - return 0; - } - - if (streq(rvalue, "_ipv6ra")) { - n->gw_family = AF_INET6; - n->gateway_from_dhcp_or_ra = true; - TAKE_PTR(n); - return 0; - } - } - - r = in_addr_from_string_auto(rvalue, &n->gw_family, &n->gw); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); - return 0; - } + route->family = AF_INET; + route->scope = RT_SCOPE_LINK; + route->scope_set = true; + route->protocol = RTPROT_STATIC; - n->gateway_from_dhcp_or_ra = false; - TAKE_PTR(n); + TAKE_PTR(route); return 0; } @@ -2101,7 +1564,7 @@ int config_parse_preferred_src( void *userdata) { Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; int r; assert(filename); @@ -2110,7 +1573,7 @@ int config_parse_preferred_src( assert(rvalue); assert(data); - r = route_new_static(network, filename, section_line, &n); + r = route_new_static(network, filename, section_line, &route); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -2119,17 +1582,17 @@ int config_parse_preferred_src( return 0; } - if (n->family == AF_UNSPEC) - r = in_addr_from_string_auto(rvalue, &n->family, &n->prefsrc); + if (route->family == AF_UNSPEC) + r = in_addr_from_string_auto(rvalue, &route->family, &route->prefsrc); else - r = in_addr_from_string(n->family, rvalue, &n->prefsrc); + r = in_addr_from_string(route->family, rvalue, &route->prefsrc); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); return 0; } - TAKE_PTR(n); + TAKE_PTR(route); return 0; } @@ -2146,7 +1609,7 @@ int config_parse_destination( void *userdata) { Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; union in_addr_union *buffer; unsigned char *prefixlen; int r; @@ -2157,7 +1620,7 @@ int config_parse_destination( assert(rvalue); assert(data); - r = route_new_static(network, filename, section_line, &n); + r = route_new_static(network, filename, section_line, &route); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -2167,27 +1630,27 @@ int config_parse_destination( } if (streq(lvalue, "Destination")) { - buffer = &n->dst; - prefixlen = &n->dst_prefixlen; + buffer = &route->dst; + prefixlen = &route->dst_prefixlen; } else if (streq(lvalue, "Source")) { - buffer = &n->src; - prefixlen = &n->src_prefixlen; + buffer = &route->src; + prefixlen = &route->src_prefixlen; } else assert_not_reached(); - if (n->family == AF_UNSPEC) - r = in_addr_prefix_from_string_auto(rvalue, &n->family, buffer, prefixlen); + if (route->family == AF_UNSPEC) + r = in_addr_prefix_from_string_auto(rvalue, &route->family, buffer, prefixlen); else - r = in_addr_prefix_from_string(rvalue, n->family, buffer, prefixlen); + r = in_addr_prefix_from_string(rvalue, route->family, buffer, prefixlen); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); return 0; } - (void) in_addr_mask(n->family, buffer, *prefixlen); + (void) in_addr_mask(route->family, buffer, *prefixlen); - TAKE_PTR(n); + TAKE_PTR(route); return 0; } @@ -2204,7 +1667,7 @@ int config_parse_route_priority( void *userdata) { Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; int r; assert(filename); @@ -2213,7 +1676,7 @@ int config_parse_route_priority( assert(rvalue); assert(data); - r = route_new_static(network, filename, section_line, &n); + r = route_new_static(network, filename, section_line, &route); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -2222,15 +1685,15 @@ int config_parse_route_priority( return 0; } - r = safe_atou32(rvalue, &n->priority); + r = safe_atou32(rvalue, &route->priority); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Could not parse route priority \"%s\", ignoring assignment: %m", rvalue); return 0; } - n->priority_set = true; - TAKE_PTR(n); + route->priority_set = true; + TAKE_PTR(route); return 0; } @@ -2247,7 +1710,7 @@ int config_parse_route_scope( void *userdata) { Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; int r; assert(filename); @@ -2256,7 +1719,7 @@ int config_parse_route_scope( assert(rvalue); assert(data); - r = route_new_static(network, filename, section_line, &n); + r = route_new_static(network, filename, section_line, &route); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -2271,62 +1734,9 @@ int config_parse_route_scope( return 0; } - n->scope = r; - n->scope_set = true; - TAKE_PTR(n); - return 0; -} - -int config_parse_route_nexthop( - 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_(route_free_or_set_invalidp) Route *n = NULL; - uint32_t id; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - if (isempty(rvalue)) { - n->nexthop_id = 0; - TAKE_PTR(n); - return 0; - } - - r = safe_atou32(rvalue, &id); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse nexthop ID, ignoring assignment: %s", rvalue); - return 0; - } - if (id == 0) { - log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid nexthop ID, ignoring assignment: %s", rvalue); - return 0; - } - - n->nexthop_id = id; - TAKE_PTR(n); + route->scope = r; + route->scope_set = true; + TAKE_PTR(route); return 0; } @@ -2342,7 +1752,7 @@ int config_parse_route_table( void *data, void *userdata) { - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; Network *network = userdata; int r; @@ -2352,7 +1762,7 @@ int config_parse_route_table( assert(rvalue); assert(data); - r = route_new_static(network, filename, section_line, &n); + r = route_new_static(network, filename, section_line, &route); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -2361,68 +1771,15 @@ int config_parse_route_table( return 0; } - r = manager_get_route_table_from_string(network->manager, rvalue, &n->table); + r = manager_get_route_table_from_string(network->manager, rvalue, &route->table); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Could not parse route table \"%s\", ignoring assignment: %m", rvalue); return 0; } - n->table_set = true; - TAKE_PTR(n); - return 0; -} - -int config_parse_route_boolean( - 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_(route_free_or_set_invalidp) Route *n = NULL; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Could not parse %s=\"%s\", ignoring assignment: %m", lvalue, rvalue); - return 0; - } - - if (STR_IN_SET(lvalue, "GatewayOnLink", "GatewayOnlink")) - n->gateway_onlink = r; - else if (streq(lvalue, "QuickAck")) - n->quickack = r; - else if (streq(lvalue, "FastOpenNoCookie")) - n->fast_open_no_cookie = r; - else if (streq(lvalue, "TTLPropagate")) - n->ttl_propagate = r; - else - assert_not_reached(); - - TAKE_PTR(n); + route->table_set = true; + TAKE_PTR(route); return 0; } @@ -2439,10 +1796,10 @@ int config_parse_ipv6_route_preference( void *userdata) { Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; int r; - r = route_new_static(network, filename, section_line, &n); + r = route_new_static(network, filename, section_line, &route); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -2452,18 +1809,18 @@ int config_parse_ipv6_route_preference( } if (streq(rvalue, "low")) - n->pref = ICMPV6_ROUTER_PREF_LOW; + route->pref = SD_NDISC_PREFERENCE_LOW; else if (streq(rvalue, "medium")) - n->pref = ICMPV6_ROUTER_PREF_MEDIUM; + route->pref = SD_NDISC_PREFERENCE_MEDIUM; else if (streq(rvalue, "high")) - n->pref = ICMPV6_ROUTER_PREF_HIGH; + route->pref = SD_NDISC_PREFERENCE_HIGH; else { log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown route preference: %s", rvalue); return 0; } - n->pref_set = true; - TAKE_PTR(n); + route->pref_set = true; + TAKE_PTR(route); return 0; } @@ -2480,10 +1837,10 @@ int config_parse_route_protocol( void *userdata) { Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; int r; - r = route_new_static(network, filename, section_line, &n); + r = route_new_static(network, filename, section_line, &route); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -2499,9 +1856,9 @@ int config_parse_route_protocol( return 0; } - n->protocol = r; + route->protocol = r; - TAKE_PTR(n); + TAKE_PTR(route); return 0; } @@ -2518,10 +1875,10 @@ int config_parse_route_type( void *userdata) { Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; int t, r; - r = route_new_static(network, filename, section_line, &n); + r = route_new_static(network, filename, section_line, &route); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -2537,603 +1894,62 @@ int config_parse_route_type( return 0; } - n->type = (unsigned char) t; - - TAKE_PTR(n); - return 0; -} - -int config_parse_route_hop_limit( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - Network *network = userdata; - uint32_t k; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - if (isempty(rvalue)) { - n->hop_limit = 0; - TAKE_PTR(n); - return 0; - } - - r = safe_atou32(rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Could not parse per route hop limit, ignoring assignment: %s", rvalue); - return 0; - } - if (k > 255) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Specified per route hop limit \"%s\" is too large, ignoring assignment: %m", rvalue); - return 0; - } - if (k == 0) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Invalid per route hop limit \"%s\", ignoring assignment: %m", rvalue); - return 0; - } - - n->hop_limit = k; - - TAKE_PTR(n); - return 0; -} - -int config_parse_tcp_congestion( - 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_(route_free_or_set_invalidp) Route *n = NULL; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - r = config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, - rvalue, &n->tcp_congestion_control_algo, userdata); - if (r < 0) - return r; - - TAKE_PTR(n); - return 0; -} - -int config_parse_tcp_advmss( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - Network *network = userdata; - uint64_t u; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - if (isempty(rvalue)) { - n->advmss = 0; - TAKE_PTR(n); - return 0; - } - - r = parse_size(rvalue, 1024, &u); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Could not parse TCPAdvertisedMaximumSegmentSize= \"%s\", ignoring assignment: %m", rvalue); - return 0; - } - - if (u == 0 || u > UINT32_MAX) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Invalid TCPAdvertisedMaximumSegmentSize= \"%s\", ignoring assignment: %m", rvalue); - return 0; - } - - n->advmss = u; - - TAKE_PTR(n); - return 0; -} - -int config_parse_tcp_window( - 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) { - - uint32_t *window = ASSERT_PTR(data); - uint32_t k; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = safe_atou32(rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Could not parse TCP %s \"%s\", ignoring assignment: %m", lvalue, rvalue); - return 0; - } - if (k >= 1024) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Specified TCP %s \"%s\" is too large, ignoring assignment: %m", lvalue, rvalue); - return 0; - } - if (k == 0) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Invalid TCP %s \"%s\", ignoring assignment: %m", lvalue, rvalue); - return 0; - } - - *window = k; - return 0; -} - -int config_parse_route_tcp_window( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - Network *network = userdata; - uint32_t *d; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - if (streq(lvalue, "InitialCongestionWindow")) - d = &n->initcwnd; - else if (streq(lvalue, "InitialAdvertisedReceiveWindow")) - d = &n->initrwnd; - else - assert_not_reached(); + route->type = (unsigned char) t; - r = config_parse_tcp_window(unit, filename, line, section, section_line, lvalue, ltype, rvalue, d, userdata); - if (r < 0) - return r; - - TAKE_PTR(n); + TAKE_PTR(route); return 0; } -int config_parse_route_mtu( - 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_(route_free_or_set_invalidp) Route *n = NULL; +int route_section_verify(Route *route) { int r; - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - r = config_parse_mtu(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &n->mtu, userdata); - if (r < 0) - return r; - - TAKE_PTR(n); - return 0; -} - -int config_parse_route_tcp_rto( - 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_(route_free_or_set_invalidp) Route *n = NULL; - usec_t usec; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - r = parse_sec(rvalue, &usec); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to parse route TCP retransmission timeout (RTO), ignoring assignment: %s", rvalue); - return 0; - } - - if (IN_SET(usec, 0, USEC_INFINITY) || - DIV_ROUND_UP(usec, USEC_PER_MSEC) > UINT32_MAX) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Route TCP retransmission timeout (RTO) must be in the range 0…%"PRIu32"ms, ignoring assignment: %s", UINT32_MAX, rvalue); - return 0; - } - - n->tcp_rto_usec = usec; - - TAKE_PTR(n); - return 0; -} - -int config_parse_multipath_route( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - _cleanup_(multipath_route_freep) MultipathRoute *m = NULL; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - _cleanup_free_ char *word = NULL; - Network *network = userdata; - union in_addr_union a; - int family, r; - const char *p; - char *dev; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - if (isempty(rvalue)) { - n->multipath_routes = ordered_set_free_with_destructor(n->multipath_routes, multipath_route_free); - TAKE_PTR(n); - return 0; - } - - m = new0(MultipathRoute, 1); - if (!m) - return log_oom(); - - p = rvalue; - r = extract_first_word(&p, &word, NULL, 0); - if (r == -ENOMEM) - return log_oom(); - if (r <= 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Invalid multipath route option, ignoring assignment: %s", rvalue); - return 0; - } - - dev = strchr(word, '@'); - if (dev) { - *dev++ = '\0'; - - r = parse_ifindex(dev); - if (r > 0) - m->ifindex = r; - else { - if (!ifname_valid_full(dev, IFNAME_VALID_ALTERNATIVE)) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Invalid interface name '%s' in %s=, ignoring: %s", dev, lvalue, rvalue); - return 0; - } - - m->ifname = strdup(dev); - if (!m->ifname) - return log_oom(); - } - } - - r = in_addr_from_string_auto(word, &family, &a); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Invalid multipath route gateway '%s', ignoring assignment: %m", rvalue); - return 0; - } - m->gateway.address = a; - m->gateway.family = family; - - if (!isempty(p)) { - r = safe_atou32(p, &m->weight); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Invalid multipath route weight, ignoring assignment: %s", p); - return 0; - } - /* ip command takes weight in the range 1…255, while kernel takes the value in the - * range 0…254. MultiPathRoute= setting also takes weight in the same range which ip - * command uses, then networkd decreases by one and stores it to match the range which - * kernel uses. */ - if (m->weight == 0 || m->weight > 256) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Invalid multipath route weight, ignoring assignment: %s", p); - return 0; - } - m->weight--; - } - - r = ordered_set_ensure_put(&n->multipath_routes, NULL, m); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to store multipath route, ignoring assignment: %m"); - return 0; - } - - TAKE_PTR(m); - TAKE_PTR(n); - return 0; -} + assert(route); + assert(route->section); -static int route_section_verify(Route *route, Network *network) { if (section_is_invalid(route->section)) return -EINVAL; /* Currently, we do not support static route with finite lifetime. */ assert(route->lifetime_usec == USEC_INFINITY); - if (route->gateway_from_dhcp_or_ra) { - if (route->gw_family == AF_UNSPEC) { - /* When deprecated Gateway=_dhcp is set, then assume gateway family based on other settings. */ - switch (route->family) { - case AF_UNSPEC: - log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. " - "Please use \"_dhcp4\" or \"_ipv6ra\" instead. Assuming \"_dhcp4\".", - route->section->filename, route->section->line); - route->family = AF_INET; - break; - case AF_INET: - case AF_INET6: - log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. " - "Assuming \"%s\" based on Destination=, Source=, or PreferredSource= setting.", - route->section->filename, route->section->line, route->family == AF_INET ? "_dhcp4" : "_ipv6ra"); - break; - default: - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: Invalid route family. Ignoring [Route] section from line %u.", - route->section->filename, route->section->line); - } - route->gw_family = route->family; - } - - if (route->gw_family == AF_INET && !FLAGS_SET(network->dhcp, ADDRESS_FAMILY_IPV4)) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: Gateway=\"_dhcp4\" is specified but DHCPv4 client is disabled. " - "Ignoring [Route] section from line %u.", - route->section->filename, route->section->line); - - if (route->gw_family == AF_INET6 && !network->ipv6_accept_ra) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: Gateway=\"_ipv6ra\" is specified but IPv6AcceptRA= is disabled. " - "Ignoring [Route] section from line %u.", - route->section->filename, route->section->line); - } - - /* When only Gateway= is specified, assume the route family based on the Gateway address. */ - if (route->family == AF_UNSPEC) - route->family = route->gw_family; - - if (route->family == AF_UNSPEC) { - assert(route->section); - - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: Route section without Gateway=, Destination=, Source=, " - "or PreferredSource= field configured. " - "Ignoring [Route] section from line %u.", - route->section->filename, route->section->line); - } - - if (route->family == AF_INET6 && route->gw_family == AF_INET) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: IPv4 gateway is configured for IPv6 route. " - "Ignoring [Route] section from line %u.", - route->section->filename, route->section->line); + r = route_section_verify_nexthops(route); + if (r < 0) + return r; - if (!route->table_set && network->vrf) { - route->table = VRF(network->vrf)->table; + /* table */ + if (!route->table_set && route->network && route->network->vrf) { + route->table = VRF(route->network->vrf)->table; route->table_set = true; } if (!route->table_set && IN_SET(route->type, RTN_LOCAL, RTN_BROADCAST, RTN_ANYCAST, RTN_NAT)) route->table = RT_TABLE_LOCAL; - if (!route->scope_set && route->family != AF_INET6) { + /* scope */ + if (!route->scope_set && route->family == AF_INET) { if (IN_SET(route->type, RTN_LOCAL, RTN_NAT)) route->scope = RT_SCOPE_HOST; else if (IN_SET(route->type, RTN_BROADCAST, RTN_ANYCAST, RTN_MULTICAST)) route->scope = RT_SCOPE_LINK; else if (IN_SET(route->type, RTN_UNICAST, RTN_UNSPEC) && !route->gateway_from_dhcp_or_ra && - !in_addr_is_set(route->gw_family, &route->gw) && - ordered_set_isempty(route->multipath_routes) && + !in_addr_is_set(route->nexthop.family, &route->nexthop.gw) && + ordered_set_isempty(route->nexthops) && route->nexthop_id == 0) route->scope = RT_SCOPE_LINK; } - if (route->scope != RT_SCOPE_UNIVERSE && route->family == AF_INET6) { - log_warning("%s: Scope= is specified for IPv6 route. It will be ignored.", route->section->filename); - route->scope = RT_SCOPE_UNIVERSE; - } - - if (route->family == AF_INET6 && route->priority == 0) - route->priority = IP6_RT_PRIO_USER; + /* IPv6 route */ + if (route->family == AF_INET6) { + if (route->scope != RT_SCOPE_UNIVERSE) { + log_warning("%s: Scope= is specified for IPv6 route. It will be ignored.", route->section->filename); + route->scope = RT_SCOPE_UNIVERSE; + } - if (route->gateway_onlink < 0 && in_addr_is_set(route->gw_family, &route->gw) && - ordered_hashmap_isempty(network->addresses_by_section)) { - /* If no address is configured, in most cases the gateway cannot be reachable. - * TODO: we may need to improve the condition above. */ - log_warning("%s: Gateway= without static address configured. " - "Enabling GatewayOnLink= option.", - network->filename); - route->gateway_onlink = true; + if (route->priority == 0) + route->priority = IP6_RT_PRIO_USER; } - if (route->gateway_onlink >= 0) - SET_FLAG(route->flags, RTNH_F_ONLINK, route->gateway_onlink); - - if (route->family == AF_INET6) { - MultipathRoute *m; - - ORDERED_SET_FOREACH(m, route->multipath_routes) - if (m->gateway.family == AF_INET) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: IPv4 multipath route is specified for IPv6 route. " - "Ignoring [Route] section from line %u.", - route->section->filename, route->section->line); - } - - if ((route->gateway_from_dhcp_or_ra || - in_addr_is_set(route->gw_family, &route->gw)) && - !ordered_set_isempty(route->multipath_routes)) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: Gateway= cannot be specified with MultiPathRoute=. " - "Ignoring [Route] section from line %u.", - route->section->filename, route->section->line); - - if (route->nexthop_id > 0 && - (route->gateway_from_dhcp_or_ra || - in_addr_is_set(route->gw_family, &route->gw) || - !ordered_set_isempty(route->multipath_routes))) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: NextHopId= cannot be specified with Gateway= or MultiPathRoute=. " - "Ignoring [Route] section from line %u.", - route->section->filename, route->section->line); - return 0; } @@ -3143,6 +1959,6 @@ void network_drop_invalid_routes(Network *network) { assert(network); HASHMAP_FOREACH(route, network->routes_by_section) - if (route_section_verify(route, network) < 0) - route_free(route); + if (route_section_verify(route) < 0) + route_detach(route); } diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h index 3d85889..912c6e5 100644 --- a/src/network/networkd-route.h +++ b/src/network/networkd-route.h @@ -9,12 +9,16 @@ #include "conf-parser.h" #include "in-addr-util.h" #include "networkd-link.h" +#include "networkd-route-metric.h" +#include "networkd-route-nexthop.h" #include "networkd-util.h" typedef struct Manager Manager; typedef struct Network Network; typedef struct Request Request; typedef struct Route Route; +typedef struct Wireguard Wireguard; + typedef int (*route_netlink_handler_t)( sd_netlink *rtnl, sd_netlink_message *m, @@ -23,86 +27,93 @@ typedef int (*route_netlink_handler_t)( Route *route); struct Route { - Link *link; Manager *manager; Network *network; + Wireguard *wireguard; ConfigSection *section; NetworkConfigSource source; NetworkConfigState state; union in_addr_union provider; /* DHCP server or router address */ - int family; - int gw_family; - uint32_t gw_weight; - int quickack; - int fast_open_no_cookie; - int ttl_propagate; + unsigned n_ref; + /* rtmsg header */ + int family; unsigned char dst_prefixlen; - unsigned char src_prefixlen; - unsigned char scope; + unsigned char src_prefixlen; /* IPv6 only */ + unsigned char tos; /* IPv4 only */ unsigned char protocol; /* RTPROT_* */ - unsigned char type; /* RTN_* */ - unsigned char tos; - uint32_t priority; /* note that ip(8) calls this 'metric' */ - uint32_t table; - uint32_t mtu; - uint32_t initcwnd; - uint32_t initrwnd; - uint32_t advmss; - uint32_t hop_limit; - char *tcp_congestion_control_algo; - unsigned char pref; - unsigned flags; - int gateway_onlink; /* Only used in conf parser and route_section_verify(). */ - uint32_t nexthop_id; - usec_t tcp_rto_usec; + unsigned char scope; /* IPv4 only */ + unsigned char type; /* RTN_*, e.g. RTN_LOCAL, RTN_UNREACHABLE */ + unsigned flags; /* e.g. RTNH_F_ONLINK */ + + /* attributes */ + union in_addr_union dst; /* RTA_DST */ + union in_addr_union src; /* RTA_SRC (IPv6 only) */ + uint32_t priority; /* RTA_PRIORITY, note that ip(8) calls this 'metric' */ + union in_addr_union prefsrc; /* RTA_PREFSRC */ + uint32_t table; /* RTA_TABLE, also used in rtmsg header */ + uint8_t pref; /* RTA_PREF (IPv6 only) */ + + /* nexthops */ + RouteNextHop nexthop; /* RTA_OIF, and RTA_GATEWAY or RTA_VIA (IPv4 only) */ + OrderedSet *nexthops; /* RTA_MULTIPATH */ + uint32_t nexthop_id; /* RTA_NH_ID */ + + /* metrics (RTA_METRICS) */ + RouteMetric metric; + + /* This is an absolute point in time, and NOT a timespan/duration. + * Must be specified with clock_boottime_or_monotonic(). */ + usec_t lifetime_usec; /* RTA_EXPIRES (IPv6 only) */ + /* Used when kernel does not support RTA_EXPIRES attribute. */ + sd_event_source *expire; + bool expiration_managed_by_kernel:1; /* RTA_CACHEINFO has nonzero rta_expires */ + /* Only used by conf persers and route_section_verify(). */ bool scope_set:1; bool table_set:1; bool priority_set:1; bool protocol_set:1; bool pref_set:1; bool gateway_from_dhcp_or_ra:1; - - union in_addr_union gw; - union in_addr_union dst; - union in_addr_union src; - union in_addr_union prefsrc; - OrderedSet *multipath_routes; - - /* This is an absolute point in time, and NOT a timespan/duration. - * Must be specified with clock_boottime_or_monotonic(). */ - usec_t lifetime_usec; - /* Used when kernel does not support RTA_EXPIRES attribute. */ - sd_event_source *expire; + int gateway_onlink; }; extern const struct hash_ops route_hash_ops; +extern const struct hash_ops route_hash_ops_unref; + +Route* route_ref(Route *route); +Route* route_unref(Route *route); +DEFINE_SECTION_CLEANUP_FUNCTIONS(Route, route_unref); int route_new(Route **ret); -Route *route_free(Route *route); -DEFINE_SECTION_CLEANUP_FUNCTIONS(Route, route_free); -int route_dup(const Route *src, Route **ret); +int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret); +int route_dup(const Route *src, const RouteNextHop *nh, Route **ret); + +int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, Route *route, const char *error_msg); +int route_remove(Route *route, Manager *manager); +int route_remove_and_cancel(Route *route, Manager *manager); -int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, const char *error_msg); -int route_remove(Route *route); -int route_remove_and_drop(Route *route); +int route_get(Manager *manager, const Route *route, Route **ret); +int route_get_request(Manager *manager, const Route *route, Request **ret); -int route_get(Manager *manager, Link *link, const Route *in, Route **ret); +bool route_can_update(const Route *existing, const Route *requesting); -int link_drop_managed_routes(Link *link); -int link_drop_foreign_routes(Link *link); -void link_foreignize_routes(Link *link); +int link_drop_routes(Link *link, bool foreign); +static inline int link_drop_static_routes(Link *link) { + return link_drop_routes(link, false); +} +static inline int link_drop_foreign_routes(Link *link) { + return link_drop_routes(link, true); +} +int link_foreignize_routes(Link *link); -void route_cancel_request(Route *route, Link *link); int link_request_route( Link *link, - Route *route, - bool consume_object, + const Route *route, unsigned *message_counter, - route_netlink_handler_t netlink_handler, - Request **ret); + route_netlink_handler_t netlink_handler); int link_request_static_routes(Link *link, bool only_ipv4); int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Manager *m); @@ -110,26 +121,16 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma int network_add_ipv4ll_route(Network *network); int network_add_default_route_on_device(Network *network); void network_drop_invalid_routes(Network *network); +int route_section_verify(Route *route); DEFINE_NETWORK_CONFIG_STATE_FUNCTIONS(Route, route); -void link_mark_routes(Link *link, NetworkConfigSource source); +void manager_mark_routes(Manager *manager, Link *link, NetworkConfigSource source); -CONFIG_PARSER_PROTOTYPE(config_parse_gateway); CONFIG_PARSER_PROTOTYPE(config_parse_preferred_src); CONFIG_PARSER_PROTOTYPE(config_parse_destination); CONFIG_PARSER_PROTOTYPE(config_parse_route_priority); CONFIG_PARSER_PROTOTYPE(config_parse_route_scope); CONFIG_PARSER_PROTOTYPE(config_parse_route_table); -CONFIG_PARSER_PROTOTYPE(config_parse_route_boolean); CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_route_preference); CONFIG_PARSER_PROTOTYPE(config_parse_route_protocol); CONFIG_PARSER_PROTOTYPE(config_parse_route_type); -CONFIG_PARSER_PROTOTYPE(config_parse_route_tcp_window); -CONFIG_PARSER_PROTOTYPE(config_parse_route_hop_limit); -CONFIG_PARSER_PROTOTYPE(config_parse_tcp_window); -CONFIG_PARSER_PROTOTYPE(config_parse_route_tcp_rto); -CONFIG_PARSER_PROTOTYPE(config_parse_route_mtu); -CONFIG_PARSER_PROTOTYPE(config_parse_multipath_route); -CONFIG_PARSER_PROTOTYPE(config_parse_tcp_congestion); -CONFIG_PARSER_PROTOTYPE(config_parse_tcp_advmss); -CONFIG_PARSER_PROTOTYPE(config_parse_route_nexthop); diff --git a/src/network/networkd-routing-policy-rule.c b/src/network/networkd-routing-policy-rule.c index 0cb5831..886b4da 100644 --- a/src/network/networkd-routing-policy-rule.c +++ b/src/network/networkd-routing-policy-rule.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include <net/if.h> #include <linux/fib_rules.h> @@ -156,33 +157,35 @@ static int routing_policy_rule_dup(const RoutingPolicyRule *src, RoutingPolicyRu static void routing_policy_rule_hash_func(const RoutingPolicyRule *rule, struct siphash *state) { assert(rule); - siphash24_compress(&rule->family, sizeof(rule->family), state); + siphash24_compress_typesafe(rule->family, state); switch (rule->family) { case AF_INET: case AF_INET6: - siphash24_compress(&rule->from, FAMILY_ADDRESS_SIZE(rule->family), state); - siphash24_compress(&rule->from_prefixlen, sizeof(rule->from_prefixlen), state); + in_addr_hash_func(&rule->from, rule->family, state); + siphash24_compress_typesafe(rule->from_prefixlen, state); - siphash24_compress(&rule->to, FAMILY_ADDRESS_SIZE(rule->family), state); - siphash24_compress(&rule->to_prefixlen, sizeof(rule->to_prefixlen), state); + siphash24_compress_boolean(rule->l3mdev, state); + + in_addr_hash_func(&rule->to, rule->family, state); + siphash24_compress_typesafe(rule->to_prefixlen, state); siphash24_compress_boolean(rule->invert_rule, state); - siphash24_compress(&rule->tos, sizeof(rule->tos), state); - siphash24_compress(&rule->type, sizeof(rule->type), state); - siphash24_compress(&rule->fwmark, sizeof(rule->fwmark), state); - siphash24_compress(&rule->fwmask, sizeof(rule->fwmask), state); - siphash24_compress(&rule->priority, sizeof(rule->priority), state); - siphash24_compress(&rule->table, sizeof(rule->table), state); - siphash24_compress(&rule->suppress_prefixlen, sizeof(rule->suppress_prefixlen), state); - siphash24_compress(&rule->suppress_ifgroup, sizeof(rule->suppress_ifgroup), state); - - siphash24_compress(&rule->ipproto, sizeof(rule->ipproto), state); - siphash24_compress(&rule->protocol, sizeof(rule->protocol), state); - siphash24_compress(&rule->sport, sizeof(rule->sport), state); - siphash24_compress(&rule->dport, sizeof(rule->dport), state); - siphash24_compress(&rule->uid_range, sizeof(rule->uid_range), state); + siphash24_compress_typesafe(rule->tos, state); + siphash24_compress_typesafe(rule->type, state); + siphash24_compress_typesafe(rule->fwmark, state); + siphash24_compress_typesafe(rule->fwmask, state); + siphash24_compress_typesafe(rule->priority, state); + siphash24_compress_typesafe(rule->table, state); + siphash24_compress_typesafe(rule->suppress_prefixlen, state); + siphash24_compress_typesafe(rule->suppress_ifgroup, state); + + siphash24_compress_typesafe(rule->ipproto, state); + siphash24_compress_typesafe(rule->protocol, state); + siphash24_compress_typesafe(rule->sport, state); + siphash24_compress_typesafe(rule->dport, state); + siphash24_compress_typesafe(rule->uid_range, state); siphash24_compress_string(rule->iif, state); siphash24_compress_string(rule->oif, state); @@ -212,6 +215,10 @@ static int routing_policy_rule_compare_func(const RoutingPolicyRule *a, const Ro if (r != 0) return r; + r = CMP(a->l3mdev, b->l3mdev); + if (r != 0) + return r; + r = CMP(a->to_prefixlen, b->to_prefixlen); if (r != 0) return r; @@ -476,19 +483,19 @@ static int routing_policy_rule_set_netlink_message(const RoutingPolicyRule *rule return r; } - if (rule->table < 256) { + if (rule->l3mdev) + r = sd_rtnl_message_routing_policy_rule_set_table(m, RT_TABLE_UNSPEC); + else if (rule->table < 256) r = sd_rtnl_message_routing_policy_rule_set_table(m, rule->table); - if (r < 0) - return r; - } else { + else { r = sd_rtnl_message_routing_policy_rule_set_table(m, RT_TABLE_UNSPEC); if (r < 0) return r; r = sd_netlink_message_append_u32(m, FRA_TABLE, rule->table); - if (r < 0) - return r; } + if (r < 0) + return r; if (rule->fwmark > 0) { r = sd_netlink_message_append_u32(m, FRA_FWMARK, rule->fwmark); @@ -544,6 +551,12 @@ static int routing_policy_rule_set_netlink_message(const RoutingPolicyRule *rule return r; } + if (rule->l3mdev) { + r = sd_netlink_message_append_u8(m, FRA_L3MDEV, 1); + if (r < 0) + return r; + } + if (rule->suppress_prefixlen >= 0) { r = sd_netlink_message_append_u32(m, FRA_SUPPRESS_PREFIXLEN, (uint32_t) rule->suppress_prefixlen); if (r < 0) @@ -642,7 +655,7 @@ static void manager_mark_routing_policy_rules(Manager *m, bool foreign, const Li continue; /* When 'foreign' is true, mark only foreign rules, and vice versa. */ - if (foreign != (rule->source == NETWORK_CONFIG_SOURCE_FOREIGN)) + if (rule->source != (foreign ? NETWORK_CONFIG_SOURCE_FOREIGN : NETWORK_CONFIG_SOURCE_STATIC)) continue; /* Ignore rules not assigned yet or already removing. */ @@ -853,20 +866,17 @@ int link_request_static_routing_policy_rules(Link *link) { static const RoutingPolicyRule kernel_rules[] = { { .family = AF_INET, .priority_set = true, .priority = 0, .table = RT_TABLE_LOCAL, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, }, + { .family = AF_INET, .priority_set = true, .priority = 1000, .table = RT_TABLE_UNSPEC, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, .l3mdev = true }, { .family = AF_INET, .priority_set = true, .priority = 32766, .table = RT_TABLE_MAIN, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, }, { .family = AF_INET, .priority_set = true, .priority = 32767, .table = RT_TABLE_DEFAULT, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, }, { .family = AF_INET6, .priority_set = true, .priority = 0, .table = RT_TABLE_LOCAL, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, }, + { .family = AF_INET6, .priority_set = true, .priority = 1000, .table = RT_TABLE_UNSPEC, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, .l3mdev = true }, { .family = AF_INET6, .priority_set = true, .priority = 32766, .table = RT_TABLE_MAIN, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, }, }; static bool routing_policy_rule_is_created_by_kernel(const RoutingPolicyRule *rule) { assert(rule); - if (rule->l3mdev > 0) - /* Currently, [RoutingPolicyRule] does not explicitly set FRA_L3MDEV. So, if the flag - * is set, it is safe to treat the rule as created by kernel. */ - return true; - for (size_t i = 0; i < ELEMENTSOF(kernel_rules); i++) if (routing_policy_rule_equal(rule, &kernel_rules[i])) return true; @@ -1016,11 +1026,13 @@ int manager_rtnl_process_rule(sd_netlink *rtnl, sd_netlink_message *message, Man return 0; } - r = sd_netlink_message_read_u8(message, FRA_L3MDEV, &tmp->l3mdev); + uint8_t l3mdev = 0; + r = sd_netlink_message_read_u8(message, FRA_L3MDEV, &l3mdev); if (r < 0 && r != -ENODATA) { log_warning_errno(r, "rtnl: could not get FRA_L3MDEV attribute, ignoring: %m"); return 0; } + tmp->l3mdev = l3mdev != 0; r = sd_netlink_message_read(message, FRA_SPORT_RANGE, sizeof(tmp->sport), &tmp->sport); if (r < 0 && r != -ENODATA) { @@ -1408,7 +1420,7 @@ int config_parse_routing_policy_rule_port_range( if (r < 0) return log_oom(); - r = parse_ip_port_range(rvalue, &low, &high); + r = parse_ip_port_range(rvalue, &low, &high, /* allow_zero = */ false); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse routing policy rule port range '%s'", rvalue); return 0; @@ -1502,6 +1514,44 @@ int config_parse_routing_policy_rule_invert( return 0; } +int config_parse_routing_policy_rule_l3mdev( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse RPDB rule l3mdev, ignoring: %s", rvalue); + return 0; + } + + n->l3mdev = r; + + TAKE_PTR(n); + return 0; +} + int config_parse_routing_policy_rule_family( const char *unit, const char *filename, @@ -1734,12 +1784,6 @@ static int routing_policy_rule_section_verify(RoutingPolicyRule *rule) { /* rule->family can be AF_UNSPEC only when Family=both. */ } - /* Currently, [RoutingPolicyRule] does not have a setting to set FRA_L3MDEV flag. Please also - * update routing_policy_rule_is_created_by_kernel() when a new setting which sets the flag is - * added in the future. */ - if (rule->l3mdev > 0) - assert_not_reached(); - return 0; } diff --git a/src/network/networkd-routing-policy-rule.h b/src/network/networkd-routing-policy-rule.h index b6ce2fa..42a575c 100644 --- a/src/network/networkd-routing-policy-rule.h +++ b/src/network/networkd-routing-policy-rule.h @@ -22,6 +22,7 @@ typedef struct RoutingPolicyRule { bool invert_rule; bool priority_set; + bool l3mdev; /* FRA_L3MDEV */ uint8_t tos; uint8_t type; @@ -29,7 +30,6 @@ typedef struct RoutingPolicyRule { uint8_t protocol; /* FRA_PROTOCOL */ uint8_t to_prefixlen; uint8_t from_prefixlen; - uint8_t l3mdev; /* FRA_L3MDEV */ uint32_t table; uint32_t fwmark; @@ -66,7 +66,7 @@ int manager_drop_routing_policy_rules_internal(Manager *m, bool foreign, const L static inline int manager_drop_foreign_routing_policy_rules(Manager *m) { return manager_drop_routing_policy_rules_internal(m, true, NULL); } -static inline int link_drop_managed_routing_policy_rules(Link *link) { +static inline int link_drop_static_routing_policy_rules(Link *link) { assert(link); return manager_drop_routing_policy_rules_internal(link->manager, false, link); } @@ -80,6 +80,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_fwmark_mask); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_prefix); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_priority); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_device); +CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_l3mdev); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_port_range); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_ip_protocol); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_invert); diff --git a/src/network/networkd-setlink.c b/src/network/networkd-setlink.c index 011ea1f..058bc00 100644 --- a/src/network/networkd-setlink.c +++ b/src/network/networkd-setlink.c @@ -103,6 +103,19 @@ static int link_set_bridge_handler(sd_netlink *rtnl, sd_netlink_message *m, Requ } static int link_set_bridge_vlan_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, void *userdata) { + int r; + + assert(link); + + r = set_link_handler_internal(rtnl, m, req, link, /* ignore = */ false, NULL); + if (r <= 0) + return r; + + link->bridge_vlan_set = true; + return 0; +} + +static int link_del_bridge_vlan_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, void *userdata) { return set_link_handler_internal(rtnl, m, req, link, /* ignore = */ false, NULL); } @@ -160,19 +173,7 @@ static int link_unset_master_handler(sd_netlink *rtnl, sd_netlink_message *m, Re } static int link_set_mtu_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, void *userdata) { - int r; - - r = set_link_handler_internal(rtnl, m, req, link, /* ignore = */ true, get_link_default_handler); - if (r <= 0) - return r; - - /* The kernel resets ipv6 mtu after changing device mtu; - * we must set this here, after we've set device mtu */ - r = link_set_ipv6_mtu(link); - if (r < 0) - log_link_warning_errno(link, r, "Failed to set IPv6 MTU, ignoring: %m"); - - return 0; + return set_link_handler_internal(rtnl, m, req, link, /* ignore = */ true, get_link_default_handler); } static int link_configure_fill_message( @@ -326,29 +327,14 @@ static int link_configure_fill_message( return r; break; case REQUEST_TYPE_SET_LINK_BRIDGE_VLAN: - r = sd_rtnl_message_link_set_family(req, AF_BRIDGE); - if (r < 0) - return r; - - r = sd_netlink_message_open_container(req, IFLA_AF_SPEC); + r = bridge_vlan_set_message(link, req, /* is_set = */ true); if (r < 0) return r; - - if (link->master_ifindex <= 0) { - /* master needs BRIDGE_FLAGS_SELF flag */ - r = sd_netlink_message_append_u16(req, IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_SELF); - if (r < 0) - return r; - } - - r = bridge_vlan_append_info(link, req, link->network->pvid, link->network->br_vid_bitmap, link->network->br_untagged_bitmap); - if (r < 0) - return r; - - r = sd_netlink_message_close_container(req); + break; + case REQUEST_TYPE_DEL_LINK_BRIDGE_VLAN: + r = bridge_vlan_set_message(link, req, /* is_set = */ false); if (r < 0) return r; - break; case REQUEST_TYPE_SET_LINK_CAN: r = can_set_netlink_message(link, req); @@ -430,6 +416,8 @@ static int link_configure(Link *link, Request *req) { r = sd_rtnl_message_new_link(link->manager->rtnl, &m, RTM_NEWLINK, link->master_ifindex); else if (IN_SET(req->type, REQUEST_TYPE_SET_LINK_CAN, REQUEST_TYPE_SET_LINK_IPOIB)) r = sd_rtnl_message_new_link(link->manager->rtnl, &m, RTM_NEWLINK, link->ifindex); + else if (req->type == REQUEST_TYPE_DEL_LINK_BRIDGE_VLAN) + r = sd_rtnl_message_new_link(link->manager->rtnl, &m, RTM_DELLINK, link->ifindex); else r = sd_rtnl_message_new_link(link->manager->rtnl, &m, RTM_SETLINK, link->ifindex); if (r < 0) @@ -453,6 +441,43 @@ static bool netdev_is_ready(NetDev *netdev) { return true; } +static uint32_t link_adjust_mtu(Link *link, uint32_t mtu) { + const char *origin; + uint32_t min_mtu; + + assert(link); + assert(link->network); + + min_mtu = link->min_mtu; + origin = "the minimum MTU of the interface"; + if (link_ipv6_enabled(link)) { + /* IPv6 protocol requires a minimum MTU of IPV6_MTU_MIN(1280) bytes on the interface. Bump up + * MTU bytes to IPV6_MTU_MIN. */ + if (min_mtu < IPV6_MIN_MTU) { + min_mtu = IPV6_MIN_MTU; + origin = "the minimum IPv6 MTU"; + } + if (min_mtu < link->network->ipv6_mtu) { + min_mtu = link->network->ipv6_mtu; + origin = "the requested IPv6 MTU in IPv6MTUBytes="; + } + } + + if (mtu < min_mtu) { + log_link_warning(link, "Bumping the requested MTU %"PRIu32" to %s (%"PRIu32")", + mtu, origin, min_mtu); + mtu = min_mtu; + } + + if (mtu > link->max_mtu) { + log_link_warning(link, "Reducing the requested MTU %"PRIu32" to the interface's maximum MTU %"PRIu32".", + mtu, link->max_mtu); + mtu = link->max_mtu; + } + + return mtu; +} + static int link_is_ready_to_set_link(Link *link, Request *req) { int r; @@ -480,9 +505,11 @@ static int link_is_ready_to_set_link(Link *link, Request *req) { if (link->network->keep_master && link->master_ifindex <= 0 && !streq_ptr(link->kind, "bridge")) return false; - break; + case REQUEST_TYPE_DEL_LINK_BRIDGE_VLAN: + return link->bridge_vlan_set; + case REQUEST_TYPE_SET_LINK_CAN: /* Do not check link->set_flags_messages here, as it is ok even if link->flags * is outdated, and checking the counter causes a deadlock. */ @@ -568,13 +595,24 @@ static int link_is_ready_to_set_link(Link *link, Request *req) { })) return false; - /* Changing FD mode may affect MTU. */ + /* Changing FD mode may affect MTU. + * See https://docs.kernel.org/networking/can.html#can-fd-flexible-data-rate-driver-support + * MTU = 16 (CAN_MTU) => Classical CAN device + * MTU = 72 (CANFD_MTU) => CAN FD capable device */ if (ordered_set_contains(link->manager->request_queue, &(const Request) { .link = link, .type = REQUEST_TYPE_SET_LINK_CAN, })) return false; + + /* Now, it is ready to set MTU, but before setting, adjust requested MTU. */ + uint32_t mtu = link_adjust_mtu(link, PTR_TO_UINT32(req->userdata)); + if (mtu == link->mtu) + return -EALREADY; /* Not necessary to set the same value. */ + + req->userdata = UINT32_TO_PTR(mtu); + return true; } default: break; @@ -712,10 +750,14 @@ int link_request_to_set_bridge(Link *link) { } int link_request_to_set_bridge_vlan(Link *link) { + int r; + assert(link); assert(link->network); - if (!link->network->use_br_vlan) + /* If nothing configured, use the default vlan ID. */ + if (memeqzero(link->network->bridge_vlan_bitmap, BRIDGE_VLAN_BITMAP_LEN * sizeof(uint32_t)) && + link->network->bridge_vlan_pvid == BRIDGE_VLAN_KEEP_PVID) return 0; if (!link->network->bridge && !streq_ptr(link->kind, "bridge")) { @@ -731,9 +773,21 @@ int link_request_to_set_bridge_vlan(Link *link) { return 0; } - return link_request_set_link(link, REQUEST_TYPE_SET_LINK_BRIDGE_VLAN, - link_set_bridge_vlan_handler, - NULL); + link->bridge_vlan_set = false; + + r = link_request_set_link(link, REQUEST_TYPE_SET_LINK_BRIDGE_VLAN, + link_set_bridge_vlan_handler, + NULL); + if (r < 0) + return r; + + r = link_request_set_link(link, REQUEST_TYPE_DEL_LINK_BRIDGE_VLAN, + link_del_bridge_vlan_handler, + NULL); + if (r < 0) + return r; + + return 0; } int link_request_to_set_can(Link *link) { @@ -847,51 +901,12 @@ int link_request_to_set_master(Link *link) { } int link_request_to_set_mtu(Link *link, uint32_t mtu) { - const char *origin; - uint32_t min_mtu, max_mtu; Request *req; int r; assert(link); - assert(link->network); - - min_mtu = link->min_mtu; - origin = "the minimum MTU of the interface"; - if (link_ipv6_enabled(link)) { - /* IPv6 protocol requires a minimum MTU of IPV6_MTU_MIN(1280) bytes on the interface. Bump up - * MTU bytes to IPV6_MTU_MIN. */ - if (min_mtu < IPV6_MIN_MTU) { - min_mtu = IPV6_MIN_MTU; - origin = "the minimum IPv6 MTU"; - } - if (min_mtu < link->network->ipv6_mtu) { - min_mtu = link->network->ipv6_mtu; - origin = "the requested IPv6 MTU in IPv6MTUBytes="; - } - } - - if (mtu < min_mtu) { - log_link_warning(link, "Bumping the requested MTU %"PRIu32" to %s (%"PRIu32")", - mtu, origin, min_mtu); - mtu = min_mtu; - } - - max_mtu = link->max_mtu; - if (link->iftype == ARPHRD_CAN) - /* The maximum MTU may be changed when FD mode is changed. - * See https://docs.kernel.org/networking/can.html#can-fd-flexible-data-rate-driver-support - * MTU = 16 (CAN_MTU) => Classical CAN device - * MTU = 72 (CANFD_MTU) => CAN FD capable device - * So, even if the current maximum is 16, we should not reduce the requested value now. */ - max_mtu = MAX(max_mtu, 72u); - - if (mtu > max_mtu) { - log_link_warning(link, "Reducing the requested MTU %"PRIu32" to the interface's maximum MTU %"PRIu32".", - mtu, max_mtu); - mtu = max_mtu; - } - if (link->mtu == mtu) + if (mtu == 0) return 0; r = link_request_set_link(link, REQUEST_TYPE_SET_LINK_MTU, diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c index bba84bb..fbe4fee 100644 --- a/src/network/networkd-state-file.c +++ b/src/network/networkd-state-file.c @@ -15,6 +15,7 @@ #include "networkd-manager-bus.h" #include "networkd-manager.h" #include "networkd-network.h" +#include "networkd-ntp.h" #include "networkd-state-file.h" #include "ordered-set.h" #include "set.h" @@ -101,7 +102,7 @@ static int link_put_dns(Link *link, OrderedSet **s) { if (r < 0) return r; - if (link->dhcp_lease && link->network->dhcp_use_dns) { + if (link->dhcp_lease && link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4)) { const struct in_addr *addresses; r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses); @@ -112,7 +113,7 @@ static int link_put_dns(Link *link, OrderedSet **s) { } } - if (link->dhcp6_lease && link->network->dhcp6_use_dns) { + if (link->dhcp6_lease && link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP6)) { const struct in6_addr *addresses; r = sd_dhcp6_lease_get_dns(link->dhcp6_lease, &addresses); @@ -123,7 +124,7 @@ static int link_put_dns(Link *link, OrderedSet **s) { } } - if (link->network->ipv6_accept_ra_use_dns) { + if (link_get_use_dns(link, NETWORK_CONFIG_SOURCE_NDISC)) { NDiscRDNSS *a; SET_FOREACH(a, link->ndisc_rdnss) { @@ -150,7 +151,7 @@ static int link_put_ntp(Link *link, OrderedSet **s) { if (r < 0) return r; - if (link->dhcp_lease && link->network->dhcp_use_ntp) { + if (link->dhcp_lease && link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4)) { const struct in_addr *addresses; r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses); @@ -161,7 +162,7 @@ static int link_put_ntp(Link *link, OrderedSet **s) { } } - if (link->dhcp6_lease && link->network->dhcp6_use_ntp) { + if (link->dhcp6_lease && link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP6)) { const struct in6_addr *addresses; char **fqdn; @@ -206,7 +207,7 @@ static int link_put_sip(Link *link, OrderedSet **s) { static int link_put_domains(Link *link, bool is_route, OrderedSet **s) { OrderedSet *link_domains, *network_domains; - DHCPUseDomains use_domains; + UseDomains use_domains; int r; assert(link); @@ -215,7 +216,7 @@ static int link_put_domains(Link *link, bool is_route, OrderedSet **s) { link_domains = is_route ? link->route_domains : link->search_domains; network_domains = is_route ? link->network->route_domains : link->network->search_domains; - use_domains = is_route ? DHCP_USE_DOMAINS_ROUTE : DHCP_USE_DOMAINS_YES; + use_domains = is_route ? USE_DOMAINS_ROUTE : USE_DOMAINS_YES; if (link_domains) return ordered_set_put_string_set(s, link_domains); @@ -224,7 +225,7 @@ static int link_put_domains(Link *link, bool is_route, OrderedSet **s) { if (r < 0) return r; - if (link->dhcp_lease && link->network->dhcp_use_domains == use_domains) { + if (link->dhcp_lease && link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP4) == use_domains) { const char *domainname; char **domains; @@ -243,7 +244,7 @@ static int link_put_domains(Link *link, bool is_route, OrderedSet **s) { } } - if (link->dhcp6_lease && link->network->dhcp6_use_domains == use_domains) { + if (link->dhcp6_lease && link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP6) == use_domains) { char **domains; r = sd_dhcp6_lease_get_domains(link->dhcp6_lease, &domains); @@ -254,7 +255,7 @@ static int link_put_domains(Link *link, bool is_route, OrderedSet **s) { } } - if (link->network->ipv6_accept_ra_use_domains == use_domains) { + if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_NDISC) == use_domains) { NDiscDNSSL *a; SET_FOREACH(a, link->ndisc_dnssl) { @@ -528,7 +529,7 @@ static void serialize_addresses( fputc('\n', f); } -static void link_save_domains(Link *link, FILE *f, OrderedSet *static_domains, DHCPUseDomains use_domains) { +static void link_save_domains(Link *link, FILE *f, OrderedSet *static_domains, UseDomains use_domains) { bool space = false; const char *p; @@ -537,33 +538,33 @@ static void link_save_domains(Link *link, FILE *f, OrderedSet *static_domains, D assert(f); ORDERED_SET_FOREACH(p, static_domains) - fputs_with_space(f, p, NULL, &space); + fputs_with_separator(f, p, NULL, &space); - if (use_domains == DHCP_USE_DOMAINS_NO) + if (use_domains == USE_DOMAINS_NO) return; - if (link->dhcp_lease && link->network->dhcp_use_domains == use_domains) { + if (link->dhcp_lease && link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP4) == use_domains) { const char *domainname; char **domains; if (sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname) >= 0) - fputs_with_space(f, domainname, NULL, &space); + fputs_with_separator(f, domainname, NULL, &space); if (sd_dhcp_lease_get_search_domains(link->dhcp_lease, &domains) >= 0) fputstrv(f, domains, NULL, &space); } - if (link->dhcp6_lease && link->network->dhcp6_use_domains == use_domains) { + if (link->dhcp6_lease && link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP6) == use_domains) { char **domains; if (sd_dhcp6_lease_get_domains(link->dhcp6_lease, &domains) >= 0) fputstrv(f, domains, NULL, &space); } - if (link->network->ipv6_accept_ra_use_domains == use_domains) { + if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_NDISC) == use_domains) { NDiscDNSSL *dd; SET_FOREACH(dd, link->ndisc_dnssl) - fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space); + fputs_with_separator(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space); } } @@ -583,8 +584,6 @@ static int link_save(Link *link) { if (link->state == LINK_STATE_LINGER) return 0; - link_lldp_save(link); - admin_state = link_state_to_string(link->state); assert(admin_state); @@ -630,14 +629,14 @@ static int link_save(Link *link) { fprintf(f, "REQUIRED_FOR_ONLINE=%s\n", yes_no(link->network->required_for_online)); - LinkOperationalStateRange st = link->network->required_operstate_for_online; - fprintf(f, "REQUIRED_OPER_STATE_FOR_ONLINE=%s%s%s\n", - strempty(link_operstate_to_string(st.min)), - st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? ":" : "", - st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? strempty(link_operstate_to_string(st.max)) : ""); + LinkOperationalStateRange st; + link_required_operstate_for_online(link, &st); + + fprintf(f, "REQUIRED_OPER_STATE_FOR_ONLINE=%s:%s\n", + link_operstate_to_string(st.min), link_operstate_to_string(st.max)); fprintf(f, "REQUIRED_FAMILY_FOR_ONLINE=%s\n", - link_required_address_family_to_string(link->network->required_family_for_online)); + link_required_address_family_to_string(link_required_family_for_online(link))); fprintf(f, "ACTIVATION_POLICY=%s\n", activation_policy_to_string(link->network->activation_policy)); @@ -652,7 +651,7 @@ static int link_save(Link *link) { if (!escaped) return -ENOMEM; - fputs_with_space(f, escaped, ":", &space); + fputs_with_separator(f, escaped, ":", &space); } fputs("\"\n", f); @@ -668,14 +667,14 @@ static int link_save(Link *link) { serialize_addresses(f, NULL, &space, NULL, link->dhcp_lease, - link->network->dhcp_use_dns, + link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4), SD_DHCP_LEASE_DNS, link->dhcp6_lease, - link->network->dhcp6_use_dns, + link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP6), sd_dhcp6_lease_get_dns, NULL); - if (link->network->ipv6_accept_ra_use_dns) { + if (link_get_use_dns(link, NETWORK_CONFIG_SOURCE_NDISC)) { NDiscRDNSS *dd; SET_FOREACH(dd, link->ndisc_rdnss) @@ -695,10 +694,10 @@ static int link_save(Link *link) { serialize_addresses(f, "NTP", NULL, link->network->ntp, link->dhcp_lease, - link->network->dhcp_use_ntp, + link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4), SD_DHCP_LEASE_NTP, link->dhcp6_lease, - link->network->dhcp6_use_ntp, + link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP6), sd_dhcp6_lease_get_ntp_addrs, sd_dhcp6_lease_get_ntp_fqdn); @@ -722,18 +721,18 @@ static int link_save(Link *link) { fputs("DOMAINS=", f); if (link->search_domains) - link_save_domains(link, f, link->search_domains, DHCP_USE_DOMAINS_NO); + link_save_domains(link, f, link->search_domains, USE_DOMAINS_NO); else - link_save_domains(link, f, link->network->search_domains, DHCP_USE_DOMAINS_YES); + link_save_domains(link, f, link->network->search_domains, USE_DOMAINS_YES); fputc('\n', f); /************************************************************/ fputs("ROUTE_DOMAINS=", f); if (link->route_domains) - link_save_domains(link, f, link->route_domains, DHCP_USE_DOMAINS_NO); + link_save_domains(link, f, link->route_domains, USE_DOMAINS_NO); else - link_save_domains(link, f, link->network->route_domains, DHCP_USE_DOMAINS_ROUTE); + link_save_domains(link, f, link->network->route_domains, USE_DOMAINS_ROUTE); fputc('\n', f); /************************************************************/ @@ -782,7 +781,7 @@ static int link_save(Link *link) { fputs("DNSSEC_NTA=", f); space = false; SET_FOREACH(n, nta_anchors) - fputs_with_space(f, n, NULL, &space); + fputs_with_separator(f, n, NULL, &space); fputc('\n', f); } } @@ -861,3 +860,26 @@ int link_save_and_clean_full(Link *link, bool also_save_manager) { link_clean(link); return k; } + +int manager_clean_all(Manager *manager) { + int r, ret = 0; + + assert(manager); + + if (manager->dirty) { + r = manager_save(manager); + if (r < 0) + log_warning_errno(r, "Failed to update state file %s, ignoring: %m", manager->state_file); + RET_GATHER(ret, r); + } + + Link *link; + SET_FOREACH(link, manager->dirty_links) { + r = link_save_and_clean(link); + if (r < 0) + log_link_warning_errno(link, r, "Failed to update link state file %s, ignoring: %m", link->state_file); + RET_GATHER(ret, r); + } + + return ret; +} diff --git a/src/network/networkd-state-file.h b/src/network/networkd-state-file.h index 684f0d1..7efd157 100644 --- a/src/network/networkd-state-file.h +++ b/src/network/networkd-state-file.h @@ -12,3 +12,4 @@ static inline int link_save_and_clean(Link *link) { } int manager_save(Manager *m); +int manager_clean_all(Manager *manager); diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index 2b226b2..68c23e0 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -4,6 +4,7 @@ #include <linux/if.h> #include <linux/if_arp.h> +#include "af-list.h" #include "missing_network.h" #include "networkd-link.h" #include "networkd-manager.h" @@ -13,6 +14,40 @@ #include "string-table.h" #include "sysctl-util.h" +static void manager_set_ip_forwarding(Manager *manager, int family) { + int r, t; + + assert(manager); + assert(IN_SET(family, AF_INET, AF_INET6)); + + if (family == AF_INET6 && !socket_ipv6_is_supported()) + return; + + t = manager->ip_forwarding[family == AF_INET6]; + if (t < 0) + return; /* keep */ + + /* First, set the default value. */ + r = sysctl_write_ip_property_boolean(family, "default", "forwarding", t); + if (r < 0) + log_warning_errno(r, "Failed to %s the default %s forwarding: %m", + enable_disable(t), af_to_ipv4_ipv6(family)); + + /* Then, set the value to all interfaces. */ + r = sysctl_write_ip_property_boolean(family, "all", "forwarding", t); + if (r < 0) + log_warning_errno(r, "Failed to %s %s forwarding for all interfaces: %m", + enable_disable(t), af_to_ipv4_ipv6(family)); +} + +void manager_set_sysctl(Manager *manager) { + assert(manager); + assert(!manager->test_mode); + + manager_set_ip_forwarding(manager, AF_INET); + manager_set_ip_forwarding(manager, AF_INET6); +} + static bool link_is_configured_for_family(Link *link, int family) { assert(link); @@ -58,48 +93,62 @@ static int link_set_proxy_arp(Link *link) { return sysctl_write_ip_property_boolean(AF_INET, link->ifname, "proxy_arp", link->network->proxy_arp > 0); } -static bool link_ip_forward_enabled(Link *link, int family) { +static int link_set_proxy_arp_pvlan(Link *link) { assert(link); - assert(IN_SET(family, AF_INET, AF_INET6)); - if (!link_is_configured_for_family(link, family)) - return false; + if (!link_is_configured_for_family(link, AF_INET)) + return 0; - return link->network->ip_forward & (family == AF_INET ? ADDRESS_FAMILY_IPV4 : ADDRESS_FAMILY_IPV6); + if (link->network->proxy_arp_pvlan < 0) + return 0; + + return sysctl_write_ip_property_boolean(AF_INET, link->ifname, "proxy_arp_pvlan", link->network->proxy_arp_pvlan > 0); } -static int link_set_ipv4_forward(Link *link) { +int link_get_ip_forwarding(Link *link, int family) { assert(link); + assert(link->manager); + assert(link->network); + assert(IN_SET(family, AF_INET, AF_INET6)); - if (!link_ip_forward_enabled(link, AF_INET)) - return 0; + /* If it is explicitly specified, then honor the setting. */ + int t = link->network->ip_forwarding[family == AF_INET6]; + if (t >= 0) + return t; + + /* If IPMasquerade= is enabled, also enable IP forwarding. */ + if (family == AF_INET && FLAGS_SET(link->network->ip_masquerade, ADDRESS_FAMILY_IPV4)) + return true; + if (family == AF_INET6 && FLAGS_SET(link->network->ip_masquerade, ADDRESS_FAMILY_IPV6)) + return true; - /* We propagate the forwarding flag from one interface to the - * global setting one way. This means: as long as at least one - * interface was configured at any time that had IP forwarding - * enabled the setting will stay on for good. We do this - * primarily to keep IPv4 and IPv6 packet forwarding behaviour - * somewhat in sync (see below). */ + /* If IPv6SendRA= is enabled, also enable IPv6 forwarding. */ + if (family == AF_INET6 && link_radv_enabled(link)) + return true; - return sysctl_write_ip_property(AF_INET, NULL, "ip_forward", "1"); + /* Otherwise, use the global setting. */ + return link->manager->ip_forwarding[family == AF_INET6]; } -static int link_set_ipv6_forward(Link *link) { +static int link_set_ip_forwarding(Link *link, int family) { + int r, t; + assert(link); + assert(IN_SET(family, AF_INET, AF_INET6)); - if (!link_ip_forward_enabled(link, AF_INET6)) + if (!link_is_configured_for_family(link, family)) return 0; - /* On Linux, the IPv6 stack does not know a per-interface - * packet forwarding setting: either packet forwarding is on - * for all, or off for all. We hence don't bother with a - * per-interface setting, but simply propagate the interface - * flag, if it is set, to the global flag, one-way. Note that - * while IPv4 would allow a per-interface flag, we expose the - * same behaviour there and also propagate the setting from - * one to all, to keep things simple (see above). */ + t = link_get_ip_forwarding(link, family); + if (t < 0) + return 0; /* keep */ - return sysctl_write_ip_property(AF_INET6, "all", "forwarding", "1"); + r = sysctl_write_ip_property_boolean(family, link->ifname, "forwarding", t); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to %s %s forwarding, ignoring: %m", + enable_disable(t), af_to_ipv4_ipv6(family)); + + return 0; } static int link_set_ipv4_rp_filter(Link *link) { @@ -167,6 +216,24 @@ static int link_set_ipv6_hop_limit(Link *link) { return sysctl_write_ip_property_int(AF_INET6, link->ifname, "hop_limit", link->network->ipv6_hop_limit); } +static int link_set_ipv6_retransmission_time(Link *link) { + usec_t retrans_time_ms; + + assert(link); + + if (!link_is_configured_for_family(link, AF_INET6)) + return 0; + + if (!timestamp_is_set(link->network->ipv6_retransmission_time)) + return 0; + + retrans_time_ms = DIV_ROUND_UP(link->network->ipv6_retransmission_time, USEC_PER_MSEC); + if (retrans_time_ms <= 0 || retrans_time_ms > UINT32_MAX) + return 0; + + return sysctl_write_ip_neighbor_property_uint32(AF_INET6, link->ifname, "retrans_time_ms", retrans_time_ms); +} + static int link_set_ipv6_proxy_ndp(Link *link) { bool v; @@ -183,22 +250,28 @@ static int link_set_ipv6_proxy_ndp(Link *link) { return sysctl_write_ip_property_boolean(AF_INET6, link->ifname, "proxy_ndp", v); } -int link_set_ipv6_mtu(Link *link) { - uint32_t mtu; +int link_set_ipv6_mtu(Link *link, int log_level) { + uint32_t mtu = 0; assert(link); if (!link_is_configured_for_family(link, AF_INET6)) return 0; - if (link->network->ipv6_mtu == 0) + assert(link->network); + + if (link->network->ndisc_use_mtu) + mtu = link->ndisc_mtu; + if (mtu == 0) + mtu = link->network->ipv6_mtu; + if (mtu == 0) return 0; - mtu = link->network->ipv6_mtu; - if (mtu > link->max_mtu) { - log_link_warning(link, "Reducing requested IPv6 MTU %"PRIu32" to the interface's maximum MTU %"PRIu32".", - mtu, link->max_mtu); - mtu = link->max_mtu; + if (mtu > link->mtu) { + log_link_full(link, log_level, + "Reducing requested IPv6 MTU %"PRIu32" to the interface's maximum MTU %"PRIu32".", + mtu, link->mtu); + mtu = link->mtu; } return sysctl_write_ip_property_uint32(AF_INET6, link->ifname, "mtu", mtu); @@ -257,13 +330,12 @@ int link_set_sysctl(Link *link) { if (r < 0) log_link_warning_errno(link, r, "Cannot configure proxy ARP for interface, ignoring: %m"); - r = link_set_ipv4_forward(link); + r = link_set_proxy_arp_pvlan(link); if (r < 0) - log_link_warning_errno(link, r, "Cannot turn on IPv4 packet forwarding, ignoring: %m"); + log_link_warning_errno(link, r, "Cannot configure proxy ARP private VLAN for interface, ignoring: %m"); - r = link_set_ipv6_forward(link); - if (r < 0) - log_link_warning_errno(link, r, "Cannot configure IPv6 packet forwarding, ignoring: %m"); + (void) link_set_ip_forwarding(link, AF_INET); + (void) link_set_ip_forwarding(link, AF_INET6); r = link_set_ipv6_privacy_extensions(link); if (r < 0) @@ -281,11 +353,15 @@ int link_set_sysctl(Link *link) { if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv6 hop limit for interface, ignoring: %m"); + r = link_set_ipv6_retransmission_time(link); + if (r < 0) + log_link_warning_errno(link, r, "Cannot set IPv6 retransmission time for interface, ignoring: %m"); + r = link_set_ipv6_proxy_ndp(link); if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv6 proxy NDP, ignoring: %m"); - r = link_set_ipv6_mtu(link); + r = link_set_ipv6_mtu(link, LOG_INFO); if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv6 MTU, ignoring: %m"); @@ -333,3 +409,24 @@ static const char* const ip_reverse_path_filter_table[_IP_REVERSE_PATH_FILTER_MA DEFINE_STRING_TABLE_LOOKUP(ip_reverse_path_filter, IPReversePathFilter); DEFINE_CONFIG_PARSE_ENUM(config_parse_ip_reverse_path_filter, ip_reverse_path_filter, IPReversePathFilter, "Failed to parse IP reverse path filter option"); + +int config_parse_ip_forward_deprecated( + 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) { + + assert(filename); + + log_syntax(unit, LOG_WARNING, filename, line, 0, + "IPForward= setting is deprecated. " + "Please use IPv4Forwarding= and/or IPv6Forwarding= in networkd.conf for global setting, " + "and the same settings in .network files for per-interface setting."); + return 0; +} diff --git a/src/network/networkd-sysctl.h b/src/network/networkd-sysctl.h index 0644384..d7a9b1f 100644 --- a/src/network/networkd-sysctl.h +++ b/src/network/networkd-sysctl.h @@ -6,6 +6,7 @@ #include "conf-parser.h" typedef struct Link Link; +typedef struct Manager Manager; typedef enum IPv6PrivacyExtensions { /* These values map to the kernel's /proc/sys/net/ipv6/conf/xxx/use_tempaddr values. Do not reorder! */ @@ -26,8 +27,11 @@ typedef enum IPReversePathFilter { _IP_REVERSE_PATH_FILTER_INVALID = -EINVAL, } IPReversePathFilter; +void manager_set_sysctl(Manager *manager); + +int link_get_ip_forwarding(Link *link, int family); int link_set_sysctl(Link *link); -int link_set_ipv6_mtu(Link *link); +int link_set_ipv6_mtu(Link *link, int log_level); const char* ipv6_privacy_extensions_to_string(IPv6PrivacyExtensions i) _const_; IPv6PrivacyExtensions ipv6_privacy_extensions_from_string(const char *s) _pure_; @@ -37,3 +41,4 @@ IPReversePathFilter ip_reverse_path_filter_from_string(const char *s) _pure_; CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_privacy_extensions); CONFIG_PARSER_PROTOTYPE(config_parse_ip_reverse_path_filter); +CONFIG_PARSER_PROTOTYPE(config_parse_ip_forward_deprecated); diff --git a/src/network/networkd-util.c b/src/network/networkd-util.c index 33352ba..46f9008 100644 --- a/src/network/networkd-util.c +++ b/src/network/networkd-util.c @@ -116,48 +116,6 @@ DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(dhcp_deprecated_address_family, AddressFa DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(ip_masquerade_address_family, AddressFamily); DEFINE_STRING_TABLE_LOOKUP(dhcp_lease_server_type, sd_dhcp_lease_server_type_t); -int config_parse_address_family_with_kernel( - 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) { - - AddressFamily *fwd = data, s; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - /* This function is mostly obsolete now. It simply redirects - * "kernel" to "no". In older networkd versions we used to - * distinguish IPForward=off from IPForward=kernel, where the - * former would explicitly turn off forwarding while the - * latter would simply not touch the setting. But that logic - * is gone, hence silently accept the old setting, but turn it - * to "no". */ - - s = address_family_from_string(rvalue); - if (s < 0) { - if (streq(rvalue, "kernel")) - s = ADDRESS_FAMILY_NO; - else { - log_syntax(unit, LOG_WARNING, filename, line, 0, "Failed to parse IPForward= option, ignoring: %s", rvalue); - return 0; - } - } - - *fwd = s; - - return 0; -} - int config_parse_ip_masquerade( const char *unit, const char *filename, diff --git a/src/network/networkd-util.h b/src/network/networkd-util.h index 9c360f5..c3b4586 100644 --- a/src/network/networkd-util.h +++ b/src/network/networkd-util.h @@ -52,7 +52,6 @@ static inline uint32_t usec_to_sec(usec_t usec, usec_t now_usec) { } CONFIG_PARSER_PROTOTYPE(config_parse_link_local_address_family); -CONFIG_PARSER_PROTOTYPE(config_parse_address_family_with_kernel); CONFIG_PARSER_PROTOTYPE(config_parse_ip_masquerade); CONFIG_PARSER_PROTOTYPE(config_parse_mud_url); diff --git a/src/network/networkd-wifi.c b/src/network/networkd-wifi.c index 98e7a72..ee63c3e 100644 --- a/src/network/networkd-wifi.c +++ b/src/network/networkd-wifi.c @@ -128,7 +128,7 @@ int manager_genl_process_nl80211_config(sd_netlink *genl, sd_netlink_message *me return 0; } - r = sd_netlink_message_read_data_suffix0(message, NL80211_ATTR_SSID, &len, (void**) &ssid); + r = sd_netlink_message_read_data(message, NL80211_ATTR_SSID, &len, (void**) &ssid); if (r < 0 && r != -ENODATA) { log_link_debug_errno(link, r, "nl80211: received %s(%u) message without valid SSID, ignoring: %m", strna(nl80211_cmd_to_string(cmd)), cmd); diff --git a/src/network/networkd-wiphy.c b/src/network/networkd-wiphy.c index 13f2d72..441713f 100644 --- a/src/network/networkd-wiphy.c +++ b/src/network/networkd-wiphy.c @@ -118,11 +118,7 @@ static int link_get_wiphy(Link *link, Wiphy **ret) { if (!link->dev) return -ENODEV; - r = sd_device_get_devtype(link->dev, &s); - if (r < 0) - return r; - - if (!streq_ptr(s, "wlan")) + if (!device_is_devtype(link->dev, "wlan")) return -EOPNOTSUPP; r = sd_device_new_child(&phy, link->dev, "phy80211"); diff --git a/src/network/networkd.c b/src/network/networkd.c index 46c2c74..69a2864 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -18,6 +18,7 @@ #include "networkd-manager.h" #include "service-util.h" #include "signal-util.h" +#include "strv.h" #include "user-util.h" static int run(int argc, char *argv[]) { @@ -69,17 +70,13 @@ static int run(int argc, char *argv[]) { /* Always create the directories people can create inotify watches in. * It is necessary to create the following subdirectories after drop_privileges() * to support old kernels not supporting AmbientCapabilities=. */ - r = mkdir_safe_label("/run/systemd/netif/links", 0755, UID_INVALID, GID_INVALID, MKDIR_WARN_MODE); - if (r < 0) - log_warning_errno(r, "Could not create runtime directory 'links': %m"); - - r = mkdir_safe_label("/run/systemd/netif/leases", 0755, UID_INVALID, GID_INVALID, MKDIR_WARN_MODE); - if (r < 0) - log_warning_errno(r, "Could not create runtime directory 'leases': %m"); - - r = mkdir_safe_label("/run/systemd/netif/lldp", 0755, UID_INVALID, GID_INVALID, MKDIR_WARN_MODE); - if (r < 0) - log_warning_errno(r, "Could not create runtime directory 'lldp': %m"); + FOREACH_STRING(p, + "/run/systemd/netif/links/", + "/run/systemd/netif/leases/") { + r = mkdir_safe_label(p, 0755, UID_INVALID, GID_INVALID, MKDIR_WARN_MODE); + if (r < 0) + log_warning_errno(r, "Could not create directory '%s': %m", p); + } r = manager_new(&m, /* test_mode = */ false); if (r < 0) diff --git a/src/network/networkd.conf b/src/network/networkd.conf index e5a5e88..06d4362 100644 --- a/src/network/networkd.conf +++ b/src/network/networkd.conf @@ -21,13 +21,23 @@ #SpeedMeterIntervalSec=10sec #ManageForeignRoutingPolicyRules=yes #ManageForeignRoutes=yes +#ManageForeignNextHops=yes #RouteTable= #IPv6PrivacyExtensions=no +#UseDomains=no + +[IPv6AcceptRA] +#UseDomains= [DHCPv4] #DUIDType=vendor #DUIDRawData= +#UseDomains= [DHCPv6] #DUIDType=vendor #DUIDRawData= +#UseDomains= + +[DHCPServer] +#PersistLeases=yes diff --git a/src/network/org.freedesktop.network1.policy b/src/network/org.freedesktop.network1.policy index 1e2d8d7..eed3169 100644 --- a/src/network/org.freedesktop.network1.policy +++ b/src/network/org.freedesktop.network1.policy @@ -183,4 +183,15 @@ <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate> </action> + <action id="org.freedesktop.network1.set-persistent-storage"> + <description gettext-domain="systemd">Specify whether persistent storage for systemd-networkd is available</description> + <message gettext-domain="systemd">Authentication is required to specify whether persistent storage for systemd-networkd is available.</message> + <defaults> + <allow_any>auth_admin</allow_any> + <allow_inactive>auth_admin</allow_inactive> + <allow_active>auth_admin_keep</allow_active> + </defaults> + <annotate key="org.freedesktop.policykit.owner">unix-user:systemd-network</annotate> + </action> + </policyconfig> diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c index f9b9437..38dee2c 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -155,8 +155,8 @@ static void qdisc_hash_func(const QDisc *qdisc, struct siphash *state) { assert(qdisc); assert(state); - siphash24_compress(&qdisc->handle, sizeof(qdisc->handle), state); - siphash24_compress(&qdisc->parent, sizeof(qdisc->parent), state); + siphash24_compress_typesafe(qdisc->handle, state); + siphash24_compress_typesafe(qdisc->parent, state); siphash24_compress_string(qdisc_get_tca_kind(qdisc), state); } @@ -285,37 +285,57 @@ int link_find_qdisc(Link *link, uint32_t handle, const char *kind, QDisc **ret) return -ENOENT; } -QDisc* qdisc_drop(QDisc *qdisc) { +void qdisc_mark_recursive(QDisc *qdisc) { TClass *tclass; - Link *link; assert(qdisc); + assert(qdisc->link); - link = ASSERT_PTR(qdisc->link); + if (qdisc_is_marked(qdisc)) + return; - qdisc_mark(qdisc); /* To avoid stack overflow. */ + qdisc_mark(qdisc); - /* also drop all child classes assigned to the qdisc. */ - SET_FOREACH(tclass, link->tclasses) { - if (tclass_is_marked(tclass)) + /* also mark all child classes assigned to the qdisc. */ + SET_FOREACH(tclass, qdisc->link->tclasses) { + if (TC_H_MAJ(tclass->classid) != qdisc->handle) continue; - if (TC_H_MAJ(tclass->classid) != qdisc->handle) + tclass_mark_recursive(tclass); + } +} + +void link_qdisc_drop_marked(Link *link) { + QDisc *qdisc; + + assert(link); + + SET_FOREACH(qdisc, link->qdiscs) { + if (!qdisc_is_marked(qdisc)) continue; - tclass_drop(tclass); + qdisc_unmark(qdisc); + qdisc_enter_removed(qdisc); + + if (qdisc->state == 0) { + log_qdisc_debug(qdisc, link, "Forgetting"); + qdisc_free(qdisc); + } else + log_qdisc_debug(qdisc, link, "Removed"); } +} - qdisc_unmark(qdisc); - qdisc_enter_removed(qdisc); +QDisc* qdisc_drop(QDisc *qdisc) { + assert(qdisc); + assert(qdisc->link); - if (qdisc->state == 0) { - log_qdisc_debug(qdisc, link, "Forgetting"); - qdisc = qdisc_free(qdisc); - } else - log_qdisc_debug(qdisc, link, "Removed"); + qdisc_mark_recursive(qdisc); + + /* link_qdisc_drop_marked() may invalidate qdisc, so run link_tclass_drop_marked() first. */ + link_tclass_drop_marked(qdisc->link); + link_qdisc_drop_marked(qdisc->link); - return qdisc; + return NULL; } static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, QDisc *qdisc) { diff --git a/src/network/tc/qdisc.h b/src/network/tc/qdisc.h index a62b941..cbba1be 100644 --- a/src/network/tc/qdisc.h +++ b/src/network/tc/qdisc.h @@ -77,7 +77,9 @@ DEFINE_NETWORK_CONFIG_STATE_FUNCTIONS(QDisc, qdisc); QDisc* qdisc_free(QDisc *qdisc); int qdisc_new_static(QDiscKind kind, Network *network, const char *filename, unsigned section_line, QDisc **ret); +void qdisc_mark_recursive(QDisc *qdisc); QDisc* qdisc_drop(QDisc *qdisc); +void link_qdisc_drop_marked(Link *link); int link_find_qdisc(Link *link, uint32_t handle, const char *kind, QDisc **qdisc); diff --git a/src/network/tc/tclass.c b/src/network/tc/tclass.c index 394e06d..fcbe8cb 100644 --- a/src/network/tc/tclass.c +++ b/src/network/tc/tclass.c @@ -125,8 +125,8 @@ static void tclass_hash_func(const TClass *tclass, struct siphash *state) { assert(tclass); assert(state); - siphash24_compress(&tclass->classid, sizeof(tclass->classid), state); - siphash24_compress(&tclass->parent, sizeof(tclass->parent), state); + siphash24_compress_typesafe(tclass->classid, state); + siphash24_compress_typesafe(tclass->parent, state); siphash24_compress_string(tclass_get_tca_kind(tclass), state); } @@ -252,37 +252,56 @@ static void log_tclass_debug(TClass *tclass, Link *link, const char *str) { strna(tclass_get_tca_kind(tclass))); } -TClass* tclass_drop(TClass *tclass) { +void tclass_mark_recursive(TClass *tclass) { QDisc *qdisc; - Link *link; assert(tclass); + assert(tclass->link); - link = ASSERT_PTR(tclass->link); + if (tclass_is_marked(tclass)) + return; - tclass_mark(tclass); /* To avoid stack overflow. */ + tclass_mark(tclass); - /* Also drop all child qdiscs assigned to the class. */ - SET_FOREACH(qdisc, link->qdiscs) { - if (qdisc_is_marked(qdisc)) + /* Also mark all child qdiscs assigned to the class. */ + SET_FOREACH(qdisc, tclass->link->qdiscs) { + if (qdisc->parent != tclass->classid) continue; - if (qdisc->parent != tclass->classid) + qdisc_mark_recursive(qdisc); + } +} + +void link_tclass_drop_marked(Link *link) { + TClass *tclass; + + assert(link); + + SET_FOREACH(tclass, link->tclasses) { + if (!tclass_is_marked(tclass)) continue; - qdisc_drop(qdisc); + tclass_unmark(tclass); + tclass_enter_removed(tclass); + + if (tclass->state == 0) { + log_tclass_debug(tclass, link, "Forgetting"); + tclass_free(tclass); + } else + log_tclass_debug(tclass, link, "Removed"); } +} - tclass_unmark(tclass); - tclass_enter_removed(tclass); +TClass* tclass_drop(TClass *tclass) { + assert(tclass); - if (tclass->state == 0) { - log_tclass_debug(tclass, link, "Forgetting"); - tclass = tclass_free(tclass); - } else - log_tclass_debug(tclass, link, "Removed"); + tclass_mark_recursive(tclass); + + /* link_tclass_drop_marked() may invalidate tclass, so run link_qdisc_drop_marked() first. */ + link_qdisc_drop_marked(tclass->link); + link_tclass_drop_marked(tclass->link); - return tclass; + return NULL; } static int tclass_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, TClass *tclass) { diff --git a/src/network/tc/tclass.h b/src/network/tc/tclass.h index e73e23c..85df57d 100644 --- a/src/network/tc/tclass.h +++ b/src/network/tc/tclass.h @@ -58,7 +58,9 @@ DEFINE_NETWORK_CONFIG_STATE_FUNCTIONS(TClass, tclass); TClass* tclass_free(TClass *tclass); int tclass_new_static(TClassKind kind, Network *network, const char *filename, unsigned section_line, TClass **ret); +void tclass_mark_recursive(TClass *tclass); TClass* tclass_drop(TClass *tclass); +void link_tclass_drop_marked(Link *link); int link_find_tclass(Link *link, uint32_t classid, TClass **ret); diff --git a/src/network/test-network-tables.c b/src/network/test-network-tables.c index 564ca09..f4e14c6 100644 --- a/src/network/test-network-tables.c +++ b/src/network/test-network-tables.c @@ -1,5 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ +#include <net/if.h> +#include <linux/if.h> + #include "bond.h" #include "dhcp6-internal.h" #include "dhcp6-protocol.h" @@ -28,7 +32,7 @@ int main(int argc, char **argv) { test_table(bond_xmit_hash_policy, NETDEV_BOND_XMIT_HASH_POLICY); test_table(dhcp6_message_status, DHCP6_STATUS); test_table_sparse(dhcp6_message_type, DHCP6_MESSAGE_TYPE); /* enum starts from 1 */ - test_table(dhcp_use_domains, DHCP_USE_DOMAINS); + test_table(use_domains, USE_DOMAINS); test_table(duplex, DUP); test_table(ip6tnl_mode, NETDEV_IP6_TNL_MODE); test_table(ipv6_privacy_extensions, IPV6_PRIVACY_EXTENSIONS); diff --git a/src/network/test-networkd-conf.c b/src/network/test-networkd-conf.c index 808db99..3581524 100644 --- a/src/network/test-networkd-conf.c +++ b/src/network/test-networkd-conf.c @@ -80,7 +80,7 @@ static void test_config_parse_ether_addrs_one(const char *rvalue, const struct e assert_se(q = set_remove(s, &list[m])); } - assert_se(set_size(s) == 0); + assert_se(set_isempty(s)); } #define STR_OK \ diff --git a/src/network/wait-online/manager.c b/src/network/wait-online/manager.c index 40a9fba..7838350 100644 --- a/src/network/wait-online/manager.c +++ b/src/network/wait-online/manager.c @@ -52,13 +52,27 @@ static bool manager_ignore_link(Manager *m, Link *link) { return false; } -static int manager_link_is_online(Manager *m, Link *l, LinkOperationalStateRange s) { +static const LinkOperationalStateRange* get_state_range(Manager *m, Link *l, const LinkOperationalStateRange *from_cmdline) { + assert(m); + assert(l); + + const LinkOperationalStateRange *range; + FOREACH_ARGUMENT(range, from_cmdline, &m->required_operstate, &l->required_operstate) + if (operational_state_range_is_valid(range)) + return range; + + /* l->requred_operstate should be always valid. */ + assert_not_reached(); +} + +static int manager_link_is_online(Manager *m, Link *l, const LinkOperationalStateRange *range) { AddressFamily required_family; bool needs_ipv4; bool needs_ipv6; assert(m); assert(l); + assert(range); /* This returns the following: * -EAGAIN : not processed by udev @@ -91,25 +105,17 @@ static int manager_link_is_online(Manager *m, Link *l, LinkOperationalStateRange "link is being processed by networkd: setup state is %s.", l->state); - if (s.min < 0) - s.min = m->required_operstate.min >= 0 ? m->required_operstate.min - : l->required_operstate.min; - - if (s.max < 0) - s.max = m->required_operstate.max >= 0 ? m->required_operstate.max - : l->required_operstate.max; - - if (l->operational_state < s.min || l->operational_state > s.max) + if (!operational_state_is_in_range(l->operational_state, range)) return log_link_debug_errno(l, SYNTHETIC_ERRNO(EADDRNOTAVAIL), "Operational state '%s' is not in range ['%s':'%s']", link_operstate_to_string(l->operational_state), - link_operstate_to_string(s.min), link_operstate_to_string(s.max)); + link_operstate_to_string(range->min), link_operstate_to_string(range->max)); required_family = m->required_family > 0 ? m->required_family : l->required_family; needs_ipv4 = required_family & ADDRESS_FAMILY_IPV4; needs_ipv6 = required_family & ADDRESS_FAMILY_IPV6; - if (s.min < LINK_OPERSTATE_ROUTABLE) { + if (range->min < LINK_OPERSTATE_ROUTABLE) { if (needs_ipv4 && l->ipv4_address_state < LINK_ADDRESS_STATE_DEGRADED) return log_link_debug_errno(l, SYNTHETIC_ERRNO(EADDRNOTAVAIL), "No routable or link-local IPv4 address is configured."); @@ -136,7 +142,7 @@ bool manager_configured(Manager *m) { int r; if (!hashmap_isempty(m->command_line_interfaces_by_name)) { - LinkOperationalStateRange *range; + const LinkOperationalStateRange *range; const char *ifname; /* wait for all the links given on the command line to appear */ @@ -155,7 +161,9 @@ bool manager_configured(Manager *m) { continue; } - r = manager_link_is_online(m, l, *range); + range = get_state_range(m, l, range); + + r = manager_link_is_online(m, l, range); if (r <= 0 && !m->any) return false; if (r > 0 && m->any) @@ -170,14 +178,16 @@ bool manager_configured(Manager *m) { /* wait for all links networkd manages */ bool has_online = false; HASHMAP_FOREACH(l, m->links_by_index) { + const LinkOperationalStateRange *range; + if (manager_ignore_link(m, l)) { log_link_debug(l, "link is ignored"); continue; } - r = manager_link_is_online(m, l, - (LinkOperationalStateRange) { _LINK_OPERSTATE_INVALID, - _LINK_OPERSTATE_INVALID }); + range = get_state_range(m, l, /* from_cmdline = */ NULL); + + r = manager_link_is_online(m, l, range); /* Unlike the above loop, unmanaged interfaces are ignored here. Also, Configured but offline * interfaces are ignored. See issue #29506. */ if (r < 0 && r != -EADDRNOTAVAIL && !m->any) diff --git a/src/network/wait-online/wait-online.c b/src/network/wait-online/wait-online.c index 5328bba..4f8270d 100644 --- a/src/network/wait-online/wait-online.c +++ b/src/network/wait-online/wait-online.c @@ -19,7 +19,7 @@ static bool arg_quiet = false; static usec_t arg_timeout = 120 * USEC_PER_SEC; static Hashmap *arg_interfaces = NULL; static char **arg_ignore = NULL; -static LinkOperationalStateRange arg_required_operstate = { _LINK_OPERSTATE_INVALID, _LINK_OPERSTATE_INVALID }; +static LinkOperationalStateRange arg_required_operstate = LINK_OPERSTATE_RANGE_INVALID; static AddressFamily arg_required_family = ADDRESS_FAMILY_NO; static bool arg_any = false; @@ -71,12 +71,11 @@ static int parse_interface_with_operstate_range(const char *str) { if (p) { r = parse_operational_state_range(p + 1, range); if (r < 0) - log_error_errno(r, "Invalid operational state range '%s'", p + 1); + return log_error_errno(r, "Invalid operational state range: %s", p + 1); ifname = strndup(optarg, p - optarg); } else { - range->min = _LINK_OPERSTATE_INVALID; - range->max = _LINK_OPERSTATE_INVALID; + *range = LINK_OPERSTATE_RANGE_INVALID; ifname = strdup(str); } if (!ifname) @@ -84,18 +83,19 @@ static int parse_interface_with_operstate_range(const char *str) { if (!ifname_valid(ifname)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid interface name '%s'", ifname); + "Invalid interface name: %s", ifname); - r = hashmap_ensure_put(&arg_interfaces, &string_hash_ops, ifname, TAKE_PTR(range)); + r = hashmap_ensure_put(&arg_interfaces, &string_hash_ops, ifname, range); if (r == -ENOMEM) return log_oom(); if (r < 0) return log_error_errno(r, "Failed to store interface name: %m"); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Interface name %s is already specified", ifname); + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "Interface name %s is already specified.", ifname); TAKE_PTR(ifname); + TAKE_PTR(range); return 0; } @@ -154,17 +154,11 @@ static int parse_argv(int argc, char *argv[]) { break; - case 'o': { - LinkOperationalStateRange range; - - r = parse_operational_state_range(optarg, &range); + case 'o': + r = parse_operational_state_range(optarg, &arg_required_operstate); if (r < 0) return log_error_errno(r, "Invalid operational state range '%s'", optarg); - - arg_required_operstate = range; - break; - } case '4': arg_required_family |= ADDRESS_FAMILY_IPV4; @@ -210,7 +204,7 @@ static int run(int argc, char *argv[]) { if (arg_quiet) log_set_max_level(LOG_ERR); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT) >= 0); r = manager_new(&m, arg_interfaces, arg_ignore, arg_required_operstate, arg_required_family, arg_any, arg_timeout); if (r < 0) |