summaryrefslogtreecommitdiffstats
path: root/src/network
diff options
context:
space:
mode:
Diffstat (limited to 'src/network')
-rw-r--r--src/network/generator/main.c51
-rw-r--r--src/network/generator/network-generator.c180
-rw-r--r--src/network/meson.build17
-rw-r--r--src/network/netdev/bond.c12
-rw-r--r--src/network/netdev/bond.h3
-rw-r--r--src/network/netdev/bridge.c3
-rw-r--r--src/network/netdev/fou-tunnel.c3
-rw-r--r--src/network/netdev/geneve.c3
-rw-r--r--src/network/netdev/ipvlan.c1
-rw-r--r--src/network/netdev/macsec.c5
-rw-r--r--src/network/netdev/macvlan.c60
-rw-r--r--src/network/netdev/macvlan.h2
-rw-r--r--src/network/netdev/netdev-gperf.gperf3
-rw-r--r--src/network/netdev/netdev.c22
-rw-r--r--src/network/netdev/tuntap.c10
-rw-r--r--src/network/netdev/veth.c5
-rw-r--r--src/network/netdev/vlan.c7
-rw-r--r--src/network/netdev/vrf.c3
-rw-r--r--src/network/netdev/vxlan.c3
-rw-r--r--src/network/netdev/wireguard.c155
-rw-r--r--src/network/networkctl-config-file.c632
-rw-r--r--src/network/networkctl-config-file.h8
-rw-r--r--src/network/networkctl.c964
-rw-r--r--src/network/networkctl.h23
-rw-r--r--src/network/networkd-address-generation.c257
-rw-r--r--src/network/networkd-address-generation.h15
-rw-r--r--src/network/networkd-address-label.c1
-rw-r--r--src/network/networkd-address.c563
-rw-r--r--src/network/networkd-address.h24
-rw-r--r--src/network/networkd-bridge-mdb.c1
-rw-r--r--src/network/networkd-bridge-vlan.c457
-rw-r--r--src/network/networkd-bridge-vlan.h20
-rw-r--r--src/network/networkd-can.c1
-rw-r--r--src/network/networkd-conf.c19
-rw-r--r--src/network/networkd-dhcp-common.c193
-rw-r--r--src/network/networkd-dhcp-common.h20
-rw-r--r--src/network/networkd-dhcp-prefix-delegation.c136
-rw-r--r--src/network/networkd-dhcp-prefix-delegation.h2
-rw-r--r--src/network/networkd-dhcp-server-bus.c6
-rw-r--r--src/network/networkd-dhcp-server.c149
-rw-r--r--src/network/networkd-dhcp-server.h8
-rw-r--r--src/network/networkd-dhcp4.c206
-rw-r--r--src/network/networkd-dhcp6.c38
-rw-r--r--src/network/networkd-dns.c294
-rw-r--r--src/network/networkd-dns.h28
-rw-r--r--src/network/networkd-gperf.gperf9
-rw-r--r--src/network/networkd-ipv4acd.c4
-rw-r--r--src/network/networkd-ipv4ll.c18
-rw-r--r--src/network/networkd-json.c371
-rw-r--r--src/network/networkd-link-bus.c139
-rw-r--r--src/network/networkd-link.c358
-rw-r--r--src/network/networkd-link.h19
-rw-r--r--src/network/networkd-lldp-rx.c69
-rw-r--r--src/network/networkd-lldp-rx.h1
-rw-r--r--src/network/networkd-lldp-tx.c6
-rw-r--r--src/network/networkd-manager-bus.c46
-rw-r--r--src/network/networkd-manager-varlink.c314
-rw-r--r--src/network/networkd-manager-varlink.h7
-rw-r--r--src/network/networkd-manager.c138
-rw-r--r--src/network/networkd-manager.h25
-rw-r--r--src/network/networkd-ndisc.c1347
-rw-r--r--src/network/networkd-ndisc.h9
-rw-r--r--src/network/networkd-neighbor.c230
-rw-r--r--src/network/networkd-neighbor.h10
-rw-r--r--src/network/networkd-network-gperf.gperf110
-rw-r--r--src/network/networkd-network.c392
-rw-r--r--src/network/networkd-network.h92
-rw-r--r--src/network/networkd-nexthop.c1092
-rw-r--r--src/network/networkd-nexthop.h44
-rw-r--r--src/network/networkd-ntp.c101
-rw-r--r--src/network/networkd-ntp.h11
-rw-r--r--src/network/networkd-queue.c264
-rw-r--r--src/network/networkd-queue.h57
-rw-r--r--src/network/networkd-radv.c348
-rw-r--r--src/network/networkd-radv.h33
-rw-r--r--src/network/networkd-route-metric.c483
-rw-r--r--src/network/networkd-route-metric.h47
-rw-r--r--src/network/networkd-route-nexthop.c1225
-rw-r--r--src/network/networkd-route-nexthop.h57
-rw-r--r--src/network/networkd-route-util.c38
-rw-r--r--src/network/networkd-route-util.h2
-rw-r--r--src/network/networkd-route.c2952
-rw-r--r--src/network/networkd-route.h127
-rw-r--r--src/network/networkd-routing-policy-rule.c122
-rw-r--r--src/network/networkd-routing-policy-rule.h5
-rw-r--r--src/network/networkd-setlink.c171
-rw-r--r--src/network/networkd-state-file.c96
-rw-r--r--src/network/networkd-state-file.h1
-rw-r--r--src/network/networkd-sysctl.c177
-rw-r--r--src/network/networkd-sysctl.h7
-rw-r--r--src/network/networkd-util.c42
-rw-r--r--src/network/networkd-util.h1
-rw-r--r--src/network/networkd-wifi.c2
-rw-r--r--src/network/networkd-wiphy.c6
-rw-r--r--src/network/networkd.c19
-rw-r--r--src/network/networkd.conf10
-rw-r--r--src/network/org.freedesktop.network1.policy11
-rw-r--r--src/network/tc/qdisc.c58
-rw-r--r--src/network/tc/qdisc.h2
-rw-r--r--src/network/tc/tclass.c57
-rw-r--r--src/network/tc/tclass.h2
-rw-r--r--src/network/test-network-tables.c6
-rw-r--r--src/network/test-networkd-conf.c2
-rw-r--r--src/network/wait-online/manager.c44
-rw-r--r--src/network/wait-online/wait-online.c28
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, &current_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(&current_address, &original_address))
+ return 0; /* the router address is not changed */
+
+ r = ndisc_update_router_address(link, &original_address, &current_address);
+ if (r < 0)
+ return r;
+
+ r = ndisc_update_redirect_sender(link, &original_address, &current_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)