summaryrefslogtreecommitdiffstats
path: root/src/network
diff options
context:
space:
mode:
Diffstat (limited to 'src/network')
-rw-r--r--src/network/fuzz-netdev-parser.c25
-rw-r--r--src/network/fuzz-network-parser.c25
-rw-r--r--src/network/meson.build191
-rw-r--r--src/network/netdev/bond.c561
-rw-r--r--src/network/netdev/bond.h163
-rw-r--r--src/network/netdev/bridge.c163
-rw-r--r--src/network/netdev/bridge.h24
-rw-r--r--src/network/netdev/dummy.c9
-rw-r--r--src/network/netdev/dummy.h11
-rw-r--r--src/network/netdev/fou-tunnel.c129
-rw-r--r--src/network/netdev/fou-tunnel.h36
-rw-r--r--src/network/netdev/geneve.c299
-rw-r--r--src/network/netdev/geneve.h38
-rw-r--r--src/network/netdev/ipvlan.c73
-rw-r--r--src/network/netdev/ipvlan.h42
-rw-r--r--src/network/netdev/macvlan.c72
-rw-r--r--src/network/netdev/macvlan.h31
-rw-r--r--src/network/netdev/netdev-gperf.gperf174
-rw-r--r--src/network/netdev/netdev.c816
-rw-r--r--src/network/netdev/netdev.h195
-rw-r--r--src/network/netdev/netdevsim.c10
-rw-r--r--src/network/netdev/netdevsim.h13
-rw-r--r--src/network/netdev/tunnel.c936
-rw-r--r--src/network/netdev/tunnel.h91
-rw-r--r--src/network/netdev/tuntap.c161
-rw-r--r--src/network/netdev/tuntap.h22
-rw-r--r--src/network/netdev/vcan.c9
-rw-r--r--src/network/netdev/vcan.h16
-rw-r--r--src/network/netdev/veth.c95
-rw-r--r--src/network/netdev/veth.h16
-rw-r--r--src/network/netdev/vlan.c92
-rw-r--r--src/network/netdev/vlan.h20
-rw-r--r--src/network/netdev/vrf.c33
-rw-r--r--src/network/netdev/vrf.h15
-rw-r--r--src/network/netdev/vxcan.c76
-rw-r--r--src/network/netdev/vxcan.h16
-rw-r--r--src/network/netdev/vxlan.c311
-rw-r--r--src/network/netdev/vxlan.h52
-rw-r--r--src/network/netdev/wireguard.c778
-rw-r--r--src/network/netdev/wireguard.h73
-rw-r--r--src/network/networkctl.c1183
-rw-r--r--src/network/networkd-address-label.c229
-rw-r--r--src/network/networkd-address-label.h38
-rw-r--r--src/network/networkd-address-pool.c156
-rw-r--r--src/network/networkd-address-pool.h26
-rw-r--r--src/network/networkd-address.c969
-rw-r--r--src/network/networkd-address.h69
-rw-r--r--src/network/networkd-brvlan.c340
-rw-r--r--src/network/networkd-brvlan.h18
-rw-r--r--src/network/networkd-conf.c166
-rw-r--r--src/network/networkd-conf.h17
-rw-r--r--src/network/networkd-dhcp4.c822
-rw-r--r--src/network/networkd-dhcp6.c702
-rw-r--r--src/network/networkd-fdb.c260
-rw-r--r--src/network/networkd-fdb.h34
-rw-r--r--src/network/networkd-gperf.gperf22
-rw-r--r--src/network/networkd-ipv4ll.c227
-rw-r--r--src/network/networkd-ipv6-proxy-ndp.c204
-rw-r--r--src/network/networkd-ipv6-proxy-ndp.h26
-rw-r--r--src/network/networkd-link-bus.c122
-rw-r--r--src/network/networkd-link.c4184
-rw-r--r--src/network/networkd-link.h216
-rw-r--r--src/network/networkd-lldp-tx.c399
-rw-r--r--src/network/networkd-lldp-tx.h18
-rw-r--r--src/network/networkd-manager-bus.c33
-rw-r--r--src/network/networkd-manager.c1860
-rw-r--r--src/network/networkd-manager.h98
-rw-r--r--src/network/networkd-ndisc.c671
-rw-r--r--src/network/networkd-ndisc.h22
-rw-r--r--src/network/networkd-neighbor.c237
-rw-r--r--src/network/networkd-neighbor.h37
-rw-r--r--src/network/networkd-network-bus.c137
-rw-r--r--src/network/networkd-network-gperf.gperf201
-rw-r--r--src/network/networkd-network.c1507
-rw-r--r--src/network/networkd-network.h335
-rw-r--r--src/network/networkd-radv.c512
-rw-r--r--src/network/networkd-radv.h36
-rw-r--r--src/network/networkd-route.c1192
-rw-r--r--src/network/networkd-route.h73
-rw-r--r--src/network/networkd-routing-policy-rule.c1274
-rw-r--r--src/network/networkd-routing-policy-rule.h84
-rw-r--r--src/network/networkd-util.c104
-rw-r--r--src/network/networkd-util.h23
-rw-r--r--src/network/networkd.c117
-rw-r--r--src/network/networkd.conf16
-rw-r--r--src/network/org.freedesktop.network1.conf42
-rw-r--r--src/network/org.freedesktop.network1.service14
-rw-r--r--src/network/systemd-networkd.pkla4
-rw-r--r--src/network/systemd-networkd.rules10
-rw-r--r--src/network/test-network-tables.c47
-rw-r--r--src/network/test-network.c249
-rw-r--r--src/network/test-networkd-conf.c223
-rw-r--r--src/network/test-routing-policy-rule.c92
-rw-r--r--src/network/wait-online/link.c116
-rw-r--r--src/network/wait-online/link.h26
-rw-r--r--src/network/wait-online/manager.c316
-rw-r--r--src/network/wait-online/manager.h35
-rw-r--r--src/network/wait-online/wait-online.c152
98 files changed, 26254 insertions, 0 deletions
diff --git a/src/network/fuzz-netdev-parser.c b/src/network/fuzz-netdev-parser.c
new file mode 100644
index 0000000..37f7cf8
--- /dev/null
+++ b/src/network/fuzz-netdev-parser.c
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "fd-util.h"
+#include "fs-util.h"
+#include "fuzz.h"
+#include "networkd-manager.h"
+#include "tmpfile-util.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_(manager_freep) Manager *manager = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_(unlink_tempfilep) char netdev_config[] = "/tmp/fuzz-networkd.XXXXXX";
+
+ if (!getenv("SYSTEMD_LOG_LEVEL"))
+ log_set_max_level(LOG_CRIT);
+
+ assert_se(fmkostemp_safe(netdev_config, "r+", &f) == 0);
+ if (size != 0)
+ assert_se(fwrite(data, size, 1, f) == 1);
+
+ rewind(f);
+ assert_se(manager_new(&manager) >= 0);
+ (void) netdev_load_one(manager, netdev_config);
+ return 0;
+}
diff --git a/src/network/fuzz-network-parser.c b/src/network/fuzz-network-parser.c
new file mode 100644
index 0000000..4b79500
--- /dev/null
+++ b/src/network/fuzz-network-parser.c
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "fd-util.h"
+#include "fs-util.h"
+#include "fuzz.h"
+#include "networkd-manager.h"
+#include "tmpfile-util.h"
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ _cleanup_(manager_freep) Manager *manager = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_(unlink_tempfilep) char network_config[] = "/tmp/fuzz-networkd.XXXXXX";
+
+ if (!getenv("SYSTEMD_LOG_LEVEL"))
+ log_set_max_level(LOG_CRIT);
+
+ assert_se(fmkostemp_safe(network_config, "r+", &f) == 0);
+ if (size != 0)
+ assert_se(fwrite(data, size, 1, f) == 1);
+
+ rewind(f);
+ assert_se(manager_new(&manager) >= 0);
+ (void) network_load_one(manager, network_config);
+ return 0;
+}
diff --git a/src/network/meson.build b/src/network/meson.build
new file mode 100644
index 0000000..8fe4854
--- /dev/null
+++ b/src/network/meson.build
@@ -0,0 +1,191 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
+sources = files('''
+ netdev/bond.c
+ netdev/bond.h
+ netdev/bridge.c
+ netdev/bridge.h
+ netdev/dummy.c
+ netdev/dummy.h
+ netdev/ipvlan.c
+ netdev/ipvlan.h
+ netdev/macvlan.c
+ netdev/macvlan.h
+ netdev/netdev.c
+ netdev/netdev.h
+ netdev/tunnel.c
+ netdev/tunnel.h
+ netdev/tuntap.c
+ netdev/tuntap.h
+ netdev/vcan.c
+ netdev/vcan.h
+ netdev/veth.c
+ netdev/veth.h
+ netdev/vlan.c
+ netdev/vlan.h
+ netdev/vrf.c
+ netdev/vrf.h
+ netdev/vxlan.c
+ netdev/vxlan.h
+ netdev/geneve.c
+ netdev/geneve.h
+ netdev/vxcan.c
+ netdev/vxcan.h
+ netdev/wireguard.c
+ netdev/wireguard.h
+ netdev/netdevsim.c
+ netdev/netdevsim.h
+ netdev/fou-tunnel.c
+ netdev/fou-tunnel.h
+ networkd-address-label.c
+ networkd-address-label.h
+ networkd-address-pool.c
+ networkd-address-pool.h
+ networkd-address.c
+ networkd-address.h
+ networkd-brvlan.c
+ networkd-brvlan.h
+ networkd-conf.c
+ networkd-conf.h
+ networkd-dhcp4.c
+ networkd-dhcp6.c
+ networkd-fdb.c
+ networkd-fdb.h
+ networkd-ipv4ll.c
+ networkd-ipv6-proxy-ndp.c
+ networkd-ipv6-proxy-ndp.h
+ networkd-link-bus.c
+ networkd-link.c
+ networkd-link.h
+ networkd-lldp-tx.c
+ networkd-lldp-tx.h
+ networkd-manager-bus.c
+ networkd-manager.c
+ networkd-manager.h
+ networkd-ndisc.c
+ networkd-ndisc.h
+ networkd-neighbor.c
+ networkd-neighbor.h
+ networkd-radv.c
+ networkd-radv.h
+ networkd-network-bus.c
+ networkd-network.c
+ networkd-network.h
+ networkd-route.c
+ networkd-route.h
+ networkd-routing-policy-rule.c
+ networkd-routing-policy-rule.h
+ networkd-util.c
+ networkd-util.h
+'''.split())
+
+systemd_networkd_sources = files('networkd.c')
+
+systemd_networkd_wait_online_sources = files('''
+ wait-online/link.c
+ wait-online/link.h
+ wait-online/manager.c
+ wait-online/manager.h
+ wait-online/wait-online.c
+'''.split()) + network_internal_h
+
+networkctl_sources = files('networkctl.c')
+
+network_include_dir = include_directories('.')
+
+if conf.get('ENABLE_NETWORKD') == 1
+ networkd_gperf_c = custom_target(
+ 'networkd-gperf.c',
+ input : 'networkd-gperf.gperf',
+ output : 'networkd-gperf.c',
+ command : [gperf, '@INPUT@', '--output-file', '@OUTPUT@'])
+
+ networkd_network_gperf_c = custom_target(
+ 'networkd-network-gperf.c',
+ input : 'networkd-network-gperf.gperf',
+ output : 'networkd-network-gperf.c',
+ command : [gperf, '@INPUT@', '--output-file', '@OUTPUT@'])
+
+ netdev_gperf_c = custom_target(
+ 'netdev-gperf.c',
+ input : 'netdev/netdev-gperf.gperf',
+ output : 'netdev-gperf.c',
+ command : [gperf, '@INPUT@', '--output-file', '@OUTPUT@'])
+
+ libnetworkd_core = static_library(
+ 'networkd-core',
+ sources,
+ network_internal_h,
+ networkd_gperf_c,
+ networkd_network_gperf_c,
+ netdev_gperf_c,
+ include_directories : includes,
+ link_with : [libshared])
+
+ install_data('org.freedesktop.network1.conf',
+ install_dir : dbuspolicydir)
+ install_data('org.freedesktop.network1.service',
+ install_dir : dbussystemservicedir)
+ if install_polkit
+ install_data('systemd-networkd.rules',
+ install_dir : polkitrulesdir)
+ endif
+ if install_polkit_pkla
+ install_data('systemd-networkd.pkla',
+ install_dir : polkitpkladir)
+ endif
+
+ install_data('networkd.conf',
+ install_dir : pkgsysconfdir)
+
+ fuzzers += [
+ [['src/network/fuzz-netdev-parser.c',
+ 'src/fuzz/fuzz.h'],
+ [libnetworkd_core,
+ libudev_static,
+ libsystemd_network,
+ libshared],
+ [threads]],
+
+ [['src/network/fuzz-network-parser.c',
+ 'src/fuzz/fuzz.h'],
+ [libnetworkd_core,
+ libudev_static,
+ libsystemd_network,
+ libshared],
+ [threads]]
+ ]
+
+ tests += [
+ [['src/network/test-networkd-conf.c'],
+ [libnetworkd_core,
+ libsystemd_network,
+ libudev],
+ []],
+
+ [['src/network/test-network.c'],
+ [libnetworkd_core,
+ libudev_static,
+ libsystemd_network,
+ libshared],
+ [threads]],
+
+ [['src/network/test-routing-policy-rule.c'],
+ [libnetworkd_core,
+ libsystemd_network,
+ libudev],
+ []],
+
+ [['src/network/test-network-tables.c',
+ 'src/network/test-network-tables.c',
+ test_tables_h],
+ [libnetworkd_core,
+ libudev_static,
+ libudev_core,
+ libsystemd_network,
+ libshared],
+ [threads],
+ '', '', [],
+ [network_include_dir] + libudev_core_includes],
+ ]
+endif
diff --git a/src/network/netdev/bond.c b/src/network/netdev/bond.c
new file mode 100644
index 0000000..550a7f8
--- /dev/null
+++ b/src/network/netdev/bond.c
@@ -0,0 +1,561 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <netinet/ether.h>
+#include <linux/if_bonding.h>
+
+#include "sd-netlink.h"
+
+#include "alloc-util.h"
+#include "conf-parser.h"
+#include "ether-addr-util.h"
+#include "extract-word.h"
+#include "missing.h"
+#include "netdev/bond.h"
+#include "string-table.h"
+#include "string-util.h"
+
+/*
+ * Number of seconds between instances where the bonding
+ * driver sends learning packets to each slaves peer switch
+ */
+#define LEARNING_PACKETS_INTERVAL_MIN_SEC (1 * USEC_PER_SEC)
+#define LEARNING_PACKETS_INTERVAL_MAX_SEC (0x7fffffff * USEC_PER_SEC)
+
+/* Number of IGMP membership reports to be issued after
+ * a failover event.
+ */
+#define RESEND_IGMP_MIN 0
+#define RESEND_IGMP_MAX 255
+#define RESEND_IGMP_DEFAULT 1
+
+/*
+ * Number of packets to transmit through a slave before
+ * moving to the next one.
+ */
+#define PACKETS_PER_SLAVE_MIN 0
+#define PACKETS_PER_SLAVE_MAX 65535
+#define PACKETS_PER_SLAVE_DEFAULT 1
+
+/*
+ * Number of peer notifications (gratuitous ARPs and
+ * unsolicited IPv6 Neighbor Advertisements) to be issued after a
+ * failover event.
+ */
+#define GRATUITOUS_ARP_MIN 0
+#define GRATUITOUS_ARP_MAX 255
+#define GRATUITOUS_ARP_DEFAULT 1
+
+static const char* const bond_mode_table[_NETDEV_BOND_MODE_MAX] = {
+ [NETDEV_BOND_MODE_BALANCE_RR] = "balance-rr",
+ [NETDEV_BOND_MODE_ACTIVE_BACKUP] = "active-backup",
+ [NETDEV_BOND_MODE_BALANCE_XOR] = "balance-xor",
+ [NETDEV_BOND_MODE_BROADCAST] = "broadcast",
+ [NETDEV_BOND_MODE_802_3AD] = "802.3ad",
+ [NETDEV_BOND_MODE_BALANCE_TLB] = "balance-tlb",
+ [NETDEV_BOND_MODE_BALANCE_ALB] = "balance-alb",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(bond_mode, BondMode);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_mode, bond_mode, BondMode, "Failed to parse bond mode");
+
+static const char* const bond_xmit_hash_policy_table[_NETDEV_BOND_XMIT_HASH_POLICY_MAX] = {
+ [NETDEV_BOND_XMIT_HASH_POLICY_LAYER2] = "layer2",
+ [NETDEV_BOND_XMIT_HASH_POLICY_LAYER34] = "layer3+4",
+ [NETDEV_BOND_XMIT_HASH_POLICY_LAYER23] = "layer2+3",
+ [NETDEV_BOND_XMIT_HASH_POLICY_ENCAP23] = "encap2+3",
+ [NETDEV_BOND_XMIT_HASH_POLICY_ENCAP34] = "encap3+4",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(bond_xmit_hash_policy, BondXmitHashPolicy);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_xmit_hash_policy,
+ bond_xmit_hash_policy,
+ BondXmitHashPolicy,
+ "Failed to parse bond transmit hash policy")
+
+static const char* const bond_lacp_rate_table[_NETDEV_BOND_LACP_RATE_MAX] = {
+ [NETDEV_BOND_LACP_RATE_SLOW] = "slow",
+ [NETDEV_BOND_LACP_RATE_FAST] = "fast",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(bond_lacp_rate, BondLacpRate);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_lacp_rate, bond_lacp_rate, BondLacpRate, "Failed to parse bond lacp rate")
+
+static const char* const bond_ad_select_table[_NETDEV_BOND_AD_SELECT_MAX] = {
+ [NETDEV_BOND_AD_SELECT_STABLE] = "stable",
+ [NETDEV_BOND_AD_SELECT_BANDWIDTH] = "bandwidth",
+ [NETDEV_BOND_AD_SELECT_COUNT] = "count",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(bond_ad_select, BondAdSelect);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_ad_select, bond_ad_select, BondAdSelect, "Failed to parse bond AD select");
+
+static const char* const bond_fail_over_mac_table[_NETDEV_BOND_FAIL_OVER_MAC_MAX] = {
+ [NETDEV_BOND_FAIL_OVER_MAC_NONE] = "none",
+ [NETDEV_BOND_FAIL_OVER_MAC_ACTIVE] = "active",
+ [NETDEV_BOND_FAIL_OVER_MAC_FOLLOW] = "follow",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(bond_fail_over_mac, BondFailOverMac);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_fail_over_mac, bond_fail_over_mac, BondFailOverMac, "Failed to parse bond fail over MAC");
+
+static const char *const bond_arp_validate_table[_NETDEV_BOND_ARP_VALIDATE_MAX] = {
+ [NETDEV_BOND_ARP_VALIDATE_NONE] = "none",
+ [NETDEV_BOND_ARP_VALIDATE_ACTIVE]= "active",
+ [NETDEV_BOND_ARP_VALIDATE_BACKUP]= "backup",
+ [NETDEV_BOND_ARP_VALIDATE_ALL]= "all",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(bond_arp_validate, BondArpValidate);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_arp_validate, bond_arp_validate, BondArpValidate, "Failed to parse bond arp validate");
+
+static const char *const bond_arp_all_targets_table[_NETDEV_BOND_ARP_ALL_TARGETS_MAX] = {
+ [NETDEV_BOND_ARP_ALL_TARGETS_ANY] = "any",
+ [NETDEV_BOND_ARP_ALL_TARGETS_ALL] = "all",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(bond_arp_all_targets, BondArpAllTargets);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_arp_all_targets, bond_arp_all_targets, BondArpAllTargets, "Failed to parse bond Arp all targets");
+
+static const char *bond_primary_reselect_table[_NETDEV_BOND_PRIMARY_RESELECT_MAX] = {
+ [NETDEV_BOND_PRIMARY_RESELECT_ALWAYS] = "always",
+ [NETDEV_BOND_PRIMARY_RESELECT_BETTER]= "better",
+ [NETDEV_BOND_PRIMARY_RESELECT_FAILURE]= "failure",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(bond_primary_reselect, BondPrimaryReselect);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_bond_primary_reselect, bond_primary_reselect, BondPrimaryReselect, "Failed to parse bond primary reselect");
+
+static uint8_t bond_mode_to_kernel(BondMode mode) {
+ switch (mode) {
+ case NETDEV_BOND_MODE_BALANCE_RR:
+ return BOND_MODE_ROUNDROBIN;
+ case NETDEV_BOND_MODE_ACTIVE_BACKUP:
+ return BOND_MODE_ACTIVEBACKUP;
+ case NETDEV_BOND_MODE_BALANCE_XOR:
+ return BOND_MODE_XOR;
+ case NETDEV_BOND_MODE_BROADCAST:
+ return BOND_MODE_BROADCAST;
+ case NETDEV_BOND_MODE_802_3AD:
+ return BOND_MODE_8023AD;
+ case NETDEV_BOND_MODE_BALANCE_TLB:
+ return BOND_MODE_TLB;
+ case NETDEV_BOND_MODE_BALANCE_ALB:
+ return BOND_MODE_ALB;
+ default:
+ return (uint8_t) -1;
+ }
+}
+
+static uint8_t bond_xmit_hash_policy_to_kernel(BondXmitHashPolicy policy) {
+ switch (policy) {
+ case NETDEV_BOND_XMIT_HASH_POLICY_LAYER2:
+ return BOND_XMIT_POLICY_LAYER2;
+ case NETDEV_BOND_XMIT_HASH_POLICY_LAYER34:
+ return BOND_XMIT_POLICY_LAYER34;
+ case NETDEV_BOND_XMIT_HASH_POLICY_LAYER23:
+ return BOND_XMIT_POLICY_LAYER23;
+ case NETDEV_BOND_XMIT_HASH_POLICY_ENCAP23:
+ return BOND_XMIT_POLICY_ENCAP23;
+ case NETDEV_BOND_XMIT_HASH_POLICY_ENCAP34:
+ return BOND_XMIT_POLICY_ENCAP34;
+ default:
+ return (uint8_t) -1;
+ }
+}
+
+static int netdev_bond_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ Bond *b;
+ ArpIpTarget *target = NULL;
+ int r, i = 0;
+
+ assert(netdev);
+ assert(!link);
+ assert(m);
+
+ b = BOND(netdev);
+
+ assert(b);
+
+ if (b->mode != _NETDEV_BOND_MODE_INVALID) {
+ r = sd_netlink_message_append_u8(m, IFLA_BOND_MODE,
+ bond_mode_to_kernel(b->mode));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_MODE attribute: %m");
+ }
+
+ if (b->xmit_hash_policy != _NETDEV_BOND_XMIT_HASH_POLICY_INVALID) {
+ r = sd_netlink_message_append_u8(m, IFLA_BOND_XMIT_HASH_POLICY,
+ bond_xmit_hash_policy_to_kernel(b->xmit_hash_policy));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_XMIT_HASH_POLICY attribute: %m");
+ }
+
+ if (b->lacp_rate != _NETDEV_BOND_LACP_RATE_INVALID &&
+ b->mode == NETDEV_BOND_MODE_802_3AD) {
+ r = sd_netlink_message_append_u8(m, IFLA_BOND_AD_LACP_RATE, b->lacp_rate );
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_AD_LACP_RATE attribute: %m");
+ }
+
+ if (b->miimon != 0) {
+ r = sd_netlink_message_append_u32(m, IFLA_BOND_MIIMON, b->miimon / USEC_PER_MSEC);
+ if (r < 0)
+ log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_BOND_MIIMON attribute: %m");
+ }
+
+ if (b->downdelay != 0) {
+ r = sd_netlink_message_append_u32(m, IFLA_BOND_DOWNDELAY, b->downdelay / USEC_PER_MSEC);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_DOWNDELAY attribute: %m");
+ }
+
+ if (b->updelay != 0) {
+ r = sd_netlink_message_append_u32(m, IFLA_BOND_UPDELAY, b->updelay / USEC_PER_MSEC);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_UPDELAY attribute: %m");
+ }
+
+ if (b->arp_interval != 0) {
+ r = sd_netlink_message_append_u32(m, IFLA_BOND_ARP_INTERVAL, b->arp_interval / USEC_PER_MSEC);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_ARP_INTERVAL attribute: %m");
+
+ if ((b->lp_interval >= LEARNING_PACKETS_INTERVAL_MIN_SEC) &&
+ (b->lp_interval <= LEARNING_PACKETS_INTERVAL_MAX_SEC)) {
+ r = sd_netlink_message_append_u32(m, IFLA_BOND_LP_INTERVAL, b->lp_interval / USEC_PER_SEC);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_LP_INTERVAL attribute: %m");
+ }
+ }
+
+ if (b->ad_select != _NETDEV_BOND_AD_SELECT_INVALID &&
+ b->mode == NETDEV_BOND_MODE_802_3AD) {
+ r = sd_netlink_message_append_u8(m, IFLA_BOND_AD_SELECT, b->ad_select);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_AD_SELECT attribute: %m");
+ }
+
+ if (b->fail_over_mac != _NETDEV_BOND_FAIL_OVER_MAC_INVALID &&
+ b->mode == NETDEV_BOND_MODE_ACTIVE_BACKUP) {
+ r = sd_netlink_message_append_u8(m, IFLA_BOND_FAIL_OVER_MAC, b->fail_over_mac);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_FAIL_OVER_MAC attribute: %m");
+ }
+
+ if (b->arp_validate != _NETDEV_BOND_ARP_VALIDATE_INVALID) {
+ r = sd_netlink_message_append_u32(m, IFLA_BOND_ARP_VALIDATE, b->arp_validate);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_ARP_VALIDATE attribute: %m");
+ }
+
+ if (b->arp_all_targets != _NETDEV_BOND_ARP_ALL_TARGETS_INVALID) {
+ r = sd_netlink_message_append_u32(m, IFLA_BOND_ARP_ALL_TARGETS, b->arp_all_targets);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_ARP_ALL_TARGETS attribute: %m");
+ }
+
+ if (b->primary_reselect != _NETDEV_BOND_PRIMARY_RESELECT_INVALID) {
+ r = sd_netlink_message_append_u8(m, IFLA_BOND_PRIMARY_RESELECT, b->primary_reselect);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_PRIMARY_RESELECT attribute: %m");
+ }
+
+ if (b->resend_igmp <= RESEND_IGMP_MAX) {
+ r = sd_netlink_message_append_u32(m, IFLA_BOND_RESEND_IGMP, b->resend_igmp);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_RESEND_IGMP attribute: %m");
+ }
+
+ if (b->packets_per_slave <= PACKETS_PER_SLAVE_MAX &&
+ b->mode == NETDEV_BOND_MODE_BALANCE_RR) {
+ r = sd_netlink_message_append_u32(m, IFLA_BOND_PACKETS_PER_SLAVE, b->packets_per_slave);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_PACKETS_PER_SLAVE attribute: %m");
+ }
+
+ if (b->num_grat_arp <= GRATUITOUS_ARP_MAX) {
+ r = sd_netlink_message_append_u8(m, IFLA_BOND_NUM_PEER_NOTIF, b->num_grat_arp);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_NUM_PEER_NOTIF attribute: %m");
+ }
+
+ if (b->min_links != 0) {
+ r = sd_netlink_message_append_u32(m, IFLA_BOND_MIN_LINKS, b->min_links);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_MIN_LINKS attribute: %m");
+ }
+
+ if (b->ad_actor_sys_prio != 0) {
+ r = sd_netlink_message_append_u16(m, IFLA_BOND_AD_ACTOR_SYS_PRIO, b->ad_actor_sys_prio);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_AD_ACTOR_SYS_PRIO attribute: %m");
+ }
+
+ if (b->ad_user_port_key != 0) {
+ r = sd_netlink_message_append_u16(m, IFLA_BOND_AD_USER_PORT_KEY, b->ad_user_port_key);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_AD_USER_PORT_KEY attribute: %m");
+ }
+
+ if (b->ad_actor_system) {
+ r = sd_netlink_message_append_ether_addr(m, IFLA_BOND_AD_ACTOR_SYSTEM, b->ad_actor_system);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_AD_ACTOR_SYSTEM attribute: %m");
+ }
+
+ r = sd_netlink_message_append_u8(m, IFLA_BOND_ALL_SLAVES_ACTIVE, b->all_slaves_active);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_ALL_SLAVES_ACTIVE attribute: %m");
+
+ if (b->tlb_dynamic_lb >= 0) {
+ r = sd_netlink_message_append_u8(m, IFLA_BOND_TLB_DYNAMIC_LB, b->tlb_dynamic_lb);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_TLB_DYNAMIC_LB attribute: %m");
+ }
+
+ if (b->arp_interval > 0) {
+ if (b->n_arp_ip_targets > 0) {
+
+ r = sd_netlink_message_open_container(m, IFLA_BOND_ARP_IP_TARGET);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not open contaniner IFLA_BOND_ARP_IP_TARGET : %m");
+
+ LIST_FOREACH(arp_ip_target, target, b->arp_ip_targets) {
+ r = sd_netlink_message_append_u32(m, i++, target->ip.in.s_addr);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BOND_ARP_ALL_TARGETS attribute: %m");
+ }
+
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not close contaniner IFLA_BOND_ARP_IP_TARGET : %m");
+ }
+ }
+
+ return 0;
+}
+
+int config_parse_arp_ip_target_address(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Bond *b = userdata;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ for (;;) {
+ _cleanup_free_ ArpIpTarget *buffer = NULL;
+ _cleanup_free_ char *n = NULL;
+ int f;
+
+ r = extract_first_word(&rvalue, &n, NULL, 0);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse Bond ARP ip target address, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ if (r == 0)
+ break;
+
+ buffer = new0(ArpIpTarget, 1);
+ if (!buffer)
+ return -ENOMEM;
+
+ r = in_addr_from_string_auto(n, &f, &buffer->ip);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Bond ARP ip target address is invalid, ignoring assignment: %s", n);
+ return 0;
+ }
+
+ if (f != AF_INET) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Bond ARP ip target address is invalid, ignoring assignment: %s", n);
+ return 0;
+ }
+
+ LIST_PREPEND(arp_ip_target, b->arp_ip_targets, TAKE_PTR(buffer));
+ b->n_arp_ip_targets++;
+ }
+
+ if (b->n_arp_ip_targets > NETDEV_BOND_ARP_TARGETS_MAX)
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "More than the maximum number of kernel-supported ARP ip targets specified: %d > %d",
+ b->n_arp_ip_targets, NETDEV_BOND_ARP_TARGETS_MAX);
+
+ return 0;
+}
+
+int config_parse_ad_actor_sys_prio(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) {
+ Bond *b = userdata;
+ uint16_t v;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = safe_atou16(rvalue, &v);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse actor system priority '%s', ignoring: %m", rvalue);
+ return 0;
+ }
+
+ if (v == 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse actor system priority '%s'. Range is [1,65535], ignoring.", rvalue);
+ return 0;
+ }
+
+ b->ad_actor_sys_prio = v;
+
+ return 0;
+}
+
+int config_parse_ad_user_port_key(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) {
+ Bond *b = userdata;
+ uint16_t v;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = safe_atou16(rvalue, &v);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse user port key '%s', ignoring: %m", rvalue);
+ return 0;
+ }
+
+ if (v > 1023) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse user port key '%s'. Range is [0,1023], ignoring.", rvalue);
+ return 0;
+ }
+
+ b->ad_user_port_key = v;
+
+ return 0;
+}
+
+int config_parse_ad_actor_system(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) {
+ Bond *b = userdata;
+ _cleanup_free_ struct ether_addr *n = NULL;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ n = new0(struct ether_addr, 1);
+ if (!n)
+ return log_oom();
+
+ r = ether_addr_from_string(rvalue, n);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Not a valid MAC address %s. Ignoring assignment: %m", rvalue);
+ return 0;
+ }
+
+ if (ether_addr_is_null(n) || (n->ether_addr_octet[0] & 0x01)) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Not a valid MAC address %s, can not be null or multicast. Ignoring assignment.", rvalue);
+ return 0;
+ }
+
+ free_and_replace(b->ad_actor_system, n);
+
+ return 0;
+}
+
+static void bond_done(NetDev *netdev) {
+ ArpIpTarget *t = NULL, *n = NULL;
+ Bond *b;
+
+ assert(netdev);
+
+ b = BOND(netdev);
+
+ assert(b);
+
+ free(b->ad_actor_system);
+
+ LIST_FOREACH_SAFE(arp_ip_target, t, n, b->arp_ip_targets)
+ free(t);
+
+ b->arp_ip_targets = NULL;
+}
+
+static void bond_init(NetDev *netdev) {
+ Bond *b;
+
+ assert(netdev);
+
+ b = BOND(netdev);
+
+ assert(b);
+
+ b->mode = _NETDEV_BOND_MODE_INVALID;
+ b->xmit_hash_policy = _NETDEV_BOND_XMIT_HASH_POLICY_INVALID;
+ b->lacp_rate = _NETDEV_BOND_LACP_RATE_INVALID;
+ b->ad_select = _NETDEV_BOND_AD_SELECT_INVALID;
+ b->fail_over_mac = _NETDEV_BOND_FAIL_OVER_MAC_INVALID;
+ b->arp_validate = _NETDEV_BOND_ARP_VALIDATE_INVALID;
+ b->arp_all_targets = _NETDEV_BOND_ARP_ALL_TARGETS_INVALID;
+ b->primary_reselect = _NETDEV_BOND_PRIMARY_RESELECT_INVALID;
+
+ b->all_slaves_active = false;
+ b->tlb_dynamic_lb = -1;
+
+ b->resend_igmp = RESEND_IGMP_DEFAULT;
+ b->packets_per_slave = PACKETS_PER_SLAVE_DEFAULT;
+ b->num_grat_arp = GRATUITOUS_ARP_DEFAULT;
+ b->lp_interval = LEARNING_PACKETS_INTERVAL_MIN_SEC;
+
+ LIST_HEAD_INIT(b->arp_ip_targets);
+ b->n_arp_ip_targets = 0;
+}
+
+const NetDevVTable bond_vtable = {
+ .object_size = sizeof(Bond),
+ .init = bond_init,
+ .done = bond_done,
+ .sections = "Match\0NetDev\0Bond\0",
+ .fill_message_create = netdev_bond_fill_message_create,
+ .create_type = NETDEV_CREATE_MASTER,
+};
diff --git a/src/network/netdev/bond.h b/src/network/netdev/bond.h
new file mode 100644
index 0000000..31b922b
--- /dev/null
+++ b/src/network/netdev/bond.h
@@ -0,0 +1,163 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "in-addr-util.h"
+#include "list.h"
+
+#include "netdev/netdev.h"
+
+/*
+ * Maximum number of targets supported by the kernel for a single
+ * bond netdev.
+ */
+#define NETDEV_BOND_ARP_TARGETS_MAX 16
+
+typedef enum BondMode {
+ NETDEV_BOND_MODE_BALANCE_RR,
+ NETDEV_BOND_MODE_ACTIVE_BACKUP,
+ NETDEV_BOND_MODE_BALANCE_XOR,
+ NETDEV_BOND_MODE_BROADCAST,
+ NETDEV_BOND_MODE_802_3AD,
+ NETDEV_BOND_MODE_BALANCE_TLB,
+ NETDEV_BOND_MODE_BALANCE_ALB,
+ _NETDEV_BOND_MODE_MAX,
+ _NETDEV_BOND_MODE_INVALID = -1
+} BondMode;
+
+typedef enum BondXmitHashPolicy {
+ NETDEV_BOND_XMIT_HASH_POLICY_LAYER2,
+ NETDEV_BOND_XMIT_HASH_POLICY_LAYER34,
+ NETDEV_BOND_XMIT_HASH_POLICY_LAYER23,
+ NETDEV_BOND_XMIT_HASH_POLICY_ENCAP23,
+ NETDEV_BOND_XMIT_HASH_POLICY_ENCAP34,
+ _NETDEV_BOND_XMIT_HASH_POLICY_MAX,
+ _NETDEV_BOND_XMIT_HASH_POLICY_INVALID = -1
+} BondXmitHashPolicy;
+
+typedef enum BondLacpRate {
+ NETDEV_BOND_LACP_RATE_SLOW,
+ NETDEV_BOND_LACP_RATE_FAST,
+ _NETDEV_BOND_LACP_RATE_MAX,
+ _NETDEV_BOND_LACP_RATE_INVALID = -1,
+} BondLacpRate;
+
+typedef enum BondAdSelect {
+ NETDEV_BOND_AD_SELECT_STABLE,
+ NETDEV_BOND_AD_SELECT_BANDWIDTH,
+ NETDEV_BOND_AD_SELECT_COUNT,
+ _NETDEV_BOND_AD_SELECT_MAX,
+ _NETDEV_BOND_AD_SELECT_INVALID = -1,
+} BondAdSelect;
+
+typedef enum BondFailOverMac {
+ NETDEV_BOND_FAIL_OVER_MAC_NONE,
+ NETDEV_BOND_FAIL_OVER_MAC_ACTIVE,
+ NETDEV_BOND_FAIL_OVER_MAC_FOLLOW,
+ _NETDEV_BOND_FAIL_OVER_MAC_MAX,
+ _NETDEV_BOND_FAIL_OVER_MAC_INVALID = -1,
+} BondFailOverMac;
+
+typedef enum BondArpValidate {
+ NETDEV_BOND_ARP_VALIDATE_NONE,
+ NETDEV_BOND_ARP_VALIDATE_ACTIVE,
+ NETDEV_BOND_ARP_VALIDATE_BACKUP,
+ NETDEV_BOND_ARP_VALIDATE_ALL,
+ _NETDEV_BOND_ARP_VALIDATE_MAX,
+ _NETDEV_BOND_ARP_VALIDATE_INVALID = -1,
+} BondArpValidate;
+
+typedef enum BondArpAllTargets {
+ NETDEV_BOND_ARP_ALL_TARGETS_ANY,
+ NETDEV_BOND_ARP_ALL_TARGETS_ALL,
+ _NETDEV_BOND_ARP_ALL_TARGETS_MAX,
+ _NETDEV_BOND_ARP_ALL_TARGETS_INVALID = -1,
+} BondArpAllTargets;
+
+typedef enum BondPrimaryReselect {
+ NETDEV_BOND_PRIMARY_RESELECT_ALWAYS,
+ NETDEV_BOND_PRIMARY_RESELECT_BETTER,
+ NETDEV_BOND_PRIMARY_RESELECT_FAILURE,
+ _NETDEV_BOND_PRIMARY_RESELECT_MAX,
+ _NETDEV_BOND_PRIMARY_RESELECT_INVALID = -1,
+} BondPrimaryReselect;
+
+typedef struct ArpIpTarget {
+ union in_addr_union ip;
+
+ LIST_FIELDS(struct ArpIpTarget, arp_ip_target);
+} ArpIpTarget;
+
+typedef struct Bond {
+ NetDev meta;
+
+ BondMode mode;
+ BondXmitHashPolicy xmit_hash_policy;
+ BondLacpRate lacp_rate;
+ BondAdSelect ad_select;
+ BondFailOverMac fail_over_mac;
+ BondArpValidate arp_validate;
+ BondArpAllTargets arp_all_targets;
+ BondPrimaryReselect primary_reselect;
+
+ int tlb_dynamic_lb;
+
+ bool all_slaves_active;
+
+ unsigned resend_igmp;
+ unsigned packets_per_slave;
+ unsigned num_grat_arp;
+ unsigned min_links;
+
+ uint16_t ad_actor_sys_prio;
+ uint16_t ad_user_port_key;
+ struct ether_addr *ad_actor_system;
+
+ usec_t miimon;
+ usec_t updelay;
+ usec_t downdelay;
+ usec_t arp_interval;
+ usec_t lp_interval;
+
+ int n_arp_ip_targets;
+ ArpIpTarget *arp_ip_targets;
+} Bond;
+
+DEFINE_NETDEV_CAST(BOND, Bond);
+extern const NetDevVTable bond_vtable;
+
+const char *bond_mode_to_string(BondMode d) _const_;
+BondMode bond_mode_from_string(const char *d) _pure_;
+
+const char *bond_xmit_hash_policy_to_string(BondXmitHashPolicy d) _const_;
+BondXmitHashPolicy bond_xmit_hash_policy_from_string(const char *d) _pure_;
+
+const char *bond_lacp_rate_to_string(BondLacpRate d) _const_;
+BondLacpRate bond_lacp_rate_from_string(const char *d) _pure_;
+
+const char *bond_fail_over_mac_to_string(BondFailOverMac d) _const_;
+BondFailOverMac bond_fail_over_mac_from_string(const char *d) _pure_;
+
+const char *bond_ad_select_to_string(BondAdSelect d) _const_;
+BondAdSelect bond_ad_select_from_string(const char *d) _pure_;
+
+const char *bond_arp_validate_to_string(BondArpValidate d) _const_;
+BondArpValidate bond_arp_validate_from_string(const char *d) _pure_;
+
+const char *bond_arp_all_targets_to_string(BondArpAllTargets d) _const_;
+BondArpAllTargets bond_arp_all_targets_from_string(const char *d) _pure_;
+
+const char *bond_primary_reselect_to_string(BondPrimaryReselect d) _const_;
+BondPrimaryReselect bond_primary_reselect_from_string(const char *d) _pure_;
+
+CONFIG_PARSER_PROTOTYPE(config_parse_bond_mode);
+CONFIG_PARSER_PROTOTYPE(config_parse_bond_xmit_hash_policy);
+CONFIG_PARSER_PROTOTYPE(config_parse_bond_lacp_rate);
+CONFIG_PARSER_PROTOTYPE(config_parse_bond_ad_select);
+CONFIG_PARSER_PROTOTYPE(config_parse_bond_fail_over_mac);
+CONFIG_PARSER_PROTOTYPE(config_parse_bond_arp_validate);
+CONFIG_PARSER_PROTOTYPE(config_parse_bond_arp_all_targets);
+CONFIG_PARSER_PROTOTYPE(config_parse_bond_primary_reselect);
+CONFIG_PARSER_PROTOTYPE(config_parse_arp_ip_target_address);
+CONFIG_PARSER_PROTOTYPE(config_parse_ad_actor_sys_prio);
+CONFIG_PARSER_PROTOTYPE(config_parse_ad_user_port_key);
+CONFIG_PARSER_PROTOTYPE(config_parse_ad_actor_system);
diff --git a/src/network/netdev/bridge.c b/src/network/netdev/bridge.c
new file mode 100644
index 0000000..aadb3ab
--- /dev/null
+++ b/src/network/netdev/bridge.c
@@ -0,0 +1,163 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <net/if.h>
+
+#include "missing.h"
+#include "netlink-util.h"
+#include "netdev/bridge.h"
+#include "networkd-manager.h"
+#include "vlan-util.h"
+
+/* callback for brige netdev's parameter set */
+static int netdev_bridge_set_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) {
+ int r;
+
+ assert(netdev);
+ assert(m);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0) {
+ log_netdev_warning_errno(netdev, r, "Bridge parameters could not be set: %m");
+ return 1;
+ }
+
+ log_netdev_debug(netdev, "Bridge parameters set success");
+
+ return 1;
+}
+
+static int netdev_bridge_post_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ Bridge *b;
+ int r;
+
+ assert(netdev);
+
+ b = BRIDGE(netdev);
+
+ assert(b);
+
+ r = sd_rtnl_message_new_link(netdev->manager->rtnl, &req, RTM_NEWLINK, netdev->ifindex);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not allocate RTM_SETLINK message: %m");
+
+ r = sd_netlink_message_set_flags(req, NLM_F_REQUEST | NLM_F_ACK);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not set netlink flags: %m");
+
+ r = sd_netlink_message_open_container(req, IFLA_LINKINFO);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINKINFO attribute: %m");
+
+ r = sd_netlink_message_open_container_union(req, IFLA_INFO_DATA, netdev_kind_to_string(netdev->kind));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m");
+
+ /* convert to jiffes */
+ if (b->forward_delay != USEC_INFINITY) {
+ r = sd_netlink_message_append_u32(req, IFLA_BR_FORWARD_DELAY, usec_to_jiffies(b->forward_delay));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_FORWARD_DELAY attribute: %m");
+ }
+
+ if (b->hello_time > 0) {
+ r = sd_netlink_message_append_u32(req, IFLA_BR_HELLO_TIME, usec_to_jiffies(b->hello_time));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_HELLO_TIME attribute: %m");
+ }
+
+ if (b->max_age > 0) {
+ r = sd_netlink_message_append_u32(req, IFLA_BR_MAX_AGE, usec_to_jiffies(b->max_age));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_MAX_AGE attribute: %m");
+ }
+
+ if (b->ageing_time != USEC_INFINITY) {
+ r = sd_netlink_message_append_u32(req, IFLA_BR_AGEING_TIME, usec_to_jiffies(b->ageing_time));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_AGEING_TIME attribute: %m");
+ }
+
+ if (b->priority > 0) {
+ r = sd_netlink_message_append_u16(req, IFLA_BR_PRIORITY, b->priority);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_PRIORITY attribute: %m");
+ }
+
+ if (b->group_fwd_mask > 0) {
+ r = sd_netlink_message_append_u16(req, IFLA_BR_GROUP_FWD_MASK, b->group_fwd_mask);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_GROUP_FWD_MASK attribute: %m");
+ }
+
+ if (b->default_pvid != VLANID_INVALID) {
+ r = sd_netlink_message_append_u16(req, IFLA_BR_VLAN_DEFAULT_PVID, b->default_pvid);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_VLAN_DEFAULT_PVID attribute: %m");
+ }
+
+ if (b->mcast_querier >= 0) {
+ r = sd_netlink_message_append_u8(req, IFLA_BR_MCAST_QUERIER, b->mcast_querier);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_MCAST_QUERIER attribute: %m");
+ }
+
+ if (b->mcast_snooping >= 0) {
+ r = sd_netlink_message_append_u8(req, IFLA_BR_MCAST_SNOOPING, b->mcast_snooping);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_MCAST_SNOOPING attribute: %m");
+ }
+
+ if (b->vlan_filtering >= 0) {
+ r = sd_netlink_message_append_u8(req, IFLA_BR_VLAN_FILTERING, b->vlan_filtering);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_VLAN_FILTERING attribute: %m");
+ }
+
+ if (b->stp >= 0) {
+ r = sd_netlink_message_append_u32(req, IFLA_BR_STP_STATE, b->stp);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_BR_STP_STATE attribute: %m");
+ }
+
+ r = sd_netlink_message_close_container(req);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINKINFO attribute: %m");
+
+ r = sd_netlink_message_close_container(req);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m");
+
+ r = netlink_call_async(netdev->manager->rtnl, NULL, req, netdev_bridge_set_handler,
+ netdev_destroy_callback, netdev);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not send rtnetlink message: %m");
+
+ netdev_ref(netdev);
+
+ return r;
+}
+
+static void bridge_init(NetDev *n) {
+ Bridge *b;
+
+ b = BRIDGE(n);
+
+ assert(b);
+
+ b->mcast_querier = -1;
+ b->mcast_snooping = -1;
+ b->vlan_filtering = -1;
+ b->stp = -1;
+ b->default_pvid = VLANID_INVALID;
+ b->forward_delay = USEC_INFINITY;
+ b->ageing_time = USEC_INFINITY;
+}
+
+const NetDevVTable bridge_vtable = {
+ .object_size = sizeof(Bridge),
+ .init = bridge_init,
+ .sections = "Match\0NetDev\0Bridge\0",
+ .post_create = netdev_bridge_post_create,
+ .create_type = NETDEV_CREATE_MASTER,
+};
diff --git a/src/network/netdev/bridge.h b/src/network/netdev/bridge.h
new file mode 100644
index 0000000..3edc93a
--- /dev/null
+++ b/src/network/netdev/bridge.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "netdev/netdev.h"
+
+typedef struct Bridge {
+ NetDev meta;
+
+ int mcast_querier;
+ int mcast_snooping;
+ int vlan_filtering;
+ int stp;
+ uint16_t priority;
+ uint16_t group_fwd_mask;
+ uint16_t default_pvid;
+
+ usec_t forward_delay;
+ usec_t hello_time;
+ usec_t max_age;
+ usec_t ageing_time;
+} Bridge;
+
+DEFINE_NETDEV_CAST(BRIDGE, Bridge);
+extern const NetDevVTable bridge_vtable;
diff --git a/src/network/netdev/dummy.c b/src/network/netdev/dummy.c
new file mode 100644
index 0000000..aded1c5
--- /dev/null
+++ b/src/network/netdev/dummy.c
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "netdev/dummy.h"
+
+const NetDevVTable dummy_vtable = {
+ .object_size = sizeof(Dummy),
+ .sections = "Match\0NetDev\0",
+ .create_type = NETDEV_CREATE_INDEPENDENT,
+};
diff --git a/src/network/netdev/dummy.h b/src/network/netdev/dummy.h
new file mode 100644
index 0000000..93e0651
--- /dev/null
+++ b/src/network/netdev/dummy.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "netdev/netdev.h"
+
+typedef struct Dummy {
+ NetDev meta;
+} Dummy;
+
+DEFINE_NETDEV_CAST(DUMMY, Dummy);
+extern const NetDevVTable dummy_vtable;
diff --git a/src/network/netdev/fou-tunnel.c b/src/network/netdev/fou-tunnel.c
new file mode 100644
index 0000000..65dad38
--- /dev/null
+++ b/src/network/netdev/fou-tunnel.c
@@ -0,0 +1,129 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <linux/ip.h>
+
+#include "conf-parser.h"
+#include "missing.h"
+#include "netdev/fou-tunnel.h"
+#include "networkd-link.h"
+#include "networkd-manager.h"
+#include "parse-util.h"
+#include "sd-netlink.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "util.h"
+
+static const char* const fou_encap_type_table[_NETDEV_FOO_OVER_UDP_ENCAP_MAX] = {
+ [NETDEV_FOO_OVER_UDP_ENCAP_DIRECT] = "FooOverUDP",
+ [NETDEV_FOO_OVER_UDP_ENCAP_GUE] = "GenericUDPEncapsulation",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(fou_encap_type, FooOverUDPEncapType);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_fou_encap_type, fou_encap_type, FooOverUDPEncapType,
+ "Failed to parse Encapsulation=");
+
+static int netdev_fill_fou_tunnel_message(NetDev *netdev, sd_netlink_message **ret) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+ FouTunnel *t;
+ int r;
+
+ assert(netdev);
+
+ t = FOU(netdev);
+
+ assert(t);
+
+ r = sd_genl_message_new(netdev->manager->genl, SD_GENL_FOU, FOU_CMD_ADD, &m);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Failed to allocate generic netlink message: %m");
+
+ r = sd_netlink_message_append_u16(m, FOU_ATTR_PORT, htobe16(t->port));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_PORT attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, FOU_ATTR_TYPE, FOU_ENCAP_GUE);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_TYPE attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, FOU_ATTR_AF, AF_INET);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_AF attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, FOU_ATTR_IPPROTO, t->fou_protocol);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append FOU_ATTR_IPPROTO attribute: %m");
+
+ *ret = m;
+ m = NULL;
+
+ return 0;
+}
+
+static int netdev_fou_tunnel_create(NetDev *netdev) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+ uint32_t serial;
+ FouTunnel *t;
+ int r;
+
+ assert(netdev);
+
+ t = FOU(netdev);
+
+ assert(t);
+
+ r = netdev_fill_fou_tunnel_message(netdev, &m);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_send(netdev->manager->genl, m, &serial);
+ if (r < 0 && r != -EADDRINUSE)
+ return log_netdev_error_errno(netdev, r, "Failed to add FooOverUDP tunnel: %m");
+
+ return 0;
+}
+
+static int netdev_fou_tunnel_verify(NetDev *netdev, const char *filename) {
+ FouTunnel *t;
+
+ assert(netdev);
+ assert(filename);
+
+ t = FOU(netdev);
+
+ assert(t);
+
+ if (t->fou_encap_type == NETDEV_FOO_OVER_UDP_ENCAP_DIRECT && t->fou_protocol <= 0) {
+ log_netdev_error(netdev, "FooOverUDP protocol not configured in %s. Rejecting configuration.", filename);
+ return -EINVAL;
+ }
+
+ if (t->fou_encap_type == NETDEV_FOO_OVER_UDP_ENCAP_GUE && t->fou_protocol > 0) {
+ log_netdev_error(netdev, "FooOverUDP GUE can't be set with protocol configured in %s. Rejecting configuration.", filename);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void fou_tunnel_init(NetDev *netdev) {
+ FouTunnel *t;
+
+ assert(netdev);
+
+ t = FOU(netdev);
+
+ assert(t);
+
+ t->fou_encap_type = NETDEV_FOO_OVER_UDP_ENCAP_DIRECT;
+}
+
+const NetDevVTable foutnl_vtable = {
+ .object_size = sizeof(FouTunnel),
+ .init = fou_tunnel_init,
+ .sections = "Match\0NetDev\0FooOverUDP\0",
+ .create = netdev_fou_tunnel_create,
+ .create_type = NETDEV_CREATE_INDEPENDENT,
+ .config_verify = netdev_fou_tunnel_verify,
+};
diff --git a/src/network/netdev/fou-tunnel.h b/src/network/netdev/fou-tunnel.h
new file mode 100644
index 0000000..b8abed1
--- /dev/null
+++ b/src/network/netdev/fou-tunnel.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#if HAVE_LINUX_FOU_H
+#include <linux/fou.h>
+#endif
+
+#include "in-addr-util.h"
+#include "missing_fou.h"
+#include "netdev/netdev.h"
+
+typedef enum FooOverUDPEncapType {
+ NETDEV_FOO_OVER_UDP_ENCAP_UNSPEC = FOU_ENCAP_UNSPEC,
+ NETDEV_FOO_OVER_UDP_ENCAP_DIRECT = FOU_ENCAP_DIRECT,
+ NETDEV_FOO_OVER_UDP_ENCAP_GUE = FOU_ENCAP_GUE,
+ _NETDEV_FOO_OVER_UDP_ENCAP_MAX,
+ _NETDEV_FOO_OVER_UDP_ENCAP_INVALID = -1,
+} FooOverUDPEncapType;
+
+typedef struct FouTunnel {
+ NetDev meta;
+
+ uint8_t fou_protocol;
+
+ uint16_t port;
+
+ FooOverUDPEncapType fou_encap_type;
+} FouTunnel;
+
+DEFINE_NETDEV_CAST(FOU, FouTunnel);
+extern const NetDevVTable foutnl_vtable;
+
+const char *fou_encap_type_to_string(FooOverUDPEncapType d) _const_;
+FooOverUDPEncapType fou_encap_type_from_string(const char *d) _pure_;
+
+CONFIG_PARSER_PROTOTYPE(config_parse_fou_encap_type);
diff --git a/src/network/netdev/geneve.c b/src/network/netdev/geneve.c
new file mode 100644
index 0000000..089bbfe
--- /dev/null
+++ b/src/network/netdev/geneve.c
@@ -0,0 +1,299 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <net/if.h>
+
+#include "sd-netlink.h"
+
+#include "alloc-util.h"
+#include "conf-parser.h"
+#include "extract-word.h"
+#include "geneve.h"
+#include "netlink-util.h"
+#include "parse-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "missing.h"
+#include "networkd-manager.h"
+
+#define GENEVE_FLOW_LABEL_MAX_MASK 0xFFFFFU
+#define DEFAULT_GENEVE_DESTINATION_PORT 6081
+
+/* callback for geneve netdev's created without a backing Link */
+static int geneve_netdev_create_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) {
+ int r;
+
+ assert(netdev);
+ assert(netdev->state != _NETDEV_STATE_INVALID);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r == -EEXIST)
+ log_netdev_info(netdev, "Geneve netdev exists, using existing without changing its parameters");
+ else if (r < 0) {
+ log_netdev_warning_errno(netdev, r, "Geneve netdev could not be created: %m");
+ netdev_drop(netdev);
+
+ return 1;
+ }
+
+ log_netdev_debug(netdev, "Geneve created");
+
+ return 1;
+}
+
+static int netdev_geneve_create(NetDev *netdev) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+ Geneve *v;
+ int r;
+
+ assert(netdev);
+
+ v = GENEVE(netdev);
+
+ r = sd_rtnl_message_new_link(netdev->manager->rtnl, &m, RTM_NEWLINK, 0);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not allocate RTM_NEWLINK message: %m");
+
+ r = sd_netlink_message_append_string(m, IFLA_IFNAME, netdev->ifname);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IFNAME, attribute: %m");
+
+ if (netdev->mac) {
+ r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, netdev->mac);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_ADDRESS attribute: %m");
+ }
+
+ if (netdev->mtu != 0) {
+ r = sd_netlink_message_append_u32(m, IFLA_MTU, netdev->mtu);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_MTU attribute: %m");
+ }
+
+ r = sd_netlink_message_open_container(m, IFLA_LINKINFO);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINKINFO attribute: %m");
+
+ r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, netdev_kind_to_string(netdev->kind));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m");
+
+ if (v->id <= GENEVE_VID_MAX) {
+ r = sd_netlink_message_append_u32(m, IFLA_GENEVE_ID, v->id);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_ID attribute: %m");
+ }
+
+ if (!in_addr_is_null(v->remote_family, &v->remote)) {
+
+ if (v->remote_family == AF_INET)
+ r = sd_netlink_message_append_in_addr(m, IFLA_GENEVE_REMOTE, &v->remote.in);
+ else
+ r = sd_netlink_message_append_in6_addr(m, IFLA_GENEVE_REMOTE6, &v->remote.in6);
+
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_GROUP attribute: %m");
+
+ }
+
+ if (v->ttl) {
+ r = sd_netlink_message_append_u8(m, IFLA_GENEVE_TTL, v->ttl);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_TTL attribute: %m");
+ }
+
+ r = sd_netlink_message_append_u8(m, IFLA_GENEVE_TOS, v->tos);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_TOS attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_GENEVE_UDP_CSUM, v->udpcsum);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_UDP_CSUM attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_GENEVE_UDP_ZERO_CSUM6_TX, v->udp6zerocsumtx);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_UDP_ZERO_CSUM6_TX attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_GENEVE_UDP_ZERO_CSUM6_RX, v->udp6zerocsumrx);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_UDP_ZERO_CSUM6_RX attribute: %m");
+
+ if (v->dest_port != DEFAULT_GENEVE_DESTINATION_PORT) {
+ r = sd_netlink_message_append_u16(m, IFLA_GENEVE_PORT, htobe16(v->dest_port));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_PORT attribute: %m");
+ }
+
+ if (v->flow_label > 0) {
+ r = sd_netlink_message_append_u32(m, IFLA_GENEVE_LABEL, htobe32(v->flow_label));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GENEVE_LABEL attribute: %m");
+ }
+
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m");
+
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINKINFO attribute: %m");
+
+ r = netlink_call_async(netdev->manager->rtnl, NULL, m, geneve_netdev_create_handler,
+ netdev_destroy_callback, netdev);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not send rtnetlink message: %m");
+
+ netdev_ref(netdev);
+ netdev->state = NETDEV_STATE_CREATING;
+
+ log_netdev_debug(netdev, "Creating");
+
+ return r;
+}
+
+int config_parse_geneve_vni(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) {
+ Geneve *v = userdata;
+ uint32_t f;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = safe_atou32(rvalue, &f);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse Geneve VNI '%s'.", rvalue);
+ return 0;
+ }
+
+ if (f > GENEVE_VID_MAX){
+ log_syntax(unit, LOG_ERR, filename, line, r, "Geneve VNI out is of range '%s'.", rvalue);
+ return 0;
+ }
+
+ v->id = f;
+
+ return 0;
+}
+
+int config_parse_geneve_address(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Geneve *v = userdata;
+ union in_addr_union *addr = data, buffer;
+ int r, f;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = in_addr_from_string_auto(rvalue, &f, &buffer);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "geneve '%s' address is invalid, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ r = in_addr_is_multicast(f, &buffer);
+ if (r > 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "geneve invalid multicast '%s' address, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ v->remote_family = f;
+ *addr = buffer;
+
+ return 0;
+}
+
+int config_parse_geneve_flow_label(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Geneve *v = userdata;
+ uint32_t f;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = safe_atou32(rvalue, &f);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse Geneve flow label '%s'.", rvalue);
+ return 0;
+ }
+
+ if (f & ~GENEVE_FLOW_LABEL_MAX_MASK) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Geneve flow label '%s' not valid. Flow label range should be [0-1048575].", rvalue);
+ return 0;
+ }
+
+ v->flow_label = f;
+
+ return 0;
+}
+
+static int netdev_geneve_verify(NetDev *netdev, const char *filename) {
+ Geneve *v = GENEVE(netdev);
+
+ assert(netdev);
+ assert(v);
+ assert(filename);
+
+ if (v->ttl == 0) {
+ log_warning("Invalid Geneve TTL value '0' configured in '%s'. Ignoring", filename);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void geneve_init(NetDev *netdev) {
+ Geneve *v;
+
+ assert(netdev);
+
+ v = GENEVE(netdev);
+
+ assert(v);
+
+ v->id = GENEVE_VID_MAX + 1;
+ v->dest_port = DEFAULT_GENEVE_DESTINATION_PORT;
+ v->udpcsum = false;
+ v->udp6zerocsumtx = false;
+ v->udp6zerocsumrx = false;
+}
+
+const NetDevVTable geneve_vtable = {
+ .object_size = sizeof(Geneve),
+ .init = geneve_init,
+ .sections = "Match\0NetDev\0GENEVE\0",
+ .create = netdev_geneve_create,
+ .create_type = NETDEV_CREATE_INDEPENDENT,
+ .config_verify = netdev_geneve_verify,
+};
diff --git a/src/network/netdev/geneve.h b/src/network/netdev/geneve.h
new file mode 100644
index 0000000..c201981
--- /dev/null
+++ b/src/network/netdev/geneve.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+typedef struct Geneve Geneve;
+
+#include "in-addr-util.h"
+#include "netdev.h"
+#include "networkd-link.h"
+#include "networkd-network.h"
+
+#define GENEVE_VID_MAX (1u << 24) - 1
+
+struct Geneve {
+ NetDev meta;
+
+ uint32_t id;
+ uint32_t flow_label;
+
+ int remote_family;
+
+ uint8_t tos;
+ uint8_t ttl;
+
+ uint16_t dest_port;
+
+ bool udpcsum;
+ bool udp6zerocsumtx;
+ bool udp6zerocsumrx;
+
+ union in_addr_union remote;
+};
+
+DEFINE_NETDEV_CAST(GENEVE, Geneve);
+extern const NetDevVTable geneve_vtable;
+
+CONFIG_PARSER_PROTOTYPE(config_parse_geneve_vni);
+CONFIG_PARSER_PROTOTYPE(config_parse_geneve_address);
+CONFIG_PARSER_PROTOTYPE(config_parse_geneve_flow_label);
diff --git a/src/network/netdev/ipvlan.c b/src/network/netdev/ipvlan.c
new file mode 100644
index 0000000..5bb6a5b
--- /dev/null
+++ b/src/network/netdev/ipvlan.c
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <net/if.h>
+
+#include "conf-parser.h"
+#include "netdev/ipvlan.h"
+#include "string-table.h"
+
+static const char* const ipvlan_mode_table[_NETDEV_IPVLAN_MODE_MAX] = {
+ [NETDEV_IPVLAN_MODE_L2] = "L2",
+ [NETDEV_IPVLAN_MODE_L3] = "L3",
+ [NETDEV_IPVLAN_MODE_L3S] = "L3S",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(ipvlan_mode, IPVlanMode);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_ipvlan_mode, ipvlan_mode, IPVlanMode, "Failed to parse ipvlan mode");
+
+static const char* const ipvlan_flags_table[_NETDEV_IPVLAN_FLAGS_MAX] = {
+ [NETDEV_IPVLAN_FLAGS_BRIGDE] = "bridge",
+ [NETDEV_IPVLAN_FLAGS_PRIVATE] = "private",
+ [NETDEV_IPVLAN_FLAGS_VEPA] = "vepa",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(ipvlan_flags, IPVlanFlags);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_ipvlan_flags, ipvlan_flags, IPVlanFlags, "Failed to parse ipvlan flags");
+
+static int netdev_ipvlan_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *req) {
+ IPVlan *m;
+ int r;
+
+ assert(netdev);
+ assert(link);
+ assert(netdev->ifname);
+
+ m = IPVLAN(netdev);
+
+ assert(m);
+
+ if (m->mode != _NETDEV_IPVLAN_MODE_INVALID) {
+ r = sd_netlink_message_append_u16(req, IFLA_IPVLAN_MODE, m->mode);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPVLAN_MODE attribute: %m");
+ }
+
+ if (m->flags != _NETDEV_IPVLAN_FLAGS_INVALID) {
+ r = sd_netlink_message_append_u16(req, IFLA_IPVLAN_FLAGS, m->flags);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPVLAN_FLAGS attribute: %m");
+ }
+
+ return 0;
+}
+
+static void ipvlan_init(NetDev *n) {
+ IPVlan *m;
+
+ assert(n);
+
+ m = IPVLAN(n);
+
+ assert(m);
+
+ m->mode = _NETDEV_IPVLAN_MODE_INVALID;
+ m->flags = _NETDEV_IPVLAN_FLAGS_INVALID;
+}
+
+const NetDevVTable ipvlan_vtable = {
+ .object_size = sizeof(IPVlan),
+ .init = ipvlan_init,
+ .sections = "Match\0NetDev\0IPVLAN\0",
+ .fill_message_create = netdev_ipvlan_fill_message_create,
+ .create_type = NETDEV_CREATE_STACKED,
+};
diff --git a/src/network/netdev/ipvlan.h b/src/network/netdev/ipvlan.h
new file mode 100644
index 0000000..fb426d3
--- /dev/null
+++ b/src/network/netdev/ipvlan.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <linux/if_link.h>
+
+#include "missing_if_link.h"
+#include "netdev/netdev.h"
+
+typedef enum IPVlanMode {
+ NETDEV_IPVLAN_MODE_L2 = IPVLAN_MODE_L2,
+ NETDEV_IPVLAN_MODE_L3 = IPVLAN_MODE_L3,
+ NETDEV_IPVLAN_MODE_L3S = IPVLAN_MODE_L3S,
+ _NETDEV_IPVLAN_MODE_MAX,
+ _NETDEV_IPVLAN_MODE_INVALID = -1
+} IPVlanMode;
+
+typedef enum IPVlanFlags {
+ NETDEV_IPVLAN_FLAGS_BRIGDE,
+ NETDEV_IPVLAN_FLAGS_PRIVATE = IPVLAN_F_PRIVATE,
+ NETDEV_IPVLAN_FLAGS_VEPA = IPVLAN_F_VEPA,
+ _NETDEV_IPVLAN_FLAGS_MAX,
+ _NETDEV_IPVLAN_FLAGS_INVALID = -1
+} IPVlanFlags;
+
+typedef struct IPVlan {
+ NetDev meta;
+
+ IPVlanMode mode;
+ IPVlanFlags flags;
+} IPVlan;
+
+DEFINE_NETDEV_CAST(IPVLAN, IPVlan);
+extern const NetDevVTable ipvlan_vtable;
+
+const char *ipvlan_mode_to_string(IPVlanMode d) _const_;
+IPVlanMode ipvlan_mode_from_string(const char *d) _pure_;
+
+const char *ipvlan_flags_to_string(IPVlanFlags d) _const_;
+IPVlanFlags ipvlan_flags_from_string(const char *d) _pure_;
+
+CONFIG_PARSER_PROTOTYPE(config_parse_ipvlan_mode);
+CONFIG_PARSER_PROTOTYPE(config_parse_ipvlan_flags);
diff --git a/src/network/netdev/macvlan.c b/src/network/netdev/macvlan.c
new file mode 100644
index 0000000..871f020
--- /dev/null
+++ b/src/network/netdev/macvlan.c
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <net/if.h>
+
+#include "conf-parser.h"
+#include "netdev/macvlan.h"
+#include "string-table.h"
+
+static const char* const macvlan_mode_table[_NETDEV_MACVLAN_MODE_MAX] = {
+ [NETDEV_MACVLAN_MODE_PRIVATE] = "private",
+ [NETDEV_MACVLAN_MODE_VEPA] = "vepa",
+ [NETDEV_MACVLAN_MODE_BRIDGE] = "bridge",
+ [NETDEV_MACVLAN_MODE_PASSTHRU] = "passthru",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(macvlan_mode, MacVlanMode);
+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) {
+ MacVlan *m;
+ int r;
+
+ assert(netdev);
+ assert(link);
+ assert(netdev->ifname);
+
+ if (netdev->kind == NETDEV_KIND_MACVLAN)
+ m = MACVLAN(netdev);
+ else
+ m = MACVTAP(netdev);
+
+ assert(m);
+
+ if (m->mode != _NETDEV_MACVLAN_MODE_INVALID) {
+ r = sd_netlink_message_append_u32(req, IFLA_MACVLAN_MODE, m->mode);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_MACVLAN_MODE attribute: %m");
+ }
+
+ return 0;
+}
+
+static void macvlan_init(NetDev *n) {
+ MacVlan *m;
+
+ assert(n);
+
+ if (n->kind == NETDEV_KIND_MACVLAN)
+ m = MACVLAN(n);
+ else
+ m = MACVTAP(n);
+
+ assert(m);
+
+ m->mode = _NETDEV_MACVLAN_MODE_INVALID;
+}
+
+const NetDevVTable macvtap_vtable = {
+ .object_size = sizeof(MacVlan),
+ .init = macvlan_init,
+ .sections = "Match\0NetDev\0MACVTAP\0",
+ .fill_message_create = netdev_macvlan_fill_message_create,
+ .create_type = NETDEV_CREATE_STACKED,
+};
+
+const NetDevVTable macvlan_vtable = {
+ .object_size = sizeof(MacVlan),
+ .init = macvlan_init,
+ .sections = "Match\0NetDev\0MACVLAN\0",
+ .fill_message_create = netdev_macvlan_fill_message_create,
+ .create_type = NETDEV_CREATE_STACKED,
+};
diff --git a/src/network/netdev/macvlan.h b/src/network/netdev/macvlan.h
new file mode 100644
index 0000000..b473f1e
--- /dev/null
+++ b/src/network/netdev/macvlan.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+typedef struct MacVlan MacVlan;
+
+#include "netdev/netdev.h"
+
+typedef enum MacVlanMode {
+ NETDEV_MACVLAN_MODE_PRIVATE = MACVLAN_MODE_PRIVATE,
+ NETDEV_MACVLAN_MODE_VEPA = MACVLAN_MODE_VEPA,
+ NETDEV_MACVLAN_MODE_BRIDGE = MACVLAN_MODE_BRIDGE,
+ NETDEV_MACVLAN_MODE_PASSTHRU = MACVLAN_MODE_PASSTHRU,
+ _NETDEV_MACVLAN_MODE_MAX,
+ _NETDEV_MACVLAN_MODE_INVALID = -1
+} MacVlanMode;
+
+struct MacVlan {
+ NetDev meta;
+
+ MacVlanMode mode;
+};
+
+DEFINE_NETDEV_CAST(MACVLAN, MacVlan);
+DEFINE_NETDEV_CAST(MACVTAP, MacVlan);
+extern const NetDevVTable macvlan_vtable;
+extern const NetDevVTable macvtap_vtable;
+
+const char *macvlan_mode_to_string(MacVlanMode d) _const_;
+MacVlanMode macvlan_mode_from_string(const char *d) _pure_;
+
+CONFIG_PARSER_PROTOTYPE(config_parse_macvlan_mode);
diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf
new file mode 100644
index 0000000..f7ca98f
--- /dev/null
+++ b/src/network/netdev/netdev-gperf.gperf
@@ -0,0 +1,174 @@
+%{
+#if __GNUC__ >= 7
+_Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"")
+#endif
+#include <stddef.h>
+#include "conf-parser.h"
+#include "network-internal.h"
+#include "netdev/bond.h"
+#include "netdev/bridge.h"
+#include "netdev/geneve.h"
+#include "netdev/ipvlan.h"
+#include "netdev/macvlan.h"
+#include "netdev/tunnel.h"
+#include "netdev/tuntap.h"
+#include "netdev/veth.h"
+#include "netdev/vlan.h"
+#include "netdev/vxlan.h"
+#include "netdev/vrf.h"
+#include "netdev/netdev.h"
+#include "netdev/vxcan.h"
+#include "netdev/wireguard.h"
+#include "netdev/fou-tunnel.h"
+#include "vlan-util.h"
+%}
+struct ConfigPerfItem;
+%null_strings
+%language=ANSI-C
+%define slot-name section_and_lvalue
+%define hash-function-name network_netdev_gperf_hash
+%define lookup-function-name network_netdev_gperf_lookup
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(NetDev, match_host)
+Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(NetDev, match_virt)
+Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(NetDev, match_kernel_cmdline)
+Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(NetDev, match_kernel_version)
+Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(NetDev, match_arch)
+NetDev.Description, config_parse_string, 0, offsetof(NetDev, description)
+NetDev.Name, config_parse_ifname, 0, offsetof(NetDev, ifname)
+NetDev.Kind, config_parse_netdev_kind, 0, offsetof(NetDev, kind)
+NetDev.MTUBytes, config_parse_mtu, AF_UNSPEC, offsetof(NetDev, mtu)
+NetDev.MACAddress, config_parse_hwaddr, 0, offsetof(NetDev, mac)
+VLAN.Id, config_parse_vlanid, 0, offsetof(VLan, id)
+VLAN.GVRP, config_parse_tristate, 0, offsetof(VLan, gvrp)
+VLAN.MVRP, config_parse_tristate, 0, offsetof(VLan, mvrp)
+VLAN.LooseBinding, config_parse_tristate, 0, offsetof(VLan, loose_binding)
+VLAN.ReorderHeader, config_parse_tristate, 0, offsetof(VLan, reorder_hdr)
+MACVLAN.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode)
+MACVTAP.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode)
+IPVLAN.Mode, config_parse_ipvlan_mode, 0, offsetof(IPVlan, mode)
+IPVLAN.Flags, config_parse_ipvlan_flags, 0, offsetof(IPVlan, flags)
+Tunnel.Local, config_parse_tunnel_address, 0, offsetof(Tunnel, local)
+Tunnel.Remote, config_parse_tunnel_address, 0, offsetof(Tunnel, remote)
+Tunnel.TOS, config_parse_unsigned, 0, offsetof(Tunnel, tos)
+Tunnel.TTL, config_parse_unsigned, 0, offsetof(Tunnel, ttl)
+Tunnel.Key, config_parse_tunnel_key, 0, offsetof(Tunnel, key)
+Tunnel.InputKey, config_parse_tunnel_key, 0, offsetof(Tunnel, ikey)
+Tunnel.OutputKey, config_parse_tunnel_key, 0, offsetof(Tunnel, okey)
+Tunnel.DiscoverPathMTU, config_parse_bool, 0, offsetof(Tunnel, pmtudisc)
+Tunnel.Mode, config_parse_ip6tnl_mode, 0, offsetof(Tunnel, ip6tnl_mode)
+Tunnel.IPv6FlowLabel, config_parse_ipv6_flowlabel, 0, offsetof(Tunnel, ipv6_flowlabel)
+Tunnel.CopyDSCP, config_parse_bool, 0, offsetof(Tunnel, copy_dscp)
+Tunnel.EncapsulationLimit, config_parse_encap_limit, 0, offsetof(Tunnel, encap_limit)
+Tunnel.Independent, config_parse_bool, 0, offsetof(Tunnel, independent)
+Tunnel.AllowLocalRemote, config_parse_tristate, 0, offsetof(Tunnel, allow_localremote)
+Tunnel.FooOverUDP, config_parse_bool, 0, offsetof(Tunnel, fou_tunnel)
+Tunnel.FOUDestinationPort, config_parse_ip_port, 0, offsetof(Tunnel, fou_destination_port)
+Tunnel.FOUSourcePort, config_parse_ip_port, 0, offsetof(Tunnel, encap_src_port)
+Tunnel.Encapsulation, config_parse_fou_encap_type, 0, offsetof(Tunnel, fou_encap_type)
+Tunnel.IPv6RapidDeploymentPrefix, config_parse_6rd_prefix, 0, 0
+Tunnel.ERSPANIndex, config_parse_uint32, 0, offsetof(Tunnel, erspan_index)
+Tunnel.SerializeTunneledPackets, config_parse_tristate, 0, offsetof(Tunnel, erspan_sequence)
+Tunnel.ISATAP, config_parse_tristate, 0, offsetof(Tunnel, isatap)
+FooOverUDP.Protocol, config_parse_uint8, 0, offsetof(FouTunnel, fou_protocol)
+FooOverUDP.Encapsulation, config_parse_fou_encap_type, 0, offsetof(FouTunnel, fou_encap_type)
+FooOverUDP.Port, config_parse_ip_port, 0, offsetof(FouTunnel, port)
+Peer.Name, config_parse_ifname, 0, offsetof(Veth, ifname_peer)
+Peer.MACAddress, config_parse_hwaddr, 0, offsetof(Veth, mac_peer)
+VXCAN.Peer, config_parse_ifname, 0, offsetof(VxCan, ifname_peer)
+VXLAN.Id, config_parse_uint64, 0, offsetof(VxLan, id)
+VXLAN.Group, config_parse_vxlan_address, 0, offsetof(VxLan, remote)
+VXLAN.Local, config_parse_vxlan_address, 0, offsetof(VxLan, local)
+VXLAN.Remote, config_parse_vxlan_address, 0, offsetof(VxLan, remote)
+VXLAN.TOS, config_parse_unsigned, 0, offsetof(VxLan, tos)
+VXLAN.TTL, config_parse_unsigned, 0, offsetof(VxLan, ttl)
+VXLAN.MacLearning, config_parse_bool, 0, offsetof(VxLan, learning)
+VXLAN.ARPProxy, config_parse_bool, 0, offsetof(VxLan, arp_proxy)
+VXLAN.ReduceARPProxy, config_parse_bool, 0, offsetof(VxLan, arp_proxy)
+VXLAN.L2MissNotification, config_parse_bool, 0, offsetof(VxLan, l2miss)
+VXLAN.L3MissNotification, config_parse_bool, 0, offsetof(VxLan, l3miss)
+VXLAN.RouteShortCircuit, config_parse_bool, 0, offsetof(VxLan, route_short_circuit)
+VXLAN.UDPCheckSum, config_parse_bool, 0, offsetof(VxLan, udpcsum)
+VXLAN.UDPChecksum, config_parse_bool, 0, offsetof(VxLan, udpcsum)
+VXLAN.UDP6ZeroCheckSumRx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumrx)
+VXLAN.UDP6ZeroChecksumRx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumrx)
+VXLAN.UDP6ZeroCheckSumTx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumtx)
+VXLAN.UDP6ZeroChecksumTx, config_parse_bool, 0, offsetof(VxLan, udp6zerocsumtx)
+VXLAN.RemoteChecksumTx, config_parse_bool, 0, offsetof(VxLan, remote_csum_tx)
+VXLAN.RemoteChecksumRx, config_parse_bool, 0, offsetof(VxLan, remote_csum_rx)
+VXLAN.FDBAgeingSec, config_parse_sec, 0, offsetof(VxLan, fdb_ageing)
+VXLAN.GroupPolicyExtension, config_parse_bool, 0, offsetof(VxLan, group_policy)
+VXLAN.MaximumFDBEntries, config_parse_unsigned, 0, offsetof(VxLan, max_fdb)
+VXLAN.PortRange, config_parse_port_range, 0, 0
+VXLAN.DestinationPort, config_parse_ip_port, 0, offsetof(VxLan, dest_port)
+VXLAN.FlowLabel, config_parse_flow_label, 0, 0
+GENEVE.Id, config_parse_geneve_vni, 0, offsetof(Geneve, id)
+GENEVE.Remote, config_parse_geneve_address, 0, offsetof(Geneve, remote)
+GENEVE.TOS, config_parse_uint8, 0, offsetof(Geneve, tos)
+GENEVE.TTL, config_parse_uint8, 0, offsetof(Geneve, ttl)
+GENEVE.UDPChecksum, config_parse_bool, 0, offsetof(Geneve, udpcsum)
+GENEVE.UDP6ZeroCheckSumRx, config_parse_bool, 0, offsetof(Geneve, udp6zerocsumrx)
+GENEVE.UDP6ZeroChecksumRx, config_parse_bool, 0, offsetof(Geneve, udp6zerocsumrx)
+GENEVE.UDP6ZeroCheckSumTx, config_parse_bool, 0, offsetof(Geneve, udp6zerocsumtx)
+GENEVE.UDP6ZeroChecksumTx, config_parse_bool, 0, offsetof(Geneve, udp6zerocsumtx)
+GENEVE.DestinationPort, config_parse_ip_port, 0, offsetof(Geneve, dest_port)
+GENEVE.FlowLabel, config_parse_geneve_flow_label, 0, 0
+Tun.OneQueue, config_parse_bool, 0, offsetof(TunTap, one_queue)
+Tun.MultiQueue, config_parse_bool, 0, offsetof(TunTap, multi_queue)
+Tun.PacketInfo, config_parse_bool, 0, offsetof(TunTap, packet_info)
+Tun.User, config_parse_string, 0, offsetof(TunTap, user_name)
+Tun.Group, config_parse_string, 0, offsetof(TunTap, group_name)
+Tap.OneQueue, config_parse_bool, 0, offsetof(TunTap, one_queue)
+Tap.MultiQueue, config_parse_bool, 0, offsetof(TunTap, multi_queue)
+Tap.PacketInfo, config_parse_bool, 0, offsetof(TunTap, packet_info)
+Tap.VNetHeader, config_parse_bool, 0, offsetof(TunTap, vnet_hdr)
+Tap.User, config_parse_string, 0, offsetof(TunTap, user_name)
+Tap.Group, config_parse_string, 0, offsetof(TunTap, group_name)
+Bond.Mode, config_parse_bond_mode, 0, offsetof(Bond, mode)
+Bond.TransmitHashPolicy, config_parse_bond_xmit_hash_policy, 0, offsetof(Bond, xmit_hash_policy)
+Bond.LACPTransmitRate, config_parse_bond_lacp_rate, 0, offsetof(Bond, lacp_rate)
+Bond.AdSelect, config_parse_bond_ad_select, 0, offsetof(Bond, ad_select)
+Bond.FailOverMACPolicy, config_parse_bond_fail_over_mac, 0, offsetof(Bond, fail_over_mac)
+Bond.ARPIPTargets, config_parse_arp_ip_target_address, 0, 0
+Bond.ARPValidate, config_parse_bond_arp_validate, 0, offsetof(Bond, arp_validate)
+Bond.ARPAllTargets, config_parse_bond_arp_all_targets, 0, offsetof(Bond, arp_all_targets)
+Bond.PrimaryReselectPolicy, config_parse_bond_primary_reselect, 0, offsetof(Bond, primary_reselect)
+Bond.ResendIGMP, config_parse_unsigned, 0, offsetof(Bond, resend_igmp)
+Bond.PacketsPerSlave, config_parse_unsigned, 0, offsetof(Bond, packets_per_slave)
+Bond.GratuitousARP, config_parse_unsigned, 0, offsetof(Bond, num_grat_arp)
+Bond.AllSlavesActive, config_parse_bool, 0, offsetof(Bond, all_slaves_active)
+Bond.DynamicTransmitLoadBalancing, config_parse_tristate, 0, offsetof(Bond, tlb_dynamic_lb)
+Bond.MinLinks, config_parse_unsigned, 0, offsetof(Bond, min_links)
+Bond.MIIMonitorSec, config_parse_sec, 0, offsetof(Bond, miimon)
+Bond.UpDelaySec, config_parse_sec, 0, offsetof(Bond, updelay)
+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.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)
+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)
+Bridge.ForwardDelaySec, config_parse_sec, 0, offsetof(Bridge, forward_delay)
+Bridge.Priority, config_parse_uint16, 0, offsetof(Bridge, priority)
+Bridge.GroupForwardMask, config_parse_uint16, 0, offsetof(Bridge, group_fwd_mask)
+Bridge.DefaultPVID, config_parse_default_port_vlanid, 0, offsetof(Bridge, default_pvid)
+Bridge.MulticastQuerier, config_parse_tristate, 0, offsetof(Bridge, mcast_querier)
+Bridge.MulticastSnooping, config_parse_tristate, 0, offsetof(Bridge, mcast_snooping)
+Bridge.VLANFiltering, config_parse_tristate, 0, offsetof(Bridge, vlan_filtering)
+Bridge.STP, config_parse_tristate, 0, offsetof(Bridge, stp)
+VRF.TableId, config_parse_uint32, 0, offsetof(Vrf, table) /* deprecated */
+VRF.Table, config_parse_uint32, 0, offsetof(Vrf, table)
+WireGuard.FwMark, config_parse_unsigned, 0, offsetof(Wireguard, fwmark)
+WireGuard.ListenPort, config_parse_wireguard_listen_port, 0, offsetof(Wireguard, port)
+WireGuard.PrivateKey, config_parse_wireguard_private_key, 0, 0
+WireGuardPeer.AllowedIPs, config_parse_wireguard_allowed_ips, 0, 0
+WireGuardPeer.Endpoint, config_parse_wireguard_endpoint, 0, 0
+WireGuardPeer.PublicKey, config_parse_wireguard_public_key, 0, 0
+WireGuardPeer.PresharedKey, config_parse_wireguard_preshared_key, 0, 0
+WireGuardPeer.PersistentKeepalive, config_parse_wireguard_keepalive, 0, 0
diff --git a/src/network/netdev/netdev.c b/src/network/netdev/netdev.c
new file mode 100644
index 0000000..0263917
--- /dev/null
+++ b/src/network/netdev/netdev.c
@@ -0,0 +1,816 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <net/if.h>
+
+#include "alloc-util.h"
+#include "conf-files.h"
+#include "conf-parser.h"
+#include "fd-util.h"
+#include "list.h"
+#include "netlink-util.h"
+#include "network-internal.h"
+#include "netdev/netdev.h"
+#include "networkd-manager.h"
+#include "networkd-link.h"
+#include "siphash24.h"
+#include "stat-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+
+#include "netdev/bridge.h"
+#include "netdev/bond.h"
+#include "netdev/geneve.h"
+#include "netdev/vlan.h"
+#include "netdev/macvlan.h"
+#include "netdev/ipvlan.h"
+#include "netdev/vxlan.h"
+#include "netdev/tunnel.h"
+#include "netdev/tuntap.h"
+#include "netdev/veth.h"
+#include "netdev/dummy.h"
+#include "netdev/vrf.h"
+#include "netdev/vcan.h"
+#include "netdev/vxcan.h"
+#include "netdev/wireguard.h"
+#include "netdev/netdevsim.h"
+#include "netdev/fou-tunnel.h"
+
+const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX] = {
+ [NETDEV_KIND_BRIDGE] = &bridge_vtable,
+ [NETDEV_KIND_BOND] = &bond_vtable,
+ [NETDEV_KIND_VLAN] = &vlan_vtable,
+ [NETDEV_KIND_MACVLAN] = &macvlan_vtable,
+ [NETDEV_KIND_MACVTAP] = &macvtap_vtable,
+ [NETDEV_KIND_IPVLAN] = &ipvlan_vtable,
+ [NETDEV_KIND_VXLAN] = &vxlan_vtable,
+ [NETDEV_KIND_IPIP] = &ipip_vtable,
+ [NETDEV_KIND_GRE] = &gre_vtable,
+ [NETDEV_KIND_GRETAP] = &gretap_vtable,
+ [NETDEV_KIND_IP6GRE] = &ip6gre_vtable,
+ [NETDEV_KIND_IP6GRETAP] = &ip6gretap_vtable,
+ [NETDEV_KIND_SIT] = &sit_vtable,
+ [NETDEV_KIND_VTI] = &vti_vtable,
+ [NETDEV_KIND_VTI6] = &vti6_vtable,
+ [NETDEV_KIND_VETH] = &veth_vtable,
+ [NETDEV_KIND_DUMMY] = &dummy_vtable,
+ [NETDEV_KIND_TUN] = &tun_vtable,
+ [NETDEV_KIND_TAP] = &tap_vtable,
+ [NETDEV_KIND_IP6TNL] = &ip6tnl_vtable,
+ [NETDEV_KIND_VRF] = &vrf_vtable,
+ [NETDEV_KIND_VCAN] = &vcan_vtable,
+ [NETDEV_KIND_GENEVE] = &geneve_vtable,
+ [NETDEV_KIND_VXCAN] = &vxcan_vtable,
+ [NETDEV_KIND_WIREGUARD] = &wireguard_vtable,
+ [NETDEV_KIND_NETDEVSIM] = &netdevsim_vtable,
+ [NETDEV_KIND_FOU] = &foutnl_vtable,
+ [NETDEV_KIND_ERSPAN] = &erspan_vtable,
+};
+
+static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = {
+ [NETDEV_KIND_BRIDGE] = "bridge",
+ [NETDEV_KIND_BOND] = "bond",
+ [NETDEV_KIND_VLAN] = "vlan",
+ [NETDEV_KIND_MACVLAN] = "macvlan",
+ [NETDEV_KIND_MACVTAP] = "macvtap",
+ [NETDEV_KIND_IPVLAN] = "ipvlan",
+ [NETDEV_KIND_VXLAN] = "vxlan",
+ [NETDEV_KIND_IPIP] = "ipip",
+ [NETDEV_KIND_GRE] = "gre",
+ [NETDEV_KIND_GRETAP] = "gretap",
+ [NETDEV_KIND_IP6GRE] = "ip6gre",
+ [NETDEV_KIND_IP6GRETAP] = "ip6gretap",
+ [NETDEV_KIND_SIT] = "sit",
+ [NETDEV_KIND_VETH] = "veth",
+ [NETDEV_KIND_VTI] = "vti",
+ [NETDEV_KIND_VTI6] = "vti6",
+ [NETDEV_KIND_DUMMY] = "dummy",
+ [NETDEV_KIND_TUN] = "tun",
+ [NETDEV_KIND_TAP] = "tap",
+ [NETDEV_KIND_IP6TNL] = "ip6tnl",
+ [NETDEV_KIND_VRF] = "vrf",
+ [NETDEV_KIND_VCAN] = "vcan",
+ [NETDEV_KIND_GENEVE] = "geneve",
+ [NETDEV_KIND_VXCAN] = "vxcan",
+ [NETDEV_KIND_WIREGUARD] = "wireguard",
+ [NETDEV_KIND_NETDEVSIM] = "netdevsim",
+ [NETDEV_KIND_FOU] = "fou",
+ [NETDEV_KIND_ERSPAN] = "erspan",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(netdev_kind, NetDevKind);
+
+int config_parse_netdev_kind(
+ 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) {
+
+ NetDevKind k, *kind = data;
+
+ assert(rvalue);
+ assert(data);
+
+ k = netdev_kind_from_string(rvalue);
+ if (k < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse netdev kind, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ if (*kind != _NETDEV_KIND_INVALID && *kind != k) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "Specified netdev kind is different from the previous value '%s', ignoring assignment: %s",
+ netdev_kind_to_string(*kind), rvalue);
+ return 0;
+ }
+
+ *kind = k;
+
+ return 0;
+}
+
+static void netdev_callbacks_clear(NetDev *netdev) {
+ netdev_join_callback *callback;
+
+ if (!netdev)
+ return;
+
+ while ((callback = netdev->callbacks)) {
+ LIST_REMOVE(callbacks, netdev->callbacks, callback);
+ link_unref(callback->link);
+ free(callback);
+ }
+}
+
+bool netdev_is_managed(NetDev *netdev) {
+ if (!netdev || !netdev->manager || !netdev->ifname)
+ return false;
+
+ return hashmap_get(netdev->manager->netdevs, netdev->ifname) == netdev;
+}
+
+static void netdev_detach_from_manager(NetDev *netdev) {
+ if (netdev->ifname && netdev->manager)
+ hashmap_remove(netdev->manager->netdevs, netdev->ifname);
+}
+
+static NetDev *netdev_free(NetDev *netdev) {
+ assert(netdev);
+
+ netdev_callbacks_clear(netdev);
+
+ netdev_detach_from_manager(netdev);
+
+ free(netdev->filename);
+
+ free(netdev->description);
+ free(netdev->ifname);
+ free(netdev->mac);
+
+ condition_free_list(netdev->match_host);
+ condition_free_list(netdev->match_virt);
+ condition_free_list(netdev->match_kernel_cmdline);
+ condition_free_list(netdev->match_kernel_version);
+ condition_free_list(netdev->match_arch);
+
+ /* 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,
+ * comprehensive NetDev structure allocation with enough space for whatever the specific kind needs). Now, in
+ * the first case we shouldn't try to destruct the per-kind NetDev fields on destruction, in the second case we
+ * should. We use the state field to discern the two cases: it's _NETDEV_STATE_INVALID on the first "raw"
+ * call. */
+ if (netdev->state != _NETDEV_STATE_INVALID &&
+ NETDEV_VTABLE(netdev) &&
+ NETDEV_VTABLE(netdev)->done)
+ NETDEV_VTABLE(netdev)->done(netdev);
+
+ return mfree(netdev);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(NetDev, netdev, netdev_free);
+
+void netdev_drop(NetDev *netdev) {
+ if (!netdev || netdev->state == NETDEV_STATE_LINGER)
+ return;
+
+ netdev->state = NETDEV_STATE_LINGER;
+
+ log_netdev_debug(netdev, "netdev removed");
+
+ netdev_callbacks_clear(netdev);
+
+ netdev_detach_from_manager(netdev);
+
+ netdev_unref(netdev);
+
+ return;
+}
+
+int netdev_get(Manager *manager, const char *name, NetDev **ret) {
+ NetDev *netdev;
+
+ assert(manager);
+ assert(name);
+ assert(ret);
+
+ netdev = hashmap_get(manager->netdevs, name);
+ if (!netdev) {
+ *ret = NULL;
+ return -ENOENT;
+ }
+
+ *ret = netdev;
+
+ return 0;
+}
+
+static int netdev_enter_failed(NetDev *netdev) {
+ netdev->state = NETDEV_STATE_FAILED;
+
+ netdev_callbacks_clear(netdev);
+
+ return 0;
+}
+
+static int netdev_enslave_ready(NetDev *netdev, Link* link, link_netlink_message_handler_t callback) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ int r;
+
+ assert(netdev);
+ assert(netdev->state == NETDEV_STATE_READY);
+ assert(netdev->manager);
+ assert(netdev->manager->rtnl);
+ assert(IN_SET(netdev->kind, NETDEV_KIND_BRIDGE, NETDEV_KIND_BOND, NETDEV_KIND_VRF));
+ assert(link);
+ assert(callback);
+
+ if (link->flags & IFF_UP && netdev->kind == NETDEV_KIND_BOND) {
+ log_netdev_debug(netdev, "Link '%s' was up when attempting to enslave it. Bringing link down.", link->ifname);
+ r = link_down(link);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not bring link down: %m");
+ }
+
+ r = sd_rtnl_message_new_link(netdev->manager->rtnl, &req, RTM_SETLINK, link->ifindex);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not allocate RTM_SETLINK message: %m");
+
+ r = sd_netlink_message_append_u32(req, IFLA_MASTER, netdev->ifindex);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_MASTER attribute: %m");
+
+ r = netlink_call_async(netdev->manager->rtnl, NULL, req, callback,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ log_netdev_debug(netdev, "Enslaving link '%s'", link->ifname);
+
+ return 0;
+}
+
+static int netdev_enter_ready(NetDev *netdev) {
+ netdev_join_callback *callback, *callback_next;
+ int r;
+
+ assert(netdev);
+ assert(netdev->ifname);
+
+ if (netdev->state != NETDEV_STATE_CREATING)
+ return 0;
+
+ netdev->state = NETDEV_STATE_READY;
+
+ log_netdev_info(netdev, "netdev ready");
+
+ LIST_FOREACH_SAFE(callbacks, callback, callback_next, netdev->callbacks) {
+ /* enslave the links that were attempted to be enslaved before the
+ * link was ready */
+ r = netdev_enslave_ready(netdev, callback->link, callback->callback);
+ if (r < 0)
+ return r;
+
+ LIST_REMOVE(callbacks, netdev->callbacks, callback);
+ link_unref(callback->link);
+ free(callback);
+ }
+
+ if (NETDEV_VTABLE(netdev)->post_create)
+ NETDEV_VTABLE(netdev)->post_create(netdev, NULL, NULL);
+
+ return 0;
+}
+
+/* callback for netdev's created without a backing Link */
+static int netdev_create_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) {
+ int r;
+
+ assert(netdev);
+ assert(netdev->state != _NETDEV_STATE_INVALID);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r == -EEXIST)
+ log_netdev_info(netdev, "netdev exists, using existing without changing its parameters");
+ else if (r < 0) {
+ log_netdev_warning_errno(netdev, r, "netdev could not be created: %m");
+ netdev_drop(netdev);
+
+ return 1;
+ }
+
+ log_netdev_debug(netdev, "Created");
+
+ return 1;
+}
+
+static int netdev_enslave(NetDev *netdev, Link *link, link_netlink_message_handler_t callback) {
+ int r;
+
+ assert(netdev);
+ assert(netdev->manager);
+ assert(netdev->manager->rtnl);
+ assert(IN_SET(netdev->kind, NETDEV_KIND_BRIDGE, NETDEV_KIND_BOND, NETDEV_KIND_VRF));
+
+ if (netdev->state == NETDEV_STATE_READY) {
+ r = netdev_enslave_ready(netdev, link, callback);
+ if (r < 0)
+ return r;
+ } else if (IN_SET(netdev->state, NETDEV_STATE_LINGER, NETDEV_STATE_FAILED)) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+
+ r = rtnl_message_new_synthetic_error(netdev->manager->rtnl, -ENODEV, 0, &m);
+ if (r >= 0)
+ callback(netdev->manager->rtnl, m, link);
+ } else {
+ /* the netdev is not yet read, save this request for when it is */
+ netdev_join_callback *cb;
+
+ cb = new(netdev_join_callback, 1);
+ if (!cb)
+ return log_oom();
+
+ *cb = (netdev_join_callback) {
+ .callback = callback,
+ .link = link_ref(link),
+ };
+
+ LIST_PREPEND(callbacks, netdev->callbacks, cb);
+
+ log_netdev_debug(netdev, "Will enslave '%s', when ready", link->ifname);
+ }
+
+ return 0;
+}
+
+int netdev_set_ifindex(NetDev *netdev, sd_netlink_message *message) {
+ uint16_t type;
+ const char *kind;
+ const char *received_kind;
+ const char *received_name;
+ int r, ifindex;
+
+ assert(netdev);
+ assert(message);
+
+ r = sd_netlink_message_get_type(message, &type);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not get rtnl message type: %m");
+
+ if (type != RTM_NEWLINK) {
+ log_netdev_error(netdev, "Cannot set ifindex from unexpected rtnl message type.");
+ return -EINVAL;
+ }
+
+ r = sd_rtnl_message_link_get_ifindex(message, &ifindex);
+ if (r < 0) {
+ log_netdev_error_errno(netdev, r, "Could not get ifindex: %m");
+ netdev_enter_failed(netdev);
+ return r;
+ } else if (ifindex <= 0) {
+ log_netdev_error(netdev, "Got invalid ifindex: %d", ifindex);
+ netdev_enter_failed(netdev);
+ return -EINVAL;
+ }
+
+ if (netdev->ifindex > 0) {
+ if (netdev->ifindex != ifindex) {
+ log_netdev_error(netdev, "Could not set ifindex to %d, already set to %d",
+ ifindex, netdev->ifindex);
+ netdev_enter_failed(netdev);
+ return -EEXIST;
+ } else
+ /* ifindex already set to the same for this netdev */
+ return 0;
+ }
+
+ r = sd_netlink_message_read_string(message, IFLA_IFNAME, &received_name);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not get IFNAME: %m");
+
+ if (!streq(netdev->ifname, received_name)) {
+ log_netdev_error(netdev, "Received newlink with wrong IFNAME %s", received_name);
+ netdev_enter_failed(netdev);
+ return r;
+ }
+
+ r = sd_netlink_message_enter_container(message, IFLA_LINKINFO);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not get LINKINFO: %m");
+
+ r = sd_netlink_message_read_string(message, IFLA_INFO_KIND, &received_kind);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not get KIND: %m");
+
+ r = sd_netlink_message_exit_container(message);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not exit container: %m");
+
+ if (netdev->kind == NETDEV_KIND_TAP)
+ /* the kernel does not distinguish between tun and tap */
+ kind = "tun";
+ else {
+ kind = netdev_kind_to_string(netdev->kind);
+ if (!kind) {
+ log_netdev_error(netdev, "Could not get kind");
+ netdev_enter_failed(netdev);
+ return -EINVAL;
+ }
+ }
+
+ if (!streq(kind, received_kind)) {
+ log_netdev_error(netdev,
+ "Received newlink with wrong KIND %s, "
+ "expected %s", received_kind, kind);
+ netdev_enter_failed(netdev);
+ return r;
+ }
+
+ netdev->ifindex = ifindex;
+
+ log_netdev_debug(netdev, "netdev has index %d", netdev->ifindex);
+
+ netdev_enter_ready(netdev);
+
+ return 0;
+}
+
+#define HASH_KEY SD_ID128_MAKE(52,e1,45,bd,00,6f,29,96,21,c6,30,6d,83,71,04,48)
+
+int netdev_get_mac(const char *ifname, struct ether_addr **ret) {
+ _cleanup_free_ struct ether_addr *mac = NULL;
+ uint64_t result;
+ size_t l, sz;
+ uint8_t *v;
+ int r;
+
+ assert(ifname);
+ assert(ret);
+
+ mac = new0(struct ether_addr, 1);
+ if (!mac)
+ return -ENOMEM;
+
+ l = strlen(ifname);
+ sz = sizeof(sd_id128_t) + l;
+ v = newa(uint8_t, sz);
+
+ /* fetch some persistent data unique to the machine */
+ r = sd_id128_get_machine((sd_id128_t*) v);
+ if (r < 0)
+ return r;
+
+ /* combine with some data unique (on this machine) to this
+ * netdev */
+ memcpy(v + sizeof(sd_id128_t), ifname, l);
+
+ /* Let's hash the host machine ID plus the container name. We
+ * use a fixed, but originally randomly created hash key here. */
+ result = siphash24(v, sz, HASH_KEY.bytes);
+
+ assert_cc(ETH_ALEN <= sizeof(result));
+ memcpy(mac->ether_addr_octet, &result, ETH_ALEN);
+
+ /* see eth_random_addr in the kernel */
+ mac->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */
+ mac->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */
+
+ *ret = TAKE_PTR(mac);
+
+ return 0;
+}
+
+static int netdev_create(NetDev *netdev, Link *link, link_netlink_message_handler_t callback) {
+ int r;
+
+ assert(netdev);
+ assert(!link || callback);
+
+ /* create netdev */
+ if (NETDEV_VTABLE(netdev)->create) {
+ assert(!link);
+
+ r = NETDEV_VTABLE(netdev)->create(netdev);
+ if (r < 0)
+ return r;
+
+ log_netdev_debug(netdev, "Created");
+ } else {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+
+ r = sd_rtnl_message_new_link(netdev->manager->rtnl, &m, RTM_NEWLINK, 0);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not allocate RTM_NEWLINK message: %m");
+
+ r = sd_netlink_message_append_string(m, IFLA_IFNAME, netdev->ifname);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IFNAME, attribute: %m");
+
+ if (netdev->mac) {
+ r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, netdev->mac);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_ADDRESS attribute: %m");
+ }
+
+ if (netdev->mtu) {
+ r = sd_netlink_message_append_u32(m, IFLA_MTU, netdev->mtu);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_MTU attribute: %m");
+ }
+
+ if (link) {
+ r = sd_netlink_message_append_u32(m, IFLA_LINK, link->ifindex);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINK attribute: %m");
+ }
+
+ r = sd_netlink_message_open_container(m, IFLA_LINKINFO);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINKINFO attribute: %m");
+
+ r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, netdev_kind_to_string(netdev->kind));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m");
+
+ if (NETDEV_VTABLE(netdev)->fill_message_create) {
+ r = NETDEV_VTABLE(netdev)->fill_message_create(netdev, link, m);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m");
+
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_LINKINFO attribute: %m");
+
+ if (link) {
+ r = netlink_call_async(netdev->manager->rtnl, NULL, m, callback,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+ } else {
+ r = netlink_call_async(netdev->manager->rtnl, NULL, m, netdev_create_handler,
+ netdev_destroy_callback, netdev);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not send rtnetlink message: %m");
+
+ netdev_ref(netdev);
+ }
+
+ netdev->state = NETDEV_STATE_CREATING;
+
+ log_netdev_debug(netdev, "Creating");
+ }
+
+ return 0;
+}
+
+/* the callback must be called, possibly after a timeout, as otherwise the Link will hang */
+int netdev_join(NetDev *netdev, Link *link, link_netlink_message_handler_t callback) {
+ int r;
+
+ assert(netdev);
+ assert(netdev->manager);
+ assert(netdev->manager->rtnl);
+ assert(NETDEV_VTABLE(netdev));
+
+ switch (NETDEV_VTABLE(netdev)->create_type) {
+ case NETDEV_CREATE_MASTER:
+ r = netdev_enslave(netdev, link, callback);
+ if (r < 0)
+ return r;
+
+ break;
+ case NETDEV_CREATE_STACKED:
+ r = netdev_create(netdev, link, callback);
+ if (r < 0)
+ return r;
+
+ break;
+ default:
+ assert_not_reached("Can not join independent netdev");
+ }
+
+ return 0;
+}
+
+int netdev_load_one(Manager *manager, const char *filename) {
+ _cleanup_(netdev_unrefp) NetDev *netdev_raw = NULL, *netdev = NULL;
+ _cleanup_fclose_ FILE *file = NULL;
+ const char *dropin_dirname;
+ bool independent = false;
+ int r;
+
+ assert(manager);
+ assert(filename);
+
+ file = fopen(filename, "re");
+ if (!file) {
+ if (errno == ENOENT)
+ return 0;
+
+ return -errno;
+ }
+
+ if (null_or_empty_fd(fileno(file))) {
+ log_debug("Skipping empty file: %s", filename);
+ return 0;
+ }
+
+ netdev_raw = new(NetDev, 1);
+ if (!netdev_raw)
+ return log_oom();
+
+ *netdev_raw = (NetDev) {
+ .n_ref = 1,
+ .kind = _NETDEV_KIND_INVALID,
+ .state = _NETDEV_STATE_INVALID, /* an invalid state means done() of the implementation won't be called on destruction */
+ };
+
+ dropin_dirname = strjoina(basename(filename), ".d");
+ r = config_parse_many(filename, network_dirs, dropin_dirname,
+ "Match\0NetDev\0",
+ config_item_perf_lookup, network_netdev_gperf_lookup,
+ CONFIG_PARSE_WARN|CONFIG_PARSE_RELAXED, netdev_raw);
+ if (r < 0)
+ return r;
+
+ /* skip out early if configuration does not match the environment */
+ if (net_match_config(NULL, NULL, NULL, NULL, NULL,
+ netdev_raw->match_host, netdev_raw->match_virt,
+ netdev_raw->match_kernel_cmdline, netdev_raw->match_kernel_version,
+ netdev_raw->match_arch,
+ NULL, NULL, NULL, NULL, NULL, NULL) <= 0)
+ return 0;
+
+ if (netdev_raw->kind == _NETDEV_KIND_INVALID) {
+ log_warning("NetDev has no Kind configured in %s. Ignoring", filename);
+ return 0;
+ }
+
+ if (!netdev_raw->ifname) {
+ log_warning("NetDev without Name configured in %s. Ignoring", filename);
+ return 0;
+ }
+
+ r = fseek(file, 0, SEEK_SET);
+ if (r < 0)
+ return -errno;
+
+ netdev = malloc0(NETDEV_VTABLE(netdev_raw)->object_size);
+ if (!netdev)
+ return log_oom();
+
+ netdev->n_ref = 1;
+ netdev->manager = manager;
+ netdev->kind = netdev_raw->kind;
+ netdev->state = NETDEV_STATE_LOADING; /* we initialize the state here for the first time, so that done() will be called on destruction */
+
+ if (NETDEV_VTABLE(netdev)->init)
+ NETDEV_VTABLE(netdev)->init(netdev);
+
+ r = config_parse_many(filename, network_dirs, dropin_dirname,
+ NETDEV_VTABLE(netdev)->sections,
+ config_item_perf_lookup, network_netdev_gperf_lookup,
+ CONFIG_PARSE_WARN, netdev);
+ if (r < 0)
+ return r;
+
+ /* verify configuration */
+ if (NETDEV_VTABLE(netdev)->config_verify) {
+ r = NETDEV_VTABLE(netdev)->config_verify(netdev, filename);
+ if (r < 0)
+ return 0;
+ }
+
+ netdev->filename = strdup(filename);
+ if (!netdev->filename)
+ return log_oom();
+
+ if (!netdev->mac && netdev->kind != NETDEV_KIND_VLAN) {
+ r = netdev_get_mac(netdev->ifname, &netdev->mac);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate predictable MAC address for %s: %m", netdev->ifname);
+ }
+
+ r = hashmap_ensure_allocated(&netdev->manager->netdevs, &string_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(netdev->manager->netdevs, netdev->ifname, netdev);
+ if (r < 0)
+ return r;
+
+ LIST_HEAD_INIT(netdev->callbacks);
+
+ log_netdev_debug(netdev, "loaded %s", netdev_kind_to_string(netdev->kind));
+
+ switch (NETDEV_VTABLE(netdev)->create_type) {
+ case NETDEV_CREATE_MASTER:
+ case NETDEV_CREATE_INDEPENDENT:
+ r = netdev_create(netdev, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ break;
+ default:
+ break;
+ }
+
+ switch (netdev->kind) {
+ case NETDEV_KIND_IPIP:
+ independent = IPIP(netdev)->independent;
+ break;
+ case NETDEV_KIND_GRE:
+ independent = GRE(netdev)->independent;
+ break;
+ case NETDEV_KIND_GRETAP:
+ independent = GRETAP(netdev)->independent;
+ break;
+ case NETDEV_KIND_IP6GRE:
+ independent = IP6GRE(netdev)->independent;
+ break;
+ case NETDEV_KIND_IP6GRETAP:
+ independent = IP6GRETAP(netdev)->independent;
+ break;
+ case NETDEV_KIND_SIT:
+ independent = SIT(netdev)->independent;
+ break;
+ case NETDEV_KIND_VTI:
+ independent = VTI(netdev)->independent;
+ break;
+ case NETDEV_KIND_VTI6:
+ independent = VTI6(netdev)->independent;
+ break;
+ case NETDEV_KIND_IP6TNL:
+ independent = IP6TNL(netdev)->independent;
+ break;
+ default:
+ break;
+ }
+
+ if (independent) {
+ r = netdev_create(netdev, NULL, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ netdev = NULL;
+
+ return 0;
+}
+
+int netdev_load(Manager *manager) {
+ _cleanup_strv_free_ char **files = NULL;
+ char **f;
+ int r;
+
+ assert(manager);
+
+ hashmap_clear_with_destructor(manager->netdevs, netdev_unref);
+
+ r = conf_files_list_strv(&files, ".netdev", NULL, 0, network_dirs);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enumerate netdev files: %m");
+
+ STRV_FOREACH_BACKWARDS(f, files) {
+ r = netdev_load_one(manager, *f);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
diff --git a/src/network/netdev/netdev.h b/src/network/netdev/netdev.h
new file mode 100644
index 0000000..d6524da
--- /dev/null
+++ b/src/network/netdev/netdev.h
@@ -0,0 +1,195 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "sd-netlink.h"
+
+#include "conf-parser.h"
+#include "list.h"
+#include "../networkd-link.h"
+#include "time-util.h"
+
+typedef struct netdev_join_callback netdev_join_callback;
+
+struct netdev_join_callback {
+ link_netlink_message_handler_t callback;
+ Link *link;
+
+ LIST_FIELDS(netdev_join_callback, callbacks);
+};
+
+typedef enum NetDevKind {
+ NETDEV_KIND_BRIDGE,
+ NETDEV_KIND_BOND,
+ NETDEV_KIND_VLAN,
+ NETDEV_KIND_MACVLAN,
+ NETDEV_KIND_MACVTAP,
+ NETDEV_KIND_IPVLAN,
+ NETDEV_KIND_VXLAN,
+ NETDEV_KIND_IPIP,
+ NETDEV_KIND_GRE,
+ NETDEV_KIND_GRETAP,
+ NETDEV_KIND_IP6GRE,
+ NETDEV_KIND_IP6GRETAP,
+ NETDEV_KIND_SIT,
+ NETDEV_KIND_VETH,
+ NETDEV_KIND_VTI,
+ NETDEV_KIND_VTI6,
+ NETDEV_KIND_IP6TNL,
+ NETDEV_KIND_DUMMY,
+ NETDEV_KIND_TUN,
+ NETDEV_KIND_TAP,
+ NETDEV_KIND_VRF,
+ NETDEV_KIND_VCAN,
+ NETDEV_KIND_GENEVE,
+ NETDEV_KIND_VXCAN,
+ NETDEV_KIND_WIREGUARD,
+ NETDEV_KIND_NETDEVSIM,
+ NETDEV_KIND_FOU,
+ NETDEV_KIND_ERSPAN,
+ _NETDEV_KIND_MAX,
+ _NETDEV_KIND_INVALID = -1
+} NetDevKind;
+
+typedef enum NetDevState {
+ NETDEV_STATE_LOADING,
+ NETDEV_STATE_FAILED,
+ NETDEV_STATE_CREATING,
+ NETDEV_STATE_READY,
+ NETDEV_STATE_LINGER,
+ _NETDEV_STATE_MAX,
+ _NETDEV_STATE_INVALID = -1,
+} NetDevState;
+
+typedef enum NetDevCreateType {
+ NETDEV_CREATE_INDEPENDENT,
+ NETDEV_CREATE_MASTER,
+ NETDEV_CREATE_STACKED,
+ _NETDEV_CREATE_MAX,
+ _NETDEV_CREATE_INVALID = -1,
+} NetDevCreateType;
+
+typedef struct Manager Manager;
+typedef struct Condition Condition;
+
+typedef struct NetDev {
+ Manager *manager;
+
+ unsigned n_ref;
+
+ char *filename;
+
+ Condition *match_host;
+ Condition *match_virt;
+ Condition *match_kernel_cmdline;
+ Condition *match_kernel_version;
+ Condition *match_arch;
+
+ NetDevState state;
+ NetDevKind kind;
+ char *description;
+ char *ifname;
+ struct ether_addr *mac;
+ uint32_t mtu;
+ int ifindex;
+
+ LIST_HEAD(netdev_join_callback, callbacks);
+} NetDev;
+
+typedef struct NetDevVTable {
+ /* How much memory does an object of this unit type need */
+ size_t object_size;
+
+ /* Config file sections this netdev kind understands, separated
+ * by NUL chars */
+ const char *sections;
+
+ /* This should reset all type-specific variables. This should
+ * not allocate memory, and is called with zero-initialized
+ * data. It should hence only initialize variables that need
+ * to be set != 0. */
+ void (*init)(NetDev *n);
+
+ /* This should free all kind-specific variables. It should be
+ * idempotent. */
+ void (*done)(NetDev *n);
+
+ /* fill in message to create netdev */
+ int (*fill_message_create)(NetDev *netdev, Link *link, sd_netlink_message *message);
+
+ /* specifies if netdev is independent, or a master device or a stacked device */
+ NetDevCreateType create_type;
+
+ /* create netdev, if not done via rtnl */
+ int (*create)(NetDev *netdev);
+
+ /* perform additional configuration after netdev has been createad */
+ int (*post_create)(NetDev *netdev, Link *link, sd_netlink_message *message);
+
+ /* verify that compulsory configuration options were specified */
+ int (*config_verify)(NetDev *netdev, const char *filename);
+} NetDevVTable;
+
+extern const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX];
+
+#define NETDEV_VTABLE(n) ((n)->kind != _NETDEV_KIND_INVALID ? netdev_vtable[(n)->kind] : NULL)
+
+/* For casting a netdev into the various netdev kinds */
+#define DEFINE_NETDEV_CAST(UPPERCASE, MixedCase) \
+ static inline MixedCase* UPPERCASE(NetDev *n) { \
+ if (_unlikely_(!n || \
+ n->kind != NETDEV_KIND_##UPPERCASE) || \
+ n->state == _NETDEV_STATE_INVALID) \
+ return NULL; \
+ \
+ return (MixedCase*) n; \
+ }
+
+/* For casting the various netdev kinds into a netdev */
+#define NETDEV(n) (&(n)->meta)
+
+int netdev_load(Manager *manager);
+int netdev_load_one(Manager *manager, const char *filename);
+void netdev_drop(NetDev *netdev);
+
+NetDev *netdev_unref(NetDev *netdev);
+NetDev *netdev_ref(NetDev *netdev);
+DEFINE_TRIVIAL_DESTRUCTOR(netdev_destroy_callback, NetDev, netdev_unref);
+DEFINE_TRIVIAL_CLEANUP_FUNC(NetDev*, netdev_unref);
+
+bool netdev_is_managed(NetDev *netdev);
+int netdev_get(Manager *manager, const char *name, NetDev **ret);
+int netdev_set_ifindex(NetDev *netdev, sd_netlink_message *newlink);
+int netdev_get_mac(const char *ifname, struct ether_addr **ret);
+int netdev_join(NetDev *netdev, Link *link, link_netlink_message_handler_t cb);
+
+const char *netdev_kind_to_string(NetDevKind d) _const_;
+NetDevKind netdev_kind_from_string(const char *d) _pure_;
+
+CONFIG_PARSER_PROTOTYPE(config_parse_netdev_kind);
+
+/* gperf */
+const struct ConfigPerfItem* network_netdev_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
+
+/* Macros which append INTERFACE= to the message */
+
+#define log_netdev_full(netdev, level, error, ...) \
+ ({ \
+ const NetDev *_n = (netdev); \
+ _n ? log_object_internal(level, error, __FILE__, __LINE__, __func__, "INTERFACE=", _n->ifname, NULL, NULL, ##__VA_ARGS__) : \
+ log_internal(level, error, __FILE__, __LINE__, __func__, ##__VA_ARGS__); \
+ })
+
+#define log_netdev_debug(netdev, ...) log_netdev_full(netdev, LOG_DEBUG, 0, ##__VA_ARGS__)
+#define log_netdev_info(netdev, ...) log_netdev_full(netdev, LOG_INFO, 0, ##__VA_ARGS__)
+#define log_netdev_notice(netdev, ...) log_netdev_full(netdev, LOG_NOTICE, 0, ##__VA_ARGS__)
+#define log_netdev_warning(netdev, ...) log_netdev_full(netdev, LOG_WARNING, 0, ## __VA_ARGS__)
+#define log_netdev_error(netdev, ...) log_netdev_full(netdev, LOG_ERR, 0, ##__VA_ARGS__)
+
+#define log_netdev_debug_errno(netdev, error, ...) log_netdev_full(netdev, LOG_DEBUG, error, ##__VA_ARGS__)
+#define log_netdev_info_errno(netdev, error, ...) log_netdev_full(netdev, LOG_INFO, error, ##__VA_ARGS__)
+#define log_netdev_notice_errno(netdev, error, ...) log_netdev_full(netdev, LOG_NOTICE, error, ##__VA_ARGS__)
+#define log_netdev_warning_errno(netdev, error, ...) log_netdev_full(netdev, LOG_WARNING, error, ##__VA_ARGS__)
+#define log_netdev_error_errno(netdev, error, ...) log_netdev_full(netdev, LOG_ERR, error, ##__VA_ARGS__)
+
+#define LOG_NETDEV_MESSAGE(netdev, fmt, ...) "MESSAGE=%s: " fmt, (netdev)->ifname, ##__VA_ARGS__
+#define LOG_NETDEV_INTERFACE(netdev) "INTERFACE=%s", (netdev)->ifname
diff --git a/src/network/netdev/netdevsim.c b/src/network/netdev/netdevsim.c
new file mode 100644
index 0000000..8caba67
--- /dev/null
+++ b/src/network/netdev/netdevsim.c
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "netdev/netdevsim.h"
+#include "missing.h"
+
+const NetDevVTable netdevsim_vtable = {
+ .object_size = sizeof(NetDevSim),
+ .sections = "Match\0NetDev\0",
+ .create_type = NETDEV_CREATE_INDEPENDENT,
+};
diff --git a/src/network/netdev/netdevsim.h b/src/network/netdev/netdevsim.h
new file mode 100644
index 0000000..d3ed0c0
--- /dev/null
+++ b/src/network/netdev/netdevsim.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+typedef struct NetDevSim NetDevSim;
+
+#include "netdev/netdev.h"
+
+struct NetDevSim {
+ NetDev meta;
+};
+
+DEFINE_NETDEV_CAST(NETDEVSIM, NetDevSim);
+extern const NetDevVTable netdevsim_vtable;
diff --git a/src/network/netdev/tunnel.c b/src/network/netdev/tunnel.c
new file mode 100644
index 0000000..684eddd
--- /dev/null
+++ b/src/network/netdev/tunnel.c
@@ -0,0 +1,936 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include <linux/ip6_tunnel.h>
+
+#if HAVE_LINUX_FOU_H
+#include <linux/fou.h>
+#endif
+
+#include "sd-netlink.h"
+
+#include "conf-parser.h"
+#include "missing.h"
+#include "networkd-link.h"
+#include "netdev/tunnel.h"
+#include "parse-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "util.h"
+
+#define DEFAULT_TNL_HOP_LIMIT 64
+#define IP6_FLOWINFO_FLOWLABEL htobe32(0x000FFFFF)
+#define IP6_TNL_F_ALLOW_LOCAL_REMOTE 0x40
+
+static const char* const ip6tnl_mode_table[_NETDEV_IP6_TNL_MODE_MAX] = {
+ [NETDEV_IP6_TNL_MODE_IP6IP6] = "ip6ip6",
+ [NETDEV_IP6_TNL_MODE_IPIP6] = "ipip6",
+ [NETDEV_IP6_TNL_MODE_ANYIP6] = "any",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(ip6tnl_mode, Ip6TnlMode);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_ip6tnl_mode, ip6tnl_mode, Ip6TnlMode, "Failed to parse ip6 tunnel Mode");
+
+static int netdev_ipip_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ Tunnel *t = IPIP(netdev);
+ int r;
+
+ assert(netdev);
+ assert(m);
+ assert(t);
+ assert(IN_SET(t->family, AF_INET, AF_UNSPEC));
+
+ if (link) {
+ r = sd_netlink_message_append_u32(m, IFLA_IPTUN_LINK, link->ifindex);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LINK attribute: %m");
+ }
+
+ r = sd_netlink_message_append_in_addr(m, IFLA_IPTUN_LOCAL, &t->local.in);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LOCAL attribute: %m");
+
+ r = sd_netlink_message_append_in_addr(m, IFLA_IPTUN_REMOTE, &t->remote.in);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_REMOTE attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_IPTUN_TTL, t->ttl);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_TTL attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_IPTUN_PMTUDISC, t->pmtudisc);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_PMTUDISC attribute: %m");
+
+ if (t->fou_tunnel) {
+
+ r = sd_netlink_message_append_u16(m, IFLA_IPTUN_ENCAP_TYPE, t->fou_encap_type);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_ENCAP_TYPE attribute: %m");
+
+ r = sd_netlink_message_append_u16(m, IFLA_IPTUN_ENCAP_SPORT, htobe16(t->encap_src_port));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_ENCAP_SPORT attribute: %m");
+
+ r = sd_netlink_message_append_u16(m, IFLA_IPTUN_ENCAP_DPORT, htobe16(t->fou_destination_port));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_ENCAP_DPORT attribute: %m");
+ }
+
+ return r;
+}
+
+static int netdev_sit_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ Tunnel *t = SIT(netdev);
+ int r;
+
+ assert(netdev);
+ assert(m);
+ assert(t);
+ assert(IN_SET(t->family, AF_INET, AF_UNSPEC));
+
+ if (link) {
+ r = sd_netlink_message_append_u32(m, IFLA_IPTUN_LINK, link->ifindex);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LINK attribute: %m");
+ }
+
+ r = sd_netlink_message_append_in_addr(m, IFLA_IPTUN_LOCAL, &t->local.in);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LOCAL attribute: %m");
+
+ r = sd_netlink_message_append_in_addr(m, IFLA_IPTUN_REMOTE, &t->remote.in);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_REMOTE attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_IPTUN_TTL, t->ttl);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_TTL attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_IPTUN_PMTUDISC, t->pmtudisc);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_PMTUDISC attribute: %m");
+
+ if (t->sixrd_prefixlen > 0) {
+ r = sd_netlink_message_append_in6_addr(m, IFLA_IPTUN_6RD_PREFIX, &t->sixrd_prefix);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_6RD_PREFIX attribute: %m");
+
+ /* u16 is deliberate here, even though we're passing a netmask that can never be >128. The kernel is
+ * expecting to receive the prefixlen as a u16.
+ */
+ r = sd_netlink_message_append_u16(m, IFLA_IPTUN_6RD_PREFIXLEN, t->sixrd_prefixlen);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_6RD_PREFIXLEN attribute: %m");
+ }
+
+ if (t->isatap >= 0) {
+ uint16_t flags = 0;
+
+ SET_FLAG(flags, SIT_ISATAP, t->isatap);
+
+ r = sd_netlink_message_append_u16(m, IFLA_IPTUN_FLAGS, flags);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_FLAGS attribute: %m");
+ }
+
+ return r;
+}
+
+static int netdev_gre_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ Tunnel *t;
+ int r;
+
+ assert(netdev);
+
+ if (netdev->kind == NETDEV_KIND_GRE)
+ t = GRE(netdev);
+ else
+ t = GRETAP(netdev);
+
+ assert(t);
+ assert(IN_SET(t->family, AF_INET, AF_UNSPEC));
+ assert(m);
+
+ if (link) {
+ r = sd_netlink_message_append_u32(m, IFLA_GRE_LINK, link->ifindex);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_LINK attribute: %m");
+ }
+
+ r = sd_netlink_message_append_in_addr(m, IFLA_GRE_LOCAL, &t->local.in);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_LOCAL attribute: %m");
+
+ r = sd_netlink_message_append_in_addr(m, IFLA_GRE_REMOTE, &t->remote.in);
+ if (r < 0)
+ log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_REMOTE attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_GRE_TTL, t->ttl);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_TTL attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_GRE_TOS, t->tos);
+ if (r < 0)
+ log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_TOS attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_GRE_PMTUDISC, t->pmtudisc);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_PMTUDISC attribute: %m");
+
+ return r;
+}
+
+static int netdev_erspan_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ uint32_t ikey = 0;
+ uint32_t okey = 0;
+ uint16_t iflags = 0;
+ uint16_t oflags = 0;
+ Tunnel *t;
+ int r;
+
+ assert(netdev);
+
+ t = ERSPAN(netdev);
+
+ assert(t);
+ assert(IN_SET(t->family, AF_INET, AF_UNSPEC));
+ assert(m);
+
+ r = sd_netlink_message_append_u32(m, IFLA_GRE_ERSPAN_INDEX, t->erspan_index);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_ERSPAN_INDEX attribute: %m");
+
+ if (t->key != 0) {
+ ikey = okey = htobe32(t->key);
+ iflags |= GRE_KEY;
+ oflags |= GRE_KEY;
+ }
+
+ if (t->ikey != 0) {
+ ikey = htobe32(t->ikey);
+ iflags |= GRE_KEY;
+ }
+
+ if (t->okey != 0) {
+ okey = htobe32(t->okey);
+ oflags |= GRE_KEY;
+ }
+
+ if (t->erspan_sequence > 0) {
+ iflags |= GRE_SEQ;
+ oflags |= GRE_SEQ;
+ } else if (t->erspan_sequence == 0) {
+ iflags &= ~GRE_SEQ;
+ oflags &= ~GRE_SEQ;
+ }
+
+ r = sd_netlink_message_append_u32(m, IFLA_GRE_IKEY, ikey);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_IKEY attribute: %m");
+
+ r = sd_netlink_message_append_u32(m, IFLA_GRE_OKEY, okey);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_OKEY attribute: %m");
+
+ r = sd_netlink_message_append_u16(m, IFLA_GRE_IFLAGS, iflags);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_IFLAGS attribute: %m");
+
+ r = sd_netlink_message_append_u16(m, IFLA_GRE_OFLAGS, oflags);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_OFLAGS, attribute: %m");
+
+ r = sd_netlink_message_append_in_addr(m, IFLA_GRE_LOCAL, &t->local.in);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_LOCAL attribute: %m");
+
+ r = sd_netlink_message_append_in_addr(m, IFLA_GRE_REMOTE, &t->remote.in);
+ if (r < 0)
+ log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_REMOTE attribute: %m");
+
+ return r;
+}
+
+static int netdev_ip6gre_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ Tunnel *t;
+ int r;
+
+ assert(netdev);
+
+ if (netdev->kind == NETDEV_KIND_IP6GRE)
+ t = IP6GRE(netdev);
+ else
+ t = IP6GRETAP(netdev);
+
+ assert(t);
+ assert(t->family == AF_INET6);
+ assert(m);
+
+ if (link) {
+ r = sd_netlink_message_append_u32(m, IFLA_GRE_LINK, link->ifindex);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_LINK attribute: %m");
+ }
+
+ r = sd_netlink_message_append_in6_addr(m, IFLA_GRE_LOCAL, &t->local.in6);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_LOCAL attribute: %m");
+
+ r = sd_netlink_message_append_in6_addr(m, IFLA_GRE_REMOTE, &t->remote.in6);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_REMOTE attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_GRE_TTL, t->ttl);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_TTL attribute: %m");
+
+ if (t->ipv6_flowlabel != _NETDEV_IPV6_FLOWLABEL_INVALID) {
+ r = sd_netlink_message_append_u32(m, IFLA_GRE_FLOWINFO, t->ipv6_flowlabel);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_FLOWINFO attribute: %m");
+ }
+
+ r = sd_netlink_message_append_u32(m, IFLA_GRE_FLAGS, t->flags);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_GRE_FLAGS attribute: %m");
+
+ return r;
+}
+
+static int netdev_vti_fill_message_key(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ uint32_t ikey, okey;
+ Tunnel *t;
+ int r;
+
+ assert(m);
+
+ if (netdev->kind == NETDEV_KIND_VTI)
+ t = VTI(netdev);
+ else
+ t = VTI6(netdev);
+
+ assert(t);
+
+ if (t->key != 0)
+ ikey = okey = htobe32(t->key);
+ else {
+ ikey = htobe32(t->ikey);
+ okey = htobe32(t->okey);
+ }
+
+ r = sd_netlink_message_append_u32(m, IFLA_VTI_IKEY, ikey);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VTI_IKEY attribute: %m");
+
+ r = sd_netlink_message_append_u32(m, IFLA_VTI_OKEY, okey);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VTI_OKEY attribute: %m");
+
+ return 0;
+}
+
+static int netdev_vti_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ Tunnel *t = VTI(netdev);
+ int r;
+
+ assert(netdev);
+ assert(m);
+ assert(t);
+ assert(t->family == AF_INET);
+
+ if (link) {
+ r = sd_netlink_message_append_u32(m, IFLA_VTI_LINK, link->ifindex);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LINK attribute: %m");
+ }
+
+ r = netdev_vti_fill_message_key(netdev, link, m);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_append_in_addr(m, IFLA_VTI_LOCAL, &t->local.in);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LOCAL attribute: %m");
+
+ r = sd_netlink_message_append_in_addr(m, IFLA_VTI_REMOTE, &t->remote.in);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_REMOTE attribute: %m");
+
+ return r;
+}
+
+static int netdev_vti6_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ Tunnel *t = VTI6(netdev);
+ int r;
+
+ assert(netdev);
+ assert(m);
+ assert(t);
+ assert(t->family == AF_INET6);
+
+ if (link) {
+ r = sd_netlink_message_append_u32(m, IFLA_VTI_LINK, link->ifindex);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LINK attribute: %m");
+ }
+
+ r = netdev_vti_fill_message_key(netdev, link, m);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_append_in6_addr(m, IFLA_VTI_LOCAL, &t->local.in6);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LOCAL attribute: %m");
+
+ r = sd_netlink_message_append_in6_addr(m, IFLA_VTI_REMOTE, &t->remote.in6);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_REMOTE attribute: %m");
+
+ return r;
+}
+
+static int netdev_ip6tnl_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ Tunnel *t = IP6TNL(netdev);
+ uint8_t proto;
+ int r;
+
+ assert(netdev);
+ assert(m);
+ assert(t);
+ assert(t->family == AF_INET6);
+
+ if (link) {
+ r = sd_netlink_message_append_u32(m, IFLA_IPTUN_LINK, link->ifindex);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LINK attribute: %m");
+ }
+
+ r = sd_netlink_message_append_in6_addr(m, IFLA_IPTUN_LOCAL, &t->local.in6);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_LOCAL attribute: %m");
+
+ r = sd_netlink_message_append_in6_addr(m, IFLA_IPTUN_REMOTE, &t->remote.in6);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_REMOTE attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_IPTUN_TTL, t->ttl);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_TTL attribute: %m");
+
+ if (t->ipv6_flowlabel != _NETDEV_IPV6_FLOWLABEL_INVALID) {
+ r = sd_netlink_message_append_u32(m, IFLA_IPTUN_FLOWINFO, t->ipv6_flowlabel);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_FLOWINFO attribute: %m");
+ }
+
+ if (t->copy_dscp)
+ t->flags |= IP6_TNL_F_RCV_DSCP_COPY;
+
+ if (t->allow_localremote != -1)
+ SET_FLAG(t->flags, IP6_TNL_F_ALLOW_LOCAL_REMOTE, t->allow_localremote);
+
+ if (t->encap_limit != IPV6_DEFAULT_TNL_ENCAP_LIMIT) {
+ r = sd_netlink_message_append_u8(m, IFLA_IPTUN_ENCAP_LIMIT, t->encap_limit);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_ENCAP_LIMIT attribute: %m");
+ }
+
+ r = sd_netlink_message_append_u32(m, IFLA_IPTUN_FLAGS, t->flags);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_FLAGS attribute: %m");
+
+ switch (t->ip6tnl_mode) {
+ case NETDEV_IP6_TNL_MODE_IP6IP6:
+ proto = IPPROTO_IPV6;
+ break;
+ case NETDEV_IP6_TNL_MODE_IPIP6:
+ proto = IPPROTO_IPIP;
+ break;
+ case NETDEV_IP6_TNL_MODE_ANYIP6:
+ default:
+ proto = 0;
+ break;
+ }
+
+ r = sd_netlink_message_append_u8(m, IFLA_IPTUN_PROTO, proto);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_IPTUN_MODE attribute: %m");
+
+ return r;
+}
+
+static int netdev_tunnel_verify(NetDev *netdev, const char *filename) {
+ Tunnel *t = NULL;
+
+ assert(netdev);
+ assert(filename);
+
+ switch (netdev->kind) {
+ case NETDEV_KIND_IPIP:
+ t = IPIP(netdev);
+ break;
+ case NETDEV_KIND_SIT:
+ t = SIT(netdev);
+ break;
+ case NETDEV_KIND_GRE:
+ t = GRE(netdev);
+ break;
+ case NETDEV_KIND_GRETAP:
+ t = GRETAP(netdev);
+ break;
+ case NETDEV_KIND_IP6GRE:
+ t = IP6GRE(netdev);
+ break;
+ case NETDEV_KIND_IP6GRETAP:
+ t = IP6GRETAP(netdev);
+ break;
+ case NETDEV_KIND_VTI:
+ t = VTI(netdev);
+ break;
+ case NETDEV_KIND_VTI6:
+ t = VTI6(netdev);
+ break;
+ case NETDEV_KIND_IP6TNL:
+ t = IP6TNL(netdev);
+ break;
+ case NETDEV_KIND_ERSPAN:
+ t = ERSPAN(netdev);
+ break;
+ default:
+ assert_not_reached("Invalid tunnel kind");
+ }
+
+ assert(t);
+
+ if (!IN_SET(t->family, AF_INET, AF_INET6, AF_UNSPEC)) {
+ log_netdev_error(netdev,
+ "Tunnel with invalid address family configured in %s. Ignoring", filename);
+ return -EINVAL;
+ }
+
+ if (IN_SET(netdev->kind, NETDEV_KIND_VTI, NETDEV_KIND_IPIP, NETDEV_KIND_SIT, NETDEV_KIND_GRE, NETDEV_KIND_GRETAP, NETDEV_KIND_ERSPAN) &&
+ (t->family != AF_INET || in_addr_is_null(t->family, &t->local))) {
+ log_netdev_error(netdev,
+ "vti/ipip/sit/gre/gretap/erspan tunnel without a local IPv4 address configured in %s. Ignoring", filename);
+ return -EINVAL;
+ }
+
+ if (IN_SET(netdev->kind, NETDEV_KIND_VTI6, NETDEV_KIND_IP6TNL, NETDEV_KIND_IP6GRE, NETDEV_KIND_IP6GRETAP) &&
+ (t->family != AF_INET6 || in_addr_is_null(t->family, &t->local))) {
+ log_netdev_error(netdev,
+ "vti6/ip6tnl/ip6gre/ip6gretap tunnel without a local IPv6 address configured in %s. Ignoring", filename);
+ return -EINVAL;
+ }
+
+ if (netdev->kind == NETDEV_KIND_IP6TNL &&
+ t->ip6tnl_mode == _NETDEV_IP6_TNL_MODE_INVALID) {
+ log_netdev_error(netdev,
+ "ip6tnl without mode configured in %s. Ignoring", filename);
+ return -EINVAL;
+ }
+
+ if (t->fou_tunnel && t->fou_destination_port <= 0) {
+ log_netdev_error(netdev, "FooOverUDP missing port configured in %s. Ignoring", filename);
+ return -EINVAL;
+ }
+
+ if (netdev->kind == NETDEV_KIND_ERSPAN && (t->erspan_index >= (1 << 20) || t->erspan_index == 0)) {
+ log_netdev_error(netdev, "Invalid erspan index %d. Ignoring", t->erspan_index);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int config_parse_tunnel_address(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Tunnel *t = userdata;
+ union in_addr_union *addr = data, buffer;
+ int r, f;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ /* This is used to parse addresses on both local and remote ends of the tunnel.
+ * Address families must match.
+ *
+ * "any" is a special value which means that the address is unspecified.
+ */
+
+ if (streq(rvalue, "any")) {
+ *addr = IN_ADDR_NULL;
+
+ /* As a special case, if both the local and remote addresses are
+ * unspecified, also clear the address family.
+ */
+ if (t->family != AF_UNSPEC &&
+ in_addr_is_null(t->family, &t->local) &&
+ in_addr_is_null(t->family, &t->remote))
+ t->family = AF_UNSPEC;
+ return 0;
+ }
+
+ r = in_addr_from_string_auto(rvalue, &f, &buffer);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Tunnel address \"%s\" invalid, ignoring assignment: %m", rvalue);
+ return 0;
+ }
+
+ if (t->family != AF_UNSPEC && t->family != f) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "Tunnel addresses incompatible, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ t->family = f;
+ *addr = buffer;
+ return 0;
+}
+
+int config_parse_tunnel_key(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) {
+ union in_addr_union buffer;
+ Tunnel *t = userdata;
+ uint32_t k;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = in_addr_from_string(AF_INET, rvalue, &buffer);
+ if (r < 0) {
+ r = safe_atou32(rvalue, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse tunnel key ignoring assignment: %s", rvalue);
+ return 0;
+ }
+ } else
+ k = be32toh(buffer.in.s_addr);
+
+ if (streq(lvalue, "Key"))
+ t->key = k;
+ else if (streq(lvalue, "InputKey"))
+ t->ikey = k;
+ else
+ t->okey = k;
+
+ return 0;
+}
+
+int config_parse_ipv6_flowlabel(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) {
+ IPv6FlowLabel *ipv6_flowlabel = data;
+ Tunnel *t = userdata;
+ int k = 0;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(ipv6_flowlabel);
+
+ if (streq(rvalue, "inherit")) {
+ *ipv6_flowlabel = IP6_FLOWINFO_FLOWLABEL;
+ t->flags |= IP6_TNL_F_USE_ORIG_FLOWLABEL;
+ } else {
+ r = config_parse_int(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &k, userdata);
+ if (r < 0)
+ return r;
+
+ if (k > 0xFFFFF)
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse IPv6 flowlabel option, ignoring: %s", rvalue);
+ else {
+ *ipv6_flowlabel = htobe32(k) & IP6_FLOWINFO_FLOWLABEL;
+ t->flags &= ~IP6_TNL_F_USE_ORIG_FLOWLABEL;
+ }
+ }
+
+ return 0;
+}
+
+int config_parse_encap_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) {
+ Tunnel *t = userdata;
+ int k = 0;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (streq(rvalue, "none"))
+ t->flags |= IP6_TNL_F_IGN_ENCAP_LIMIT;
+ else {
+ r = safe_atoi(rvalue, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse Tunnel Encapsulation Limit option, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ if (k > 255 || k < 0)
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid Tunnel Encapsulation value, ignoring: %d", k);
+ else {
+ t->encap_limit = k;
+ t->flags &= ~IP6_TNL_F_IGN_ENCAP_LIMIT;
+ }
+ }
+
+ return 0;
+}
+
+int config_parse_6rd_prefix(const char* unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Tunnel *t = userdata;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ union in_addr_union p;
+ uint8_t l;
+ int r;
+
+ r = in_addr_prefix_from_string(rvalue, AF_INET6, &p, &l);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse 6rd prefix \"%s\", ignoring: %m", rvalue);
+ return 0;
+ }
+ if (l == 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "6rd prefix length of \"%s\" must be greater than zero, ignoring", rvalue);
+ return 0;
+ }
+
+ t->sixrd_prefix = p.in6;
+ t->sixrd_prefixlen = l;
+
+ return 0;
+}
+
+static void ipip_init(NetDev *n) {
+ Tunnel *t = IPIP(n);
+
+ assert(n);
+ assert(t);
+
+ t->pmtudisc = true;
+ t->fou_encap_type = FOU_ENCAP_DIRECT;
+}
+
+static void sit_init(NetDev *n) {
+ Tunnel *t = SIT(n);
+
+ assert(n);
+ assert(t);
+
+ t->pmtudisc = true;
+ t->isatap = -1;
+}
+
+static void vti_init(NetDev *n) {
+ Tunnel *t;
+
+ assert(n);
+
+ if (n->kind == NETDEV_KIND_VTI)
+ t = VTI(n);
+ else
+ t = VTI6(n);
+
+ assert(t);
+
+ t->pmtudisc = true;
+}
+
+static void gre_init(NetDev *n) {
+ Tunnel *t;
+
+ assert(n);
+
+ if (n->kind == NETDEV_KIND_GRE)
+ t = GRE(n);
+ else
+ t = GRETAP(n);
+
+ assert(t);
+
+ t->pmtudisc = true;
+}
+
+static void ip6gre_init(NetDev *n) {
+ Tunnel *t;
+
+ assert(n);
+
+ if (n->kind == NETDEV_KIND_IP6GRE)
+ t = IP6GRE(n);
+ else
+ t = IP6GRETAP(n);
+
+ assert(t);
+
+ t->ttl = DEFAULT_TNL_HOP_LIMIT;
+}
+
+static void erspan_init(NetDev *n) {
+ Tunnel *t;
+
+ assert(n);
+
+ t = ERSPAN(n);
+
+ assert(t);
+
+ t->erspan_sequence = -1;
+}
+
+static void ip6tnl_init(NetDev *n) {
+ Tunnel *t = IP6TNL(n);
+
+ assert(n);
+ assert(t);
+
+ t->ttl = DEFAULT_TNL_HOP_LIMIT;
+ t->encap_limit = IPV6_DEFAULT_TNL_ENCAP_LIMIT;
+ t->ip6tnl_mode = _NETDEV_IP6_TNL_MODE_INVALID;
+ t->ipv6_flowlabel = _NETDEV_IPV6_FLOWLABEL_INVALID;
+ t->allow_localremote = -1;
+}
+
+const NetDevVTable ipip_vtable = {
+ .object_size = sizeof(Tunnel),
+ .init = ipip_init,
+ .sections = "Match\0NetDev\0Tunnel\0",
+ .fill_message_create = netdev_ipip_fill_message_create,
+ .create_type = NETDEV_CREATE_STACKED,
+ .config_verify = netdev_tunnel_verify,
+};
+
+const NetDevVTable sit_vtable = {
+ .object_size = sizeof(Tunnel),
+ .init = sit_init,
+ .sections = "Match\0NetDev\0Tunnel\0",
+ .fill_message_create = netdev_sit_fill_message_create,
+ .create_type = NETDEV_CREATE_STACKED,
+ .config_verify = netdev_tunnel_verify,
+};
+
+const NetDevVTable vti_vtable = {
+ .object_size = sizeof(Tunnel),
+ .init = vti_init,
+ .sections = "Match\0NetDev\0Tunnel\0",
+ .fill_message_create = netdev_vti_fill_message_create,
+ .create_type = NETDEV_CREATE_STACKED,
+ .config_verify = netdev_tunnel_verify,
+};
+
+const NetDevVTable vti6_vtable = {
+ .object_size = sizeof(Tunnel),
+ .init = vti_init,
+ .sections = "Match\0NetDev\0Tunnel\0",
+ .fill_message_create = netdev_vti6_fill_message_create,
+ .create_type = NETDEV_CREATE_STACKED,
+ .config_verify = netdev_tunnel_verify,
+};
+
+const NetDevVTable gre_vtable = {
+ .object_size = sizeof(Tunnel),
+ .init = gre_init,
+ .sections = "Match\0NetDev\0Tunnel\0",
+ .fill_message_create = netdev_gre_fill_message_create,
+ .create_type = NETDEV_CREATE_STACKED,
+ .config_verify = netdev_tunnel_verify,
+};
+
+const NetDevVTable gretap_vtable = {
+ .object_size = sizeof(Tunnel),
+ .init = gre_init,
+ .sections = "Match\0NetDev\0Tunnel\0",
+ .fill_message_create = netdev_gre_fill_message_create,
+ .create_type = NETDEV_CREATE_STACKED,
+ .config_verify = netdev_tunnel_verify,
+};
+
+const NetDevVTable ip6gre_vtable = {
+ .object_size = sizeof(Tunnel),
+ .init = ip6gre_init,
+ .sections = "Match\0NetDev\0Tunnel\0",
+ .fill_message_create = netdev_ip6gre_fill_message_create,
+ .create_type = NETDEV_CREATE_STACKED,
+ .config_verify = netdev_tunnel_verify,
+};
+
+const NetDevVTable ip6gretap_vtable = {
+ .object_size = sizeof(Tunnel),
+ .init = ip6gre_init,
+ .sections = "Match\0NetDev\0Tunnel\0",
+ .fill_message_create = netdev_ip6gre_fill_message_create,
+ .create_type = NETDEV_CREATE_STACKED,
+ .config_verify = netdev_tunnel_verify,
+};
+
+const NetDevVTable ip6tnl_vtable = {
+ .object_size = sizeof(Tunnel),
+ .init = ip6tnl_init,
+ .sections = "Match\0NetDev\0Tunnel\0",
+ .fill_message_create = netdev_ip6tnl_fill_message_create,
+ .create_type = NETDEV_CREATE_STACKED,
+ .config_verify = netdev_tunnel_verify,
+};
+
+const NetDevVTable erspan_vtable = {
+ .object_size = sizeof(Tunnel),
+ .init = erspan_init,
+ .sections = "Match\0NetDev\0Tunnel\0",
+ .fill_message_create = netdev_erspan_fill_message_create,
+ .create_type = NETDEV_CREATE_INDEPENDENT,
+ .config_verify = netdev_tunnel_verify,
+};
diff --git a/src/network/netdev/tunnel.h b/src/network/netdev/tunnel.h
new file mode 100644
index 0000000..8f511dd
--- /dev/null
+++ b/src/network/netdev/tunnel.h
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "in-addr-util.h"
+
+#include "conf-parser.h"
+#include "netdev/netdev.h"
+#include "netdev/fou-tunnel.h"
+
+typedef enum Ip6TnlMode {
+ NETDEV_IP6_TNL_MODE_IP6IP6,
+ NETDEV_IP6_TNL_MODE_IPIP6,
+ NETDEV_IP6_TNL_MODE_ANYIP6,
+ _NETDEV_IP6_TNL_MODE_MAX,
+ _NETDEV_IP6_TNL_MODE_INVALID = -1,
+} Ip6TnlMode;
+
+typedef enum IPv6FlowLabel {
+ NETDEV_IPV6_FLOWLABEL_INHERIT = 0xFFFFF + 1,
+ _NETDEV_IPV6_FLOWLABEL_MAX,
+ _NETDEV_IPV6_FLOWLABEL_INVALID = -1,
+} IPv6FlowLabel;
+
+typedef struct Tunnel {
+ NetDev meta;
+
+ uint8_t encap_limit;
+
+ int family;
+ int ipv6_flowlabel;
+ int allow_localremote;
+ int erspan_sequence;
+ int isatap;
+
+ unsigned ttl;
+ unsigned tos;
+ unsigned flags;
+
+ uint32_t key;
+ uint32_t ikey;
+ uint32_t okey;
+ uint32_t erspan_index;
+
+ union in_addr_union local;
+ union in_addr_union remote;
+
+ Ip6TnlMode ip6tnl_mode;
+ FooOverUDPEncapType fou_encap_type;
+
+ bool pmtudisc;
+ bool copy_dscp;
+ bool independent;
+ bool fou_tunnel;
+
+ uint16_t encap_src_port;
+ uint16_t fou_destination_port;
+
+ struct in6_addr sixrd_prefix;
+ uint8_t sixrd_prefixlen;
+} Tunnel;
+
+DEFINE_NETDEV_CAST(IPIP, Tunnel);
+DEFINE_NETDEV_CAST(GRE, Tunnel);
+DEFINE_NETDEV_CAST(GRETAP, Tunnel);
+DEFINE_NETDEV_CAST(IP6GRE, Tunnel);
+DEFINE_NETDEV_CAST(IP6GRETAP, Tunnel);
+DEFINE_NETDEV_CAST(SIT, Tunnel);
+DEFINE_NETDEV_CAST(VTI, Tunnel);
+DEFINE_NETDEV_CAST(VTI6, Tunnel);
+DEFINE_NETDEV_CAST(IP6TNL, Tunnel);
+DEFINE_NETDEV_CAST(ERSPAN, Tunnel);
+extern const NetDevVTable ipip_vtable;
+extern const NetDevVTable sit_vtable;
+extern const NetDevVTable vti_vtable;
+extern const NetDevVTable vti6_vtable;
+extern const NetDevVTable gre_vtable;
+extern const NetDevVTable gretap_vtable;
+extern const NetDevVTable ip6gre_vtable;
+extern const NetDevVTable ip6gretap_vtable;
+extern const NetDevVTable ip6tnl_vtable;
+extern const NetDevVTable erspan_vtable;
+
+const char *ip6tnl_mode_to_string(Ip6TnlMode d) _const_;
+Ip6TnlMode ip6tnl_mode_from_string(const char *d) _pure_;
+
+CONFIG_PARSER_PROTOTYPE(config_parse_ip6tnl_mode);
+CONFIG_PARSER_PROTOTYPE(config_parse_tunnel_address);
+CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_flowlabel);
+CONFIG_PARSER_PROTOTYPE(config_parse_encap_limit);
+CONFIG_PARSER_PROTOTYPE(config_parse_tunnel_key);
+CONFIG_PARSER_PROTOTYPE(config_parse_6rd_prefix);
diff --git a/src/network/netdev/tuntap.c b/src/network/netdev/tuntap.c
new file mode 100644
index 0000000..951138d
--- /dev/null
+++ b/src/network/netdev/tuntap.c
@@ -0,0 +1,161 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/if_tun.h>
+#include <net/if.h>
+#include <netinet/if_ether.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "netdev/tuntap.h"
+#include "user-util.h"
+
+#define TUN_DEV "/dev/net/tun"
+
+static int netdev_fill_tuntap_message(NetDev *netdev, struct ifreq *ifr) {
+ TunTap *t;
+
+ assert(netdev);
+ assert(netdev->ifname);
+ assert(ifr);
+
+ if (netdev->kind == NETDEV_KIND_TAP) {
+ t = TAP(netdev);
+ ifr->ifr_flags |= IFF_TAP;
+ } else {
+ t = TUN(netdev);
+ ifr->ifr_flags |= IFF_TUN;
+ }
+
+ if (!t->packet_info)
+ ifr->ifr_flags |= IFF_NO_PI;
+
+ if (t->one_queue)
+ ifr->ifr_flags |= IFF_ONE_QUEUE;
+
+ if (t->multi_queue)
+ ifr->ifr_flags |= IFF_MULTI_QUEUE;
+
+ if (t->vnet_hdr)
+ ifr->ifr_flags |= IFF_VNET_HDR;
+
+ strncpy(ifr->ifr_name, netdev->ifname, IFNAMSIZ-1);
+
+ return 0;
+}
+
+static int netdev_tuntap_add(NetDev *netdev, struct ifreq *ifr) {
+ _cleanup_close_ int fd;
+ TunTap *t = NULL;
+ const char *user;
+ const char *group;
+ uid_t uid;
+ gid_t gid;
+ int r;
+
+ assert(netdev);
+ assert(ifr);
+
+ fd = open(TUN_DEV, O_RDWR|O_CLOEXEC);
+ if (fd < 0)
+ return log_netdev_error_errno(netdev, -errno, "Failed to open tun dev: %m");
+
+ if (ioctl(fd, TUNSETIFF, ifr) < 0)
+ return log_netdev_error_errno(netdev, -errno, "TUNSETIFF failed on tun dev: %m");
+
+ if (netdev->kind == NETDEV_KIND_TAP)
+ t = TAP(netdev);
+ else
+ t = TUN(netdev);
+
+ assert(t);
+
+ if (t->user_name) {
+ user = t->user_name;
+
+ r = get_user_creds(&user, &uid, NULL, NULL, NULL, USER_CREDS_ALLOW_MISSING);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Cannot resolve user name %s: %m", t->user_name);
+
+ if (ioctl(fd, TUNSETOWNER, uid) < 0)
+ return log_netdev_error_errno(netdev, -errno, "TUNSETOWNER failed on tun dev: %m");
+ }
+
+ if (t->group_name) {
+ group = t->group_name;
+
+ r = get_group_creds(&group, &gid, USER_CREDS_ALLOW_MISSING);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Cannot resolve group name %s: %m", t->group_name);
+
+ if (ioctl(fd, TUNSETGROUP, gid) < 0)
+ return log_netdev_error_errno(netdev, -errno, "TUNSETGROUP failed on tun dev: %m");
+
+ }
+
+ if (ioctl(fd, TUNSETPERSIST, 1) < 0)
+ return log_netdev_error_errno(netdev, -errno, "TUNSETPERSIST failed on tun dev: %m");
+
+ return 0;
+}
+
+static int netdev_create_tuntap(NetDev *netdev) {
+ struct ifreq ifr = {};
+ int r;
+
+ r = netdev_fill_tuntap_message(netdev, &ifr);
+ if (r < 0)
+ return r;
+
+ return netdev_tuntap_add(netdev, &ifr);
+}
+
+static void tuntap_done(NetDev *netdev) {
+ TunTap *t = NULL;
+
+ assert(netdev);
+
+ if (netdev->kind == NETDEV_KIND_TUN)
+ t = TUN(netdev);
+ else
+ t = TAP(netdev);
+
+ assert(t);
+
+ t->user_name = mfree(t->user_name);
+ t->group_name = mfree(t->group_name);
+}
+
+static int tuntap_verify(NetDev *netdev, const char *filename) {
+ assert(netdev);
+
+ if (netdev->mtu != 0)
+ log_netdev_warning(netdev, "MTU configured for %s, ignoring", netdev_kind_to_string(netdev->kind));
+
+ if (netdev->mac)
+ log_netdev_warning(netdev, "MAC configured for %s, ignoring", netdev_kind_to_string(netdev->kind));
+
+ return 0;
+}
+
+const NetDevVTable tun_vtable = {
+ .object_size = sizeof(TunTap),
+ .sections = "Match\0NetDev\0Tun\0",
+ .config_verify = tuntap_verify,
+ .done = tuntap_done,
+ .create = netdev_create_tuntap,
+ .create_type = NETDEV_CREATE_INDEPENDENT,
+};
+
+const NetDevVTable tap_vtable = {
+ .object_size = sizeof(TunTap),
+ .sections = "Match\0NetDev\0Tap\0",
+ .config_verify = tuntap_verify,
+ .done = tuntap_done,
+ .create = netdev_create_tuntap,
+ .create_type = NETDEV_CREATE_INDEPENDENT,
+};
diff --git a/src/network/netdev/tuntap.h b/src/network/netdev/tuntap.h
new file mode 100644
index 0000000..9d9f992
--- /dev/null
+++ b/src/network/netdev/tuntap.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+typedef struct TunTap TunTap;
+
+#include "netdev/netdev.h"
+
+struct TunTap {
+ NetDev meta;
+
+ char *user_name;
+ char *group_name;
+ bool one_queue;
+ bool multi_queue;
+ bool packet_info;
+ bool vnet_hdr;
+};
+
+DEFINE_NETDEV_CAST(TUN, TunTap);
+DEFINE_NETDEV_CAST(TAP, TunTap);
+extern const NetDevVTable tun_vtable;
+extern const NetDevVTable tap_vtable;
diff --git a/src/network/netdev/vcan.c b/src/network/netdev/vcan.c
new file mode 100644
index 0000000..574d1ca
--- /dev/null
+++ b/src/network/netdev/vcan.c
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "netdev/vcan.h"
+
+const NetDevVTable vcan_vtable = {
+ .object_size = sizeof(VCan),
+ .sections = "Match\0NetDev\0",
+ .create_type = NETDEV_CREATE_INDEPENDENT,
+};
diff --git a/src/network/netdev/vcan.h b/src/network/netdev/vcan.h
new file mode 100644
index 0000000..6f62686
--- /dev/null
+++ b/src/network/netdev/vcan.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+typedef struct VCan VCan;
+
+#include <linux/can/netlink.h>
+
+#include "netdev/netdev.h"
+
+struct VCan {
+ NetDev meta;
+};
+
+DEFINE_NETDEV_CAST(VCAN, VCan);
+
+extern const NetDevVTable vcan_vtable;
diff --git a/src/network/netdev/veth.c b/src/network/netdev/veth.c
new file mode 100644
index 0000000..3ad95ad
--- /dev/null
+++ b/src/network/netdev/veth.c
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <linux/veth.h>
+#include <net/if.h>
+
+#include "sd-netlink.h"
+
+#include "netdev/veth.h"
+
+static int netdev_veth_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ Veth *v;
+ int r;
+
+ assert(netdev);
+ assert(!link);
+ assert(m);
+
+ v = VETH(netdev);
+
+ assert(v);
+
+ r = sd_netlink_message_open_container(m, VETH_INFO_PEER);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append VETH_INFO_PEER attribute: %m");
+
+ if (v->ifname_peer) {
+ r = sd_netlink_message_append_string(m, IFLA_IFNAME, v->ifname_peer);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add netlink interface name: %m");
+ }
+
+ if (v->mac_peer) {
+ r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, v->mac_peer);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_ADDRESS attribute: %m");
+ }
+
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_INFO_DATA attribute: %m");
+
+ return r;
+}
+
+static int netdev_veth_verify(NetDev *netdev, const char *filename) {
+ Veth *v;
+ int r;
+
+ assert(netdev);
+ assert(filename);
+
+ v = VETH(netdev);
+
+ assert(v);
+
+ if (!v->ifname_peer) {
+ log_warning("Veth NetDev without peer name configured in %s. Ignoring",
+ filename);
+ return -EINVAL;
+ }
+
+ if (!v->mac_peer) {
+ r = netdev_get_mac(v->ifname_peer, &v->mac_peer);
+ if (r < 0) {
+ log_warning("Failed to generate predictable MAC address for %s. Ignoring",
+ v->ifname_peer);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static void veth_done(NetDev *n) {
+ Veth *v;
+
+ assert(n);
+
+ v = VETH(n);
+
+ assert(v);
+
+ free(v->ifname_peer);
+ free(v->mac_peer);
+}
+
+const NetDevVTable veth_vtable = {
+ .object_size = sizeof(Veth),
+ .sections = "Match\0NetDev\0Peer\0",
+ .done = veth_done,
+ .fill_message_create = netdev_veth_fill_message_create,
+ .create_type = NETDEV_CREATE_INDEPENDENT,
+ .config_verify = netdev_veth_verify,
+};
diff --git a/src/network/netdev/veth.h b/src/network/netdev/veth.h
new file mode 100644
index 0000000..0bb9947
--- /dev/null
+++ b/src/network/netdev/veth.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+typedef struct Veth Veth;
+
+#include "netdev/netdev.h"
+
+struct Veth {
+ NetDev meta;
+
+ char *ifname_peer;
+ struct ether_addr *mac_peer;
+};
+
+DEFINE_NETDEV_CAST(VETH, Veth);
+extern const NetDevVTable veth_vtable;
diff --git a/src/network/netdev/vlan.c b/src/network/netdev/vlan.c
new file mode 100644
index 0000000..dd548b3
--- /dev/null
+++ b/src/network/netdev/vlan.c
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <errno.h>
+#include <linux/if_vlan.h>
+#include <net/if.h>
+
+#include "netdev/vlan.h"
+#include "vlan-util.h"
+
+static int netdev_vlan_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *req) {
+ struct ifla_vlan_flags flags = {};
+ VLan *v;
+ int r;
+
+ assert(netdev);
+ assert(link);
+ assert(req);
+
+ v = VLAN(netdev);
+
+ assert(v);
+
+ r = sd_netlink_message_append_u16(req, IFLA_VLAN_ID, v->id);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VLAN_ID attribute: %m");
+
+ if (v->gvrp != -1) {
+ flags.mask |= VLAN_FLAG_GVRP;
+ SET_FLAG(flags.flags, VLAN_FLAG_GVRP, v->gvrp);
+ }
+
+ if (v->mvrp != -1) {
+ flags.mask |= VLAN_FLAG_MVRP;
+ SET_FLAG(flags.flags, VLAN_FLAG_MVRP, v->mvrp);
+ }
+
+ if (v->reorder_hdr != -1) {
+ flags.mask |= VLAN_FLAG_REORDER_HDR;
+ SET_FLAG(flags.flags, VLAN_FLAG_REORDER_HDR, v->reorder_hdr);
+ }
+
+ if (v->loose_binding != -1) {
+ flags.mask |= VLAN_FLAG_LOOSE_BINDING;
+ SET_FLAG(flags.flags, VLAN_FLAG_LOOSE_BINDING, v->loose_binding);
+ }
+
+ r = sd_netlink_message_append_data(req, IFLA_VLAN_FLAGS, &flags, sizeof(struct ifla_vlan_flags));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VLAN_FLAGS attribute: %m");
+
+ return 0;
+}
+
+static int netdev_vlan_verify(NetDev *netdev, const char *filename) {
+ VLan *v;
+
+ assert(netdev);
+ assert(filename);
+
+ v = VLAN(netdev);
+
+ assert(v);
+
+ if (v->id == VLANID_INVALID) {
+ log_warning("VLAN without valid Id (%"PRIu16") configured in %s.", v->id, filename);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void vlan_init(NetDev *netdev) {
+ VLan *v = VLAN(netdev);
+
+ assert(netdev);
+ assert(v);
+
+ v->id = VLANID_INVALID;
+ v->gvrp = -1;
+ v->mvrp = -1;
+ v->loose_binding = -1;
+ v->reorder_hdr = -1;
+}
+
+const NetDevVTable vlan_vtable = {
+ .object_size = sizeof(VLan),
+ .init = vlan_init,
+ .sections = "Match\0NetDev\0VLAN\0",
+ .fill_message_create = netdev_vlan_fill_message_create,
+ .create_type = NETDEV_CREATE_STACKED,
+ .config_verify = netdev_vlan_verify,
+};
diff --git a/src/network/netdev/vlan.h b/src/network/netdev/vlan.h
new file mode 100644
index 0000000..b815e03
--- /dev/null
+++ b/src/network/netdev/vlan.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+typedef struct VLan VLan;
+
+#include "netdev/netdev.h"
+
+struct VLan {
+ NetDev meta;
+
+ uint16_t id;
+
+ int gvrp;
+ int mvrp;
+ int loose_binding;
+ int reorder_hdr;
+};
+
+DEFINE_NETDEV_CAST(VLAN, VLan);
+extern const NetDevVTable vlan_vtable;
diff --git a/src/network/netdev/vrf.c b/src/network/netdev/vrf.c
new file mode 100644
index 0000000..5efab53
--- /dev/null
+++ b/src/network/netdev/vrf.c
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <net/if.h>
+
+#include "sd-netlink.h"
+#include "missing.h"
+#include "netdev/vrf.h"
+
+static int netdev_vrf_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ Vrf *v;
+ int r;
+
+ assert(netdev);
+ assert(!link);
+ assert(m);
+
+ v = VRF(netdev);
+
+ assert(v);
+
+ r = sd_netlink_message_append_u32(m, IFLA_VRF_TABLE, v->table);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IPLA_VRF_TABLE attribute: %m");
+
+ return r;
+}
+
+const NetDevVTable vrf_vtable = {
+ .object_size = sizeof(Vrf),
+ .sections = "Match\0NetDev\0VRF\0",
+ .fill_message_create = netdev_vrf_fill_message_create,
+ .create_type = NETDEV_CREATE_MASTER,
+};
diff --git a/src/network/netdev/vrf.h b/src/network/netdev/vrf.h
new file mode 100644
index 0000000..05b3937
--- /dev/null
+++ b/src/network/netdev/vrf.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+typedef struct Vrf Vrf;
+
+#include "netdev/netdev.h"
+
+struct Vrf {
+ NetDev meta;
+
+ uint32_t table;
+};
+
+DEFINE_NETDEV_CAST(VRF, Vrf);
+extern const NetDevVTable vrf_vtable;
diff --git a/src/network/netdev/vxcan.c b/src/network/netdev/vxcan.c
new file mode 100644
index 0000000..e8ea70a
--- /dev/null
+++ b/src/network/netdev/vxcan.c
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#if HAVE_LINUX_CAN_VXCAN_H
+#include <linux/can/vxcan.h>
+#endif
+
+#include "missing.h"
+#include "netdev/vxcan.h"
+
+static int netdev_vxcan_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ VxCan *v;
+ int r;
+
+ assert(netdev);
+ assert(!link);
+ assert(m);
+
+ v = VXCAN(netdev);
+
+ assert(v);
+
+ r = sd_netlink_message_open_container(m, VXCAN_INFO_PEER);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append VXCAN_INFO_PEER attribute: %m");
+
+ if (v->ifname_peer) {
+ r = sd_netlink_message_append_string(m, IFLA_IFNAME, v->ifname_peer);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add vxcan netlink interface peer name: %m");
+ }
+
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append VXCAN_INFO_PEER attribute: %m");
+
+ return r;
+}
+
+static int netdev_vxcan_verify(NetDev *netdev, const char *filename) {
+ VxCan *v;
+
+ assert(netdev);
+ assert(filename);
+
+ v = VXCAN(netdev);
+
+ assert(v);
+
+ if (!v->ifname_peer) {
+ log_warning("VxCan NetDev without peer name configured in %s. Ignoring", filename);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void vxcan_done(NetDev *n) {
+ VxCan *v;
+
+ assert(n);
+
+ v = VXCAN(n);
+
+ assert(v);
+
+ free(v->ifname_peer);
+}
+
+const NetDevVTable vxcan_vtable = {
+ .object_size = sizeof(VxCan),
+ .sections = "Match\0NetDev\0VXCAN\0",
+ .done = vxcan_done,
+ .fill_message_create = netdev_vxcan_fill_message_create,
+ .create_type = NETDEV_CREATE_INDEPENDENT,
+ .config_verify = netdev_vxcan_verify,
+};
diff --git a/src/network/netdev/vxcan.h b/src/network/netdev/vxcan.h
new file mode 100644
index 0000000..b5de197
--- /dev/null
+++ b/src/network/netdev/vxcan.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+typedef struct VxCan VxCan;
+
+#include "netdev/netdev.h"
+
+struct VxCan {
+ NetDev meta;
+
+ char *ifname_peer;
+};
+
+DEFINE_NETDEV_CAST(VXCAN, VxCan);
+
+extern const NetDevVTable vxcan_vtable;
diff --git a/src/network/netdev/vxlan.c b/src/network/netdev/vxlan.c
new file mode 100644
index 0000000..4cb2eca
--- /dev/null
+++ b/src/network/netdev/vxlan.c
@@ -0,0 +1,311 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <net/if.h>
+
+#include "sd-netlink.h"
+
+#include "conf-parser.h"
+#include "alloc-util.h"
+#include "extract-word.h"
+#include "string-util.h"
+#include "strv.h"
+#include "parse-util.h"
+#include "missing.h"
+
+#include "networkd-link.h"
+#include "netdev/vxlan.h"
+
+static int netdev_vxlan_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ VxLan *v;
+ int r;
+
+ assert(netdev);
+ assert(link);
+ assert(m);
+
+ v = VXLAN(netdev);
+
+ assert(v);
+
+ if (v->id <= VXLAN_VID_MAX) {
+ r = sd_netlink_message_append_u32(m, IFLA_VXLAN_ID, v->id);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_ID attribute: %m");
+ }
+
+ if (!in_addr_is_null(v->remote_family, &v->remote)) {
+
+ if (v->remote_family == AF_INET)
+ r = sd_netlink_message_append_in_addr(m, IFLA_VXLAN_GROUP, &v->remote.in);
+ else
+ r = sd_netlink_message_append_in6_addr(m, IFLA_VXLAN_GROUP6, &v->remote.in6);
+
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_GROUP attribute: %m");
+ }
+
+ if (!in_addr_is_null(v->local_family, &v->local)) {
+
+ if (v->local_family == AF_INET)
+ r = sd_netlink_message_append_in_addr(m, IFLA_VXLAN_LOCAL, &v->local.in);
+ else
+ r = sd_netlink_message_append_in6_addr(m, IFLA_VXLAN_LOCAL6, &v->local.in6);
+
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_LOCAL attribute: %m");
+ }
+
+ r = sd_netlink_message_append_u32(m, IFLA_VXLAN_LINK, link->ifindex);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_LINK attribute: %m");
+
+ if (v->ttl != 0) {
+ r = sd_netlink_message_append_u8(m, IFLA_VXLAN_TTL, v->ttl);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_TTL attribute: %m");
+ }
+
+ if (v->tos != 0) {
+ r = sd_netlink_message_append_u8(m, IFLA_VXLAN_TOS, v->tos);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_TOS attribute: %m");
+ }
+
+ r = sd_netlink_message_append_u8(m, IFLA_VXLAN_LEARNING, v->learning);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_LEARNING attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_VXLAN_RSC, v->route_short_circuit);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_RSC attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_VXLAN_PROXY, v->arp_proxy);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_PROXY attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_VXLAN_L2MISS, v->l2miss);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_L2MISS attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_VXLAN_L3MISS, v->l3miss);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_L3MISS attribute: %m");
+
+ if (v->fdb_ageing != 0) {
+ r = sd_netlink_message_append_u32(m, IFLA_VXLAN_AGEING, v->fdb_ageing / USEC_PER_SEC);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_AGEING attribute: %m");
+ }
+
+ if (v->max_fdb != 0) {
+ r = sd_netlink_message_append_u32(m, IFLA_VXLAN_LIMIT, v->max_fdb);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_LIMIT attribute: %m");
+ }
+
+ r = sd_netlink_message_append_u8(m, IFLA_VXLAN_UDP_CSUM, v->udpcsum);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_UDP_CSUM attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_VXLAN_UDP_ZERO_CSUM6_TX, v->udp6zerocsumtx);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_UDP_ZERO_CSUM6_TX attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_VXLAN_UDP_ZERO_CSUM6_RX, v->udp6zerocsumrx);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_UDP_ZERO_CSUM6_RX attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_VXLAN_REMCSUM_TX, v->remote_csum_tx);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_REMCSUM_TX attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, IFLA_VXLAN_REMCSUM_RX, v->remote_csum_rx);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_REMCSUM_RX attribute: %m");
+
+ r = sd_netlink_message_append_u16(m, IFLA_VXLAN_PORT, htobe16(v->dest_port));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_PORT attribute: %m");
+
+ if (v->port_range.low != 0 || v->port_range.high != 0) {
+ struct ifla_vxlan_port_range port_range;
+
+ port_range.low = htobe16(v->port_range.low);
+ port_range.high = htobe16(v->port_range.high);
+
+ r = sd_netlink_message_append_data(m, IFLA_VXLAN_PORT_RANGE, &port_range, sizeof(port_range));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_PORT_RANGE attribute: %m");
+ }
+
+ r = sd_netlink_message_append_u32(m, IFLA_VXLAN_LABEL, htobe32(v->flow_label));
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_LABEL attribute: %m");
+
+ if (v->group_policy) {
+ r = sd_netlink_message_append_flag(m, IFLA_VXLAN_GBP);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_VXLAN_GBP attribute: %m");
+ }
+
+ return r;
+}
+
+int config_parse_vxlan_address(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ VxLan *v = userdata;
+ union in_addr_union *addr = data, buffer;
+ int r, f;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = in_addr_from_string_auto(rvalue, &f, &buffer);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "vxlan '%s' address is invalid, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ r = in_addr_is_multicast(f, &buffer);
+
+ if (streq(lvalue, "Group")) {
+ if (r <= 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "vxlan %s invalid multicast address, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ v->remote_family = f;
+ } else {
+ if (r > 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "vxlan %s cannot be a multicast address, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ if (streq(lvalue, "Remote"))
+ v->remote_family = f;
+ else
+ v->local_family = f;
+ }
+
+ *addr = buffer;
+
+ return 0;
+}
+
+int config_parse_port_range(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) {
+ VxLan *v = userdata;
+ uint16_t low, high;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = parse_ip_port_range(rvalue, &low, &high);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to parse VXLAN port range '%s'. Port should be greater than 0 and less than 65535.", rvalue);
+ return 0;
+ }
+
+ v->port_range.low = low;
+ v->port_range.high = high;
+
+ return 0;
+}
+
+int config_parse_flow_label(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ VxLan *v = userdata;
+ unsigned f;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = safe_atou(rvalue, &f);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse VXLAN flow label '%s'.", rvalue);
+ return 0;
+ }
+
+ if (f & ~VXLAN_FLOW_LABEL_MAX_MASK) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "VXLAN flow label '%s' not valid. Flow label range should be [0-1048575].", rvalue);
+ return 0;
+ }
+
+ v->flow_label = f;
+
+ return 0;
+}
+
+static int netdev_vxlan_verify(NetDev *netdev, const char *filename) {
+ VxLan *v = VXLAN(netdev);
+
+ assert(netdev);
+ assert(v);
+ assert(filename);
+
+ if (v->id > VXLAN_VID_MAX) {
+ log_warning("VXLAN without valid Id configured in %s. Ignoring", filename);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void vxlan_init(NetDev *netdev) {
+ VxLan *v;
+
+ assert(netdev);
+
+ v = VXLAN(netdev);
+
+ assert(v);
+
+ v->id = VXLAN_VID_MAX + 1;
+ v->learning = true;
+ v->udpcsum = false;
+ v->udp6zerocsumtx = false;
+ v->udp6zerocsumrx = false;
+}
+
+const NetDevVTable vxlan_vtable = {
+ .object_size = sizeof(VxLan),
+ .init = vxlan_init,
+ .sections = "Match\0NetDev\0VXLAN\0",
+ .fill_message_create = netdev_vxlan_fill_message_create,
+ .create_type = NETDEV_CREATE_STACKED,
+ .config_verify = netdev_vxlan_verify,
+};
diff --git a/src/network/netdev/vxlan.h b/src/network/netdev/vxlan.h
new file mode 100644
index 0000000..3b273e9
--- /dev/null
+++ b/src/network/netdev/vxlan.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+typedef struct VxLan VxLan;
+
+#include "in-addr-util.h"
+#include "netdev/netdev.h"
+
+#define VXLAN_VID_MAX (1u << 24) - 1
+#define VXLAN_FLOW_LABEL_MAX_MASK 0xFFFFFU
+
+struct VxLan {
+ NetDev meta;
+
+ uint64_t id;
+
+ int remote_family;
+ int local_family;
+
+ union in_addr_union remote;
+ union in_addr_union local;
+
+ unsigned tos;
+ unsigned ttl;
+ unsigned max_fdb;
+ unsigned flow_label;
+
+ uint16_t dest_port;
+
+ usec_t fdb_ageing;
+
+ bool learning;
+ bool arp_proxy;
+ bool route_short_circuit;
+ bool l2miss;
+ bool l3miss;
+ bool udpcsum;
+ bool udp6zerocsumtx;
+ bool udp6zerocsumrx;
+ bool remote_csum_tx;
+ bool remote_csum_rx;
+ bool group_policy;
+
+ struct ifla_vxlan_port_range port_range;
+};
+
+DEFINE_NETDEV_CAST(VXLAN, VxLan);
+extern const NetDevVTable vxlan_vtable;
+
+CONFIG_PARSER_PROTOTYPE(config_parse_vxlan_address);
+CONFIG_PARSER_PROTOTYPE(config_parse_port_range);
+CONFIG_PARSER_PROTOTYPE(config_parse_flow_label);
diff --git a/src/network/netdev/wireguard.c b/src/network/netdev/wireguard.c
new file mode 100644
index 0000000..0c0b16d
--- /dev/null
+++ b/src/network/netdev/wireguard.c
@@ -0,0 +1,778 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/***
+ Copyright © 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+***/
+
+#include <sys/ioctl.h>
+#include <net/if.h>
+
+#include "sd-resolve.h"
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "hexdecoct.h"
+#include "networkd-link.h"
+#include "networkd-manager.h"
+#include "networkd-util.h"
+#include "parse-util.h"
+#include "resolve-private.h"
+#include "string-util.h"
+#include "strv.h"
+#include "wireguard.h"
+#include "wireguard-netlink.h"
+
+static void resolve_endpoints(NetDev *netdev);
+
+static WireguardPeer *wireguard_peer_new(Wireguard *w, unsigned section) {
+ WireguardPeer *peer;
+
+ assert(w);
+
+ if (w->last_peer_section == section && w->peers)
+ return w->peers;
+
+ peer = new(WireguardPeer, 1);
+ if (!peer)
+ return NULL;
+
+ *peer = (WireguardPeer) {
+ .flags = WGPEER_F_REPLACE_ALLOWEDIPS,
+ };
+
+ LIST_PREPEND(peers, w->peers, peer);
+ w->last_peer_section = section;
+
+ return peer;
+}
+
+static int wireguard_set_ipmask_one(NetDev *netdev, sd_netlink_message *message, const WireguardIPmask *mask, uint16_t index) {
+ int r;
+
+ assert(message);
+ assert(mask);
+ assert(index > 0);
+
+ /* This returns 1 on success, 0 on recoverable error, and negative errno on failure. */
+
+ r = sd_netlink_message_open_array(message, index);
+ if (r < 0)
+ return 0;
+
+ r = sd_netlink_message_append_u16(message, WGALLOWEDIP_A_FAMILY, mask->family);
+ if (r < 0)
+ goto cancel;
+
+ if (mask->family == AF_INET)
+ r = sd_netlink_message_append_in_addr(message, WGALLOWEDIP_A_IPADDR, &mask->ip.in);
+ else if (mask->family == AF_INET6)
+ r = sd_netlink_message_append_in6_addr(message, WGALLOWEDIP_A_IPADDR, &mask->ip.in6);
+ if (r < 0)
+ goto cancel;
+
+ r = sd_netlink_message_append_u8(message, WGALLOWEDIP_A_CIDR_MASK, mask->cidr);
+ if (r < 0)
+ goto cancel;
+
+ r = sd_netlink_message_close_container(message);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not add wireguard allowed ip: %m");
+
+ return 1;
+
+cancel:
+ r = sd_netlink_message_cancel_array(message);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not cancel wireguard allowed ip message attribute: %m");
+
+ return 0;
+}
+
+static int wireguard_set_peer_one(NetDev *netdev, sd_netlink_message *message, const WireguardPeer *peer, uint16_t index, WireguardIPmask **mask_start) {
+ WireguardIPmask *mask, *start;
+ uint16_t j = 0;
+ int r;
+
+ assert(message);
+ assert(peer);
+ assert(index > 0);
+ assert(mask_start);
+
+ /* This returns 1 on success, 0 on recoverable error, and negative errno on failure. */
+
+ start = *mask_start ?: peer->ipmasks;
+
+ r = sd_netlink_message_open_array(message, index);
+ if (r < 0)
+ return 0;
+
+ r = sd_netlink_message_append_data(message, WGPEER_A_PUBLIC_KEY, &peer->public_key, sizeof(peer->public_key));
+ if (r < 0)
+ goto cancel;
+
+ if (!*mask_start) {
+ r = sd_netlink_message_append_data(message, WGPEER_A_PRESHARED_KEY, &peer->preshared_key, WG_KEY_LEN);
+ if (r < 0)
+ goto cancel;
+
+ r = sd_netlink_message_append_u32(message, WGPEER_A_FLAGS, peer->flags);
+ if (r < 0)
+ goto cancel;
+
+ r = sd_netlink_message_append_u16(message, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval);
+ if (r < 0)
+ goto cancel;
+
+ if (peer->endpoint.sa.sa_family == AF_INET)
+ r = sd_netlink_message_append_sockaddr_in(message, WGPEER_A_ENDPOINT, &peer->endpoint.in);
+ else if (peer->endpoint.sa.sa_family == AF_INET6)
+ r = sd_netlink_message_append_sockaddr_in6(message, WGPEER_A_ENDPOINT, &peer->endpoint.in6);
+ if (r < 0)
+ goto cancel;
+ }
+
+ r = sd_netlink_message_open_container(message, WGPEER_A_ALLOWEDIPS);
+ if (r < 0)
+ goto cancel;
+
+ LIST_FOREACH(ipmasks, mask, start) {
+ r = wireguard_set_ipmask_one(netdev, message, mask, ++j);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+ }
+
+ r = sd_netlink_message_close_container(message);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not add wireguard allowed ip: %m");
+
+ r = sd_netlink_message_close_container(message);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not add wireguard peer: %m");
+
+ *mask_start = mask; /* Start next cycle from this mask. */
+ return !mask;
+
+cancel:
+ r = sd_netlink_message_cancel_array(message);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not cancel wireguard peers: %m");
+
+ return 0;
+}
+
+static int wireguard_set_interface(NetDev *netdev) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL;
+ WireguardIPmask *mask_start = NULL;
+ WireguardPeer *peer, *peer_start;
+ uint32_t serial;
+ Wireguard *w;
+ int r;
+
+ assert(netdev);
+ w = WIREGUARD(netdev);
+ assert(w);
+
+ for (peer_start = w->peers; peer_start; ) {
+ uint16_t i = 0;
+
+ message = sd_netlink_message_unref(message);
+
+ r = sd_genl_message_new(netdev->manager->genl, SD_GENL_WIREGUARD, WG_CMD_SET_DEVICE, &message);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Failed to allocate generic netlink message: %m");
+
+ r = sd_netlink_message_append_string(message, WGDEVICE_A_IFNAME, netdev->ifname);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append wireguard interface name: %m");
+
+ if (peer_start == w->peers) {
+ r = sd_netlink_message_append_data(message, WGDEVICE_A_PRIVATE_KEY, &w->private_key, WG_KEY_LEN);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append wireguard private key: %m");
+
+ r = sd_netlink_message_append_u16(message, WGDEVICE_A_LISTEN_PORT, w->port);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append wireguard port: %m");
+
+ r = sd_netlink_message_append_u32(message, WGDEVICE_A_FWMARK, w->fwmark);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append wireguard fwmark: %m");
+
+ r = sd_netlink_message_append_u32(message, WGDEVICE_A_FLAGS, w->flags);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append wireguard flags: %m");
+ }
+
+ r = sd_netlink_message_open_container(message, WGDEVICE_A_PEERS);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append wireguard peer attributes: %m");
+
+ LIST_FOREACH(peers, peer, peer_start) {
+ r = wireguard_set_peer_one(netdev, message, peer, ++i, &mask_start);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+ }
+ peer_start = peer; /* Start next cycle from this peer. */
+
+ r = sd_netlink_message_close_container(message);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not close wireguard container: %m");
+
+ r = sd_netlink_send(netdev->manager->genl, message, &serial);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not set wireguard device: %m");
+ }
+
+ return 0;
+}
+
+static WireguardEndpoint* wireguard_endpoint_free(WireguardEndpoint *e) {
+ if (!e)
+ return NULL;
+ e->host = mfree(e->host);
+ e->port = mfree(e->port);
+ return mfree(e);
+}
+
+static void wireguard_endpoint_destroy_callback(WireguardEndpoint *e) {
+ assert(e);
+ assert(e->netdev);
+
+ netdev_unref(e->netdev);
+ wireguard_endpoint_free(e);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(WireguardEndpoint*, wireguard_endpoint_free);
+
+static int on_resolve_retry(sd_event_source *s, usec_t usec, void *userdata) {
+ NetDev *netdev = userdata;
+ Wireguard *w;
+
+ assert(netdev);
+ w = WIREGUARD(netdev);
+ assert(w);
+
+ if (!netdev_is_managed(netdev))
+ return 0;
+
+ assert(!w->unresolved_endpoints);
+ w->unresolved_endpoints = TAKE_PTR(w->failed_endpoints);
+
+ resolve_endpoints(netdev);
+
+ return 0;
+}
+
+/*
+ * Given the number of retries this function will return will an exponential
+ * increasing time in milliseconds to wait starting at 200ms and capped at 25 seconds.
+ */
+static int exponential_backoff_milliseconds(unsigned n_retries) {
+ return (2 << MAX(n_retries, 7U)) * 100 * USEC_PER_MSEC;
+}
+
+static int wireguard_resolve_handler(sd_resolve_query *q,
+ int ret,
+ const struct addrinfo *ai,
+ WireguardEndpoint *e) {
+ _cleanup_(netdev_unrefp) NetDev *netdev_will_unrefed = NULL;
+ NetDev *netdev;
+ Wireguard *w;
+ int r;
+
+ assert(e);
+ assert(e->netdev);
+
+ netdev = e->netdev;
+ w = WIREGUARD(netdev);
+ assert(w);
+
+ if (!netdev_is_managed(netdev))
+ return 0;
+
+ if (ret != 0) {
+ log_netdev_error(netdev, "Failed to resolve host '%s:%s': %s", e->host, e->port, gai_strerror(ret));
+ LIST_PREPEND(endpoints, w->failed_endpoints, e);
+ (void) sd_resolve_query_set_destroy_callback(q, NULL); /* Avoid freeing endpoint by destroy callback. */
+ netdev_will_unrefed = netdev; /* But netdev needs to be unrefed. */
+ } else if ((ai->ai_family == AF_INET && ai->ai_addrlen == sizeof(struct sockaddr_in)) ||
+ (ai->ai_family == AF_INET6 && ai->ai_addrlen == sizeof(struct sockaddr_in6)))
+ memcpy(&e->peer->endpoint, ai->ai_addr, ai->ai_addrlen);
+ else
+ log_netdev_error(netdev, "Neither IPv4 nor IPv6 address found for peer endpoint: %s:%s", e->host, e->port);
+
+ if (w->unresolved_endpoints) {
+ resolve_endpoints(netdev);
+ return 0;
+ }
+
+ (void) wireguard_set_interface(netdev);
+ if (w->failed_endpoints) {
+ _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
+
+ w->n_retries++;
+ r = sd_event_add_time(netdev->manager->event,
+ &s,
+ CLOCK_MONOTONIC,
+ now(CLOCK_MONOTONIC) + exponential_backoff_milliseconds(w->n_retries),
+ 0,
+ on_resolve_retry,
+ netdev);
+ if (r < 0) {
+ log_netdev_warning_errno(netdev, r, "Could not arm resolve retry handler: %m");
+ return 0;
+ }
+
+ r = sd_event_source_set_destroy_callback(s, (sd_event_destroy_t) netdev_destroy_callback);
+ if (r < 0) {
+ log_netdev_warning_errno(netdev, r, "Failed to set destroy callback to event source: %m");
+ return 0;
+ }
+
+ (void) sd_event_source_set_floating(s, true);
+ netdev_ref(netdev);
+ }
+
+ return 0;
+}
+
+static void resolve_endpoints(NetDev *netdev) {
+ static const struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_DGRAM,
+ .ai_protocol = IPPROTO_UDP
+ };
+ WireguardEndpoint *endpoint;
+ Wireguard *w;
+ int r = 0;
+
+ assert(netdev);
+ w = WIREGUARD(netdev);
+ assert(w);
+
+ LIST_FOREACH(endpoints, endpoint, w->unresolved_endpoints) {
+ r = resolve_getaddrinfo(netdev->manager->resolve,
+ NULL,
+ endpoint->host,
+ endpoint->port,
+ &hints,
+ wireguard_resolve_handler,
+ wireguard_endpoint_destroy_callback,
+ endpoint);
+
+ if (r == -ENOBUFS)
+ break;
+ if (r < 0) {
+ log_netdev_error_errno(netdev, r, "Failed to create resolver: %m");
+ continue;
+ }
+
+ /* Avoid freeing netdev. It will be unrefed by the destroy callback. */
+ netdev_ref(netdev);
+
+ LIST_REMOVE(endpoints, w->unresolved_endpoints, endpoint);
+ }
+}
+
+static int netdev_wireguard_post_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ Wireguard *w;
+
+ assert(netdev);
+ w = WIREGUARD(netdev);
+ assert(w);
+
+ (void) wireguard_set_interface(netdev);
+ resolve_endpoints(netdev);
+ return 0;
+}
+
+int config_parse_wireguard_listen_port(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) {
+ uint16_t *s = data;
+ uint16_t port = 0;
+ int r;
+
+ assert(rvalue);
+ assert(data);
+
+ if (!streq(rvalue, "auto")) {
+ r = parse_ip_port(rvalue, &port);
+ if (r < 0)
+ log_syntax(unit, LOG_ERR, filename, line, r, "Invalid port specification, ignoring assignment: %s", rvalue);
+ }
+
+ *s = port;
+
+ return 0;
+}
+
+static int parse_wireguard_key(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_free_ void *key = NULL;
+ size_t len;
+ int r;
+
+ assert(filename);
+ assert(rvalue);
+ assert(userdata);
+
+ r = unbase64mem(rvalue, strlen(rvalue), &key, &len);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Could not parse wireguard key \"%s\", ignoring assignment: %m", rvalue);
+ return 0;
+ }
+ if (len != WG_KEY_LEN) {
+ log_syntax(unit, LOG_ERR, filename, line, EINVAL,
+ "Wireguard key is too short, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ memcpy(userdata, key, WG_KEY_LEN);
+ return true;
+}
+
+int config_parse_wireguard_private_key(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) {
+ Wireguard *w;
+
+ assert(data);
+
+ w = WIREGUARD(data);
+
+ assert(w);
+
+ return parse_wireguard_key(unit,
+ filename,
+ line,
+ section,
+ section_line,
+ lvalue,
+ ltype,
+ rvalue,
+ data,
+ &w->private_key);
+
+}
+
+int config_parse_wireguard_preshared_key(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) {
+ Wireguard *w;
+ WireguardPeer *peer;
+
+ assert(data);
+
+ w = WIREGUARD(data);
+
+ assert(w);
+
+ peer = wireguard_peer_new(w, section_line);
+ if (!peer)
+ return log_oom();
+
+ return parse_wireguard_key(unit,
+ filename,
+ line,
+ section,
+ section_line,
+ lvalue,
+ ltype,
+ rvalue,
+ data,
+ peer->preshared_key);
+}
+
+int config_parse_wireguard_public_key(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) {
+ Wireguard *w;
+ WireguardPeer *peer;
+
+ assert(data);
+
+ w = WIREGUARD(data);
+
+ assert(w);
+
+ peer = wireguard_peer_new(w, section_line);
+ if (!peer)
+ return log_oom();
+
+ return parse_wireguard_key(unit,
+ filename,
+ line,
+ section,
+ section_line,
+ lvalue,
+ ltype,
+ rvalue,
+ data,
+ peer->public_key);
+}
+
+int config_parse_wireguard_allowed_ips(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) {
+ union in_addr_union addr;
+ unsigned char prefixlen;
+ int r, family;
+ Wireguard *w;
+ WireguardPeer *peer;
+ WireguardIPmask *ipmask;
+
+ assert(rvalue);
+ assert(data);
+
+ w = WIREGUARD(data);
+
+ peer = wireguard_peer_new(w, section_line);
+ if (!peer)
+ return log_oom();
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL;
+
+ r = extract_first_word(&rvalue, &word, "," WHITESPACE, 0);
+ if (r == 0)
+ break;
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to split allowed ips \"%s\" option: %m", rvalue);
+ break;
+ }
+
+ r = in_addr_prefix_from_string_auto(word, &family, &addr, &prefixlen);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Network address is invalid, ignoring assignment: %s", word);
+ return 0;
+ }
+
+ ipmask = new(WireguardIPmask, 1);
+ if (!ipmask)
+ return log_oom();
+
+ *ipmask = (WireguardIPmask) {
+ .family = family,
+ .ip.in6 = addr.in6,
+ .cidr = prefixlen,
+ };
+
+ LIST_PREPEND(ipmasks, peer->ipmasks, ipmask);
+ }
+
+ return 0;
+}
+
+int config_parse_wireguard_endpoint(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) {
+ Wireguard *w;
+ WireguardPeer *peer;
+ size_t len;
+ const char *begin, *end = NULL;
+ _cleanup_free_ char *host = NULL, *port = NULL;
+ _cleanup_(wireguard_endpoint_freep) WireguardEndpoint *endpoint = NULL;
+
+ assert(data);
+ assert(rvalue);
+
+ w = WIREGUARD(data);
+
+ assert(w);
+
+ peer = wireguard_peer_new(w, section_line);
+ if (!peer)
+ return log_oom();
+
+ if (rvalue[0] == '[') {
+ begin = &rvalue[1];
+ end = strchr(rvalue, ']');
+ if (!end) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Unable to find matching brace of endpoint, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+ len = end - begin;
+ ++end;
+ if (*end != ':' || !*(end + 1)) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Unable to find port of endpoint: %s", rvalue);
+ return 0;
+ }
+ ++end;
+ } else {
+ begin = rvalue;
+ end = strrchr(rvalue, ':');
+ if (!end || !*(end + 1)) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Unable to find port of endpoint: %s", rvalue);
+ return 0;
+ }
+ len = end - begin;
+ ++end;
+ }
+
+ host = strndup(begin, len);
+ if (!host)
+ return log_oom();
+
+ port = strdup(end);
+ if (!port)
+ return log_oom();
+
+ endpoint = new(WireguardEndpoint, 1);
+ if (!endpoint)
+ return log_oom();
+
+ *endpoint = (WireguardEndpoint) {
+ .peer = TAKE_PTR(peer),
+ .host = TAKE_PTR(host),
+ .port = TAKE_PTR(port),
+ .netdev = data,
+ };
+ LIST_PREPEND(endpoints, w->unresolved_endpoints, TAKE_PTR(endpoint));
+
+ return 0;
+}
+
+int config_parse_wireguard_keepalive(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ int r;
+ uint16_t keepalive = 0;
+ Wireguard *w;
+ WireguardPeer *peer;
+
+ assert(rvalue);
+ assert(data);
+
+ w = WIREGUARD(data);
+
+ assert(w);
+
+ peer = wireguard_peer_new(w, section_line);
+ if (!peer)
+ return log_oom();
+
+ if (streq(rvalue, "off"))
+ keepalive = 0;
+ else {
+ r = safe_atou16(rvalue, &keepalive);
+ if (r < 0)
+ log_syntax(unit, LOG_ERR, filename, line, r, "The persistent keepalive interval must be 0-65535. Ignore assignment: %s", rvalue);
+ }
+
+ peer->persistent_keepalive_interval = keepalive;
+ return 0;
+}
+
+static void wireguard_init(NetDev *netdev) {
+ Wireguard *w;
+
+ assert(netdev);
+
+ w = WIREGUARD(netdev);
+
+ assert(w);
+
+ w->flags = WGDEVICE_F_REPLACE_PEERS;
+}
+
+static void wireguard_done(NetDev *netdev) {
+ Wireguard *w;
+ WireguardPeer *peer;
+ WireguardIPmask *mask;
+ WireguardEndpoint *e;
+
+ assert(netdev);
+ w = WIREGUARD(netdev);
+ assert(w);
+
+ while ((peer = w->peers)) {
+ LIST_REMOVE(peers, w->peers, peer);
+ while ((mask = peer->ipmasks)) {
+ LIST_REMOVE(ipmasks, peer->ipmasks, mask);
+ free(mask);
+ }
+ free(peer);
+ }
+
+ while ((e = w->unresolved_endpoints)) {
+ LIST_REMOVE(endpoints, w->unresolved_endpoints, e);
+ wireguard_endpoint_free(e);
+ }
+
+ while ((e = w->failed_endpoints)) {
+ LIST_REMOVE(endpoints, w->failed_endpoints, e);
+ wireguard_endpoint_free(e);
+ }
+}
+
+const NetDevVTable wireguard_vtable = {
+ .object_size = sizeof(Wireguard),
+ .sections = "Match\0NetDev\0WireGuard\0WireGuardPeer\0",
+ .post_create = netdev_wireguard_post_create,
+ .init = wireguard_init,
+ .done = wireguard_done,
+ .create_type = NETDEV_CREATE_INDEPENDENT,
+};
diff --git a/src/network/netdev/wireguard.h b/src/network/netdev/wireguard.h
new file mode 100644
index 0000000..143e93c
--- /dev/null
+++ b/src/network/netdev/wireguard.h
@@ -0,0 +1,73 @@
+#pragma once
+
+typedef struct Wireguard Wireguard;
+
+#include "in-addr-util.h"
+#include "netdev.h"
+#include "socket-util.h"
+#include "wireguard-netlink.h"
+
+#ifndef IFNAMSIZ
+#define IFNAMSIZ 16
+#endif
+
+typedef struct WireguardIPmask {
+ uint16_t family;
+ union in_addr_union ip;
+ uint8_t cidr;
+
+ LIST_FIELDS(struct WireguardIPmask, ipmasks);
+} WireguardIPmask;
+
+typedef struct WireguardPeer {
+ uint8_t public_key[WG_KEY_LEN];
+ uint8_t preshared_key[WG_KEY_LEN];
+ uint32_t flags;
+
+ union sockaddr_union endpoint;
+
+ uint16_t persistent_keepalive_interval;
+
+ LIST_HEAD(WireguardIPmask, ipmasks);
+ LIST_FIELDS(struct WireguardPeer, peers);
+} WireguardPeer;
+
+typedef struct WireguardEndpoint {
+ char *host;
+ char *port;
+
+ NetDev *netdev;
+ WireguardPeer *peer;
+
+ LIST_FIELDS(struct WireguardEndpoint, endpoints);
+} WireguardEndpoint;
+
+struct Wireguard {
+ NetDev meta;
+ unsigned last_peer_section;
+
+ uint32_t flags;
+
+ uint8_t private_key[WG_KEY_LEN];
+ uint32_t fwmark;
+
+ uint16_t port;
+
+ LIST_HEAD(WireguardPeer, peers);
+
+ LIST_HEAD(WireguardEndpoint, unresolved_endpoints);
+ LIST_HEAD(WireguardEndpoint, failed_endpoints);
+ unsigned n_retries;
+};
+
+DEFINE_NETDEV_CAST(WIREGUARD, Wireguard);
+extern const NetDevVTable wireguard_vtable;
+
+CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_allowed_ips);
+CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_endpoint);
+CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_listen_port);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_public_key);
+CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_private_key);
+CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_preshared_key);
+CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_keepalive);
diff --git a/src/network/networkctl.c b/src/network/networkctl.c
new file mode 100644
index 0000000..b14b81c
--- /dev/null
+++ b/src/network/networkctl.c
@@ -0,0 +1,1183 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <getopt.h>
+#include <linux/if_addrlabel.h>
+#include <net/if.h>
+#include <stdbool.h>
+
+#include "sd-device.h"
+#include "sd-hwdb.h"
+#include "sd-lldp.h"
+#include "sd-netlink.h"
+#include "sd-network.h"
+
+#include "alloc-util.h"
+#include "arphrd-list.h"
+#include "device-util.h"
+#include "ether-addr-util.h"
+#include "fd-util.h"
+#include "hwdb-util.h"
+#include "local-addresses.h"
+#include "locale-util.h"
+#include "macro.h"
+#include "main-func.h"
+#include "netlink-util.h"
+#include "pager.h"
+#include "parse-util.h"
+#include "pretty-print.h"
+#include "socket-util.h"
+#include "sparse-endian.h"
+#include "stdio-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+#include "strxcpyx.h"
+#include "terminal-util.h"
+#include "util.h"
+#include "verbs.h"
+
+static PagerFlags arg_pager_flags = 0;
+static bool arg_legend = true;
+static bool arg_all = false;
+
+static char *link_get_type_string(unsigned short iftype, sd_device *d) {
+ const char *t, *devtype;
+ char *p;
+
+ if (d &&
+ sd_device_get_devtype(d, &devtype) >= 0 &&
+ !isempty(devtype))
+ return strdup(devtype);
+
+ t = arphrd_to_name(iftype);
+ if (!t)
+ return NULL;
+
+ p = strdup(t);
+ if (!p)
+ return NULL;
+
+ ascii_strlower(p);
+ return p;
+}
+
+static void operational_state_to_color(const char *state, const char **on, const char **off) {
+ assert(on);
+ assert(off);
+
+ if (streq_ptr(state, "routable")) {
+ *on = ansi_highlight_green();
+ *off = ansi_normal();
+ } else if (streq_ptr(state, "degraded")) {
+ *on = ansi_highlight_yellow();
+ *off = ansi_normal();
+ } else
+ *on = *off = "";
+}
+
+static void setup_state_to_color(const char *state, const char **on, const char **off) {
+ assert(on);
+ assert(off);
+
+ if (streq_ptr(state, "configured")) {
+ *on = ansi_highlight_green();
+ *off = ansi_normal();
+ } else if (streq_ptr(state, "configuring")) {
+ *on = ansi_highlight_yellow();
+ *off = ansi_normal();
+ } else if (STRPTR_IN_SET(state, "failed", "linger")) {
+ *on = ansi_highlight_red();
+ *off = ansi_normal();
+ } else
+ *on = *off = "";
+}
+
+typedef struct LinkInfo {
+ char name[IFNAMSIZ+1];
+ int ifindex;
+ unsigned short iftype;
+ struct ether_addr mac_address;
+ uint32_t mtu;
+
+ bool has_mac_address:1;
+ bool has_mtu:1;
+} LinkInfo;
+
+static int link_info_compare(const LinkInfo *a, const LinkInfo *b) {
+ return CMP(a->ifindex, b->ifindex);
+}
+
+static int decode_link(sd_netlink_message *m, LinkInfo *info) {
+ const char *name;
+ uint16_t type;
+ int r;
+
+ assert(m);
+ assert(info);
+
+ r = sd_netlink_message_get_type(m, &type);
+ if (r < 0)
+ return r;
+
+ if (type != RTM_NEWLINK)
+ return 0;
+
+ r = sd_rtnl_message_link_get_ifindex(m, &info->ifindex);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_read_string(m, IFLA_IFNAME, &name);
+ if (r < 0)
+ return r;
+
+ r = sd_rtnl_message_link_get_type(m, &info->iftype);
+ if (r < 0)
+ return r;
+
+ strscpy(info->name, sizeof info->name, name);
+
+ info->has_mac_address =
+ sd_netlink_message_read_ether_addr(m, IFLA_ADDRESS, &info->mac_address) >= 0 &&
+ memcmp(&info->mac_address, &ETHER_ADDR_NULL, sizeof(struct ether_addr)) != 0;
+
+ info->has_mtu =
+ sd_netlink_message_read_u32(m, IFLA_MTU, &info->mtu) &&
+ info->mtu > 0;
+
+ return 1;
+}
+
+static int acquire_link_info_strv(sd_netlink *rtnl, char **l, LinkInfo **ret) {
+ _cleanup_free_ LinkInfo *links = NULL;
+ char **i;
+ size_t c = 0;
+ int r;
+
+ assert(rtnl);
+ assert(ret);
+
+ links = new(LinkInfo, strv_length(l));
+ if (!links)
+ return log_oom();
+
+ STRV_FOREACH(i, l) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
+ int ifindex;
+
+ if (parse_ifindex(*i, &ifindex) >= 0)
+ r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, ifindex);
+ else {
+ r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
+ if (r < 0)
+ return rtnl_log_create_error(r);
+
+ r = sd_netlink_message_append_string(req, IFLA_IFNAME, *i);
+ }
+ if (r < 0)
+ return rtnl_log_create_error(r);
+
+ r = sd_netlink_call(rtnl, req, 0, &reply);
+ if (r < 0)
+ return log_error_errno(r, "Failed to request link: %m");
+
+ r = decode_link(reply, links + c);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ c++;
+ }
+
+ typesafe_qsort(links, c, link_info_compare);
+
+ *ret = TAKE_PTR(links);
+
+ return (int) c;
+}
+
+static int acquire_link_info_all(sd_netlink *rtnl, LinkInfo **ret) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
+ _cleanup_free_ LinkInfo *links = NULL;
+ size_t allocated = 0, c = 0;
+ sd_netlink_message *i;
+ int r;
+
+ assert(rtnl);
+ assert(ret);
+
+ r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
+ if (r < 0)
+ return rtnl_log_create_error(r);
+
+ r = sd_netlink_message_request_dump(req, true);
+ if (r < 0)
+ return rtnl_log_create_error(r);
+
+ r = sd_netlink_call(rtnl, req, 0, &reply);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enumerate links: %m");
+
+ for (i = reply; i; i = sd_netlink_message_next(i)) {
+ if (!GREEDY_REALLOC(links, allocated, c+1))
+ return -ENOMEM;
+
+ r = decode_link(i, links + c);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ c++;
+ }
+
+ typesafe_qsort(links, c, link_info_compare);
+
+ *ret = TAKE_PTR(links);
+
+ return (int) c;
+}
+
+static int list_links(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+ _cleanup_free_ LinkInfo *links = NULL;
+ int c, i, r;
+
+ r = sd_netlink_open(&rtnl);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to netlink: %m");
+
+ if (argc > 1)
+ c = acquire_link_info_strv(rtnl, argv + 1, &links);
+ else
+ c = acquire_link_info_all(rtnl, &links);
+ if (c < 0)
+ return c;
+
+ (void) pager_open(arg_pager_flags);
+
+ if (arg_legend)
+ printf("%3s %-16s %-18s %-11s %-10s\n",
+ "IDX",
+ "LINK",
+ "TYPE",
+ "OPERATIONAL",
+ "SETUP");
+
+ for (i = 0; i < c; i++) {
+ _cleanup_free_ char *setup_state = NULL, *operational_state = NULL;
+ _cleanup_(sd_device_unrefp) sd_device *d = NULL;
+ const char *on_color_operational, *off_color_operational,
+ *on_color_setup, *off_color_setup;
+ char devid[2 + DECIMAL_STR_MAX(int)];
+ _cleanup_free_ char *t = NULL;
+
+ (void) sd_network_link_get_operational_state(links[i].ifindex, &operational_state);
+ operational_state_to_color(operational_state, &on_color_operational, &off_color_operational);
+
+ r = sd_network_link_get_setup_state(links[i].ifindex, &setup_state);
+ if (r == -ENODATA) /* If there's no info available about this iface, it's unmanaged by networkd */
+ setup_state = strdup("unmanaged");
+ setup_state_to_color(setup_state, &on_color_setup, &off_color_setup);
+
+ xsprintf(devid, "n%i", links[i].ifindex);
+ (void) sd_device_new_from_device_id(&d, devid);
+
+ t = link_get_type_string(links[i].iftype, d);
+
+ printf("%3i %-16s %-18s %s%-11s%s %s%-10s%s\n",
+ links[i].ifindex, links[i].name, strna(t),
+ on_color_operational, strna(operational_state), off_color_operational,
+ on_color_setup, strna(setup_state), off_color_setup);
+ }
+
+ if (arg_legend)
+ printf("\n%i links listed.\n", c);
+
+ return 0;
+}
+
+/* IEEE Organizationally Unique Identifier vendor string */
+static int ieee_oui(sd_hwdb *hwdb, const struct ether_addr *mac, char **ret) {
+ const char *description;
+ char modalias[STRLEN("OUI:XXYYXXYYXXYY") + 1], *desc;
+ int r;
+
+ assert(ret);
+
+ if (!hwdb)
+ return -EINVAL;
+
+ if (!mac)
+ return -EINVAL;
+
+ /* skip commonly misused 00:00:00 (Xerox) prefix */
+ if (memcmp(mac, "\0\0\0", 3) == 0)
+ return -EINVAL;
+
+ xsprintf(modalias, "OUI:" ETHER_ADDR_FORMAT_STR,
+ ETHER_ADDR_FORMAT_VAL(*mac));
+
+ r = sd_hwdb_get(hwdb, modalias, "ID_OUI_FROM_DATABASE", &description);
+ if (r < 0)
+ return r;
+
+ desc = strdup(description);
+ if (!desc)
+ return -ENOMEM;
+
+ *ret = desc;
+
+ return 0;
+}
+
+static int get_gateway_description(
+ sd_netlink *rtnl,
+ sd_hwdb *hwdb,
+ int ifindex,
+ int family,
+ union in_addr_union *gateway,
+ char **gateway_description) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
+ sd_netlink_message *m;
+ int r;
+
+ assert(rtnl);
+ assert(ifindex >= 0);
+ assert(IN_SET(family, AF_INET, AF_INET6));
+ assert(gateway);
+ assert(gateway_description);
+
+ r = sd_rtnl_message_new_neigh(rtnl, &req, RTM_GETNEIGH, ifindex, family);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_request_dump(req, true);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_call(rtnl, req, 0, &reply);
+ if (r < 0)
+ return r;
+
+ for (m = reply; m; m = sd_netlink_message_next(m)) {
+ union in_addr_union gw = {};
+ struct ether_addr mac = {};
+ uint16_t type;
+ int ifi, fam;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0) {
+ log_error_errno(r, "got error: %m");
+ continue;
+ }
+
+ r = sd_netlink_message_get_type(m, &type);
+ if (r < 0) {
+ log_error_errno(r, "could not get type: %m");
+ continue;
+ }
+
+ if (type != RTM_NEWNEIGH) {
+ log_error("type is not RTM_NEWNEIGH");
+ continue;
+ }
+
+ r = sd_rtnl_message_neigh_get_family(m, &fam);
+ if (r < 0) {
+ log_error_errno(r, "could not get family: %m");
+ continue;
+ }
+
+ if (fam != family) {
+ log_error("family is not correct");
+ continue;
+ }
+
+ r = sd_rtnl_message_neigh_get_ifindex(m, &ifi);
+ if (r < 0) {
+ log_error_errno(r, "could not get ifindex: %m");
+ continue;
+ }
+
+ if (ifindex > 0 && ifi != ifindex)
+ continue;
+
+ switch (fam) {
+ case AF_INET:
+ r = sd_netlink_message_read_in_addr(m, NDA_DST, &gw.in);
+ if (r < 0)
+ continue;
+
+ break;
+ case AF_INET6:
+ r = sd_netlink_message_read_in6_addr(m, NDA_DST, &gw.in6);
+ if (r < 0)
+ continue;
+
+ break;
+ default:
+ continue;
+ }
+
+ if (!in_addr_equal(fam, &gw, gateway))
+ continue;
+
+ r = sd_netlink_message_read_ether_addr(m, NDA_LLADDR, &mac);
+ if (r < 0)
+ continue;
+
+ r = ieee_oui(hwdb, &mac, gateway_description);
+ if (r < 0)
+ continue;
+
+ return 0;
+ }
+
+ return -ENODATA;
+}
+
+static int dump_gateways(
+ sd_netlink *rtnl,
+ sd_hwdb *hwdb,
+ const char *prefix,
+ int ifindex) {
+ _cleanup_free_ struct local_address *local = NULL;
+ int r, n, i;
+
+ assert(rtnl);
+ assert(prefix);
+
+ n = local_gateways(rtnl, ifindex, AF_UNSPEC, &local);
+ if (n < 0)
+ return n;
+
+ for (i = 0; i < n; i++) {
+ _cleanup_free_ char *gateway = NULL, *description = NULL;
+
+ r = in_addr_to_string(local[i].family, &local[i].address, &gateway);
+ if (r < 0)
+ return r;
+
+ r = get_gateway_description(rtnl, hwdb, local[i].ifindex, local[i].family, &local[i].address, &description);
+ if (r < 0)
+ log_debug_errno(r, "Could not get description of gateway: %m");
+
+ printf("%*s%s",
+ (int) strlen(prefix),
+ i == 0 ? prefix : "",
+ gateway);
+
+ if (description)
+ printf(" (%s)", description);
+
+ /* Show interface name for the entry if we show
+ * entries for all interfaces */
+ if (ifindex <= 0) {
+ char name[IF_NAMESIZE+1];
+
+ if (if_indextoname(local[i].ifindex, name)) {
+ fputs(" on ", stdout);
+ fputs(name, stdout);
+ } else
+ printf(" on %%%i", local[i].ifindex);
+ }
+
+ fputc('\n', stdout);
+ }
+
+ return 0;
+}
+
+static int dump_addresses(
+ sd_netlink *rtnl,
+ const char *prefix,
+ int ifindex) {
+
+ _cleanup_free_ struct local_address *local = NULL;
+ int r, n, i;
+
+ assert(rtnl);
+ assert(prefix);
+
+ n = local_addresses(rtnl, ifindex, AF_UNSPEC, &local);
+ if (n < 0)
+ return n;
+
+ for (i = 0; i < n; i++) {
+ _cleanup_free_ char *pretty = NULL;
+
+ r = in_addr_to_string(local[i].family, &local[i].address, &pretty);
+ if (r < 0)
+ return r;
+
+ printf("%*s%s",
+ (int) strlen(prefix),
+ i == 0 ? prefix : "",
+ pretty);
+
+ if (ifindex <= 0) {
+ char name[IF_NAMESIZE+1];
+
+ if (if_indextoname(local[i].ifindex, name)) {
+ fputs(" on ", stdout);
+ fputs(name, stdout);
+ } else
+ printf(" on %%%i", local[i].ifindex);
+ }
+
+ fputc('\n', stdout);
+ }
+
+ return 0;
+}
+
+static int dump_address_labels(sd_netlink *rtnl) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
+ sd_netlink_message *m;
+ int r;
+
+ assert(rtnl);
+
+ r = sd_rtnl_message_new_addrlabel(rtnl, &req, RTM_GETADDRLABEL, 0, AF_INET6);
+ if (r < 0)
+ return log_error_errno(r, "Could not allocate RTM_GETADDRLABEL message: %m");
+
+ r = sd_netlink_message_request_dump(req, true);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_call(rtnl, req, 0, &reply);
+ if (r < 0)
+ return r;
+
+ printf("%10s/%s %30s\n", "Prefix", "Prefixlen", "Label");
+
+ for (m = reply; m; m = sd_netlink_message_next(m)) {
+ _cleanup_free_ char *pretty = NULL;
+ union in_addr_union prefix = {};
+ uint8_t prefixlen;
+ uint32_t label;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0) {
+ log_error_errno(r, "got error: %m");
+ continue;
+ }
+
+ r = sd_netlink_message_read_u32(m, IFAL_LABEL, &label);
+ if (r < 0 && r != -ENODATA) {
+ log_error_errno(r, "Could not read IFAL_LABEL, ignoring: %m");
+ continue;
+ }
+
+ r = sd_netlink_message_read_in6_addr(m, IFAL_ADDRESS, &prefix.in6);
+ if (r < 0)
+ continue;
+
+ r = in_addr_to_string(AF_INET6, &prefix, &pretty);
+ if (r < 0)
+ continue;
+
+ r = sd_rtnl_message_addrlabel_get_prefixlen(m, &prefixlen);
+ if (r < 0)
+ continue;
+
+ printf("%10s/%-5u %30u\n", pretty, prefixlen, label);
+ }
+
+ return 0;
+}
+
+static int list_address_labels(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+ int r;
+
+ r = sd_netlink_open(&rtnl);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to netlink: %m");
+
+ dump_address_labels(rtnl);
+
+ return 0;
+}
+
+static int open_lldp_neighbors(int ifindex, FILE **ret) {
+ _cleanup_free_ char *p = NULL;
+ FILE *f;
+
+ if (asprintf(&p, "/run/systemd/netif/lldp/%i", ifindex) < 0)
+ return -ENOMEM;
+
+ f = fopen(p, "re");
+ if (!f)
+ return -errno;
+
+ *ret = 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;
+
+ return 1;
+}
+
+static int dump_lldp_neighbors(const char *prefix, int ifindex) {
+ _cleanup_fclose_ FILE *f = NULL;
+ int r, c = 0;
+
+ assert(prefix);
+ assert(ifindex > 0);
+
+ r = open_lldp_neighbors(ifindex, &f);
+ 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;
+
+ r = next_lldp_neighbor(f, &n);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ printf("%*s",
+ (int) strlen(prefix),
+ c == 0 ? prefix : "");
+
+ (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);
+
+ printf("%s on port %s", strna(system_name), strna(port_id));
+
+ if (!isempty(port_description))
+ printf(" (%s)", port_description);
+
+ putchar('\n');
+
+ c++;
+ }
+
+ return c;
+}
+
+static void dump_ifindexes(const char *prefix, const int *ifindexes) {
+ unsigned c;
+
+ assert(prefix);
+
+ if (!ifindexes || ifindexes[0] <= 0)
+ return;
+
+ for (c = 0; ifindexes[c] > 0; c++) {
+ char name[IF_NAMESIZE+1];
+
+ printf("%*s",
+ (int) strlen(prefix),
+ c == 0 ? prefix : "");
+
+ if (if_indextoname(ifindexes[c], name))
+ fputs(name, stdout);
+ else
+ printf("%i", ifindexes[c]);
+
+ fputc('\n', stdout);
+ }
+}
+
+static void dump_list(const char *prefix, char **l) {
+ char **i;
+
+ if (strv_isempty(l))
+ return;
+
+ STRV_FOREACH(i, l) {
+ printf("%*s%s\n",
+ (int) strlen(prefix),
+ i == l ? prefix : "",
+ *i);
+ }
+}
+
+static int link_status_one(
+ sd_netlink *rtnl,
+ sd_hwdb *hwdb,
+ const LinkInfo *info) {
+
+ _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **search_domains = NULL, **route_domains = NULL;
+ _cleanup_free_ char *setup_state = NULL, *operational_state = NULL, *tz = NULL;
+ _cleanup_(sd_device_unrefp) sd_device *d = NULL;
+ char devid[2 + DECIMAL_STR_MAX(int)];
+ _cleanup_free_ char *t = NULL, *network = NULL;
+ const char *driver = NULL, *path = NULL, *vendor = NULL, *model = NULL, *link = NULL;
+ const char *on_color_operational, *off_color_operational,
+ *on_color_setup, *off_color_setup;
+ _cleanup_free_ int *carrier_bound_to = NULL, *carrier_bound_by = NULL;
+ int r;
+
+ assert(rtnl);
+ assert(info);
+
+ (void) sd_network_link_get_operational_state(info->ifindex, &operational_state);
+ operational_state_to_color(operational_state, &on_color_operational, &off_color_operational);
+
+ r = sd_network_link_get_setup_state(info->ifindex, &setup_state);
+ if (r == -ENODATA) /* If there's no info available about this iface, it's unmanaged by networkd */
+ setup_state = strdup("unmanaged");
+ setup_state_to_color(setup_state, &on_color_setup, &off_color_setup);
+
+ (void) sd_network_link_get_dns(info->ifindex, &dns);
+ (void) sd_network_link_get_search_domains(info->ifindex, &search_domains);
+ (void) sd_network_link_get_route_domains(info->ifindex, &route_domains);
+ (void) sd_network_link_get_ntp(info->ifindex, &ntp);
+
+ xsprintf(devid, "n%i", info->ifindex);
+
+ (void) sd_device_new_from_device_id(&d, devid);
+
+ if (d) {
+ (void) sd_device_get_property_value(d, "ID_NET_LINK_FILE", &link);
+ (void) sd_device_get_property_value(d, "ID_NET_DRIVER", &driver);
+ (void) sd_device_get_property_value(d, "ID_PATH", &path);
+
+ if (sd_device_get_property_value(d, "ID_VENDOR_FROM_DATABASE", &vendor) < 0)
+ (void) sd_device_get_property_value(d, "ID_VENDOR", &vendor);
+
+ if (sd_device_get_property_value(d, "ID_MODEL_FROM_DATABASE", &model) < 0)
+ (void) sd_device_get_property_value(d, "ID_MODEL", &model);
+ }
+
+ t = link_get_type_string(info->iftype, d);
+
+ (void) sd_network_link_get_network_file(info->ifindex, &network);
+
+ (void) sd_network_link_get_carrier_bound_to(info->ifindex, &carrier_bound_to);
+ (void) sd_network_link_get_carrier_bound_by(info->ifindex, &carrier_bound_by);
+
+ printf("%s%s%s %i: %s\n", on_color_operational, special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE), off_color_operational, info->ifindex, info->name);
+
+ printf(" Link File: %s\n"
+ " Network File: %s\n"
+ " Type: %s\n"
+ " State: %s%s%s (%s%s%s)\n",
+ strna(link),
+ strna(network),
+ strna(t),
+ on_color_operational, strna(operational_state), off_color_operational,
+ on_color_setup, strna(setup_state), off_color_setup);
+
+ if (path)
+ printf(" Path: %s\n", path);
+ if (driver)
+ printf(" Driver: %s\n", driver);
+ if (vendor)
+ printf(" Vendor: %s\n", vendor);
+ if (model)
+ printf(" Model: %s\n", model);
+
+ if (info->has_mac_address) {
+ _cleanup_free_ char *description = NULL;
+ char ea[ETHER_ADDR_TO_STRING_MAX];
+
+ (void) ieee_oui(hwdb, &info->mac_address, &description);
+
+ if (description)
+ printf(" HW Address: %s (%s)\n", ether_addr_to_string(&info->mac_address, ea), description);
+ else
+ printf(" HW Address: %s\n", ether_addr_to_string(&info->mac_address, ea));
+ }
+
+ if (info->has_mtu)
+ printf(" MTU: %" PRIu32 "\n", info->mtu);
+
+ (void) dump_addresses(rtnl, " Address: ", info->ifindex);
+ (void) dump_gateways(rtnl, hwdb, " Gateway: ", info->ifindex);
+
+ dump_list(" DNS: ", dns);
+ dump_list(" Search Domains: ", search_domains);
+ dump_list(" Route Domains: ", route_domains);
+
+ dump_list(" NTP: ", ntp);
+
+ dump_ifindexes("Carrier Bound To: ", carrier_bound_to);
+ dump_ifindexes("Carrier Bound By: ", carrier_bound_by);
+
+ (void) sd_network_link_get_timezone(info->ifindex, &tz);
+ if (tz)
+ printf(" Time Zone: %s\n", tz);
+
+ (void) dump_lldp_neighbors(" Connected To: ", info->ifindex);
+
+ return 0;
+}
+
+static int system_status(sd_netlink *rtnl, sd_hwdb *hwdb) {
+ _cleanup_free_ char *operational_state = NULL;
+ _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **search_domains = NULL, **route_domains = NULL;
+ const char *on_color_operational, *off_color_operational;
+
+ assert(rtnl);
+
+ (void) sd_network_get_operational_state(&operational_state);
+ operational_state_to_color(operational_state, &on_color_operational, &off_color_operational);
+
+ printf("%s%s%s State: %s%s%s\n",
+ on_color_operational, special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE), off_color_operational,
+ on_color_operational, strna(operational_state), off_color_operational);
+
+ (void) dump_addresses(rtnl, " Address: ", 0);
+ (void) dump_gateways(rtnl, hwdb, " Gateway: ", 0);
+
+ (void) sd_network_get_dns(&dns);
+ dump_list(" DNS: ", dns);
+
+ (void) sd_network_get_search_domains(&search_domains);
+ dump_list("Search Domains: ", search_domains);
+
+ (void) sd_network_get_route_domains(&route_domains);
+ dump_list(" Route Domains: ", route_domains);
+
+ (void) sd_network_get_ntp(&ntp);
+ dump_list(" NTP: ", ntp);
+
+ return 0;
+}
+
+static int link_status(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+ _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL;
+ _cleanup_free_ LinkInfo *links = NULL;
+ int r, c, i;
+
+ (void) pager_open(arg_pager_flags);
+
+ r = sd_netlink_open(&rtnl);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to netlink: %m");
+
+ r = sd_hwdb_new(&hwdb);
+ if (r < 0)
+ log_debug_errno(r, "Failed to open hardware database: %m");
+
+ if (arg_all)
+ c = acquire_link_info_all(rtnl, &links);
+ else if (argc <= 1)
+ return system_status(rtnl, hwdb);
+ else
+ c = acquire_link_info_strv(rtnl, argv + 1, &links);
+ if (c < 0)
+ return c;
+
+ for (i = 0; i < c; i++) {
+ if (i > 0)
+ fputc('\n', stdout);
+
+ link_status_one(rtnl, hwdb, links + i);
+ }
+
+ return 0;
+}
+
+static char *lldp_capabilities_to_string(uint16_t x) {
+ static const char characters[] = {
+ 'o', 'p', 'b', 'w', 'r', 't', 'd', 'a', 'c', 's', 'm',
+ };
+ char *ret;
+ unsigned i;
+
+ ret = new(char, ELEMENTSOF(characters) + 1);
+ if (!ret)
+ return NULL;
+
+ for (i = 0; i < ELEMENTSOF(characters); i++)
+ ret[i] = (x & (1U << i)) ? characters[i] : '.';
+
+ ret[i] = 0;
+ return ret;
+}
+
+static void lldp_capabilities_legend(uint16_t x) {
+ unsigned w, i, cols = columns();
+ static const char* const table[] = {
+ "o - Other",
+ "p - Repeater",
+ "b - Bridge",
+ "w - WLAN Access Point",
+ "r - Router",
+ "t - Telephone",
+ "d - DOCSIS cable device",
+ "a - Station",
+ "c - Customer VLAN",
+ "s - Service VLAN",
+ "m - Two-port MAC Relay (TPMR)",
+ };
+
+ if (x == 0)
+ return;
+
+ printf("\nCapability Flags:\n");
+ for (w = 0, i = 0; i < ELEMENTSOF(table); i++)
+ if (x & (1U << i) || arg_all) {
+ bool newline;
+
+ newline = w + strlen(table[i]) + (w == 0 ? 0 : 2) > cols;
+ if (newline)
+ w = 0;
+ w += printf("%s%s%s", newline ? "\n" : "", w == 0 ? "" : "; ", table[i]);
+ }
+ puts("");
+}
+
+static int link_lldp_status(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+ _cleanup_free_ LinkInfo *links = NULL;
+ int i, r, c, m = 0;
+ uint16_t all = 0;
+
+ r = sd_netlink_open(&rtnl);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to netlink: %m");
+
+ if (argc > 1)
+ c = acquire_link_info_strv(rtnl, argv + 1, &links);
+ else
+ c = acquire_link_info_all(rtnl, &links);
+ if (c < 0)
+ return c;
+
+ (void) pager_open(arg_pager_flags);
+
+ if (arg_legend)
+ printf("%-16s %-17s %-16s %-11s %-17s %-16s\n",
+ "LINK",
+ "CHASSIS ID",
+ "SYSTEM NAME",
+ "CAPS",
+ "PORT ID",
+ "PORT DESCRIPTION");
+
+ for (i = 0; i < c; i++) {
+ _cleanup_fclose_ FILE *f = NULL;
+
+ r = open_lldp_neighbors(links[i].ifindex, &f);
+ if (r == -ENOENT)
+ continue;
+ if (r < 0) {
+ log_warning_errno(r, "Failed to open LLDP data for %i, ignoring: %m", links[i].ifindex);
+ continue;
+ }
+
+ for (;;) {
+ _cleanup_free_ char *cid = NULL, *pid = NULL, *sname = NULL, *pdesc = NULL;
+ const char *chassis_id = NULL, *port_id = NULL, *system_name = NULL, *port_description = NULL, *capabilities = NULL;
+ _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
+ uint16_t cc;
+
+ r = next_lldp_neighbor(f, &n);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to read neighbor data: %m");
+ break;
+ }
+ if (r == 0)
+ break;
+
+ (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);
+
+ if (chassis_id) {
+ cid = ellipsize(chassis_id, 17, 100);
+ if (cid)
+ chassis_id = cid;
+ }
+
+ if (port_id) {
+ pid = ellipsize(port_id, 17, 100);
+ if (pid)
+ port_id = pid;
+ }
+
+ if (system_name) {
+ sname = ellipsize(system_name, 16, 100);
+ if (sname)
+ system_name = sname;
+ }
+
+ if (port_description) {
+ pdesc = ellipsize(port_description, 16, 100);
+ if (pdesc)
+ port_description = pdesc;
+ }
+
+ if (sd_lldp_neighbor_get_enabled_capabilities(n, &cc) >= 0) {
+ capabilities = lldp_capabilities_to_string(cc);
+ all |= cc;
+ }
+
+ printf("%-16s %-17s %-16s %-11s %-17s %-16s\n",
+ links[i].name,
+ strna(chassis_id),
+ strna(system_name),
+ strna(capabilities),
+ strna(port_id),
+ strna(port_description));
+
+ m++;
+ }
+ }
+
+ if (arg_legend) {
+ lldp_capabilities_legend(all);
+ printf("\n%i neighbors listed.\n", m);
+ }
+
+ return 0;
+}
+
+static int help(void) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("networkctl", "1", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%s [OPTIONS...]\n\n"
+ "Query and control the networking subsystem.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --no-pager Do not pipe output into a pager\n"
+ " --no-legend Do not show the headers and footers\n"
+ " -a --all Show status for all links\n\n"
+ "Commands:\n"
+ " list [LINK...] List links\n"
+ " status [LINK...] Show link status\n"
+ " lldp [LINK...] Show LLDP neighbors\n"
+ " label Show current address label entries in the kernel\n"
+ "\nSee the %s for details.\n"
+ , program_invocation_short_name
+ , link
+ );
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_NO_PAGER,
+ ARG_NO_LEGEND,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
+ { "all", no_argument, NULL, 'a' },
+ {}
+ };
+
+ int c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "ha", options, NULL)) >= 0) {
+
+ switch (c) {
+
+ case 'h':
+ return help();
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_NO_PAGER:
+ arg_pager_flags |= PAGER_DISABLE;
+ break;
+
+ case ARG_NO_LEGEND:
+ arg_legend = false;
+ break;
+
+ case 'a':
+ arg_all = true;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option");
+ }
+ }
+
+ return 1;
+}
+
+static int networkctl_main(int argc, char *argv[]) {
+ static const Verb verbs[] = {
+ { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT, list_links },
+ { "status", VERB_ANY, VERB_ANY, 0, link_status },
+ { "lldp", VERB_ANY, VERB_ANY, 0, link_lldp_status },
+ { "label", VERB_ANY, VERB_ANY, 0, list_address_labels },
+ {}
+ };
+
+ return dispatch_verb(argc, argv, verbs, NULL);
+}
+
+static void warn_networkd_missing(void) {
+
+ if (access("/run/systemd/netif/state", F_OK) >= 0)
+ return;
+
+ fprintf(stderr, "WARNING: systemd-networkd is not running, output will be incomplete.\n\n");
+}
+
+static int run(int argc, char* argv[]) {
+ int r;
+
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ warn_networkd_missing();
+
+ return networkctl_main(argc, argv);
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/network/networkd-address-label.c b/src/network/networkd-address-label.c
new file mode 100644
index 0000000..94a12f8
--- /dev/null
+++ b/src/network/networkd-address-label.c
@@ -0,0 +1,229 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <net/if.h>
+#include <linux/if_addrlabel.h>
+
+#include "alloc-util.h"
+#include "conf-parser.h"
+#include "networkd-address-label.h"
+#include "netlink-util.h"
+#include "networkd-manager.h"
+#include "parse-util.h"
+#include "socket-util.h"
+
+void address_label_free(AddressLabel *label) {
+ if (!label)
+ return;
+
+ if (label->network) {
+ LIST_REMOVE(labels, label->network->address_labels, label);
+ assert(label->network->n_address_labels > 0);
+ label->network->n_address_labels--;
+
+ if (label->section) {
+ hashmap_remove(label->network->address_labels_by_section, label->section);
+ network_config_section_free(label->section);
+ }
+ }
+
+ free(label);
+}
+
+static int address_label_new_static(Network *network, const char *filename, unsigned section_line, AddressLabel **ret) {
+ _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
+ _cleanup_(address_label_freep) AddressLabel *label = NULL;
+ int r;
+
+ assert(network);
+ assert(ret);
+ assert(!!filename == (section_line > 0));
+
+ if (filename) {
+ r = network_config_section_new(filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ label = hashmap_get(network->address_labels_by_section, n);
+ if (label) {
+ *ret = TAKE_PTR(label);
+
+ return 0;
+ }
+ }
+
+ label = new(AddressLabel, 1);
+ if (!label)
+ return -ENOMEM;
+
+ *label = (AddressLabel) {
+ .network = network,
+ };
+
+ LIST_APPEND(labels, network->address_labels, label);
+ network->n_address_labels++;
+
+ if (filename) {
+ label->section = TAKE_PTR(n);
+
+ r = hashmap_ensure_allocated(&network->address_labels_by_section, &network_config_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(network->address_labels_by_section, label->section, label);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(label);
+
+ return 0;
+}
+
+static int address_label_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(rtnl);
+ assert(m);
+ assert(link);
+ assert(link->ifname);
+ assert(link->address_label_messages > 0);
+
+ link->address_label_messages--;
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST)
+ log_link_warning_errno(link, r, "could not set address label: %m");
+ else if (r >= 0)
+ manager_rtnl_process_address(rtnl, m, link->manager);
+
+ if (link->address_label_messages == 0)
+ log_link_debug(link, "Addresses label set");
+
+ return 1;
+}
+
+int address_label_configure(
+ AddressLabel *label,
+ Link *link,
+ link_netlink_message_handler_t callback,
+ bool update) {
+
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ int r;
+
+ assert(label);
+ assert(link);
+ assert(link->ifindex > 0);
+ assert(link->manager);
+ assert(link->manager->rtnl);
+
+ r = sd_rtnl_message_new_addrlabel(link->manager->rtnl, &req, RTM_NEWADDRLABEL,
+ link->ifindex, AF_INET6);
+ if (r < 0)
+ return log_error_errno(r, "Could not allocate RTM_NEWADDR message: %m");
+
+ r = sd_rtnl_message_addrlabel_set_prefixlen(req, label->prefixlen);
+ if (r < 0)
+ return log_error_errno(r, "Could not set prefixlen: %m");
+
+ r = sd_netlink_message_append_u32(req, IFAL_LABEL, label->label);
+ if (r < 0)
+ return log_error_errno(r, "Could not append IFAL_LABEL attribute: %m");
+
+ r = sd_netlink_message_append_in6_addr(req, IFA_ADDRESS, &label->in_addr.in6);
+ if (r < 0)
+ return log_error_errno(r, "Could not append IFA_ADDRESS attribute: %m");
+
+ r = netlink_call_async(link->manager->rtnl, NULL, req,
+ callback ?: address_label_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_error_errno(r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ return 0;
+}
+
+int config_parse_address_label_prefix(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ _cleanup_(address_label_freep) AddressLabel *n = NULL;
+ Network *network = userdata;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = address_label_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ r = in_addr_prefix_from_string(rvalue, AF_INET6, &n->in_addr, &n->prefixlen);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Address label is invalid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ n = NULL;
+
+ return 0;
+}
+
+int config_parse_address_label(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ _cleanup_(address_label_freep) AddressLabel *n = NULL;
+ Network *network = userdata;
+ uint32_t k;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = address_label_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ r = safe_atou32(rvalue, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address label, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ if (k == 0xffffffffUL) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Address label is invalid, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ n->label = k;
+ n = NULL;
+
+ return 0;
+}
diff --git a/src/network/networkd-address-label.h b/src/network/networkd-address-label.h
new file mode 100644
index 0000000..6922cb0
--- /dev/null
+++ b/src/network/networkd-address-label.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include "conf-parser.h"
+#include "in-addr-util.h"
+
+typedef struct AddressLabel AddressLabel;
+
+#include "networkd-link.h"
+#include "networkd-network.h"
+
+typedef struct Network Network;
+typedef struct Link Link;
+typedef struct NetworkConfigSection NetworkConfigSection;
+
+struct AddressLabel {
+ Network *network;
+ NetworkConfigSection *section;
+
+ unsigned char prefixlen;
+ uint32_t label;
+
+ union in_addr_union in_addr;
+
+ LIST_FIELDS(AddressLabel, labels);
+};
+
+void address_label_free(AddressLabel *label);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(AddressLabel*, address_label_free);
+
+int address_label_configure(AddressLabel *address, Link *link, link_netlink_message_handler_t callback, bool update);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_address_label);
+CONFIG_PARSER_PROTOTYPE(config_parse_address_label_prefix);
diff --git a/src/network/networkd-address-pool.c b/src/network/networkd-address-pool.c
new file mode 100644
index 0000000..1650515
--- /dev/null
+++ b/src/network/networkd-address-pool.c
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "alloc-util.h"
+#include "networkd-address-pool.h"
+#include "networkd-manager.h"
+#include "set.h"
+#include "string-util.h"
+
+int address_pool_new(
+ Manager *m,
+ AddressPool **ret,
+ int family,
+ const union in_addr_union *u,
+ unsigned prefixlen) {
+
+ AddressPool *p;
+
+ assert(m);
+ assert(ret);
+ assert(u);
+
+ p = new(AddressPool, 1);
+ if (!p)
+ return -ENOMEM;
+
+ *p = (AddressPool) {
+ .manager = m,
+ .family = family,
+ .prefixlen = prefixlen,
+ .in_addr = *u,
+ };
+
+ LIST_PREPEND(address_pools, m->address_pools, p);
+
+ *ret = p;
+ return 0;
+}
+
+int address_pool_new_from_string(
+ Manager *m,
+ AddressPool **ret,
+ int family,
+ const char *p,
+ unsigned prefixlen) {
+
+ union in_addr_union u;
+ int r;
+
+ assert(m);
+ assert(ret);
+ assert(p);
+
+ r = in_addr_from_string(family, p, &u);
+ if (r < 0)
+ return r;
+
+ return address_pool_new(m, ret, family, &u, prefixlen);
+}
+
+void address_pool_free(AddressPool *p) {
+
+ if (!p)
+ return;
+
+ if (p->manager)
+ LIST_REMOVE(address_pools, p->manager->address_pools, p);
+
+ free(p);
+}
+
+static bool address_pool_prefix_is_taken(
+ AddressPool *p,
+ const union in_addr_union *u,
+ unsigned prefixlen) {
+
+ Iterator i;
+ Link *l;
+ Network *n;
+
+ assert(p);
+ assert(u);
+
+ HASHMAP_FOREACH(l, p->manager->links, i) {
+ Address *a;
+ Iterator j;
+
+ /* Don't clash with assigned addresses */
+ SET_FOREACH(a, l->addresses, j) {
+ if (a->family != p->family)
+ continue;
+
+ if (in_addr_prefix_intersect(p->family, u, prefixlen, &a->in_addr, a->prefixlen))
+ return true;
+ }
+
+ /* Don't clash with addresses already pulled from the pool, but not assigned yet */
+ LIST_FOREACH(addresses, a, l->pool_addresses) {
+ if (a->family != p->family)
+ continue;
+
+ if (in_addr_prefix_intersect(p->family, u, prefixlen, &a->in_addr, a->prefixlen))
+ return true;
+ }
+ }
+
+ /* And don't clash with configured but un-assigned addresses either */
+ LIST_FOREACH(networks, n, p->manager->networks) {
+ Address *a;
+
+ LIST_FOREACH(addresses, a, n->static_addresses) {
+ if (a->family != p->family)
+ continue;
+
+ if (in_addr_prefix_intersect(p->family, u, prefixlen, &a->in_addr, a->prefixlen))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int address_pool_acquire(AddressPool *p, unsigned prefixlen, union in_addr_union *found) {
+ union in_addr_union u;
+
+ assert(p);
+ assert(prefixlen > 0);
+ assert(found);
+
+ if (p->prefixlen > prefixlen)
+ return 0;
+
+ u = p->in_addr;
+ for (;;) {
+ if (!address_pool_prefix_is_taken(p, &u, prefixlen)) {
+ _cleanup_free_ char *s = NULL;
+ int r;
+
+ r = in_addr_to_string(p->family, &u, &s);
+ if (r < 0)
+ return r;
+
+ log_debug("Found range %s/%u", strna(s), prefixlen);
+
+ *found = u;
+ return 1;
+ }
+
+ if (!in_addr_prefix_next(p->family, &u, prefixlen))
+ return 0;
+
+ if (!in_addr_prefix_intersect(p->family, &p->in_addr, p->prefixlen, &u, prefixlen))
+ return 0;
+ }
+
+ return 0;
+}
diff --git a/src/network/networkd-address-pool.h b/src/network/networkd-address-pool.h
new file mode 100644
index 0000000..bd479a5
--- /dev/null
+++ b/src/network/networkd-address-pool.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+typedef struct AddressPool AddressPool;
+
+#include "in-addr-util.h"
+#include "list.h"
+
+typedef struct Manager Manager;
+
+struct AddressPool {
+ Manager *manager;
+
+ int family;
+ unsigned prefixlen;
+
+ union in_addr_union in_addr;
+
+ LIST_FIELDS(AddressPool, address_pools);
+};
+
+int address_pool_new(Manager *m, AddressPool **ret, int family, const union in_addr_union *u, unsigned prefixlen);
+int address_pool_new_from_string(Manager *m, AddressPool **ret, int family, const char *p, unsigned prefixlen);
+void address_pool_free(AddressPool *p);
+
+int address_pool_acquire(AddressPool *p, unsigned prefixlen, union in_addr_union *found);
diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c
new file mode 100644
index 0000000..3cdbd9e
--- /dev/null
+++ b/src/network/networkd-address.c
@@ -0,0 +1,969 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <net/if.h>
+
+#include "alloc-util.h"
+#include "conf-parser.h"
+#include "firewall-util.h"
+#include "missing_network.h"
+#include "netlink-util.h"
+#include "networkd-address.h"
+#include "networkd-manager.h"
+#include "parse-util.h"
+#include "set.h"
+#include "socket-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "utf8.h"
+#include "util.h"
+
+#define ADDRESSES_PER_LINK_MAX 2048U
+#define STATIC_ADDRESSES_PER_NETWORK_MAX 1024U
+
+int address_new(Address **ret) {
+ _cleanup_(address_freep) Address *address = NULL;
+
+ address = new(Address, 1);
+ if (!address)
+ return -ENOMEM;
+
+ *address = (Address) {
+ .family = AF_UNSPEC,
+ .scope = RT_SCOPE_UNIVERSE,
+ .cinfo.ifa_prefered = CACHE_INFO_INFINITY_LIFE_TIME,
+ .cinfo.ifa_valid = CACHE_INFO_INFINITY_LIFE_TIME,
+ };
+
+ *ret = TAKE_PTR(address);
+
+ return 0;
+}
+
+int address_new_static(Network *network, const char *filename, unsigned section_line, Address **ret) {
+ _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
+ _cleanup_(address_freep) Address *address = NULL;
+ int r;
+
+ assert(network);
+ assert(ret);
+ assert(!!filename == (section_line > 0));
+
+ if (filename) {
+ r = network_config_section_new(filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ address = hashmap_get(network->addresses_by_section, n);
+ if (address) {
+ *ret = TAKE_PTR(address);
+
+ return 0;
+ }
+ }
+
+ if (network->n_static_addresses >= STATIC_ADDRESSES_PER_NETWORK_MAX)
+ return -E2BIG;
+
+ r = address_new(&address);
+ if (r < 0)
+ return r;
+
+ address->network = network;
+ LIST_APPEND(addresses, network->static_addresses, address);
+ network->n_static_addresses++;
+
+ if (filename) {
+ address->section = TAKE_PTR(n);
+
+ r = hashmap_ensure_allocated(&network->addresses_by_section, &network_config_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(network->addresses_by_section, address->section, address);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(address);
+
+ return 0;
+}
+
+void address_free(Address *address) {
+ if (!address)
+ return;
+
+ if (address->network) {
+ LIST_REMOVE(addresses, address->network->static_addresses, address);
+ assert(address->network->n_static_addresses > 0);
+ address->network->n_static_addresses--;
+
+ if (address->section)
+ hashmap_remove(address->network->addresses_by_section, address->section);
+ }
+
+ if (address->link) {
+ set_remove(address->link->addresses, address);
+ set_remove(address->link->addresses_foreign, address);
+
+ if (in_addr_equal(AF_INET6, &address->in_addr, (const union in_addr_union *) &address->link->ipv6ll_address))
+ memzero(&address->link->ipv6ll_address, sizeof(struct in6_addr));
+ }
+
+ network_config_section_free(address->section);
+ free(address->label);
+ free(address);
+}
+
+static void address_hash_func(const Address *a, struct siphash *state) {
+ assert(a);
+
+ siphash24_compress(&a->family, sizeof(a->family), state);
+
+ switch (a->family) {
+ case AF_INET:
+ siphash24_compress(&a->prefixlen, sizeof(a->prefixlen), state);
+
+ /* peer prefix */
+ if (a->prefixlen != 0) {
+ uint32_t prefix;
+
+ if (a->in_addr_peer.in.s_addr != 0)
+ prefix = be32toh(a->in_addr_peer.in.s_addr) >> (32 - a->prefixlen);
+ else
+ prefix = be32toh(a->in_addr.in.s_addr) >> (32 - a->prefixlen);
+
+ siphash24_compress(&prefix, sizeof(prefix), state);
+ }
+
+ _fallthrough_;
+ case AF_INET6:
+ /* local address */
+ siphash24_compress(&a->in_addr, FAMILY_ADDRESS_SIZE(a->family), state);
+
+ break;
+ default:
+ /* treat any other address family as AF_UNSPEC */
+ break;
+ }
+}
+
+static int address_compare_func(const Address *a1, const Address *a2) {
+ int r;
+
+ r = CMP(a1->family, a2->family);
+ if (r != 0)
+ return r;
+
+ switch (a1->family) {
+ /* use the same notion of equality as the kernel does */
+ case AF_INET:
+ r = CMP(a1->prefixlen, a2->prefixlen);
+ if (r != 0)
+ return r;
+
+ /* compare the peer prefixes */
+ if (a1->prefixlen != 0) {
+ /* make sure we don't try to shift by 32.
+ * See ISO/IEC 9899:TC3 § 6.5.7.3. */
+ uint32_t b1, b2;
+
+ if (a1->in_addr_peer.in.s_addr != 0)
+ b1 = be32toh(a1->in_addr_peer.in.s_addr) >> (32 - a1->prefixlen);
+ else
+ b1 = be32toh(a1->in_addr.in.s_addr) >> (32 - a1->prefixlen);
+
+ if (a2->in_addr_peer.in.s_addr != 0)
+ b2 = be32toh(a2->in_addr_peer.in.s_addr) >> (32 - a1->prefixlen);
+ else
+ b2 = be32toh(a2->in_addr.in.s_addr) >> (32 - a1->prefixlen);
+
+ r = CMP(b1, b2);
+ if (r != 0)
+ return r;
+ }
+
+ _fallthrough_;
+ case AF_INET6:
+ return memcmp(&a1->in_addr, &a2->in_addr, FAMILY_ADDRESS_SIZE(a1->family));
+ default:
+ /* treat any other address family as AF_UNSPEC */
+ return 0;
+ }
+}
+
+DEFINE_PRIVATE_HASH_OPS(address_hash_ops, Address, address_hash_func, address_compare_func);
+
+bool address_equal(Address *a1, Address *a2) {
+ if (a1 == a2)
+ return true;
+
+ if (!a1 || !a2)
+ return false;
+
+ return address_compare_func(a1, a2) == 0;
+}
+
+static int address_establish(Address *address, Link *link) {
+ bool masq;
+ int r;
+
+ assert(address);
+ assert(link);
+
+ masq = link->network &&
+ link->network->ip_masquerade &&
+ address->family == AF_INET &&
+ address->scope < RT_SCOPE_LINK;
+
+ /* Add firewall entry if this is requested */
+ if (address->ip_masquerade_done != masq) {
+ union in_addr_union masked = address->in_addr;
+ in_addr_mask(address->family, &masked, address->prefixlen);
+
+ r = fw_add_masquerade(masq, AF_INET, 0, &masked, address->prefixlen, NULL, NULL, 0);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Could not enable IP masquerading: %m");
+
+ address->ip_masquerade_done = masq;
+ }
+
+ return 0;
+}
+
+static int address_add_internal(Link *link, Set **addresses,
+ int family,
+ const union in_addr_union *in_addr,
+ unsigned char prefixlen,
+ Address **ret) {
+ _cleanup_(address_freep) Address *address = NULL;
+ int r;
+
+ assert(link);
+ assert(addresses);
+ assert(in_addr);
+
+ r = address_new(&address);
+ if (r < 0)
+ return r;
+
+ address->family = family;
+ address->in_addr = *in_addr;
+ address->prefixlen = prefixlen;
+ /* Consider address tentative until we get the real flags from the kernel */
+ address->flags = IFA_F_TENTATIVE;
+
+ r = set_ensure_allocated(addresses, &address_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = set_put(*addresses, address);
+ if (r < 0)
+ return r;
+
+ address->link = link;
+
+ if (ret)
+ *ret = address;
+
+ address = NULL;
+
+ return 0;
+}
+
+int address_add_foreign(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret) {
+ return address_add_internal(link, &link->addresses_foreign, family, in_addr, prefixlen, ret);
+}
+
+int address_add(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret) {
+ Address *address;
+ int r;
+
+ r = address_get(link, family, in_addr, prefixlen, &address);
+ if (r == -ENOENT) {
+ /* Address does not exist, create a new one */
+ r = address_add_internal(link, &link->addresses, family, in_addr, prefixlen, &address);
+ if (r < 0)
+ return r;
+ } else if (r == 0) {
+ /* Take over a foreign address */
+ r = set_ensure_allocated(&link->addresses, &address_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = set_put(link->addresses, address);
+ if (r < 0)
+ return r;
+
+ set_remove(link->addresses_foreign, address);
+ } else if (r == 1) {
+ /* Already exists, do nothing */
+ ;
+ } else
+ return r;
+
+ if (ret)
+ *ret = address;
+
+ return 0;
+}
+
+static int address_release(Address *address) {
+ int r;
+
+ assert(address);
+ assert(address->link);
+
+ /* Remove masquerading firewall entry if it was added */
+ if (address->ip_masquerade_done) {
+ union in_addr_union masked = address->in_addr;
+ in_addr_mask(address->family, &masked, address->prefixlen);
+
+ r = fw_add_masquerade(false, AF_INET, 0, &masked, address->prefixlen, NULL, NULL, 0);
+ if (r < 0)
+ log_link_warning_errno(address->link, r, "Failed to disable IP masquerading: %m");
+
+ address->ip_masquerade_done = false;
+ }
+
+ return 0;
+}
+
+int address_update(
+ Address *address,
+ unsigned char flags,
+ unsigned char scope,
+ const struct ifa_cacheinfo *cinfo) {
+
+ bool ready;
+ int r;
+
+ assert(address);
+ assert(cinfo);
+ assert_return(address->link, 1);
+
+ if (IN_SET(address->link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ ready = address_is_ready(address);
+
+ address->flags = flags;
+ address->scope = scope;
+ address->cinfo = *cinfo;
+
+ link_update_operstate(address->link);
+ link_check_ready(address->link);
+
+ if (!ready &&
+ address_is_ready(address) &&
+ address->family == AF_INET6 &&
+ in_addr_is_link_local(AF_INET6, &address->in_addr) > 0 &&
+ in_addr_is_null(AF_INET6, (const union in_addr_union*) &address->link->ipv6ll_address) > 0) {
+
+ r = link_ipv6ll_gained(address->link, &address->in_addr.in6);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int address_drop(Address *address) {
+ Link *link;
+ bool ready;
+
+ assert(address);
+
+ ready = address_is_ready(address);
+ link = address->link;
+
+ address_release(address);
+ address_free(address);
+
+ link_update_operstate(link);
+
+ if (link && !ready)
+ link_check_ready(link);
+
+ return 0;
+}
+
+int address_get(Link *link,
+ int family,
+ const union in_addr_union *in_addr,
+ unsigned char prefixlen,
+ Address **ret) {
+
+ Address address, *existing;
+
+ assert(link);
+ assert(in_addr);
+
+ address = (Address) {
+ .family = family,
+ .in_addr = *in_addr,
+ .prefixlen = prefixlen,
+ };
+
+ existing = set_get(link->addresses, &address);
+ if (existing) {
+ if (ret)
+ *ret = existing;
+ return 1;
+ }
+
+ existing = set_get(link->addresses_foreign, &address);
+ if (existing) {
+ if (ret)
+ *ret = existing;
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+static int address_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(m);
+ assert(link);
+ assert(link->ifname);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EADDRNOTAVAIL)
+ log_link_warning_errno(link, r, "Could not drop address: %m");
+
+ return 1;
+}
+
+int address_remove(
+ Address *address,
+ Link *link,
+ link_netlink_message_handler_t callback) {
+
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ _cleanup_free_ char *b = NULL;
+ int r;
+
+ assert(address);
+ assert(IN_SET(address->family, AF_INET, AF_INET6));
+ assert(link);
+ assert(link->ifindex > 0);
+ assert(link->manager);
+ assert(link->manager->rtnl);
+
+ if (DEBUG_LOGGING) {
+ if (in_addr_to_string(address->family, &address->in_addr, &b) >= 0)
+ log_link_debug(link, "Removing address %s", b);
+ }
+
+ r = sd_rtnl_message_new_addr(link->manager->rtnl, &req, RTM_DELADDR,
+ link->ifindex, address->family);
+ if (r < 0)
+ return log_error_errno(r, "Could not allocate RTM_DELADDR message: %m");
+
+ r = sd_rtnl_message_addr_set_prefixlen(req, address->prefixlen);
+ if (r < 0)
+ return log_error_errno(r, "Could not set prefixlen: %m");
+
+ if (address->family == AF_INET)
+ r = sd_netlink_message_append_in_addr(req, IFA_LOCAL, &address->in_addr.in);
+ else if (address->family == AF_INET6)
+ r = sd_netlink_message_append_in6_addr(req, IFA_LOCAL, &address->in_addr.in6);
+ if (r < 0)
+ return log_error_errno(r, "Could not append IFA_LOCAL attribute: %m");
+
+ r = netlink_call_async(link->manager->rtnl, NULL, req,
+ callback ?: address_remove_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_error_errno(r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ return 0;
+}
+
+static int address_acquire(Link *link, Address *original, Address **ret) {
+ union in_addr_union in_addr = {};
+ struct in_addr broadcast = {};
+ _cleanup_(address_freep) Address *na = NULL;
+ int r;
+
+ assert(link);
+ assert(original);
+ assert(ret);
+
+ /* Something useful was configured? just use it */
+ if (in_addr_is_null(original->family, &original->in_addr) <= 0)
+ return 0;
+
+ /* 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 = manager_address_pool_acquire(link->manager, original->family, original->prefixlen, &in_addr);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to acquire address from pool: %m");
+ if (r == 0) {
+ log_link_error(link, "Couldn't find free address for interface, all taken.");
+ return -EBUSY;
+ }
+
+ if (original->family == AF_INET) {
+ /* Pick first address in range for ourselves ... */
+ in_addr.in.s_addr = in_addr.in.s_addr | htobe32(1);
+
+ /* .. and use last as broadcast address */
+ if (original->prefixlen > 30)
+ broadcast.s_addr = 0;
+ else
+ broadcast.s_addr = in_addr.in.s_addr | htobe32(0xFFFFFFFFUL >> original->prefixlen);
+ } else if (original->family == AF_INET6)
+ in_addr.in6.s6_addr[15] |= 1;
+
+ r = address_new(&na);
+ if (r < 0)
+ return r;
+
+ na->family = original->family;
+ na->prefixlen = original->prefixlen;
+ na->scope = original->scope;
+ na->cinfo = original->cinfo;
+
+ if (original->label) {
+ na->label = strdup(original->label);
+ if (!na->label)
+ return -ENOMEM;
+ }
+
+ na->broadcast = broadcast;
+ na->in_addr = in_addr;
+
+ LIST_PREPEND(addresses, link->pool_addresses, na);
+
+ *ret = TAKE_PTR(na);
+
+ return 0;
+}
+
+int address_configure(
+ Address *address,
+ Link *link,
+ link_netlink_message_handler_t callback,
+ bool update) {
+
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ int r;
+
+ assert(address);
+ assert(IN_SET(address->family, AF_INET, AF_INET6));
+ assert(link);
+ assert(link->ifindex > 0);
+ assert(link->manager);
+ assert(link->manager->rtnl);
+ assert(callback);
+
+ /* If this is a new address, then refuse adding more than the limit */
+ if (address_get(link, address->family, &address->in_addr, address->prefixlen, NULL) <= 0 &&
+ set_size(link->addresses) >= ADDRESSES_PER_LINK_MAX)
+ return -E2BIG;
+
+ r = address_acquire(link, address, &address);
+ if (r < 0)
+ return r;
+
+ if (update)
+ r = sd_rtnl_message_new_addr_update(link->manager->rtnl, &req,
+ link->ifindex, address->family);
+ else
+ r = sd_rtnl_message_new_addr(link->manager->rtnl, &req, RTM_NEWADDR,
+ link->ifindex, address->family);
+ if (r < 0)
+ return log_error_errno(r, "Could not allocate RTM_NEWADDR message: %m");
+
+ r = sd_rtnl_message_addr_set_prefixlen(req, address->prefixlen);
+ if (r < 0)
+ return log_error_errno(r, "Could not set prefixlen: %m");
+
+ address->flags |= IFA_F_PERMANENT;
+
+ if (address->home_address)
+ address->flags |= IFA_F_HOMEADDRESS;
+
+ if (address->duplicate_address_detection)
+ address->flags |= IFA_F_NODAD;
+
+ if (address->manage_temporary_address)
+ address->flags |= IFA_F_MANAGETEMPADDR;
+
+ if (address->prefix_route)
+ address->flags |= IFA_F_NOPREFIXROUTE;
+
+ if (address->autojoin)
+ address->flags |= IFA_F_MCAUTOJOIN;
+
+ r = sd_rtnl_message_addr_set_flags(req, (address->flags & 0xff));
+ if (r < 0)
+ return log_error_errno(r, "Could not set flags: %m");
+
+ if (address->flags & ~0xff) {
+ r = sd_netlink_message_append_u32(req, IFA_FLAGS, address->flags);
+ if (r < 0)
+ return log_error_errno(r, "Could not set extended flags: %m");
+ }
+
+ r = sd_rtnl_message_addr_set_scope(req, address->scope);
+ if (r < 0)
+ return log_error_errno(r, "Could not set scope: %m");
+
+ if (address->family == AF_INET)
+ r = sd_netlink_message_append_in_addr(req, IFA_LOCAL, &address->in_addr.in);
+ else if (address->family == AF_INET6)
+ r = sd_netlink_message_append_in6_addr(req, IFA_LOCAL, &address->in_addr.in6);
+ if (r < 0)
+ return log_error_errno(r, "Could not append IFA_LOCAL attribute: %m");
+
+ if (!in_addr_is_null(address->family, &address->in_addr_peer)) {
+ if (address->family == AF_INET)
+ r = sd_netlink_message_append_in_addr(req, IFA_ADDRESS, &address->in_addr_peer.in);
+ else if (address->family == AF_INET6)
+ r = sd_netlink_message_append_in6_addr(req, IFA_ADDRESS, &address->in_addr_peer.in6);
+ if (r < 0)
+ return log_error_errno(r, "Could not append IFA_ADDRESS attribute: %m");
+ } else if (address->family == AF_INET && address->prefixlen <= 30) {
+ r = sd_netlink_message_append_in_addr(req, IFA_BROADCAST, &address->broadcast);
+ if (r < 0)
+ return log_error_errno(r, "Could not append IFA_BROADCAST attribute: %m");
+ }
+
+ if (address->label) {
+ r = sd_netlink_message_append_string(req, IFA_LABEL, address->label);
+ if (r < 0)
+ return log_error_errno(r, "Could not append IFA_LABEL attribute: %m");
+ }
+
+ r = sd_netlink_message_append_cache_info(req, IFA_CACHEINFO, &address->cinfo);
+ if (r < 0)
+ return log_error_errno(r, "Could not append IFA_CACHEINFO attribute: %m");
+
+ r = address_establish(address, link);
+ if (r < 0)
+ return r;
+
+ r = netlink_call_async(link->manager->rtnl, NULL, req, callback, link_netlink_destroy_callback, link);
+ if (r < 0) {
+ address_release(address);
+ return log_error_errno(r, "Could not send rtnetlink message: %m");
+ }
+
+ link_ref(link);
+
+ if (address->family == AF_INET6 && !in_addr_is_null(address->family, &address->in_addr_peer))
+ r = address_add(link, address->family, &address->in_addr_peer, address->prefixlen, NULL);
+ else
+ r = address_add(link, address->family, &address->in_addr, address->prefixlen, NULL);
+ if (r < 0) {
+ address_release(address);
+ return log_error_errno(r, "Could not add address: %m");
+ }
+
+ return 0;
+}
+
+int config_parse_broadcast(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Network *network = userdata;
+ _cleanup_(address_freep) Address *n = NULL;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = address_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ if (n->family == AF_INET6) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Broadcast is not valid for IPv6 addresses, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ r = in_addr_from_string(AF_INET, rvalue, (union in_addr_union*) &n->broadcast);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Broadcast is invalid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ n->family = AF_INET;
+ n = NULL;
+
+ return 0;
+}
+
+int config_parse_address(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Network *network = userdata;
+ _cleanup_(address_freep) Address *n = NULL;
+ union in_addr_union buffer;
+ unsigned char prefixlen;
+ int r, f;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (streq(section, "Network")) {
+ /* we are not in an Address section, so treat
+ * this as the special '0' section */
+ r = address_new_static(network, NULL, 0, &n);
+ } else
+ r = address_new_static(network, filename, section_line, &n);
+
+ if (r < 0)
+ return r;
+
+ /* Address=address/prefixlen */
+ r = in_addr_prefix_from_string_auto_internal(rvalue, PREFIXLEN_REFUSE, &f, &buffer, &prefixlen);
+ if (r == -ENOANO) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "An address '%s' is specified without prefix length. "
+ "The behavior of parsing addresses without prefix length will be changed in the future release. "
+ "Please specify prefix length explicitly.", rvalue);
+
+ r = in_addr_prefix_from_string_auto_internal(rvalue, PREFIXLEN_LEGACY, &f, &buffer, &prefixlen);
+ }
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Invalid address '%s', ignoring assignment: %m", rvalue);
+ return 0;
+ }
+
+ if (n->family != AF_UNSPEC && f != n->family) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Address is incompatible, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ n->family = f;
+ n->prefixlen = prefixlen;
+
+ if (streq(lvalue, "Address"))
+ n->in_addr = buffer;
+ else
+ n->in_addr_peer = buffer;
+
+ if (n->family == AF_INET && n->broadcast.s_addr == 0)
+ n->broadcast.s_addr = n->in_addr.in.s_addr | htonl(0xfffffffflu >> n->prefixlen);
+
+ n = NULL;
+
+ return 0;
+}
+
+int config_parse_label(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ _cleanup_(address_freep) Address *n = NULL;
+ Network *network = userdata;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = address_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ if (!address_label_valid(rvalue)) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Interface label is too long or invalid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ r = free_and_strdup(&n->label, rvalue);
+ if (r < 0)
+ return log_oom();
+
+ n = NULL;
+
+ return 0;
+}
+
+int config_parse_lifetime(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Network *network = userdata;
+ _cleanup_(address_freep) Address *n = NULL;
+ unsigned k;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = address_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ if (STR_IN_SET(rvalue, "forever", "infinity")) {
+ n->cinfo.ifa_prefered = CACHE_INFO_INFINITY_LIFE_TIME;
+ n = NULL;
+
+ return 0;
+ }
+
+ r = safe_atou(rvalue, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse PreferredLifetime, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ if (k != 0)
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid PreferredLifetime value, ignoring: %d", k);
+ else {
+ n->cinfo.ifa_prefered = k;
+ n = NULL;
+ }
+
+ return 0;
+}
+
+int config_parse_address_flags(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Network *network = userdata;
+ _cleanup_(address_freep) Address *n = NULL;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = address_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ r = parse_boolean(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address flag, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ if (streq(lvalue, "HomeAddress"))
+ n->home_address = r;
+ else if (streq(lvalue, "DuplicateAddressDetection"))
+ n->duplicate_address_detection = r;
+ else if (streq(lvalue, "ManageTemporaryAddress"))
+ n->manage_temporary_address = r;
+ else if (streq(lvalue, "PrefixRoute"))
+ n->prefix_route = r;
+ else if (streq(lvalue, "AutoJoin"))
+ n->autojoin = r;
+
+ return 0;
+}
+
+int config_parse_address_scope(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Network *network = userdata;
+ _cleanup_(address_freep) Address *n = NULL;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = address_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ if (streq(rvalue, "host"))
+ n->scope = RT_SCOPE_HOST;
+ else if (streq(rvalue, "link"))
+ n->scope = RT_SCOPE_LINK;
+ else if (streq(rvalue, "global"))
+ n->scope = RT_SCOPE_UNIVERSE;
+ else {
+ r = safe_atou8(rvalue , &n->scope);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Could not parse address scope \"%s\", ignoring assignment: %m", rvalue);
+ return 0;
+ }
+ }
+
+ n = NULL;
+
+ return 0;
+}
+
+bool address_is_ready(const Address *a) {
+ assert(a);
+
+ if (a->family == AF_INET6)
+ return !(a->flags & IFA_F_TENTATIVE);
+ else
+ return !(a->flags & (IFA_F_TENTATIVE | IFA_F_DEPRECATED));
+}
diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h
new file mode 100644
index 0000000..4714c07
--- /dev/null
+++ b/src/network/networkd-address.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include "conf-parser.h"
+#include "in-addr-util.h"
+
+typedef struct Address Address;
+
+#include "networkd-link.h"
+#include "networkd-network.h"
+
+#define CACHE_INFO_INFINITY_LIFE_TIME 0xFFFFFFFFU
+
+typedef struct Network Network;
+typedef struct Link Link;
+typedef struct NetworkConfigSection NetworkConfigSection;
+
+struct Address {
+ Network *network;
+ NetworkConfigSection *section;
+
+ Link *link;
+
+ int family;
+ unsigned char prefixlen;
+ unsigned char scope;
+ uint32_t flags;
+ char *label;
+
+ struct in_addr broadcast;
+ struct ifa_cacheinfo cinfo;
+
+ union in_addr_union in_addr;
+ union in_addr_union in_addr_peer;
+
+ bool ip_masquerade_done:1;
+ bool duplicate_address_detection;
+ bool manage_temporary_address;
+ bool home_address;
+ bool prefix_route;
+ bool autojoin;
+
+ LIST_FIELDS(Address, addresses);
+};
+
+int address_new_static(Network *network, const char *filename, unsigned section, Address **ret);
+int address_new(Address **ret);
+void address_free(Address *address);
+int address_add_foreign(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret);
+int address_add(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret);
+int address_get(Link *link, int family, const union in_addr_union *in_addr, unsigned char prefixlen, Address **ret);
+int address_update(Address *address, unsigned char flags, unsigned char scope, const struct ifa_cacheinfo *cinfo);
+int address_drop(Address *address);
+int address_configure(Address *address, Link *link, link_netlink_message_handler_t callback, bool update);
+int address_remove(Address *address, Link *link, link_netlink_message_handler_t callback);
+bool address_equal(Address *a1, Address *a2);
+bool address_is_ready(const Address *a);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Address*, address_free);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_address);
+CONFIG_PARSER_PROTOTYPE(config_parse_broadcast);
+CONFIG_PARSER_PROTOTYPE(config_parse_label);
+CONFIG_PARSER_PROTOTYPE(config_parse_lifetime);
+CONFIG_PARSER_PROTOTYPE(config_parse_address_flags);
+CONFIG_PARSER_PROTOTYPE(config_parse_address_scope);
diff --git a/src/network/networkd-brvlan.c b/src/network/networkd-brvlan.c
new file mode 100644
index 0000000..8377623
--- /dev/null
+++ b/src/network/networkd-brvlan.c
@@ -0,0 +1,340 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/***
+ Copyright © 2016 BISDN GmbH. All rights reserved.
+***/
+
+#include <netinet/in.h>
+#include <linux/if_bridge.h>
+#include <stdbool.h>
+
+#include "alloc-util.h"
+#include "conf-parser.h"
+#include "missing_if_bridge.h"
+#include "netlink-util.h"
+#include "networkd-brvlan.h"
+#include "networkd-link.h"
+#include "networkd-manager.h"
+#include "networkd-network.h"
+#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 & (1 << bit);
+}
+
+static void set_bit(unsigned nr, uint32_t *addr) {
+ if (nr < BRIDGE_VLAN_BITMAP_MAX)
+ addr[nr / 32] |= (((uint32_t) 1) << (nr % 32));
+}
+
+static int find_next_bit(int i, uint32_t x) {
+ int j;
+
+ if (i >= 32)
+ return -1;
+
+ /* find first bit */
+ if (i < 0)
+ return BUILTIN_FFS_U32(x);
+
+ /* mask off prior finds to get next */
+ j = __builtin_ffs(x >> i);
+ return j ? j + i : 0;
+}
+
+static int append_vlan_info_data(Link *const link, sd_netlink_message *req, uint16_t pvid, const uint32_t *br_vid_bitmap, const uint32_t *br_untagged_bitmap) {
+ struct bridge_vlan_info br_vlan;
+ int i, j, k, r, done, cnt;
+ uint16_t begin, end;
+ bool untagged = false;
+
+ assert(link);
+ assert(req);
+ assert(br_vid_bitmap);
+ assert(br_untagged_bitmap);
+
+ i = cnt = -1;
+
+ begin = end = UINT16_MAX;
+ for (k = 0; k < BRIDGE_VLAN_BITMAP_LEN; k++) {
+ unsigned base_bit;
+ uint32_t vid_map = br_vid_bitmap[k];
+ uint32_t untagged_map = br_untagged_bitmap[k];
+
+ base_bit = k * 32;
+ i = -1;
+ done = 0;
+ do {
+ 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 = 1;
+
+ 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 log_link_error_errno(link, r, "Could not append IFLA_BRIDGE_VLAN_INFO attribute: %m");
+ } 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 log_link_error_errno(link, r, "Could not append IFLA_BRIDGE_VLAN_INFO attribute: %m");
+
+ 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 log_link_error_errno(link, r, "Could not append IFLA_BRIDGE_VLAN_INFO attribute: %m");
+ }
+
+ if (done)
+ break;
+ }
+ if (j > 0) {
+ begin = end = j - 1 + base_bit;
+ untagged = is_bit_set(j - 1, untagged_map);
+ }
+
+ next:
+ i = j;
+ } while (!done);
+ }
+ if (!cnt)
+ return -EINVAL;
+
+ return cnt;
+}
+
+static int set_brvlan_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST)
+ log_link_error_errno(link, r, "Could not add VLAN to bridge port: %m");
+
+ return 1;
+}
+
+int br_vlan_configure(Link *link, uint16_t pvid, uint32_t *br_vid_bitmap, uint32_t *br_untagged_bitmap) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ int r;
+ uint16_t flags;
+ sd_netlink *rtnl;
+
+ assert(link);
+ assert(link->manager);
+ assert(br_vid_bitmap);
+ assert(br_untagged_bitmap);
+ assert(link->network);
+
+ /* pvid might not be in br_vid_bitmap yet */
+ if (pvid)
+ set_bit(pvid, br_vid_bitmap);
+
+ rtnl = link->manager->rtnl;
+
+ /* create new RTM message */
+ r = sd_rtnl_message_new_link(rtnl, &req, RTM_SETLINK, link->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m");
+
+ r = sd_rtnl_message_link_set_family(req, PF_BRIDGE);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not set message family: %m");
+
+ r = sd_netlink_message_open_container(req, IFLA_AF_SPEC);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not open IFLA_AF_SPEC container: %m");
+
+ /* master needs flag self */
+ if (!link->network->bridge) {
+ flags = BRIDGE_FLAGS_SELF;
+ sd_netlink_message_append_data(req, IFLA_BRIDGE_FLAGS, &flags, sizeof(uint16_t));
+ }
+
+ /* add vlan info */
+ r = append_vlan_info_data(link, req, pvid, br_vid_bitmap, br_untagged_bitmap);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append VLANs: %m");
+
+ r = sd_netlink_message_close_container(req);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not close IFLA_AF_SPEC container: %m");
+
+ /* send message to the kernel */
+ r = netlink_call_async(rtnl, NULL, req, set_brvlan_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ return 0;
+}
+
+static int parse_vid_range(const char *rvalue, uint16_t *vid, uint16_t *vid_end) {
+ int r;
+ char *p;
+ char *_rvalue = NULL;
+ uint16_t _vid = UINT16_MAX;
+ uint16_t _vid_end = UINT16_MAX;
+
+ assert(rvalue);
+ assert(vid);
+ assert(vid_end);
+
+ _rvalue = strdupa(rvalue);
+ p = strchr(_rvalue, '-');
+ if (p) {
+ *p = '\0';
+ p++;
+ r = parse_vlanid(_rvalue, &_vid);
+ if (r < 0)
+ return r;
+
+ if (_vid == 0)
+ return -ERANGE;
+
+ r = parse_vlanid(p, &_vid_end);
+ if (r < 0)
+ return r;
+
+ if (_vid_end == 0)
+ return -ERANGE;
+ } else {
+ r = parse_vlanid(_rvalue, &_vid);
+ if (r < 0)
+ return r;
+
+ if (_vid == 0)
+ return -ERANGE;
+ }
+
+ *vid = _vid;
+ *vid_end = _vid_end;
+ return r;
+}
+
+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) {
+ Network *network = userdata;
+ int r;
+ uint16_t pvid;
+ r = parse_vlanid(rvalue, &pvid);
+ if (r < 0)
+ return r;
+
+ network->pvid = pvid;
+ network->use_br_vlan = true;
+
+ return 0;
+}
+
+int config_parse_brvlan_vlan(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;
+ uint16_t vid, vid_end;
+
+ 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_ERR, filename, line, r, "Failed to parse VLAN, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ if (UINT16_MAX == vid_end)
+ set_bit(vid++, network->br_vid_bitmap);
+ else {
+ if (vid >= vid_end) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid VLAN range, ignoring %s", rvalue);
+ return 0;
+ }
+ for (; vid <= vid_end; vid++)
+ set_bit(vid, network->br_vid_bitmap);
+ }
+ network->use_br_vlan = true;
+ return 0;
+}
+
+int config_parse_brvlan_untagged(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;
+ uint16_t vid, vid_end;
+
+ 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_ERR, filename, line, r, "Could not parse VLAN: %s", rvalue);
+ return 0;
+ }
+
+ if (UINT16_MAX == vid_end) {
+ set_bit(vid, network->br_vid_bitmap);
+ set_bit(vid, network->br_untagged_bitmap);
+ } else {
+ if (vid >= vid_end) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid VLAN range, ignoring %s", rvalue);
+ return 0;
+ }
+ for (; vid <= vid_end; vid++) {
+ set_bit(vid, network->br_vid_bitmap);
+ set_bit(vid, network->br_untagged_bitmap);
+ }
+ }
+ network->use_br_vlan = true;
+ return 0;
+}
diff --git a/src/network/networkd-brvlan.h b/src/network/networkd-brvlan.h
new file mode 100644
index 0000000..f6af140
--- /dev/null
+++ b/src/network/networkd-brvlan.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+/***
+ Copyright © 2016 BISDN GmbH. All rights reserved.
+***/
+
+#include <stdint.h>
+
+#include "conf-parser.h"
+
+typedef struct Link Link;
+
+int br_vlan_configure(Link *link, uint16_t pvid, uint32_t *br_vid_bitmap, uint32_t *br_untagged_bitmap);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_brvlan_pvid);
+CONFIG_PARSER_PROTOTYPE(config_parse_brvlan_vlan);
+CONFIG_PARSER_PROTOTYPE(config_parse_brvlan_untagged);
diff --git a/src/network/networkd-conf.c b/src/network/networkd-conf.c
new file mode 100644
index 0000000..334ffc3
--- /dev/null
+++ b/src/network/networkd-conf.c
@@ -0,0 +1,166 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/***
+ Copyright © 2014 Vinay Kulkarni <kulkarniv@vmware.com>
+ ***/
+
+#include <ctype.h>
+
+#include "conf-parser.h"
+#include "def.h"
+#include "dhcp-identifier.h"
+#include "extract-word.h"
+#include "hexdecoct.h"
+#include "networkd-conf.h"
+#include "networkd-network.h"
+#include "string-table.h"
+
+int manager_parse_config_file(Manager *m) {
+ assert(m);
+
+ return config_parse_many_nulstr(PKGSYSCONFDIR "/networkd.conf",
+ CONF_PATHS_NULSTR("systemd/networkd.conf.d"),
+ "DHCP\0",
+ config_item_perf_lookup, networkd_gperf_lookup,
+ CONFIG_PARSE_WARN, m);
+}
+
+static const char* const duid_type_table[_DUID_TYPE_MAX] = {
+ [DUID_TYPE_LLT] = "link-layer-time",
+ [DUID_TYPE_EN] = "vendor",
+ [DUID_TYPE_LL] = "link-layer",
+ [DUID_TYPE_UUID] = "uuid",
+};
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(duid_type, DUIDType);
+
+int config_parse_duid_type(
+ 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_free_ char *type_string = NULL;
+ const char *p = rvalue;
+ DUID *duid = data;
+ DUIDType type;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(duid);
+
+ r = extract_first_word(&p, &type_string, ":", 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) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Failed to extract DUID type from '%s', ignoring.", rvalue);
+ return 0;
+ }
+
+ type = duid_type_from_string(type_string);
+ if (type < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, 0,
+ "Failed to parse DUID type '%s', ignoring.", type_string);
+ return 0;
+ }
+
+ if (!isempty(p)) {
+ usec_t u;
+
+ if (type != DUID_TYPE_LLT) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Invalid syntax, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ r = parse_timestamp(p, &u);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to parse timestamp, ignoring: %s", p);
+ return 0;
+ }
+
+ duid->llt_time = u;
+ }
+
+ duid->type = type;
+
+ return 0;
+}
+
+int config_parse_duid_rawdata(
+ 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) {
+
+ DUID *ret = data;
+ uint8_t raw_data[MAX_DUID_LEN];
+ unsigned count = 0;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(ret);
+
+ /* RawData contains DUID in format "NN:NN:NN..." */
+ for (;;) {
+ int n1, n2, len, r;
+ uint32_t byte;
+ _cleanup_free_ char *cbyte = NULL;
+
+ r = extract_first_word(&rvalue, &cbyte, ":", 0);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to read DUID, ignoring assignment: %s.", rvalue);
+ return 0;
+ }
+ if (r == 0)
+ break;
+ if (count >= MAX_DUID_LEN) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Max DUID length exceeded, ignoring assignment: %s.", rvalue);
+ return 0;
+ }
+
+ len = strlen(cbyte);
+ if (!IN_SET(len, 1, 2)) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid length - DUID byte: %s, ignoring assignment: %s.", cbyte, rvalue);
+ return 0;
+ }
+ n1 = unhexchar(cbyte[0]);
+ if (len == 2)
+ n2 = unhexchar(cbyte[1]);
+ else
+ n2 = 0;
+
+ if (n1 < 0 || n2 < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid DUID byte: %s. Ignoring assignment: %s.", cbyte, rvalue);
+ return 0;
+ }
+
+ byte = ((uint8_t) n1 << (4 * (len-1))) | (uint8_t) n2;
+ raw_data[count++] = byte;
+ }
+
+ assert_cc(sizeof(raw_data) == sizeof(ret->raw_data));
+ memcpy(ret->raw_data, raw_data, count);
+ ret->raw_data_len = count;
+ return 0;
+}
diff --git a/src/network/networkd-conf.h b/src/network/networkd-conf.h
new file mode 100644
index 0000000..88a2c64
--- /dev/null
+++ b/src/network/networkd-conf.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+/***
+ Copyright © 2014 Vinay Kulkarni <kulkarniv@vmware.com>
+***/
+
+#include "conf-parser.h"
+
+typedef struct Manager Manager;
+
+int manager_parse_config_file(Manager *m);
+
+const struct ConfigPerfItem* networkd_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_duid_type);
+CONFIG_PARSER_PROTOTYPE(config_parse_duid_rawdata);
diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c
new file mode 100644
index 0000000..d8ac455
--- /dev/null
+++ b/src/network/networkd-dhcp4.c
@@ -0,0 +1,822 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <netinet/ether.h>
+#include <linux/if.h>
+
+#include "alloc-util.h"
+#include "hostname-util.h"
+#include "parse-util.h"
+#include "netdev/vrf.h"
+#include "network-internal.h"
+#include "networkd-link.h"
+#include "networkd-manager.h"
+#include "networkd-network.h"
+#include "string-util.h"
+#include "sysctl-util.h"
+
+static int dhcp4_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+ assert(link->dhcp4_messages > 0);
+
+ link->dhcp4_messages--;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST) {
+ log_link_error_errno(link, r, "Could not set DHCPv4 route: %m");
+ link_enter_failed(link);
+ }
+
+ if (link->dhcp4_messages == 0) {
+ link->dhcp4_configured = true;
+ link_check_ready(link);
+ }
+
+ return 1;
+}
+
+static int route_scope_from_address(const Route *route, const struct in_addr *self_addr) {
+ assert(route);
+ assert(self_addr);
+
+ if (in_addr_is_localhost(AF_INET, &route->dst) ||
+ (self_addr->s_addr && route->dst.in.s_addr == self_addr->s_addr))
+ return RT_SCOPE_HOST;
+ else if (in4_addr_is_null(&route->gw.in))
+ return RT_SCOPE_LINK;
+ else
+ return RT_SCOPE_UNIVERSE;
+}
+
+static int link_set_dhcp_routes(Link *link) {
+ _cleanup_free_ sd_dhcp_route **static_routes = NULL;
+ bool classless_route = false, static_route = false;
+ struct in_addr gateway, address;
+ int r, n, i;
+ uint32_t table;
+
+ assert(link);
+
+ if (!link->dhcp_lease) /* link went down while we configured the IP addresses? */
+ return 0;
+
+ if (!link->network) /* link went down while we configured the IP addresses? */
+ return 0;
+
+ if (!link->network->dhcp_use_routes)
+ return 0;
+
+ /* When the interface is part of an VRF use the VRFs routing table, unless
+ * there is a another table specified. */
+ table = link->network->dhcp_route_table;
+ if (!link->network->dhcp_route_table_set && link->network->vrf != NULL)
+ table = VRF(link->network->vrf)->table;
+
+ r = sd_dhcp_lease_get_address(link->dhcp_lease, &address);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "DHCP error: could not get address: %m");
+
+ n = sd_dhcp_lease_get_routes(link->dhcp_lease, &static_routes);
+ if (n == -ENODATA)
+ log_link_debug_errno(link, n, "DHCP: No routes received from DHCP server: %m");
+ else if (n < 0)
+ log_link_debug_errno(link, n, "DHCP error: could not get routes: %m");
+
+ for (i = 0; i < n; i++) {
+ switch (sd_dhcp_route_get_option(static_routes[i])) {
+ case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE:
+ classless_route = true;
+ break;
+ case SD_DHCP_OPTION_STATIC_ROUTE:
+ static_route = true;
+ break;
+ }
+ }
+
+ for (i = 0; i < n; i++) {
+ _cleanup_(route_freep) Route *route = NULL;
+
+ /* if the DHCP server returns both a Classless Static Routes option and a Static Routes option,
+ the DHCP client MUST ignore the Static Routes option. */
+ if (classless_route &&
+ sd_dhcp_route_get_option(static_routes[i]) != SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE)
+ continue;
+
+ r = route_new(&route);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate route: %m");
+
+ route->family = AF_INET;
+ route->protocol = RTPROT_DHCP;
+ assert_se(sd_dhcp_route_get_gateway(static_routes[i], &route->gw.in) >= 0);
+ assert_se(sd_dhcp_route_get_destination(static_routes[i], &route->dst.in) >= 0);
+ assert_se(sd_dhcp_route_get_destination_prefix_length(static_routes[i], &route->dst_prefixlen) >= 0);
+ route->priority = link->network->dhcp_route_metric;
+ route->table = table;
+ route->scope = route_scope_from_address(route, &address);
+
+ r = route_configure(route, link, dhcp4_route_handler);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not set host route: %m");
+
+ link->dhcp4_messages++;
+ }
+
+ r = sd_dhcp_lease_get_router(link->dhcp_lease, &gateway);
+ if (r == -ENODATA)
+ log_link_info_errno(link, r, "DHCP: No gateway received from DHCP server: %m");
+ else if (r < 0)
+ log_link_warning_errno(link, r, "DHCP error: could not get gateway: %m");
+
+ /* 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 (classless_route && static_route)
+ log_link_warning(link, "Classless static routes received from DHCP server: ignoring static-route option and router option");
+
+ if (r >= 0 && !classless_route) {
+ _cleanup_(route_freep) Route *route = NULL;
+ _cleanup_(route_freep) Route *route_gw = NULL;
+
+ r = route_new(&route);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate route: %m");
+
+ route->protocol = RTPROT_DHCP;
+
+ r = route_new(&route_gw);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate route: %m");
+
+ /* The dhcp netmask may mask out the gateway. Add an explicit
+ * route for the gw host so that we can route no matter the
+ * netmask or existing kernel route tables. */
+ route_gw->family = AF_INET;
+ route_gw->dst.in = gateway;
+ route_gw->dst_prefixlen = 32;
+ route_gw->prefsrc.in = address;
+ route_gw->scope = RT_SCOPE_LINK;
+ route_gw->protocol = RTPROT_DHCP;
+ route_gw->priority = link->network->dhcp_route_metric;
+ route_gw->table = table;
+
+ r = route_configure(route_gw, link, dhcp4_route_handler);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not set host route: %m");
+
+ link->dhcp4_messages++;
+
+ route->family = AF_INET;
+ route->gw.in = gateway;
+ route->prefsrc.in = address;
+ route->priority = link->network->dhcp_route_metric;
+ route->table = table;
+
+ r = route_configure(route, link, dhcp4_route_handler);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Could not set routes: %m");
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->dhcp4_messages++;
+ }
+
+ return 0;
+}
+
+static int dhcp_lease_lost(Link *link) {
+ _cleanup_(address_freep) Address *address = NULL;
+ struct in_addr addr;
+ struct in_addr netmask;
+ struct in_addr gateway;
+ unsigned prefixlen = 0;
+ int r;
+
+ assert(link);
+ assert(link->dhcp_lease);
+
+ log_link_warning(link, "DHCP lease lost");
+
+ if (link->network->dhcp_use_routes) {
+ _cleanup_free_ sd_dhcp_route **routes = NULL;
+ int n, i;
+
+ n = sd_dhcp_lease_get_routes(link->dhcp_lease, &routes);
+ if (n >= 0) {
+ for (i = 0; i < n; i++) {
+ _cleanup_(route_freep) Route *route = NULL;
+
+ r = route_new(&route);
+ if (r >= 0) {
+ route->family = AF_INET;
+ assert_se(sd_dhcp_route_get_gateway(routes[i], &route->gw.in) >= 0);
+ assert_se(sd_dhcp_route_get_destination(routes[i], &route->dst.in) >= 0);
+ assert_se(sd_dhcp_route_get_destination_prefix_length(routes[i], &route->dst_prefixlen) >= 0);
+
+ route_remove(route, link, NULL);
+ }
+ }
+ }
+ }
+
+ r = address_new(&address);
+ if (r >= 0) {
+ r = sd_dhcp_lease_get_router(link->dhcp_lease, &gateway);
+ if (r >= 0) {
+ _cleanup_(route_freep) Route *route_gw = NULL;
+ _cleanup_(route_freep) Route *route = NULL;
+
+ r = route_new(&route_gw);
+ if (r >= 0) {
+ route_gw->family = AF_INET;
+ route_gw->dst.in = gateway;
+ route_gw->dst_prefixlen = 32;
+ route_gw->scope = RT_SCOPE_LINK;
+
+ route_remove(route_gw, link, NULL);
+ }
+
+ r = route_new(&route);
+ if (r >= 0) {
+ route->family = AF_INET;
+ route->gw.in = gateway;
+
+ route_remove(route, link, NULL);
+ }
+ }
+
+ r = sd_dhcp_lease_get_address(link->dhcp_lease, &addr);
+ if (r >= 0) {
+ r = sd_dhcp_lease_get_netmask(link->dhcp_lease, &netmask);
+ if (r >= 0)
+ prefixlen = in4_addr_netmask_to_prefixlen(&netmask);
+
+ address->family = AF_INET;
+ address->in_addr.in = addr;
+ address->prefixlen = prefixlen;
+
+ address_remove(address, link, NULL);
+ }
+ }
+
+ if (link->network->dhcp_use_mtu) {
+ uint16_t mtu;
+
+ r = sd_dhcp_lease_get_mtu(link->dhcp_lease, &mtu);
+ if (r >= 0 && link->original_mtu != mtu) {
+ r = link_set_mtu(link, link->original_mtu);
+ if (r < 0) {
+ log_link_warning(link,
+ "DHCP error: could not reset MTU");
+ link_enter_failed(link);
+ return r;
+ }
+ }
+ }
+
+ if (link->network->dhcp_use_hostname) {
+ const char *hostname = NULL;
+
+ if (link->network->dhcp_hostname)
+ hostname = link->network->dhcp_hostname;
+ else
+ (void) sd_dhcp_lease_get_hostname(link->dhcp_lease, &hostname);
+
+ if (hostname) {
+ /* If a hostname was set due to the lease, then unset it now. */
+ r = manager_set_hostname(link->manager, NULL);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Failed to reset transient hostname: %m");
+ }
+ }
+
+ link->dhcp_lease = sd_dhcp_lease_unref(link->dhcp_lease);
+ link_dirty(link);
+ link->dhcp4_configured = false;
+
+ return 0;
+}
+
+static int dhcp4_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST) {
+ log_link_error_errno(link, r, "Could not set DHCPv4 address: %m");
+ link_enter_failed(link);
+ } else if (r >= 0)
+ manager_rtnl_process_address(rtnl, m, link->manager);
+
+ link_set_dhcp_routes(link);
+
+ if (link->dhcp4_messages == 0) {
+ link->dhcp4_configured = true;
+ link_check_ready(link);
+ }
+
+ return 1;
+}
+
+static int dhcp4_update_address(Link *link,
+ struct in_addr *address,
+ struct in_addr *netmask,
+ uint32_t lifetime) {
+ _cleanup_(address_freep) Address *addr = NULL;
+ unsigned prefixlen;
+ int r;
+
+ assert(address);
+ assert(netmask);
+ assert(lifetime);
+
+ prefixlen = in4_addr_netmask_to_prefixlen(netmask);
+
+ r = address_new(&addr);
+ if (r < 0)
+ return r;
+
+ addr->family = AF_INET;
+ addr->in_addr.in.s_addr = address->s_addr;
+ addr->cinfo.ifa_prefered = lifetime;
+ addr->cinfo.ifa_valid = lifetime;
+ addr->prefixlen = prefixlen;
+ addr->broadcast.s_addr = address->s_addr | ~netmask->s_addr;
+
+ /* allow reusing an existing address and simply update its lifetime
+ * in case it already exists */
+ r = address_configure(addr, link, dhcp4_address_handler, true);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int dhcp_lease_renew(sd_dhcp_client *client, Link *link) {
+ sd_dhcp_lease *lease;
+ struct in_addr address;
+ struct in_addr netmask;
+ uint32_t lifetime = CACHE_INFO_INFINITY_LIFE_TIME;
+ int r;
+
+ assert(link);
+ assert(client);
+ assert(link->network);
+
+ r = sd_dhcp_client_get_lease(client, &lease);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "DHCP error: no lease: %m");
+
+ sd_dhcp_lease_unref(link->dhcp_lease);
+ link->dhcp4_configured = false;
+ link->dhcp_lease = sd_dhcp_lease_ref(lease);
+ link_dirty(link);
+
+ r = sd_dhcp_lease_get_address(lease, &address);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "DHCP error: no address: %m");
+
+ r = sd_dhcp_lease_get_netmask(lease, &netmask);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "DHCP error: no netmask: %m");
+
+ if (!link->network->dhcp_critical) {
+ r = sd_dhcp_lease_get_lifetime(link->dhcp_lease, &lifetime);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "DHCP error: no lifetime: %m");
+ }
+
+ r = dhcp4_update_address(link, &address, &netmask, lifetime);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Could not update IP address: %m");
+ link_enter_failed(link);
+ return r;
+ }
+
+ return 0;
+}
+
+static int dhcp_lease_acquired(sd_dhcp_client *client, Link *link) {
+ sd_dhcp_lease *lease;
+ struct in_addr address;
+ struct in_addr netmask;
+ struct in_addr gateway;
+ unsigned prefixlen;
+ uint32_t lifetime = CACHE_INFO_INFINITY_LIFE_TIME;
+ int r;
+
+ assert(client);
+ assert(link);
+
+ r = sd_dhcp_client_get_lease(client, &lease);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP error: No lease: %m");
+
+ r = sd_dhcp_lease_get_address(lease, &address);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP error: No address: %m");
+
+ r = sd_dhcp_lease_get_netmask(lease, &netmask);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP error: No netmask: %m");
+
+ prefixlen = in4_addr_netmask_to_prefixlen(&netmask);
+
+ r = sd_dhcp_lease_get_router(lease, &gateway);
+ if (r < 0 && r != -ENODATA)
+ return log_link_error_errno(link, r, "DHCP error: Could not get gateway: %m");
+
+ if (r >= 0)
+ log_struct(LOG_INFO,
+ LOG_LINK_INTERFACE(link),
+ LOG_LINK_MESSAGE(link, "DHCPv4 address %u.%u.%u.%u/%u via %u.%u.%u.%u",
+ ADDRESS_FMT_VAL(address),
+ prefixlen,
+ ADDRESS_FMT_VAL(gateway)),
+ "ADDRESS=%u.%u.%u.%u", ADDRESS_FMT_VAL(address),
+ "PREFIXLEN=%u", prefixlen,
+ "GATEWAY=%u.%u.%u.%u", ADDRESS_FMT_VAL(gateway));
+ else
+ log_struct(LOG_INFO,
+ LOG_LINK_INTERFACE(link),
+ LOG_LINK_MESSAGE(link, "DHCPv4 address %u.%u.%u.%u/%u",
+ ADDRESS_FMT_VAL(address),
+ prefixlen),
+ "ADDRESS=%u.%u.%u.%u", ADDRESS_FMT_VAL(address),
+ "PREFIXLEN=%u", prefixlen);
+
+ link->dhcp_lease = sd_dhcp_lease_ref(lease);
+ link_dirty(link);
+
+ if (link->network->dhcp_use_mtu) {
+ uint16_t mtu;
+
+ r = sd_dhcp_lease_get_mtu(lease, &mtu);
+ if (r >= 0) {
+ r = link_set_mtu(link, mtu);
+ if (r < 0)
+ log_link_error_errno(link, r, "Failed to set MTU to %" PRIu16 ": %m", mtu);
+ }
+ }
+
+ if (link->network->dhcp_use_hostname) {
+ const char *dhcpname = NULL;
+ _cleanup_free_ char *hostname = NULL;
+
+ if (link->network->dhcp_hostname)
+ dhcpname = link->network->dhcp_hostname;
+ else
+ (void) sd_dhcp_lease_get_hostname(lease, &dhcpname);
+
+ if (dhcpname) {
+ r = shorten_overlong(dhcpname, &hostname);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Unable to shorten overlong DHCP hostname '%s', ignoring: %m", dhcpname);
+ if (r == 1)
+ log_link_notice(link, "Overlong DCHP hostname received, shortened from '%s' to '%s'", dhcpname, hostname);
+ }
+
+ if (hostname) {
+ r = manager_set_hostname(link->manager, hostname);
+ if (r < 0)
+ log_link_error_errno(link, r, "Failed to set transient hostname to '%s': %m", hostname);
+ }
+ }
+
+ if (link->network->dhcp_use_timezone) {
+ const char *tz = NULL;
+
+ (void) sd_dhcp_lease_get_timezone(link->dhcp_lease, &tz);
+
+ if (tz) {
+ r = manager_set_timezone(link->manager, tz);
+ if (r < 0)
+ log_link_error_errno(link, r, "Failed to set timezone to '%s': %m", tz);
+ }
+ }
+
+ if (!link->network->dhcp_critical) {
+ r = sd_dhcp_lease_get_lifetime(link->dhcp_lease, &lifetime);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "DHCP error: no lifetime: %m");
+ }
+
+ r = dhcp4_update_address(link, &address, &netmask, lifetime);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Could not update IP address: %m");
+ link_enter_failed(link);
+ return r;
+ }
+
+ return 0;
+}
+static void dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) {
+ Link *link = userdata;
+ int r = 0;
+
+ assert(link);
+ assert(link->network);
+ assert(link->manager);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return;
+
+ switch (event) {
+ case SD_DHCP_CLIENT_EVENT_EXPIRED:
+ case SD_DHCP_CLIENT_EVENT_STOP:
+ case SD_DHCP_CLIENT_EVENT_IP_CHANGE:
+ if (link->network->dhcp_critical) {
+ log_link_error(link, "DHCPv4 connection considered system critical, ignoring request to reconfigure it.");
+ return;
+ }
+
+ if (link->dhcp_lease) {
+ r = dhcp_lease_lost(link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return;
+ }
+ }
+
+ if (event == SD_DHCP_CLIENT_EVENT_IP_CHANGE) {
+ r = dhcp_lease_acquired(client, link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return;
+ }
+ }
+
+ break;
+ case SD_DHCP_CLIENT_EVENT_RENEW:
+ r = dhcp_lease_renew(client, link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return;
+ }
+ break;
+ case SD_DHCP_CLIENT_EVENT_IP_ACQUIRE:
+ r = dhcp_lease_acquired(client, link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return;
+ }
+ break;
+ default:
+ if (event < 0)
+ log_link_warning_errno(link, event, "DHCP error: Client failed: %m");
+ else
+ log_link_warning(link, "DHCP unknown event: %i", event);
+ break;
+ }
+
+ return;
+}
+
+static int dhcp4_set_hostname(Link *link) {
+ _cleanup_free_ char *hostname = NULL;
+ const char *hn;
+ int r;
+
+ assert(link);
+
+ if (!link->network->dhcp_send_hostname)
+ hn = NULL;
+ else if (link->network->dhcp_hostname)
+ hn = link->network->dhcp_hostname;
+ else {
+ r = gethostname_strict(&hostname);
+ if (r < 0 && r != -ENXIO) /* ENXIO: no hostname set or hostname is "localhost" */
+ return r;
+
+ hn = hostname;
+ }
+
+ r = sd_dhcp_client_set_hostname(link->dhcp_client, hn);
+ if (r == -EINVAL && hostname)
+ /* Ignore error when the machine's hostname is not suitable to send in DHCP packet. */
+ log_link_warning_errno(link, r, "DHCP4 CLIENT: Failed to set hostname from kernel hostname, ignoring: %m");
+ else if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set hostname: %m");
+
+ return 0;
+}
+
+static bool promote_secondaries_enabled(const char *ifname) {
+ _cleanup_free_ char *promote_secondaries_sysctl = NULL;
+ char *promote_secondaries_path;
+ int r;
+
+ promote_secondaries_path = strjoina("net/ipv4/conf/", ifname, "/promote_secondaries");
+ r = sysctl_read(promote_secondaries_path, &promote_secondaries_sysctl);
+ if (r < 0) {
+ log_debug_errno(r, "Cannot read sysctl %s", promote_secondaries_path);
+ return false;
+ }
+
+ truncate_nl(promote_secondaries_sysctl);
+ r = parse_boolean(promote_secondaries_sysctl);
+ if (r < 0)
+ log_warning_errno(r, "Cannot parse sysctl %s with content %s as boolean", promote_secondaries_path, promote_secondaries_sysctl);
+ return r > 0;
+}
+
+/* dhcp4_set_promote_secondaries will ensure this interface has
+ * the "promote_secondaries" option in the kernel set. If this sysctl
+ * is not set DHCP will work only as long as the IP address does not
+ * changes between leases. The kernel will remove all secondary IP
+ * addresses of an interface otherwise. The way systemd-network works
+ * is that the new IP of a lease is added as a secondary IP and when
+ * the primary one expires it relies on the kernel to promote the
+ * secondary IP. See also https://github.com/systemd/systemd/issues/7163
+ */
+int dhcp4_set_promote_secondaries(Link *link) {
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(link->network->dhcp & ADDRESS_FAMILY_IPV4);
+
+ /* check if the kernel has promote_secondaries enabled for our
+ * interface. If it is not globally enabled or enabled for the
+ * specific interface we must either enable it.
+ */
+ if (!(promote_secondaries_enabled("all") || promote_secondaries_enabled(link->ifname))) {
+ char *promote_secondaries_path = NULL;
+
+ log_link_debug(link, "promote_secondaries is unset, setting it");
+ promote_secondaries_path = strjoina("net/ipv4/conf/", link->ifname, "/promote_secondaries");
+ r = sysctl_write(promote_secondaries_path, "1");
+ if (r < 0)
+ log_link_warning_errno(link, r, "cannot set sysctl %s to 1", promote_secondaries_path);
+ return r > 0;
+ }
+
+ return 0;
+}
+
+int dhcp4_set_client_identifier(Link *link) {
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(link->dhcp_client);
+
+ switch (link->network->dhcp_client_identifier) {
+ case DHCP_CLIENT_ID_DUID: {
+ /* If configured, apply user specified DUID and IAID */
+ const DUID *duid = link_get_duid(link);
+
+ if (duid->type == DUID_TYPE_LLT && duid->raw_data_len == 0)
+ r = sd_dhcp_client_set_iaid_duid_llt(link->dhcp_client,
+ link->network->iaid_set,
+ link->network->iaid,
+ duid->llt_time);
+ else
+ r = sd_dhcp_client_set_iaid_duid(link->dhcp_client,
+ link->network->iaid_set,
+ link->network->iaid,
+ duid->type,
+ duid->raw_data_len > 0 ? duid->raw_data : NULL,
+ duid->raw_data_len);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set IAID+DUID: %m");
+ break;
+ }
+ case DHCP_CLIENT_ID_DUID_ONLY: {
+ /* If configured, apply user specified DUID */
+ const DUID *duid = link_get_duid(link);
+
+ if (duid->type == DUID_TYPE_LLT && duid->raw_data_len == 0)
+ r = sd_dhcp_client_set_duid_llt(link->dhcp_client,
+ duid->llt_time);
+ else
+ r = sd_dhcp_client_set_duid(link->dhcp_client,
+ duid->type,
+ duid->raw_data_len > 0 ? duid->raw_data : NULL,
+ duid->raw_data_len);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set DUID: %m");
+ break;
+ }
+ case DHCP_CLIENT_ID_MAC:
+ r = sd_dhcp_client_set_client_id(link->dhcp_client,
+ ARPHRD_ETHER,
+ (const uint8_t *) &link->mac,
+ sizeof(link->mac));
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set client ID: %m");
+ break;
+ default:
+ assert_not_reached("Unknown client identifier type.");
+ }
+
+ return 0;
+}
+
+int dhcp4_configure(Link *link) {
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(link->network->dhcp & ADDRESS_FAMILY_IPV4);
+
+ if (!link->dhcp_client) {
+ r = sd_dhcp_client_new(&link->dhcp_client, link->network->dhcp_anonymize);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to create DHCP4 client: %m");
+ }
+
+ r = sd_dhcp_client_attach_event(link->dhcp_client, NULL, 0);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to attach event: %m");
+
+ r = sd_dhcp_client_set_mac(link->dhcp_client,
+ (const uint8_t *) &link->mac,
+ sizeof (link->mac), ARPHRD_ETHER);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set MAC address: %m");
+
+ r = sd_dhcp_client_set_ifindex(link->dhcp_client, link->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set ifindex: %m");
+
+ r = sd_dhcp_client_set_callback(link->dhcp_client, dhcp4_handler, link);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set callback: %m");
+
+ r = sd_dhcp_client_set_request_broadcast(link->dhcp_client,
+ link->network->dhcp_broadcast);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set request flag for broadcast: %m");
+
+ if (link->mtu) {
+ r = sd_dhcp_client_set_mtu(link->dhcp_client, link->mtu);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set MTU: %m");
+ }
+
+ if (link->network->dhcp_use_mtu) {
+ r = sd_dhcp_client_set_request_option(link->dhcp_client,
+ SD_DHCP_OPTION_INTERFACE_MTU);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set request flag for MTU: %m");
+ }
+
+ /* NOTE: even if this variable is called "use", it also "sends" PRL
+ * options, maybe there should be a different configuration variable
+ * to send or not route options?. */
+ /* NOTE: when using Anonymize=yes, routes PRL options are sent
+ * by default, so they don't need to be added here. */
+ if (link->network->dhcp_use_routes && !link->network->dhcp_anonymize) {
+ r = sd_dhcp_client_set_request_option(link->dhcp_client,
+ SD_DHCP_OPTION_STATIC_ROUTE);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set request flag for static route: %m");
+
+ r = sd_dhcp_client_set_request_option(link->dhcp_client,
+ SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set request flag for classless static route: %m");
+ }
+
+ if (link->network->dhcp_use_ntp) {
+ r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_NTP_SERVER);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set request flag for NTP server: %m");
+ }
+
+ if (link->network->dhcp_use_timezone) {
+ r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_NEW_TZDB_TIMEZONE);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set request flag for timezone: %m");
+ }
+
+ r = dhcp4_set_hostname(link);
+ if (r < 0)
+ return r;
+
+ if (link->network->dhcp_vendor_class_identifier) {
+ r = sd_dhcp_client_set_vendor_class_identifier(link->dhcp_client,
+ link->network->dhcp_vendor_class_identifier);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set vendor class identifier: %m");
+ }
+
+ if (link->network->dhcp_user_class) {
+ r = sd_dhcp_client_set_user_class(link->dhcp_client, (const char **) link->network->dhcp_user_class);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set user class: %m");
+ }
+
+ if (link->network->dhcp_client_port) {
+ r = sd_dhcp_client_set_client_port(link->dhcp_client, link->network->dhcp_client_port);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP4 CLIENT: Failed to set listen port: %m");
+ }
+
+ return dhcp4_set_client_identifier(link);
+}
diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c
new file mode 100644
index 0000000..c1fba03
--- /dev/null
+++ b/src/network/networkd-dhcp6.c
@@ -0,0 +1,702 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/***
+ Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include <netinet/ether.h>
+#include <linux/if.h>
+#include "sd-radv.h"
+
+#include "sd-dhcp6-client.h"
+
+#include "hashmap.h"
+#include "hostname-util.h"
+#include "missing_network.h"
+#include "network-internal.h"
+#include "networkd-link.h"
+#include "networkd-manager.h"
+#include "siphash24.h"
+#include "string-util.h"
+#include "radv-internal.h"
+
+static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link);
+
+static bool dhcp6_get_prefix_delegation(Link *link) {
+ if (!link->network)
+ return false;
+
+ return IN_SET(link->network->router_prefix_delegation,
+ RADV_PREFIX_DELEGATION_DHCP6,
+ RADV_PREFIX_DELEGATION_BOTH);
+}
+
+static bool dhcp6_enable_prefix_delegation(Link *dhcp6_link) {
+ Manager *manager;
+ Link *l;
+ Iterator i;
+
+ assert(dhcp6_link);
+
+ manager = dhcp6_link->manager;
+ assert(manager);
+
+ HASHMAP_FOREACH(l, manager->links, i) {
+ if (l == dhcp6_link)
+ continue;
+
+ if (!dhcp6_get_prefix_delegation(l))
+ continue;
+
+ return true;
+ }
+
+ return false;
+}
+
+static int dhcp6_lease_information_acquired(sd_dhcp6_client *client,
+ Link *link) {
+ return 0;
+}
+
+static int dhcp6_pd_prefix_assign(Link *link, struct in6_addr *prefix,
+ uint8_t prefix_len,
+ uint32_t lifetime_preferred,
+ uint32_t lifetime_valid) {
+ sd_radv *radv = link->radv;
+ int r;
+ _cleanup_(sd_radv_prefix_unrefp) sd_radv_prefix *p = NULL;
+
+ r = sd_radv_prefix_new(&p);
+ if (r < 0)
+ return r;
+
+ r = sd_radv_prefix_set_prefix(p, prefix, prefix_len);
+ if (r < 0)
+ return r;
+
+ r = sd_radv_prefix_set_preferred_lifetime(p, lifetime_preferred);
+ if (r < 0)
+ return r;
+
+ r = sd_radv_prefix_set_valid_lifetime(p, lifetime_valid);
+ if (r < 0)
+ return r;
+
+ r = sd_radv_stop(radv);
+ if (r < 0)
+ return r;
+
+ r = sd_radv_add_prefix(radv, p, true);
+ if (r < 0 && r != -EEXIST)
+ return r;
+
+ r = manager_dhcp6_prefix_add(link->manager, &p->opt.in6_addr, link);
+ if (r < 0)
+ return r;
+
+ return sd_radv_start(radv);
+}
+
+static int dhcp6_route_remove_handler(sd_netlink *nl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0)
+ log_link_debug_errno(link, r, "Received error on unreachable route removal for DHCPv6 delegated subnetl: %m");
+
+ return 1;
+}
+
+int dhcp6_lease_pd_prefix_lost(sd_dhcp6_client *client, Link* link) {
+ int r;
+ sd_dhcp6_lease *lease;
+ union in_addr_union pd_prefix;
+ uint8_t pd_prefix_len;
+ uint32_t lifetime_preferred, lifetime_valid;
+
+ r = sd_dhcp6_client_get_lease(client, &lease);
+ if (r < 0)
+ return r;
+
+ sd_dhcp6_lease_reset_pd_prefix_iter(lease);
+
+ while (sd_dhcp6_lease_get_pd(lease, &pd_prefix.in6, &pd_prefix_len,
+ &lifetime_preferred,
+ &lifetime_valid) >= 0) {
+ _cleanup_free_ char *buf = NULL;
+ _cleanup_free_ Route *route = NULL;
+
+ if (pd_prefix_len > 64)
+ continue;
+
+ (void) in_addr_to_string(AF_INET6, &pd_prefix, &buf);
+
+ if (pd_prefix_len < 64) {
+ r = route_new(&route);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Cannot create unreachable route to delete for DHCPv6 delegated subnet %s/%u: %m",
+ strnull(buf),
+ pd_prefix_len);
+ continue;
+ }
+
+ route_add(link, AF_INET6, &pd_prefix, pd_prefix_len,
+ 0, 0, 0, &route);
+ route_update(route, NULL, 0, NULL, NULL, 0, 0,
+ RTN_UNREACHABLE);
+
+ r = route_remove(route, link, dhcp6_route_remove_handler);
+ if (r < 0) {
+ (void) in_addr_to_string(AF_INET6,
+ &pd_prefix, &buf);
+
+ log_link_warning_errno(link, r, "Cannot delete unreachable route for DHCPv6 delegated subnet %s/%u: %m",
+ strnull(buf),
+ pd_prefix_len);
+
+ continue;
+ }
+
+ log_link_debug(link, "Removing unreachable route %s/%u",
+ strnull(buf), pd_prefix_len);
+ }
+ }
+
+ return 0;
+}
+
+static int dhcp6_pd_prefix_distribute(Link *dhcp6_link, Iterator *i,
+ struct in6_addr *pd_prefix,
+ uint8_t pd_prefix_len,
+ uint32_t lifetime_preferred,
+ uint32_t lifetime_valid) {
+ Link *link;
+ Manager *manager = dhcp6_link->manager;
+ union in_addr_union prefix;
+ uint64_t n_prefixes, n_used = 0;
+ _cleanup_free_ char *buf = NULL;
+ _cleanup_free_ char *assigned_buf = NULL;
+ int r;
+
+ assert(manager);
+ assert(pd_prefix_len <= 64);
+
+ prefix.in6 = *pd_prefix;
+
+ r = in_addr_mask(AF_INET6, &prefix, pd_prefix_len);
+ if (r < 0)
+ return r;
+
+ n_prefixes = UINT64_C(1) << (64 - pd_prefix_len);
+
+ (void) in_addr_to_string(AF_INET6, &prefix, &buf);
+ log_link_debug(dhcp6_link, "Assigning up to %" PRIu64 " prefixes from %s/%u",
+ n_prefixes, strnull(buf), pd_prefix_len);
+
+ while (hashmap_iterate(manager->links, i, (void **)&link, NULL)) {
+ Link *assigned_link;
+
+ if (n_used == n_prefixes) {
+ log_link_debug(dhcp6_link, "Assigned %" PRIu64 "/%" PRIu64 " prefixes from %s/%u",
+ n_used, n_prefixes, strnull(buf), pd_prefix_len);
+
+ return -EAGAIN;
+ }
+
+ if (link == dhcp6_link)
+ continue;
+
+ if (!dhcp6_get_prefix_delegation(link))
+ continue;
+
+ assigned_link = manager_dhcp6_prefix_get(manager, &prefix.in6);
+ if (assigned_link != NULL && assigned_link != link)
+ continue;
+
+ (void) in_addr_to_string(AF_INET6, &prefix, &assigned_buf);
+ r = dhcp6_pd_prefix_assign(link, &prefix.in6, 64,
+ lifetime_preferred, lifetime_valid);
+ if (r < 0) {
+ log_link_error_errno(link, r, "Unable to %s prefix %s/64 from %s/%u for link: %m",
+ assigned_link ? "update": "assign",
+ strnull(assigned_buf),
+ strnull(buf), pd_prefix_len);
+
+ if (assigned_link == NULL)
+ continue;
+
+ } else
+ log_link_debug(link, "Assigned prefix %" PRIu64 "/%" PRIu64 " %s/64 from %s/%u to link",
+ n_used + 1, n_prefixes,
+ strnull(assigned_buf),
+ strnull(buf), pd_prefix_len);
+
+ n_used++;
+
+ r = in_addr_prefix_next(AF_INET6, &prefix, 64);
+ if (r < 0 && n_used < n_prefixes)
+ return r;
+ }
+
+ return 0;
+}
+
+static int dhcp6_route_handler(sd_netlink *nl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST)
+ log_link_debug_errno(link, r, "Received error when adding unreachable route for DHCPv6 delegated subnet: %m");
+
+ return 1;
+}
+
+static int dhcp6_lease_pd_prefix_acquired(sd_dhcp6_client *client, Link *link) {
+ int r;
+ sd_dhcp6_lease *lease;
+ union in_addr_union pd_prefix;
+ uint8_t pd_prefix_len;
+ uint32_t lifetime_preferred, lifetime_valid;
+ _cleanup_free_ char *buf = NULL;
+ Iterator i = ITERATOR_FIRST;
+
+ r = sd_dhcp6_client_get_lease(client, &lease);
+ if (r < 0)
+ return r;
+
+ sd_dhcp6_lease_reset_pd_prefix_iter(lease);
+
+ while (sd_dhcp6_lease_get_pd(lease, &pd_prefix.in6, &pd_prefix_len,
+ &lifetime_preferred,
+ &lifetime_valid) >= 0) {
+
+ if (pd_prefix_len > 64) {
+ (void) in_addr_to_string(AF_INET6, &pd_prefix, &buf);
+ log_link_debug(link, "PD Prefix length > 64, ignoring prefix %s/%u",
+ strnull(buf), pd_prefix_len);
+ continue;
+ }
+
+ if (pd_prefix_len < 48) {
+ (void) in_addr_to_string(AF_INET6, &pd_prefix, &buf);
+ log_link_warning(link, "PD Prefix length < 48, looks unusual %s/%u",
+ strnull(buf), pd_prefix_len);
+ }
+
+ if (pd_prefix_len < 64) {
+ Route *route = NULL;
+
+ (void) in_addr_to_string(AF_INET6, &pd_prefix, &buf);
+
+ r = route_new(&route);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Cannot create unreachable route for DHCPv6 delegated subnet %s/%u: %m",
+ strnull(buf),
+ pd_prefix_len);
+ continue;
+ }
+
+ route_add(link, AF_INET6, &pd_prefix, pd_prefix_len,
+ 0, 0, 0, &route);
+ route_update(route, NULL, 0, NULL, NULL, 0, 0,
+ RTN_UNREACHABLE);
+
+ r = route_configure(route, link, dhcp6_route_handler);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Cannot configure unreachable route for delegated subnet %s/%u: %m",
+ strnull(buf),
+ pd_prefix_len);
+ route_free(route);
+ continue;
+ }
+
+ route_free(route);
+
+ log_link_debug(link, "Configuring unreachable route for %s/%u",
+ strnull(buf), pd_prefix_len);
+
+ } else
+ log_link_debug(link, "Not adding a blocking route since distributed prefix is /64");
+
+ r = dhcp6_pd_prefix_distribute(link, &i, &pd_prefix.in6,
+ pd_prefix_len,
+ lifetime_preferred,
+ lifetime_valid);
+ if (r < 0 && r != -EAGAIN)
+ return r;
+
+ if (r >= 0)
+ i = ITERATOR_FIRST;
+ }
+
+ return 0;
+}
+
+int dhcp6_request_prefix_delegation(Link *link) {
+ Link *l;
+ Iterator i;
+
+ assert_return(link, -EINVAL);
+ assert_return(link->manager, -EOPNOTSUPP);
+
+ if (dhcp6_get_prefix_delegation(link) <= 0)
+ return 0;
+
+ log_link_debug(link, "Requesting DHCPv6 prefixes to be delegated for new link");
+
+ HASHMAP_FOREACH(l, link->manager->links, i) {
+ int r, enabled;
+
+ if (l == link)
+ continue;
+
+ if (!l->dhcp6_client)
+ continue;
+
+ r = sd_dhcp6_client_get_prefix_delegation(l->dhcp6_client, &enabled);
+ if (r < 0) {
+ log_link_warning_errno(l, r, "Cannot get prefix delegation when adding new link");
+ continue;
+ }
+
+ if (enabled == 0) {
+ r = sd_dhcp6_client_set_prefix_delegation(l->dhcp6_client, 1);
+ if (r < 0) {
+ log_link_warning_errno(l, r, "Cannot enable prefix delegation when adding new link");
+ continue;
+ }
+ }
+
+ r = sd_dhcp6_client_is_running(l->dhcp6_client);
+ if (r <= 0)
+ continue;
+
+ if (enabled != 0) {
+ log_link_debug(l, "Requesting re-assignment of delegated prefixes after adding new link");
+ (void) dhcp6_lease_pd_prefix_acquired(l->dhcp6_client, l);
+
+ continue;
+ }
+
+ r = sd_dhcp6_client_stop(l->dhcp6_client);
+ if (r < 0) {
+ log_link_warning_errno(l, r, "Cannot stop DHCPv6 prefix delegation client after adding new link");
+ continue;
+ }
+
+ r = sd_dhcp6_client_start(l->dhcp6_client);
+ if (r < 0) {
+ log_link_warning_errno(l, r, "Cannot restart DHCPv6 prefix delegation client after adding new link");
+ continue;
+ }
+
+ log_link_debug(l, "Restarted DHCPv6 client to acquire prefix delegations after adding new link");
+ }
+
+ return 0;
+}
+
+static int dhcp6_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST) {
+ if (link->rtnl_extended_attrs) {
+ log_link_warning(link, "Could not set extended netlink attributes, reverting to fallback mechanism");
+
+ link->rtnl_extended_attrs = false;
+ dhcp6_lease_address_acquired(link->dhcp6_client, link);
+
+ return 1;
+ }
+
+ log_link_error_errno(link, r, "Could not set DHCPv6 address: %m");
+
+ link_enter_failed(link);
+
+ } else if (r >= 0)
+ manager_rtnl_process_address(rtnl, m, link->manager);
+
+ return 1;
+}
+
+static int dhcp6_address_change(
+ Link *link,
+ struct in6_addr *ip6_addr,
+ uint32_t lifetime_preferred,
+ uint32_t lifetime_valid) {
+
+ _cleanup_(address_freep) Address *addr = NULL;
+ char buffer[INET6_ADDRSTRLEN];
+ int r;
+
+ r = address_new(&addr);
+ if (r < 0)
+ return r;
+
+ addr->family = AF_INET6;
+ memcpy(&addr->in_addr.in6, ip6_addr, sizeof(*ip6_addr));
+
+ addr->flags = IFA_F_NOPREFIXROUTE;
+ addr->prefixlen = 128;
+
+ addr->cinfo.ifa_prefered = lifetime_preferred;
+ addr->cinfo.ifa_valid = lifetime_valid;
+
+ log_link_info(link,
+ "DHCPv6 address %s/%d timeout preferred %d valid %d",
+ inet_ntop(AF_INET6, &addr->in_addr.in6, buffer, sizeof(buffer)),
+ addr->prefixlen, lifetime_preferred, lifetime_valid);
+
+ r = address_configure(addr, link, dhcp6_address_handler, true);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Could not assign DHCPv6 address: %m");
+
+ return r;
+}
+
+static int dhcp6_lease_address_acquired(sd_dhcp6_client *client, Link *link) {
+ int r;
+ sd_dhcp6_lease *lease;
+ struct in6_addr ip6_addr;
+ uint32_t lifetime_preferred, lifetime_valid;
+
+ r = sd_dhcp6_client_get_lease(client, &lease);
+ if (r < 0)
+ return r;
+
+ sd_dhcp6_lease_reset_address_iter(lease);
+
+ while (sd_dhcp6_lease_get_address(lease, &ip6_addr,
+ &lifetime_preferred,
+ &lifetime_valid) >= 0) {
+
+ r = dhcp6_address_change(link, &ip6_addr, lifetime_preferred, lifetime_valid);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static void dhcp6_handler(sd_dhcp6_client *client, int event, void *userdata) {
+ int r;
+ Link *link = userdata;
+
+ assert(link);
+ assert(link->network);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return;
+
+ switch(event) {
+ case SD_DHCP6_CLIENT_EVENT_STOP:
+ case SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE:
+ case SD_DHCP6_CLIENT_EVENT_RETRANS_MAX:
+ if (sd_dhcp6_client_get_lease(client, NULL) >= 0)
+ log_link_warning(link, "DHCPv6 lease lost");
+
+ (void) dhcp6_lease_pd_prefix_lost(client, link);
+ (void) manager_dhcp6_prefix_remove_all(link->manager, link);
+
+ link->dhcp6_configured = false;
+ break;
+
+ case SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE:
+ r = dhcp6_lease_address_acquired(client, link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return;
+ }
+
+ r = dhcp6_lease_pd_prefix_acquired(client, link);
+ if (r < 0)
+ log_link_debug(link, "DHCPv6 did not receive prefixes to delegate");
+
+ _fallthrough_;
+ case SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST:
+ r = dhcp6_lease_information_acquired(client, link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return;
+ }
+
+ link->dhcp6_configured = true;
+ break;
+
+ default:
+ if (event < 0)
+ log_link_warning_errno(link, event, "DHCPv6 error: %m");
+ else
+ log_link_warning(link, "DHCPv6 unknown event: %d", event);
+ return;
+ }
+
+ link_check_ready(link);
+}
+
+int dhcp6_request_address(Link *link, int ir) {
+ int r, inf_req, pd;
+ bool running;
+
+ assert(link);
+ assert(link->dhcp6_client);
+ assert(link->network);
+ assert(in_addr_is_link_local(AF_INET6, (const union in_addr_union*)&link->ipv6ll_address) > 0);
+
+ r = sd_dhcp6_client_is_running(link->dhcp6_client);
+ if (r < 0)
+ return r;
+ else
+ running = r;
+
+ r = sd_dhcp6_client_get_prefix_delegation(link->dhcp6_client, &pd);
+ if (r < 0)
+ return r;
+
+ if (pd && ir && link->network->dhcp6_force_pd_other_information) {
+ log_link_debug(link, "Enabling managed mode to request DHCPv6 PD with 'Other Information' set");
+
+ r = sd_dhcp6_client_set_address_request(link->dhcp6_client,
+ false);
+ if (r < 0 )
+ return r;
+
+ ir = false;
+ }
+
+ if (running) {
+ r = sd_dhcp6_client_get_information_request(link->dhcp6_client, &inf_req);
+ if (r < 0)
+ return r;
+
+ if (inf_req == ir)
+ return 0;
+
+ r = sd_dhcp6_client_stop(link->dhcp6_client);
+ if (r < 0)
+ return r;
+ } else {
+ r = sd_dhcp6_client_set_local_address(link->dhcp6_client, &link->ipv6ll_address);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_dhcp6_client_set_information_request(link->dhcp6_client, ir);
+ if (r < 0)
+ return r;
+
+ r = sd_dhcp6_client_start(link->dhcp6_client);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int dhcp6_set_hostname(sd_dhcp6_client *client, Link *link) {
+ _cleanup_free_ char *hostname = NULL;
+ const char *hn;
+ int r;
+
+ assert(link);
+
+ if (!link->network->dhcp_send_hostname)
+ hn = NULL;
+ else if (link->network->dhcp_hostname)
+ hn = link->network->dhcp_hostname;
+ else {
+ r = gethostname_strict(&hostname);
+ if (r < 0 && r != -ENXIO) /* ENXIO: no hostname set or hostname is "localhost" */
+ return r;
+
+ hn = hostname;
+ }
+
+ r = sd_dhcp6_client_set_fqdn(client, hn);
+ if (r == -EINVAL && hostname)
+ /* Ignore error when the machine's hostname is not suitable to send in DHCP packet. */
+ log_link_warning_errno(link, r, "DHCP6 CLIENT: Failed to set hostname from kernel hostname, ignoring: %m");
+ else if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set hostname: %m");
+
+ return 0;
+}
+
+int dhcp6_configure(Link *link) {
+ _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL;
+ const DUID *duid;
+ int r;
+
+ assert(link);
+ assert(link->network);
+
+ if (link->dhcp6_client)
+ return 0;
+
+ r = sd_dhcp6_client_new(&client);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to create DHCP6 client: %m");
+
+ r = sd_dhcp6_client_attach_event(client, NULL, 0);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to attach event: %m");
+
+ r = sd_dhcp6_client_set_mac(client,
+ (const uint8_t *) &link->mac,
+ sizeof (link->mac), ARPHRD_ETHER);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set MAC address: %m");
+
+ if (link->network->iaid_set) {
+ r = sd_dhcp6_client_set_iaid(client, link->network->iaid);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set IAID: %m");
+ }
+
+ duid = link_get_duid(link);
+ if (duid->type == DUID_TYPE_LLT && duid->raw_data_len == 0)
+ r = sd_dhcp6_client_set_duid_llt(client, duid->llt_time);
+ else
+ r = sd_dhcp6_client_set_duid(client,
+ duid->type,
+ duid->raw_data_len > 0 ? duid->raw_data : NULL,
+ duid->raw_data_len);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set DUID: %m");
+
+ r = dhcp6_set_hostname(client, link);
+ if (r < 0)
+ return r;
+
+ r = sd_dhcp6_client_set_ifindex(client, link->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set ifindex: %m");
+
+ if (link->network->rapid_commit) {
+ r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_RAPID_COMMIT);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set request flag for rapid commit: %m");
+ }
+
+ r = sd_dhcp6_client_set_callback(client, dhcp6_handler, link);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set callback: %m");
+
+ if (dhcp6_enable_prefix_delegation(link)) {
+ r = sd_dhcp6_client_set_prefix_delegation(client, true);
+ if (r < 0)
+ return log_link_error_errno(link, r, "DHCP6 CLIENT: Failed to set prefix delegation: %m");
+ }
+
+ link->dhcp6_client = TAKE_PTR(client);
+
+ return 0;
+}
diff --git a/src/network/networkd-fdb.c b/src/network/networkd-fdb.c
new file mode 100644
index 0000000..324f6a5
--- /dev/null
+++ b/src/network/networkd-fdb.c
@@ -0,0 +1,260 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/***
+ Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include <net/ethernet.h>
+#include <net/if.h>
+
+#include "alloc-util.h"
+#include "conf-parser.h"
+#include "netdev/bridge.h"
+#include "netlink-util.h"
+#include "networkd-fdb.h"
+#include "networkd-manager.h"
+#include "util.h"
+#include "vlan-util.h"
+
+#define STATIC_FDB_ENTRIES_PER_NETWORK_MAX 1024U
+
+/* create a new FDB entry or get an existing one. */
+int fdb_entry_new_static(
+ Network *network,
+ const char *filename,
+ unsigned section_line,
+ FdbEntry **ret) {
+
+ _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
+ _cleanup_(fdb_entry_freep) FdbEntry *fdb_entry = NULL;
+ _cleanup_free_ struct ether_addr *mac_addr = NULL;
+ int r;
+
+ assert(network);
+ assert(ret);
+ assert(!!filename == (section_line > 0));
+
+ /* search entry in hashmap first. */
+ if (filename) {
+ r = network_config_section_new(filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ fdb_entry = hashmap_get(network->fdb_entries_by_section, n);
+ if (fdb_entry) {
+ *ret = TAKE_PTR(fdb_entry);
+
+ return 0;
+ }
+ }
+
+ if (network->n_static_fdb_entries >= STATIC_FDB_ENTRIES_PER_NETWORK_MAX)
+ return -E2BIG;
+
+ /* allocate space for MAC address. */
+ mac_addr = new0(struct ether_addr, 1);
+ if (!mac_addr)
+ return -ENOMEM;
+
+ /* allocate space for and FDB entry. */
+ fdb_entry = new(FdbEntry, 1);
+ if (!fdb_entry)
+ return -ENOMEM;
+
+ /* init FDB structure. */
+ *fdb_entry = (FdbEntry) {
+ .network = network,
+ .mac_addr = TAKE_PTR(mac_addr),
+ };
+
+ LIST_PREPEND(static_fdb_entries, network->static_fdb_entries, fdb_entry);
+ network->n_static_fdb_entries++;
+
+ if (filename) {
+ fdb_entry->section = TAKE_PTR(n);
+
+ r = hashmap_ensure_allocated(&network->fdb_entries_by_section, &network_config_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(network->fdb_entries_by_section, fdb_entry->section, fdb_entry);
+ if (r < 0)
+ return r;
+ }
+
+ /* return allocated FDB structure. */
+ *ret = TAKE_PTR(fdb_entry);
+
+ return 0;
+}
+
+static int set_fdb_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST)
+ log_link_error_errno(link, r, "Could not add FDB entry: %m");
+
+ return 1;
+}
+
+/* send a request to the kernel to add a FDB entry in its static MAC table. */
+int fdb_entry_configure(Link *link, FdbEntry *fdb_entry) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ sd_netlink *rtnl;
+ int r;
+ uint8_t flags;
+ Bridge *bridge;
+
+ assert(link);
+ assert(link->network);
+ assert(link->manager);
+ assert(fdb_entry);
+
+ rtnl = link->manager->rtnl;
+ bridge = BRIDGE(link->network->bridge);
+
+ /* create new RTM message */
+ r = sd_rtnl_message_new_neigh(rtnl, &req, RTM_NEWNEIGH, link->ifindex, PF_BRIDGE);
+ if (r < 0)
+ return rtnl_log_create_error(r);
+
+ if (bridge)
+ flags = NTF_MASTER;
+ else
+ flags = NTF_SELF;
+
+ r = sd_rtnl_message_neigh_set_flags(req, flags);
+ if (r < 0)
+ return rtnl_log_create_error(r);
+
+ /* only NUD_PERMANENT state supported. */
+ r = sd_rtnl_message_neigh_set_state(req, NUD_NOARP | NUD_PERMANENT);
+ if (r < 0)
+ return rtnl_log_create_error(r);
+
+ r = sd_netlink_message_append_ether_addr(req, NDA_LLADDR, fdb_entry->mac_addr);
+ if (r < 0)
+ return rtnl_log_create_error(r);
+
+ /* VLAN Id is optional. We'll add VLAN Id only if it's specified. */
+ if (0 != fdb_entry->vlan_id) {
+ r = sd_netlink_message_append_u16(req, NDA_VLAN, fdb_entry->vlan_id);
+ if (r < 0)
+ return rtnl_log_create_error(r);
+ }
+
+ /* send message to the kernel to update its internal static MAC table. */
+ r = netlink_call_async(rtnl, NULL, req, set_fdb_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ return 0;
+}
+
+/* remove and FDB entry. */
+void fdb_entry_free(FdbEntry *fdb_entry) {
+ if (!fdb_entry)
+ return;
+
+ if (fdb_entry->network) {
+ LIST_REMOVE(static_fdb_entries, fdb_entry->network->static_fdb_entries, fdb_entry);
+ assert(fdb_entry->network->n_static_fdb_entries > 0);
+ fdb_entry->network->n_static_fdb_entries--;
+
+ if (fdb_entry->section)
+ hashmap_remove(fdb_entry->network->fdb_entries_by_section, fdb_entry->section);
+ }
+
+ network_config_section_free(fdb_entry->section);
+ free(fdb_entry->mac_addr);
+ free(fdb_entry);
+}
+
+/* parse the HW address from config files. */
+int config_parse_fdb_hwaddr(
+ 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_(fdb_entry_freep) FdbEntry *fdb_entry = NULL;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = fdb_entry_new_static(network, filename, section_line, &fdb_entry);
+ if (r < 0)
+ return log_oom();
+
+ /* read in the MAC address for the FDB table. */
+ r = sscanf(rvalue, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+ &fdb_entry->mac_addr->ether_addr_octet[0],
+ &fdb_entry->mac_addr->ether_addr_octet[1],
+ &fdb_entry->mac_addr->ether_addr_octet[2],
+ &fdb_entry->mac_addr->ether_addr_octet[3],
+ &fdb_entry->mac_addr->ether_addr_octet[4],
+ &fdb_entry->mac_addr->ether_addr_octet[5]);
+
+ if (r != ETHER_ADDR_LEN) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Not a valid MAC address, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ fdb_entry = NULL;
+
+ return 0;
+}
+
+/* parse the VLAN Id from config files. */
+int config_parse_fdb_vlan_id(
+ 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_(fdb_entry_freep) FdbEntry *fdb_entry = NULL;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = fdb_entry_new_static(network, filename, section_line, &fdb_entry);
+ if (r < 0)
+ return log_oom();
+
+ r = config_parse_vlanid(unit, filename, line, section,
+ section_line, lvalue, ltype,
+ rvalue, &fdb_entry->vlan_id, userdata);
+ if (r < 0)
+ return r;
+
+ fdb_entry = NULL;
+
+ return 0;
+}
diff --git a/src/network/networkd-fdb.h b/src/network/networkd-fdb.h
new file mode 100644
index 0000000..55fc3a2
--- /dev/null
+++ b/src/network/networkd-fdb.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+/***
+ Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include "conf-parser.h"
+#include "list.h"
+#include "macro.h"
+
+typedef struct Network Network;
+typedef struct FdbEntry FdbEntry;
+typedef struct Link Link;
+typedef struct NetworkConfigSection NetworkConfigSection;
+
+struct FdbEntry {
+ Network *network;
+ NetworkConfigSection *section;
+
+ struct ether_addr *mac_addr;
+ uint16_t vlan_id;
+
+ LIST_FIELDS(FdbEntry, static_fdb_entries);
+};
+
+int fdb_entry_new_static(Network *network, const char *filename, unsigned section_line, FdbEntry **ret);
+void fdb_entry_free(FdbEntry *fdb_entry);
+int fdb_entry_configure(Link *link, FdbEntry *fdb_entry);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(FdbEntry*, fdb_entry_free);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_fdb_hwaddr);
+CONFIG_PARSER_PROTOTYPE(config_parse_fdb_vlan_id);
diff --git a/src/network/networkd-gperf.gperf b/src/network/networkd-gperf.gperf
new file mode 100644
index 0000000..cc88fc0
--- /dev/null
+++ b/src/network/networkd-gperf.gperf
@@ -0,0 +1,22 @@
+%{
+#if __GNUC__ >= 7
+_Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"")
+#endif
+#include <stddef.h>
+#include "conf-parser.h"
+#include "networkd-conf.h"
+#include "networkd-manager.h"
+%}
+struct ConfigPerfItem;
+%null_strings
+%language=ANSI-C
+%define slot-name section_and_lvalue
+%define hash-function-name networkd_gperf_hash
+%define lookup-function-name networkd_gperf_lookup
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Manager, duid)
+DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Manager, duid)
diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c
new file mode 100644
index 0000000..3562e90
--- /dev/null
+++ b/src/network/networkd-ipv4ll.c
@@ -0,0 +1,227 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <netinet/ether.h>
+#include <linux/if.h>
+
+#include "network-internal.h"
+#include "networkd-address.h"
+#include "networkd-manager.h"
+#include "networkd-link.h"
+
+static int ipv4ll_address_lost(Link *link) {
+ _cleanup_(address_freep) Address *address = NULL;
+ _cleanup_(route_freep) Route *route = NULL;
+ struct in_addr addr;
+ int r;
+
+ assert(link);
+
+ link->ipv4ll_route = false;
+ link->ipv4ll_address = false;
+
+ r = sd_ipv4ll_get_address(link->ipv4ll, &addr);
+ if (r < 0)
+ return 0;
+
+ log_link_debug(link, "IPv4 link-local release %u.%u.%u.%u", ADDRESS_FMT_VAL(addr));
+
+ r = address_new(&address);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate address: %m");
+
+ address->family = AF_INET;
+ address->in_addr.in = addr;
+ address->prefixlen = 16;
+ address->scope = RT_SCOPE_LINK;
+
+ address_remove(address, link, NULL);
+
+ r = route_new(&route);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate route: %m");
+
+ route->family = AF_INET;
+ route->scope = RT_SCOPE_LINK;
+ route->priority = IPV4LL_ROUTE_METRIC;
+
+ route_remove(route, link, NULL);
+
+ link_check_ready(link);
+
+ return 0;
+}
+
+static int ipv4ll_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+ assert(!link->ipv4ll_route);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST) {
+ log_link_error_errno(link, r, "could not set ipv4ll route: %m");
+ link_enter_failed(link);
+ }
+
+ link->ipv4ll_route = true;
+
+ if (link->ipv4ll_address == true)
+ link_check_ready(link);
+
+ return 1;
+}
+
+static int ipv4ll_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+ assert(!link->ipv4ll_address);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST) {
+ log_link_error_errno(link, r, "could not set ipv4ll address: %m");
+ link_enter_failed(link);
+ } else if (r >= 0)
+ manager_rtnl_process_address(rtnl, m, link->manager);
+
+ link->ipv4ll_address = true;
+
+ if (link->ipv4ll_route)
+ link_check_ready(link);
+
+ return 1;
+}
+
+static int ipv4ll_address_claimed(sd_ipv4ll *ll, Link *link) {
+ _cleanup_(address_freep) Address *ll_addr = NULL;
+ _cleanup_(route_freep) Route *route = NULL;
+ struct in_addr address;
+ int r;
+
+ assert(ll);
+ assert(link);
+
+ r = sd_ipv4ll_get_address(ll, &address);
+ if (r == -ENOENT)
+ return 0;
+ else if (r < 0)
+ return r;
+
+ log_link_debug(link, "IPv4 link-local claim %u.%u.%u.%u",
+ ADDRESS_FMT_VAL(address));
+
+ r = address_new(&ll_addr);
+ if (r < 0)
+ return r;
+
+ ll_addr->family = AF_INET;
+ ll_addr->in_addr.in = address;
+ ll_addr->prefixlen = 16;
+ ll_addr->broadcast.s_addr = ll_addr->in_addr.in.s_addr | htobe32(0xfffffffflu >> ll_addr->prefixlen);
+ ll_addr->scope = RT_SCOPE_LINK;
+
+ r = address_configure(ll_addr, link, ipv4ll_address_handler, false);
+ if (r < 0)
+ return r;
+
+ link->ipv4ll_address = false;
+
+ r = route_new(&route);
+ if (r < 0)
+ return r;
+
+ route->family = AF_INET;
+ route->scope = RT_SCOPE_LINK;
+ route->protocol = RTPROT_STATIC;
+ route->priority = IPV4LL_ROUTE_METRIC;
+
+ r = route_configure(route, link, ipv4ll_route_handler);
+ if (r < 0)
+ return r;
+
+ link->ipv4ll_route = false;
+
+ return 0;
+}
+
+static void ipv4ll_handler(sd_ipv4ll *ll, int event, void *userdata) {
+ Link *link = userdata;
+ int r;
+
+ assert(link);
+ assert(link->network);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return;
+
+ switch(event) {
+ case SD_IPV4LL_EVENT_STOP:
+ r = ipv4ll_address_lost(link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return;
+ }
+ break;
+ case SD_IPV4LL_EVENT_CONFLICT:
+ r = ipv4ll_address_lost(link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return;
+ }
+
+ r = sd_ipv4ll_restart(ll);
+ if (r < 0)
+ log_link_warning(link, "Could not acquire IPv4 link-local address");
+ break;
+ case SD_IPV4LL_EVENT_BIND:
+ r = ipv4ll_address_claimed(ll, link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return;
+ }
+ break;
+ default:
+ log_link_warning(link, "IPv4 link-local unknown event: %d", event);
+ break;
+ }
+}
+
+int ipv4ll_configure(Link *link) {
+ uint64_t seed;
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(link->network->link_local & ADDRESS_FAMILY_IPV4);
+
+ if (!link->ipv4ll) {
+ r = sd_ipv4ll_new(&link->ipv4ll);
+ if (r < 0)
+ return r;
+ }
+
+ if (link->sd_device &&
+ net_get_unique_predictable_data(link->sd_device, &seed) >= 0) {
+ r = sd_ipv4ll_set_address_seed(link->ipv4ll, seed);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_ipv4ll_attach_event(link->ipv4ll, NULL, 0);
+ if (r < 0)
+ return r;
+
+ r = sd_ipv4ll_set_mac(link->ipv4ll, &link->mac);
+ if (r < 0)
+ return r;
+
+ r = sd_ipv4ll_set_ifindex(link->ipv4ll, link->ifindex);
+ if (r < 0)
+ return r;
+
+ r = sd_ipv4ll_set_callback(link->ipv4ll, ipv4ll_handler, link);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
diff --git a/src/network/networkd-ipv6-proxy-ndp.c b/src/network/networkd-ipv6-proxy-ndp.c
new file mode 100644
index 0000000..f594b27
--- /dev/null
+++ b/src/network/networkd-ipv6-proxy-ndp.c
@@ -0,0 +1,204 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <netinet/ether.h>
+#include <linux/if.h>
+#include <unistd.h>
+
+#include "fileio.h"
+#include "netlink-util.h"
+#include "networkd-ipv6-proxy-ndp.h"
+#include "networkd-link.h"
+#include "networkd-manager.h"
+#include "networkd-network.h"
+#include "string-util.h"
+#include "socket-util.h"
+
+static bool ipv6_proxy_ndp_is_needed(Link *link) {
+ assert(link);
+
+ if (link->flags & IFF_LOOPBACK)
+ return false;
+
+ if (!link->network)
+ return false;
+
+ if (link->network->ipv6_proxy_ndp >= 0)
+ return link->network->ipv6_proxy_ndp;
+
+ if (link->network->n_ipv6_proxy_ndp_addresses == 0)
+ return false;
+
+ return true;
+}
+
+static int ipv6_proxy_ndp_set(Link *link) {
+ const char *p = NULL;
+ int r, v;
+
+ assert(link);
+
+ if (!socket_ipv6_is_supported())
+ return 0;
+
+ v = ipv6_proxy_ndp_is_needed(link);
+ p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/proxy_ndp");
+
+ r = write_string_file(p, one_zero(v), WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Cannot configure proxy NDP for interface: %m");
+
+ return 0;
+}
+
+int ipv6_proxy_ndp_address_new_static(Network *network, IPv6ProxyNDPAddress **ret) {
+ _cleanup_(ipv6_proxy_ndp_address_freep) IPv6ProxyNDPAddress *ipv6_proxy_ndp_address = NULL;
+
+ assert(network);
+ assert(ret);
+
+ /* allocate space for IPv6ProxyNDPAddress entry */
+ ipv6_proxy_ndp_address = new(IPv6ProxyNDPAddress, 1);
+ if (!ipv6_proxy_ndp_address)
+ return -ENOMEM;
+
+ *ipv6_proxy_ndp_address = (IPv6ProxyNDPAddress) {
+ .network = network,
+ };
+
+ LIST_PREPEND(ipv6_proxy_ndp_addresses, network->ipv6_proxy_ndp_addresses, ipv6_proxy_ndp_address);
+ network->n_ipv6_proxy_ndp_addresses++;
+
+ *ret = TAKE_PTR(ipv6_proxy_ndp_address);
+
+ return 0;
+}
+
+void ipv6_proxy_ndp_address_free(IPv6ProxyNDPAddress *ipv6_proxy_ndp_address) {
+ if (!ipv6_proxy_ndp_address)
+ return;
+
+ if (ipv6_proxy_ndp_address->network) {
+ LIST_REMOVE(ipv6_proxy_ndp_addresses, ipv6_proxy_ndp_address->network->ipv6_proxy_ndp_addresses,
+ ipv6_proxy_ndp_address);
+
+ assert(ipv6_proxy_ndp_address->network->n_ipv6_proxy_ndp_addresses > 0);
+ ipv6_proxy_ndp_address->network->n_ipv6_proxy_ndp_addresses--;
+ }
+
+ free(ipv6_proxy_ndp_address);
+}
+
+int config_parse_ipv6_proxy_ndp_address(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Network *network = userdata;
+ _cleanup_(ipv6_proxy_ndp_address_freep) IPv6ProxyNDPAddress *ipv6_proxy_ndp_address = NULL;
+ int r;
+ union in_addr_union buffer;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = ipv6_proxy_ndp_address_new_static(network, &ipv6_proxy_ndp_address);
+ if (r < 0)
+ return r;
+
+ r = in_addr_from_string(AF_INET6, rvalue, &buffer);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse IPv6 proxy NDP address, ignoring: %s",
+ rvalue);
+ return 0;
+ }
+
+ r = in_addr_is_null(AF_INET6, &buffer);
+ if (r != 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "IPv6 proxy NDP address cannot be the ANY address, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ ipv6_proxy_ndp_address->in_addr = buffer.in6;
+ ipv6_proxy_ndp_address = NULL;
+
+ return 0;
+}
+
+static int set_ipv6_proxy_ndp_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST)
+ log_link_error_errno(link, r, "Could not add IPv6 proxy ndp address entry: %m");
+
+ return 1;
+}
+
+/* send a request to the kernel to add a IPv6 Proxy entry to the neighbour table */
+int ipv6_proxy_ndp_address_configure(Link *link, IPv6ProxyNDPAddress *ipv6_proxy_ndp_address) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ sd_netlink *rtnl;
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(link->manager);
+ assert(ipv6_proxy_ndp_address);
+
+ rtnl = link->manager->rtnl;
+
+ /* create new netlink message */
+ r = sd_rtnl_message_new_neigh(rtnl, &req, RTM_NEWNEIGH, link->ifindex, AF_INET6);
+ if (r < 0)
+ return rtnl_log_create_error(r);
+
+ r = sd_rtnl_message_neigh_set_flags(req, NLM_F_REQUEST | NTF_PROXY);
+ if (r < 0)
+ return rtnl_log_create_error(r);
+
+ r = sd_netlink_message_append_in6_addr(req, NDA_DST, &ipv6_proxy_ndp_address->in_addr);
+ if (r < 0)
+ return rtnl_log_create_error(r);
+
+ r = netlink_call_async(rtnl, NULL, req, set_ipv6_proxy_ndp_address_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ return 0;
+}
+
+/* configure all ipv6 proxy ndp addresses */
+int ipv6_proxy_ndp_addresses_configure(Link *link) {
+ IPv6ProxyNDPAddress *ipv6_proxy_ndp_address;
+ int r;
+
+ assert(link);
+
+ /* enable or disable proxy_ndp itself depending on whether ipv6_proxy_ndp_addresses are set or not */
+ r = ipv6_proxy_ndp_set(link);
+ if (r != 0)
+ return r;
+
+ LIST_FOREACH(ipv6_proxy_ndp_addresses, ipv6_proxy_ndp_address, link->network->ipv6_proxy_ndp_addresses) {
+ r = ipv6_proxy_ndp_address_configure(link, ipv6_proxy_ndp_address);
+ if (r != 0)
+ return r;
+ }
+ return 0;
+}
diff --git a/src/network/networkd-ipv6-proxy-ndp.h b/src/network/networkd-ipv6-proxy-ndp.h
new file mode 100644
index 0000000..546e856
--- /dev/null
+++ b/src/network/networkd-ipv6-proxy-ndp.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "conf-parser.h"
+#include "list.h"
+#include "macro.h"
+
+typedef struct Network Network;
+typedef struct IPv6ProxyNDPAddress IPv6ProxyNDPAddress;
+typedef struct Link Link;
+
+struct IPv6ProxyNDPAddress {
+ Network *network;
+ struct in6_addr in_addr;
+
+ LIST_FIELDS(IPv6ProxyNDPAddress, ipv6_proxy_ndp_addresses);
+};
+
+int ipv6_proxy_ndp_address_new_static(Network *network, IPv6ProxyNDPAddress ** ipv6_proxy_ndp_address);
+void ipv6_proxy_ndp_address_free(IPv6ProxyNDPAddress *ipv6_proxy_ndp_address);
+int ipv6_proxy_ndp_address_configure(Link *link, IPv6ProxyNDPAddress *ipv6_proxy_ndp_address);
+int ipv6_proxy_ndp_addresses_configure(Link *link);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(IPv6ProxyNDPAddress*, ipv6_proxy_ndp_address_free);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_proxy_ndp_address);
diff --git a/src/network/networkd-link-bus.c b/src/network/networkd-link-bus.c
new file mode 100644
index 0000000..0dbcd86
--- /dev/null
+++ b/src/network/networkd-link-bus.c
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "alloc-util.h"
+#include "bus-util.h"
+#include "networkd-link.h"
+#include "networkd-manager.h"
+#include "parse-util.h"
+#include "strv.h"
+
+static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_operational_state, link_operstate, LinkOperationalState);
+static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_administrative_state, link_state, LinkState);
+
+const sd_bus_vtable link_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+
+ SD_BUS_PROPERTY("OperationalState", "s", property_get_operational_state, offsetof(Link, operstate), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+ SD_BUS_PROPERTY("AdministrativeState", "s", property_get_administrative_state, offsetof(Link, state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+
+ SD_BUS_VTABLE_END
+};
+
+static char *link_bus_path(Link *link) {
+ _cleanup_free_ char *ifindex = NULL;
+ char *p;
+ int r;
+
+ assert(link);
+ assert(link->ifindex > 0);
+
+ if (asprintf(&ifindex, "%d", link->ifindex) < 0)
+ return NULL;
+
+ r = sd_bus_path_encode("/org/freedesktop/network1/link", ifindex, &p);
+ if (r < 0)
+ return NULL;
+
+ return p;
+}
+
+int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
+ _cleanup_strv_free_ char **l = NULL;
+ Manager *m = userdata;
+ unsigned c = 0;
+ Link *link;
+ Iterator i;
+
+ assert(bus);
+ assert(path);
+ assert(m);
+ assert(nodes);
+
+ l = new0(char*, hashmap_size(m->links) + 1);
+ if (!l)
+ return -ENOMEM;
+
+ HASHMAP_FOREACH(link, m->links, i) {
+ char *p;
+
+ p = link_bus_path(link);
+ if (!p)
+ return -ENOMEM;
+
+ l[c++] = p;
+ }
+
+ l[c] = NULL;
+ *nodes = TAKE_PTR(l);
+
+ return 1;
+}
+
+int link_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) {
+ _cleanup_free_ char *identifier = NULL;
+ Manager *m = userdata;
+ Link *link;
+ int ifindex, r;
+
+ assert(bus);
+ assert(path);
+ assert(interface);
+ assert(m);
+ assert(found);
+
+ r = sd_bus_path_decode(path, "/org/freedesktop/network1/link", &identifier);
+ if (r <= 0)
+ return 0;
+
+ r = parse_ifindex(identifier, &ifindex);
+ if (r < 0)
+ return 0;
+
+ r = link_get(m, ifindex, &link);
+ if (r < 0)
+ return 0;
+
+ *found = link;
+
+ return 1;
+}
+
+int link_send_changed(Link *link, const char *property, ...) {
+ _cleanup_free_ char *p = NULL;
+ char **l;
+
+ assert(link);
+ assert(link->manager);
+
+ if (!link->manager->bus)
+ return 0; /* replace with assert when we have kdbus */
+
+ l = strv_from_stdarg_alloca(property);
+
+ p = link_bus_path(link);
+ if (!p)
+ return -ENOMEM;
+
+ return sd_bus_emit_properties_changed_strv(
+ link->manager->bus,
+ p,
+ "org.freedesktop.network1.Link",
+ l);
+}
diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
new file mode 100644
index 0000000..22392d7
--- /dev/null
+++ b/src/network/networkd-link.c
@@ -0,0 +1,4184 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <netinet/ether.h>
+#include <linux/if.h>
+#include <linux/can/netlink.h>
+#include <unistd.h>
+#include <stdio_ext.h>
+
+#include "alloc-util.h"
+#include "bus-util.h"
+#include "dhcp-identifier.h"
+#include "dhcp-lease-internal.h"
+#include "env-file.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "missing_network.h"
+#include "netlink-util.h"
+#include "network-internal.h"
+#include "networkd-ipv6-proxy-ndp.h"
+#include "networkd-lldp-tx.h"
+#include "networkd-manager.h"
+#include "networkd-ndisc.h"
+#include "networkd-neighbor.h"
+#include "networkd-radv.h"
+#include "networkd-routing-policy-rule.h"
+#include "set.h"
+#include "socket-util.h"
+#include "stdio-util.h"
+#include "string-table.h"
+#include "strv.h"
+#include "tmpfile-util.h"
+#include "util.h"
+#include "virt.h"
+
+DUID* link_get_duid(Link *link) {
+ if (link->network->duid.type != _DUID_TYPE_INVALID)
+ return &link->network->duid;
+ else
+ return &link->manager->duid;
+}
+
+static bool link_dhcp6_enabled(Link *link) {
+ assert(link);
+
+ if (!socket_ipv6_is_supported())
+ return false;
+
+ if (link->flags & IFF_LOOPBACK)
+ return false;
+
+ if (!link->network)
+ return false;
+
+ return link->network->dhcp & ADDRESS_FAMILY_IPV6;
+}
+
+static bool link_dhcp4_enabled(Link *link) {
+ assert(link);
+
+ if (link->flags & IFF_LOOPBACK)
+ return false;
+
+ if (!link->network)
+ return false;
+
+ return link->network->dhcp & ADDRESS_FAMILY_IPV4;
+}
+
+static bool link_dhcp4_server_enabled(Link *link) {
+ assert(link);
+
+ if (link->flags & IFF_LOOPBACK)
+ return false;
+
+ if (!link->network)
+ return false;
+
+ return link->network->dhcp_server;
+}
+
+static bool link_ipv4ll_enabled(Link *link) {
+ assert(link);
+
+ if (link->flags & IFF_LOOPBACK)
+ return false;
+
+ if (!link->network)
+ return false;
+
+ if (streq_ptr(link->kind, "wireguard"))
+ return false;
+
+ return link->network->link_local & ADDRESS_FAMILY_IPV4;
+}
+
+static bool link_ipv6ll_enabled(Link *link) {
+ assert(link);
+
+ if (!socket_ipv6_is_supported())
+ return false;
+
+ if (link->flags & IFF_LOOPBACK)
+ return false;
+
+ if (!link->network)
+ return false;
+
+ if (streq_ptr(link->kind, "wireguard"))
+ return false;
+
+ return link->network->link_local & ADDRESS_FAMILY_IPV6;
+}
+
+static bool link_ipv6_enabled(Link *link) {
+ assert(link);
+
+ if (!socket_ipv6_is_supported())
+ return false;
+
+ if (link->network->bridge)
+ return false;
+
+ /* DHCPv6 client will not be started if no IPv6 link-local address is configured. */
+ return link_ipv6ll_enabled(link) || network_has_static_ipv6_addresses(link->network);
+}
+
+static bool link_radv_enabled(Link *link) {
+ assert(link);
+
+ if (!link_ipv6ll_enabled(link))
+ return false;
+
+ return link->network->router_prefix_delegation != RADV_PREFIX_DELEGATION_NONE;
+}
+
+static bool link_lldp_rx_enabled(Link *link) {
+ assert(link);
+
+ if (link->flags & IFF_LOOPBACK)
+ return false;
+
+ if (link->iftype != ARPHRD_ETHER)
+ return false;
+
+ if (!link->network)
+ return false;
+
+ /* LLDP should be handled on bridge slaves as those have a direct
+ * connection to their peers not on the bridge master. Linux doesn't
+ * even (by default) forward lldp packets to the bridge master.*/
+ if (streq_ptr("bridge", link->kind))
+ return false;
+
+ return link->network->lldp_mode != LLDP_MODE_NO;
+}
+
+static bool link_lldp_emit_enabled(Link *link) {
+ assert(link);
+
+ if (link->flags & IFF_LOOPBACK)
+ return false;
+
+ if (link->iftype != ARPHRD_ETHER)
+ return false;
+
+ if (!link->network)
+ return false;
+
+ return link->network->lldp_emit != LLDP_EMIT_NO;
+}
+
+static bool link_ipv4_forward_enabled(Link *link) {
+ assert(link);
+
+ if (link->flags & IFF_LOOPBACK)
+ return false;
+
+ if (!link->network)
+ return false;
+
+ if (link->network->ip_forward == _ADDRESS_FAMILY_BOOLEAN_INVALID)
+ return false;
+
+ return link->network->ip_forward & ADDRESS_FAMILY_IPV4;
+}
+
+static bool link_ipv6_forward_enabled(Link *link) {
+ assert(link);
+
+ if (!socket_ipv6_is_supported())
+ return false;
+
+ if (link->flags & IFF_LOOPBACK)
+ return false;
+
+ if (!link->network)
+ return false;
+
+ if (link->network->ip_forward == _ADDRESS_FAMILY_BOOLEAN_INVALID)
+ return false;
+
+ return link->network->ip_forward & ADDRESS_FAMILY_IPV6;
+}
+
+static bool link_proxy_arp_enabled(Link *link) {
+ assert(link);
+
+ if (link->flags & IFF_LOOPBACK)
+ return false;
+
+ if (!link->network)
+ return false;
+
+ if (link->network->proxy_arp < 0)
+ return false;
+
+ return true;
+}
+
+static bool link_ipv6_accept_ra_enabled(Link *link) {
+ assert(link);
+
+ if (!socket_ipv6_is_supported())
+ return false;
+
+ if (link->flags & IFF_LOOPBACK)
+ return false;
+
+ if (!link->network)
+ return false;
+
+ if (!link_ipv6ll_enabled(link))
+ return false;
+
+ /* If unset use system default (enabled if local forwarding is disabled.
+ * disabled if local forwarding is enabled).
+ * If set, ignore or enforce RA independent of local forwarding state.
+ */
+ if (link->network->ipv6_accept_ra < 0)
+ /* default to accept RA if ip_forward is disabled and ignore RA if ip_forward is enabled */
+ return !link_ipv6_forward_enabled(link);
+ else if (link->network->ipv6_accept_ra > 0)
+ /* accept RA even if ip_forward is enabled */
+ return true;
+ else
+ /* ignore RA */
+ return false;
+}
+
+static IPv6PrivacyExtensions link_ipv6_privacy_extensions(Link *link) {
+ assert(link);
+
+ if (!socket_ipv6_is_supported())
+ return _IPV6_PRIVACY_EXTENSIONS_INVALID;
+
+ if (link->flags & IFF_LOOPBACK)
+ return _IPV6_PRIVACY_EXTENSIONS_INVALID;
+
+ if (!link->network)
+ return _IPV6_PRIVACY_EXTENSIONS_INVALID;
+
+ return link->network->ipv6_privacy_extensions;
+}
+
+static int link_enable_ipv6(Link *link) {
+ const char *p = NULL;
+ bool disabled;
+ int r;
+
+ if (link->flags & IFF_LOOPBACK)
+ return 0;
+
+ disabled = !link_ipv6_enabled(link);
+
+ p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/disable_ipv6");
+
+ r = write_string_file(p, one_zero(disabled), WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Cannot %s IPv6 for interface %s: %m",
+ enable_disable(!disabled), link->ifname);
+ else
+ log_link_info(link, "IPv6 successfully %sd", enable_disable(!disabled));
+
+ return 0;
+}
+
+void link_update_operstate(Link *link) {
+ LinkOperationalState operstate;
+ assert(link);
+
+ if (link->kernel_operstate == IF_OPER_DORMANT)
+ operstate = LINK_OPERSTATE_DORMANT;
+ else if (link_has_carrier(link)) {
+ Address *address;
+ uint8_t scope = RT_SCOPE_NOWHERE;
+ Iterator i;
+
+ /* if we have carrier, check what addresses we have */
+ SET_FOREACH(address, link->addresses, i) {
+ if (!address_is_ready(address))
+ continue;
+
+ if (address->scope < scope)
+ scope = address->scope;
+ }
+
+ /* for operstate we also take foreign addresses into account */
+ SET_FOREACH(address, link->addresses_foreign, i) {
+ if (!address_is_ready(address))
+ continue;
+
+ if (address->scope < scope)
+ scope = address->scope;
+ }
+
+ if (scope < RT_SCOPE_SITE)
+ /* universally accessible addresses found */
+ operstate = LINK_OPERSTATE_ROUTABLE;
+ else if (scope < RT_SCOPE_HOST)
+ /* only link or site local addresses found */
+ operstate = LINK_OPERSTATE_DEGRADED;
+ else
+ /* no useful addresses found */
+ operstate = LINK_OPERSTATE_CARRIER;
+ } else if (link->flags & IFF_UP)
+ operstate = LINK_OPERSTATE_NO_CARRIER;
+ else
+ operstate = LINK_OPERSTATE_OFF;
+
+ if (link->operstate != operstate) {
+ link->operstate = operstate;
+ link_send_changed(link, "OperationalState", NULL);
+ link_dirty(link);
+ }
+}
+
+#define FLAG_STRING(string, flag, old, new) \
+ (((old ^ new) & flag) \
+ ? ((old & flag) ? (" -" string) : (" +" string)) \
+ : "")
+
+static int link_update_flags(Link *link, sd_netlink_message *m) {
+ unsigned flags, unknown_flags_added, unknown_flags_removed, unknown_flags;
+ uint8_t operstate;
+ int r;
+
+ assert(link);
+
+ r = sd_rtnl_message_link_get_flags(m, &flags);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not get link flags: %m");
+
+ r = sd_netlink_message_read_u8(m, IFLA_OPERSTATE, &operstate);
+ if (r < 0)
+ /* if we got a message without operstate, take it to mean
+ the state was unchanged */
+ operstate = link->kernel_operstate;
+
+ if ((link->flags == flags) && (link->kernel_operstate == operstate))
+ return 0;
+
+ if (link->flags != flags) {
+ log_link_debug(link, "Flags change:%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
+ FLAG_STRING("LOOPBACK", IFF_LOOPBACK, link->flags, flags),
+ FLAG_STRING("MASTER", IFF_MASTER, link->flags, flags),
+ FLAG_STRING("SLAVE", IFF_SLAVE, link->flags, flags),
+ FLAG_STRING("UP", IFF_UP, link->flags, flags),
+ FLAG_STRING("DORMANT", IFF_DORMANT, link->flags, flags),
+ FLAG_STRING("LOWER_UP", IFF_LOWER_UP, link->flags, flags),
+ FLAG_STRING("RUNNING", IFF_RUNNING, link->flags, flags),
+ FLAG_STRING("MULTICAST", IFF_MULTICAST, link->flags, flags),
+ FLAG_STRING("BROADCAST", IFF_BROADCAST, link->flags, flags),
+ FLAG_STRING("POINTOPOINT", IFF_POINTOPOINT, link->flags, flags),
+ FLAG_STRING("PROMISC", IFF_PROMISC, link->flags, flags),
+ FLAG_STRING("ALLMULTI", IFF_ALLMULTI, link->flags, flags),
+ FLAG_STRING("PORTSEL", IFF_PORTSEL, link->flags, flags),
+ FLAG_STRING("AUTOMEDIA", IFF_AUTOMEDIA, link->flags, flags),
+ FLAG_STRING("DYNAMIC", IFF_DYNAMIC, link->flags, flags),
+ FLAG_STRING("NOARP", IFF_NOARP, link->flags, flags),
+ FLAG_STRING("NOTRAILERS", IFF_NOTRAILERS, link->flags, flags),
+ FLAG_STRING("DEBUG", IFF_DEBUG, link->flags, flags),
+ FLAG_STRING("ECHO", IFF_ECHO, link->flags, flags));
+
+ unknown_flags = ~(IFF_LOOPBACK | IFF_MASTER | IFF_SLAVE | IFF_UP |
+ IFF_DORMANT | IFF_LOWER_UP | IFF_RUNNING |
+ IFF_MULTICAST | IFF_BROADCAST | IFF_POINTOPOINT |
+ IFF_PROMISC | IFF_ALLMULTI | IFF_PORTSEL |
+ IFF_AUTOMEDIA | IFF_DYNAMIC | IFF_NOARP |
+ IFF_NOTRAILERS | IFF_DEBUG | IFF_ECHO);
+ unknown_flags_added = ((link->flags ^ flags) & flags & unknown_flags);
+ unknown_flags_removed = ((link->flags ^ flags) & link->flags & unknown_flags);
+
+ /* link flags are currently at most 18 bits, let's align to
+ * printing 20 */
+ if (unknown_flags_added)
+ log_link_debug(link,
+ "Unknown link flags gained: %#.5x (ignoring)",
+ unknown_flags_added);
+
+ if (unknown_flags_removed)
+ log_link_debug(link,
+ "Unknown link flags lost: %#.5x (ignoring)",
+ unknown_flags_removed);
+ }
+
+ link->flags = flags;
+ link->kernel_operstate = operstate;
+
+ link_update_operstate(link);
+
+ return 0;
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_unref);
+
+static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) {
+ _cleanup_(link_unrefp) Link *link = NULL;
+ uint16_t type;
+ const char *ifname, *kind = NULL;
+ int r, ifindex;
+ unsigned short iftype;
+
+ assert(manager);
+ assert(message);
+ assert(ret);
+
+ /* check for link kind */
+ r = sd_netlink_message_enter_container(message, IFLA_LINKINFO);
+ if (r == 0) {
+ (void) sd_netlink_message_read_string(message, IFLA_INFO_KIND, &kind);
+ r = sd_netlink_message_exit_container(message);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_netlink_message_get_type(message, &type);
+ if (r < 0)
+ return r;
+ else if (type != RTM_NEWLINK)
+ return -EINVAL;
+
+ r = sd_rtnl_message_link_get_ifindex(message, &ifindex);
+ if (r < 0)
+ return r;
+ else if (ifindex <= 0)
+ return -EINVAL;
+
+ r = sd_rtnl_message_link_get_type(message, &iftype);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_read_string(message, IFLA_IFNAME, &ifname);
+ if (r < 0)
+ return r;
+
+ link = new(Link, 1);
+ if (!link)
+ return -ENOMEM;
+
+ *link = (Link) {
+ .n_ref = 1,
+ .manager = manager,
+ .state = LINK_STATE_PENDING,
+ .rtnl_extended_attrs = true,
+ .ifindex = ifindex,
+ .iftype = iftype,
+ };
+
+ link->ifname = strdup(ifname);
+ if (!link->ifname)
+ return -ENOMEM;
+
+ if (kind) {
+ link->kind = strdup(kind);
+ if (!link->kind)
+ return -ENOMEM;
+ }
+
+ r = sd_netlink_message_read_u32(message, IFLA_MASTER, (uint32_t *)&link->master_ifindex);
+ if (r < 0)
+ log_link_debug_errno(link, r, "New device has no master, continuing without");
+
+ r = sd_netlink_message_read_ether_addr(message, IFLA_ADDRESS, &link->mac);
+ if (r < 0)
+ log_link_debug_errno(link, r, "MAC address not found for new device, continuing without");
+
+ if (asprintf(&link->state_file, "/run/systemd/netif/links/%d", link->ifindex) < 0)
+ return -ENOMEM;
+
+ if (asprintf(&link->lease_file, "/run/systemd/netif/leases/%d", link->ifindex) < 0)
+ return -ENOMEM;
+
+ if (asprintf(&link->lldp_file, "/run/systemd/netif/lldp/%d", link->ifindex) < 0)
+ return -ENOMEM;
+
+ r = hashmap_ensure_allocated(&manager->links, NULL);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(manager->links, INT_TO_PTR(link->ifindex), link);
+ if (r < 0)
+ return r;
+
+ r = link_update_flags(link, message);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(link);
+
+ return 0;
+}
+
+static void link_detach_from_manager(Link *link) {
+ if (!link || !link->manager)
+ return;
+
+ hashmap_remove(link->manager->links, INT_TO_PTR(link->ifindex));
+ set_remove(link->manager->links_requesting_uuid, link);
+ link_clean(link);
+}
+
+static Link *link_free(Link *link) {
+ Address *address;
+ Link *carrier;
+ Route *route;
+ Iterator i;
+
+ assert(link);
+
+ while ((route = set_first(link->routes)))
+ route_free(route);
+
+ while ((route = set_first(link->routes_foreign)))
+ route_free(route);
+
+ link->routes = set_free(link->routes);
+ link->routes_foreign = set_free(link->routes_foreign);
+
+ while ((address = set_first(link->addresses)))
+ address_free(address);
+
+ while ((address = set_first(link->addresses_foreign)))
+ address_free(address);
+
+ link->addresses = set_free(link->addresses);
+ link->addresses_foreign = set_free(link->addresses_foreign);
+
+ while ((address = link->pool_addresses)) {
+ LIST_REMOVE(addresses, link->pool_addresses, address);
+ address_free(address);
+ }
+
+ sd_dhcp_server_unref(link->dhcp_server);
+ sd_dhcp_client_unref(link->dhcp_client);
+ sd_dhcp_lease_unref(link->dhcp_lease);
+
+ link_lldp_emit_stop(link);
+
+ free(link->lease_file);
+
+ sd_lldp_unref(link->lldp);
+ free(link->lldp_file);
+
+ ndisc_flush(link);
+
+ sd_ipv4ll_unref(link->ipv4ll);
+ sd_dhcp6_client_unref(link->dhcp6_client);
+ sd_ndisc_unref(link->ndisc);
+ sd_radv_unref(link->radv);
+
+ link_detach_from_manager(link);
+
+ free(link->ifname);
+
+ free(link->kind);
+
+ (void) unlink(link->state_file);
+ free(link->state_file);
+
+ sd_device_unref(link->sd_device);
+
+ HASHMAP_FOREACH (carrier, link->bound_to_links, i)
+ hashmap_remove(link->bound_to_links, INT_TO_PTR(carrier->ifindex));
+ hashmap_free(link->bound_to_links);
+
+ HASHMAP_FOREACH (carrier, link->bound_by_links, i)
+ hashmap_remove(link->bound_by_links, INT_TO_PTR(carrier->ifindex));
+ hashmap_free(link->bound_by_links);
+
+ return mfree(link);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(Link, link, link_free);
+
+int link_get(Manager *m, int ifindex, Link **ret) {
+ Link *link;
+
+ assert(m);
+ assert(ifindex);
+ assert(ret);
+
+ link = hashmap_get(m->links, INT_TO_PTR(ifindex));
+ if (!link)
+ return -ENODEV;
+
+ *ret = link;
+
+ return 0;
+}
+
+static void link_set_state(Link *link, LinkState state) {
+ assert(link);
+
+ if (link->state == state)
+ return;
+
+ link->state = state;
+
+ link_send_changed(link, "AdministrativeState", NULL);
+}
+
+static void link_enter_unmanaged(Link *link) {
+ assert(link);
+
+ log_link_debug(link, "Unmanaged");
+
+ link_set_state(link, LINK_STATE_UNMANAGED);
+
+ link_dirty(link);
+}
+
+static int link_stop_clients(Link *link) {
+ int r = 0, k;
+
+ assert(link);
+ assert(link->manager);
+ assert(link->manager->event);
+
+ if (link->dhcp_client) {
+ k = sd_dhcp_client_stop(link->dhcp_client);
+ if (k < 0)
+ r = log_link_warning_errno(link, k, "Could not stop DHCPv4 client: %m");
+ }
+
+ if (link->ipv4ll) {
+ k = sd_ipv4ll_stop(link->ipv4ll);
+ if (k < 0)
+ r = log_link_warning_errno(link, k, "Could not stop IPv4 link-local: %m");
+ }
+
+ if (link->dhcp6_client) {
+ k = sd_dhcp6_client_stop(link->dhcp6_client);
+ if (k < 0)
+ r = log_link_warning_errno(link, k, "Could not stop DHCPv6 client: %m");
+ }
+
+ if (link->ndisc) {
+ k = sd_ndisc_stop(link->ndisc);
+ if (k < 0)
+ r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Discovery: %m");
+ }
+
+ if (link->radv) {
+ k = sd_radv_stop(link->radv);
+ if (k < 0)
+ r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Advertisement: %m");
+ }
+
+ link_lldp_emit_stop(link);
+ return r;
+}
+
+void link_enter_failed(Link *link) {
+ assert(link);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return;
+
+ log_link_warning(link, "Failed");
+
+ link_set_state(link, LINK_STATE_FAILED);
+
+ link_stop_clients(link);
+
+ link_dirty(link);
+}
+
+static Address* link_find_dhcp_server_address(Link *link) {
+ Address *address;
+
+ assert(link);
+ assert(link->network);
+
+ /* The first statically configured address if there is any */
+ LIST_FOREACH(addresses, address, link->network->static_addresses) {
+
+ if (address->family != AF_INET)
+ continue;
+
+ if (in_addr_is_null(address->family, &address->in_addr))
+ continue;
+
+ return address;
+ }
+
+ /* If that didn't work, find a suitable address we got from the pool */
+ LIST_FOREACH(addresses, address, link->pool_addresses) {
+ if (address->family != AF_INET)
+ continue;
+
+ return address;
+ }
+
+ return NULL;
+}
+
+static void link_enter_configured(Link *link) {
+ assert(link);
+ assert(link->network);
+
+ if (link->state != LINK_STATE_CONFIGURING)
+ return;
+
+ log_link_info(link, "Configured");
+
+ link_set_state(link, LINK_STATE_CONFIGURED);
+
+ link_dirty(link);
+}
+
+static int link_request_set_routing_policy_rule(Link *link) {
+ RoutingPolicyRule *rule, *rrule = NULL;
+ int r;
+
+ assert(link);
+ assert(link->network);
+
+ link_set_state(link, LINK_STATE_CONFIGURING);
+ link->routing_policy_rules_configured = false;
+
+ LIST_FOREACH(rules, rule, link->network->rules) {
+ r = routing_policy_rule_get(link->manager, rule->family, &rule->from, rule->from_prefixlen, &rule->to,
+ rule->to_prefixlen, rule->tos, rule->fwmark, rule->table, rule->iif, rule->oif,
+ rule->protocol, &rule->sport, &rule->dport, &rrule);
+ if (r == 0) {
+ (void) routing_policy_rule_make_local(link->manager, rrule);
+ continue;
+ }
+
+ r = routing_policy_rule_configure(rule, link, NULL, false);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Could not set routing policy rules: %m");
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->routing_policy_rule_messages++;
+ }
+
+ routing_policy_rule_purge(link->manager, link);
+ if (link->routing_policy_rule_messages == 0) {
+ link->routing_policy_rules_configured = true;
+ link_check_ready(link);
+ } else
+ log_link_debug(link, "Setting routing policy rules");
+
+ return 0;
+}
+
+static int route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+ assert(link->route_messages > 0);
+ assert(IN_SET(link->state, LINK_STATE_CONFIGURING,
+ LINK_STATE_FAILED, LINK_STATE_LINGER));
+
+ link->route_messages--;
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST)
+ log_link_warning_errno(link, r, "Could not set route: %m");
+
+ if (link->route_messages == 0) {
+ log_link_debug(link, "Routes set");
+ link->static_routes_configured = true;
+ link_check_ready(link);
+ }
+
+ return 1;
+}
+
+static int link_request_set_routes(Link *link) {
+ enum {
+ PHASE_NON_GATEWAY, /* First phase: Routes without a gateway */
+ PHASE_GATEWAY, /* Second phase: Routes with a gateway */
+ _PHASE_MAX
+ } phase;
+ Route *rt;
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(link->addresses_configured);
+ assert(link->address_messages == 0);
+ assert(link->state != _LINK_STATE_INVALID);
+
+ link_set_state(link, LINK_STATE_CONFIGURING);
+ link->static_routes_configured = false;
+
+ r = link_request_set_routing_policy_rule(link);
+ if (r < 0)
+ return r;
+
+ /* First add the routes that enable us to talk to gateways, then add in the others that need a gateway. */
+ for (phase = 0; phase < _PHASE_MAX; phase++)
+ LIST_FOREACH(routes, rt, link->network->static_routes) {
+
+ if (in_addr_is_null(rt->family, &rt->gw) != (phase == PHASE_NON_GATEWAY))
+ continue;
+
+ r = route_configure(rt, link, route_handler);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Could not set routes: %m");
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->route_messages++;
+ }
+
+ if (link->route_messages == 0) {
+ link->static_routes_configured = true;
+ link_check_ready(link);
+ } else
+ log_link_debug(link, "Setting routes");
+
+ return 0;
+}
+
+void link_check_ready(Link *link) {
+ Address *a;
+ Iterator i;
+
+ assert(link);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return;
+
+ if (!link->network)
+ return;
+
+ if (!link->addresses_configured)
+ return;
+
+ if (!link->neighbors_configured)
+ return;
+
+ SET_FOREACH(a, link->addresses, i)
+ if (!address_is_ready(a))
+ return;
+
+ if (!link->addresses_ready) {
+ link->addresses_ready = true;
+ link_request_set_routes(link);
+ }
+
+ if (!link->static_routes_configured)
+ return;
+
+ if (!link->routing_policy_rules_configured)
+ return;
+
+ if (link_ipv4ll_enabled(link))
+ if (!link->ipv4ll_address ||
+ !link->ipv4ll_route)
+ return;
+
+ if (!link->network->bridge) {
+
+ if (link_ipv6ll_enabled(link))
+ if (in_addr_is_null(AF_INET6, (const union in_addr_union*) &link->ipv6ll_address) > 0)
+ return;
+
+ if ((link_dhcp4_enabled(link) && !link_dhcp6_enabled(link) &&
+ !link->dhcp4_configured) ||
+ (link_dhcp6_enabled(link) && !link_dhcp4_enabled(link) &&
+ !link->dhcp6_configured) ||
+ (link_dhcp4_enabled(link) && link_dhcp6_enabled(link) &&
+ !link->dhcp4_configured && !link->dhcp6_configured))
+ return;
+
+ if (link_ipv6_accept_ra_enabled(link) && !link->ndisc_configured)
+ return;
+ }
+
+ if (link->state != LINK_STATE_CONFIGURED)
+ link_enter_configured(link);
+
+ return;
+}
+
+static int link_request_set_neighbors(Link *link) {
+ Neighbor *neighbor;
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(link->state != _LINK_STATE_INVALID);
+
+ link_set_state(link, LINK_STATE_CONFIGURING);
+ link->neighbors_configured = false;
+
+ LIST_FOREACH(neighbors, neighbor, link->network->neighbors) {
+ r = neighbor_configure(neighbor, link, NULL);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Could not set neighbor: %m");
+ link_enter_failed(link);
+ return r;
+ }
+ }
+
+ if (link->neighbor_messages == 0) {
+ link->neighbors_configured = true;
+ link_check_ready(link);
+ } else
+ log_link_debug(link, "Setting neighbors");
+
+ return 0;
+}
+
+static int address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(rtnl);
+ assert(m);
+ assert(link);
+ assert(link->ifname);
+ assert(link->address_messages > 0);
+ assert(IN_SET(link->state, LINK_STATE_CONFIGURING,
+ LINK_STATE_FAILED, LINK_STATE_LINGER));
+
+ link->address_messages--;
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST)
+ log_link_warning_errno(link, r, "could not set address: %m");
+ else if (r >= 0)
+ manager_rtnl_process_address(rtnl, m, link->manager);
+
+ if (link->address_messages == 0) {
+ log_link_debug(link, "Addresses set");
+ link->addresses_configured = true;
+ link_check_ready(link);
+ }
+
+ return 1;
+}
+
+static int link_push_uplink_dns_to_dhcp_server(Link *link, sd_dhcp_server *s) {
+ _cleanup_free_ struct in_addr *addresses = NULL;
+ size_t n_addresses = 0, n_allocated = 0;
+ unsigned i;
+
+ log_debug("Copying DNS server information from %s", link->ifname);
+
+ if (!link->network)
+ return 0;
+
+ for (i = 0; i < link->network->n_dns; i++) {
+ struct in_addr ia;
+
+ /* Only look for IPv4 addresses */
+ if (link->network->dns[i].family != AF_INET)
+ continue;
+
+ ia = link->network->dns[i].address.in;
+
+ /* Never propagate obviously borked data */
+ if (in4_addr_is_null(&ia) || in4_addr_is_localhost(&ia))
+ continue;
+
+ if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + 1))
+ return log_oom();
+
+ addresses[n_addresses++] = ia;
+ }
+
+ if (link->network->dhcp_use_dns && link->dhcp_lease) {
+ const struct in_addr *da = NULL;
+ int n;
+
+ n = sd_dhcp_lease_get_dns(link->dhcp_lease, &da);
+ if (n > 0) {
+
+ if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + n))
+ return log_oom();
+
+ memcpy(addresses + n_addresses, da, n * sizeof(struct in_addr));
+ n_addresses += n;
+ }
+ }
+
+ if (n_addresses <= 0)
+ return 0;
+
+ return sd_dhcp_server_set_dns(s, addresses, n_addresses);
+}
+
+static int link_push_uplink_ntp_to_dhcp_server(Link *link, sd_dhcp_server *s) {
+ _cleanup_free_ struct in_addr *addresses = NULL;
+ size_t n_addresses = 0, n_allocated = 0;
+ char **a;
+
+ if (!link->network)
+ return 0;
+
+ log_debug("Copying NTP server information from %s", link->ifname);
+
+ STRV_FOREACH(a, link->network->ntp) {
+ struct in_addr ia;
+
+ /* Only look for IPv4 addresses */
+ if (inet_pton(AF_INET, *a, &ia) <= 0)
+ continue;
+
+ /* Never propagate obviously borked data */
+ if (in4_addr_is_null(&ia) || in4_addr_is_localhost(&ia))
+ continue;
+
+ if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + 1))
+ return log_oom();
+
+ addresses[n_addresses++] = ia;
+ }
+
+ if (link->network->dhcp_use_ntp && link->dhcp_lease) {
+ const struct in_addr *da = NULL;
+ int n;
+
+ n = sd_dhcp_lease_get_ntp(link->dhcp_lease, &da);
+ if (n > 0) {
+
+ if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + n))
+ return log_oom();
+
+ memcpy(addresses + n_addresses, da, n * sizeof(struct in_addr));
+ n_addresses += n;
+ }
+ }
+
+ if (n_addresses <= 0)
+ return 0;
+
+ return sd_dhcp_server_set_ntp(s, addresses, n_addresses);
+}
+
+static int link_set_bridge_fdb(Link *link) {
+ FdbEntry *fdb_entry;
+ int r;
+
+ LIST_FOREACH(static_fdb_entries, fdb_entry, link->network->static_fdb_entries) {
+ r = fdb_entry_configure(link, fdb_entry);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to add MAC entry to static MAC table: %m");
+ }
+
+ return 0;
+}
+
+static int link_request_set_addresses(Link *link) {
+ AddressLabel *label;
+ Address *ad;
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(link->state != _LINK_STATE_INVALID);
+
+ link_set_state(link, LINK_STATE_CONFIGURING);
+
+ /* Reset all *_configured flags we are configuring. */
+ link->addresses_configured = false;
+ link->addresses_ready = false;
+ link->neighbors_configured = false;
+ link->static_routes_configured = false;
+ link->routing_policy_rules_configured = false;
+
+ r = link_set_bridge_fdb(link);
+ if (r < 0)
+ return r;
+
+ r = link_request_set_neighbors(link);
+ if (r < 0)
+ return r;
+
+ LIST_FOREACH(addresses, ad, link->network->static_addresses) {
+ bool update;
+
+ update = address_get(link, ad->family, &ad->in_addr, ad->prefixlen, NULL) > 0;
+
+ r = address_configure(ad, link, address_handler, update);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Could not set addresses: %m");
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->address_messages++;
+ }
+
+ LIST_FOREACH(labels, label, link->network->address_labels) {
+ r = address_label_configure(label, link, NULL, false);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Could not set address label: %m");
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->address_label_messages++;
+ }
+
+ /* now that we can figure out a default address for the dhcp server,
+ start it */
+ if (link_dhcp4_server_enabled(link) && (link->flags & IFF_UP)) {
+ Address *address;
+ Link *uplink = NULL;
+ bool acquired_uplink = false;
+
+ address = link_find_dhcp_server_address(link);
+ if (!address) {
+ log_link_warning(link, "Failed to find suitable address for DHCPv4 server instance.");
+ link_enter_failed(link);
+ return 0;
+ }
+
+ /* use the server address' subnet as the pool */
+ r = sd_dhcp_server_configure_pool(link->dhcp_server, &address->in_addr.in, address->prefixlen,
+ link->network->dhcp_server_pool_offset, link->network->dhcp_server_pool_size);
+ if (r < 0)
+ return r;
+
+ /* TODO:
+ r = sd_dhcp_server_set_router(link->dhcp_server,
+ &main_address->in_addr.in);
+ if (r < 0)
+ return r;
+ */
+
+ if (link->network->dhcp_server_max_lease_time_usec > 0) {
+ r = sd_dhcp_server_set_max_lease_time(
+ link->dhcp_server,
+ DIV_ROUND_UP(link->network->dhcp_server_max_lease_time_usec, USEC_PER_SEC));
+ if (r < 0)
+ return r;
+ }
+
+ if (link->network->dhcp_server_default_lease_time_usec > 0) {
+ r = sd_dhcp_server_set_default_lease_time(
+ link->dhcp_server,
+ DIV_ROUND_UP(link->network->dhcp_server_default_lease_time_usec, USEC_PER_SEC));
+ if (r < 0)
+ return r;
+ }
+
+ if (link->network->dhcp_server_emit_dns) {
+
+ if (link->network->n_dhcp_server_dns > 0)
+ r = sd_dhcp_server_set_dns(link->dhcp_server, link->network->dhcp_server_dns, link->network->n_dhcp_server_dns);
+ else {
+ uplink = manager_find_uplink(link->manager, link);
+ acquired_uplink = true;
+
+ if (!uplink) {
+ log_link_debug(link, "Not emitting DNS server information on link, couldn't find suitable uplink.");
+ r = 0;
+ } else
+ r = link_push_uplink_dns_to_dhcp_server(uplink, link->dhcp_server);
+ }
+ if (r < 0)
+ log_link_warning_errno(link, r, "Failed to set DNS server for DHCP server, ignoring: %m");
+ }
+
+ if (link->network->dhcp_server_emit_ntp) {
+
+ if (link->network->n_dhcp_server_ntp > 0)
+ r = sd_dhcp_server_set_ntp(link->dhcp_server, link->network->dhcp_server_ntp, link->network->n_dhcp_server_ntp);
+ else {
+ if (!acquired_uplink)
+ uplink = manager_find_uplink(link->manager, link);
+
+ if (!uplink) {
+ log_link_debug(link, "Not emitting NTP server information on link, couldn't find suitable uplink.");
+ r = 0;
+ } else
+ r = link_push_uplink_ntp_to_dhcp_server(uplink, link->dhcp_server);
+
+ }
+ if (r < 0)
+ log_link_warning_errno(link, r, "Failed to set NTP server for DHCP server, ignoring: %m");
+ }
+
+ r = sd_dhcp_server_set_emit_router(link->dhcp_server, link->network->dhcp_server_emit_router);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to set router emission for DHCP server: %m");
+
+ if (link->network->dhcp_server_emit_timezone) {
+ _cleanup_free_ char *buffer = NULL;
+ const char *tz = NULL;
+
+ if (link->network->dhcp_server_timezone)
+ tz = link->network->dhcp_server_timezone;
+ else {
+ r = get_timezone(&buffer);
+ if (r < 0)
+ log_warning_errno(r, "Failed to determine timezone: %m");
+ else
+ tz = buffer;
+ }
+
+ if (tz) {
+ r = sd_dhcp_server_set_timezone(link->dhcp_server, tz);
+ if (r < 0)
+ return r;
+ }
+ }
+ if (!sd_dhcp_server_is_running(link->dhcp_server)) {
+ r = sd_dhcp_server_start(link->dhcp_server);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Could not start DHCPv4 server instance: %m");
+
+ link_enter_failed(link);
+
+ return 0;
+ }
+ }
+
+ log_link_debug(link, "Offering DHCPv4 leases");
+ }
+
+ if (link->address_messages == 0) {
+ link->addresses_configured = true;
+ link_check_ready(link);
+ } else
+ log_link_debug(link, "Setting addresses");
+
+ return 0;
+}
+
+static int link_set_bridge_vlan(Link *link) {
+ int r = 0;
+
+ r = br_vlan_configure(link, link->network->pvid, link->network->br_vid_bitmap, link->network->br_untagged_bitmap);
+ if (r < 0)
+ log_link_error_errno(link, r, "Failed to assign VLANs to bridge port: %m");
+
+ return r;
+}
+
+static int link_set_proxy_arp(Link *link) {
+ const char *p = NULL;
+ int r;
+
+ if (!link_proxy_arp_enabled(link))
+ return 0;
+
+ p = strjoina("/proc/sys/net/ipv4/conf/", link->ifname, "/proxy_arp");
+
+ r = write_string_file(p, one_zero(link->network->proxy_arp), WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Cannot configure proxy ARP for interface: %m");
+
+ return 0;
+}
+
+static int link_set_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+
+ log_link_debug(link, "Set link");
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST) {
+ log_link_error_errno(link, r, "Could not join netdev: %m");
+ link_enter_failed(link);
+ }
+
+ return 1;
+}
+
+static int link_configure_after_setting_mtu(Link *link);
+
+static int set_mtu_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(m);
+ assert(link);
+ assert(link->ifname);
+
+ link->setting_mtu = false;
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Could not set MTU: %m");
+ return 1;
+ }
+
+ log_link_debug(link, "Setting MTU done.");
+
+ if (link->state == LINK_STATE_PENDING)
+ (void) link_configure_after_setting_mtu(link);
+
+ return 1;
+}
+
+int link_set_mtu(Link *link, uint32_t mtu) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ int r;
+
+ assert(link);
+ assert(link->manager);
+ assert(link->manager->rtnl);
+
+ if (link->mtu == mtu || link->setting_mtu)
+ return 0;
+
+ log_link_debug(link, "Setting MTU: %" PRIu32, mtu);
+
+ r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m");
+
+ /* If IPv6 not configured (no static IPv6 address and IPv6LL autoconfiguration is disabled)
+ * for this interface, or if it is a bridge slave, then disable IPv6 else enable it. */
+ (void) link_enable_ipv6(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 (link_ipv6_enabled(link) && mtu < IPV6_MIN_MTU) {
+
+ log_link_warning(link, "Bumping MTU to " STRINGIFY(IPV6_MIN_MTU) ", as "
+ "IPv6 is requested and requires a minimum MTU of " STRINGIFY(IPV6_MIN_MTU) " bytes: %m");
+
+ mtu = IPV6_MIN_MTU;
+ }
+
+ r = sd_netlink_message_append_u32(req, IFLA_MTU, mtu);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append MTU: %m");
+
+ r = netlink_call_async(link->manager->rtnl, NULL, req, set_mtu_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+ link->setting_mtu = true;
+
+ return 0;
+}
+
+static int set_flags_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(m);
+ assert(link);
+ assert(link->ifname);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Could not set link flags: %m");
+
+ return 1;
+}
+
+static int link_set_flags(Link *link) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ unsigned ifi_change = 0;
+ unsigned ifi_flags = 0;
+ int r;
+
+ assert(link);
+ assert(link->manager);
+ assert(link->manager->rtnl);
+
+ if (link->flags & IFF_LOOPBACK)
+ return 0;
+
+ if (!link->network)
+ return 0;
+
+ if (link->network->arp < 0 && link->network->multicast < 0 && link->network->allmulticast < 0)
+ return 0;
+
+ r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m");
+
+ if (link->network->arp >= 0) {
+ ifi_change |= IFF_NOARP;
+ SET_FLAG(ifi_flags, IFF_NOARP, link->network->arp == 0);
+ }
+
+ if (link->network->multicast >= 0) {
+ ifi_change |= IFF_MULTICAST;
+ SET_FLAG(ifi_flags, IFF_MULTICAST, link->network->multicast);
+ }
+
+ if (link->network->allmulticast >= 0) {
+ ifi_change |= IFF_ALLMULTI;
+ SET_FLAG(ifi_flags, IFF_ALLMULTI, link->network->allmulticast);
+ }
+
+ r = sd_rtnl_message_link_set_flags(req, ifi_flags, ifi_change);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not set link flags: %m");
+
+ r = netlink_call_async(link->manager->rtnl, NULL, req, set_flags_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ return 0;
+}
+
+static int link_set_bridge(Link *link) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ int r;
+
+ assert(link);
+ assert(link->network);
+
+ r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m");
+
+ r = sd_rtnl_message_link_set_family(req, PF_BRIDGE);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not set message family: %m");
+
+ r = sd_netlink_message_open_container(req, IFLA_PROTINFO);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_PROTINFO attribute: %m");
+
+ if (link->network->use_bpdu >= 0) {
+ r = sd_netlink_message_append_u8(req, IFLA_BRPORT_GUARD, link->network->use_bpdu);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_GUARD attribute: %m");
+ }
+
+ if (link->network->hairpin >= 0) {
+ r = sd_netlink_message_append_u8(req, IFLA_BRPORT_MODE, link->network->hairpin);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_MODE attribute: %m");
+ }
+
+ if (link->network->fast_leave >= 0) {
+ r = sd_netlink_message_append_u8(req, IFLA_BRPORT_FAST_LEAVE, link->network->fast_leave);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_FAST_LEAVE attribute: %m");
+ }
+
+ if (link->network->allow_port_to_be_root >= 0) {
+ r = sd_netlink_message_append_u8(req, IFLA_BRPORT_PROTECT, link->network->allow_port_to_be_root);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_PROTECT attribute: %m");
+
+ }
+
+ if (link->network->unicast_flood >= 0) {
+ r = sd_netlink_message_append_u8(req, IFLA_BRPORT_UNICAST_FLOOD, link->network->unicast_flood);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_UNICAST_FLOOD attribute: %m");
+ }
+
+ if (link->network->multicast_to_unicast >= 0) {
+ r = sd_netlink_message_append_u8(req, IFLA_BRPORT_MCAST_TO_UCAST, link->network->multicast_to_unicast);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_MCAST_TO_UCAST attribute: %m");
+ }
+
+ if (link->network->cost != 0) {
+ r = sd_netlink_message_append_u32(req, IFLA_BRPORT_COST, link->network->cost);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_COST attribute: %m");
+ }
+
+ if (link->network->priority != LINK_BRIDGE_PORT_PRIORITY_INVALID) {
+ r = sd_netlink_message_append_u16(req, IFLA_BRPORT_PRIORITY, link->network->priority);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_BRPORT_PRIORITY attribute: %m");
+ }
+
+ r = sd_netlink_message_close_container(req);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_LINKINFO attribute: %m");
+
+ r = netlink_call_async(link->manager->rtnl, NULL, req, link_set_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ return r;
+}
+
+static int link_bond_set(Link *link) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ int r;
+
+ assert(link);
+ assert(link->network);
+
+ r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_NEWLINK, link->network->bond->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m");
+
+ r = sd_netlink_message_set_flags(req, NLM_F_REQUEST | NLM_F_ACK);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not set netlink flags: %m");
+
+ r = sd_netlink_message_open_container(req, IFLA_LINKINFO);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_PROTINFO attribute: %m");
+
+ r = sd_netlink_message_open_container_union(req, IFLA_INFO_DATA, "bond");
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_INFO_DATA attribute: %m");
+
+ if (link->network->active_slave) {
+ r = sd_netlink_message_append_u32(req, IFLA_BOND_ACTIVE_SLAVE, link->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_BOND_ACTIVE_SLAVE attribute: %m");
+ }
+
+ if (link->network->primary_slave) {
+ r = sd_netlink_message_append_u32(req, IFLA_BOND_PRIMARY, link->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_BOND_PRIMARY attribute: %m");
+ }
+
+ r = sd_netlink_message_close_container(req);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_LINKINFO attribute: %m");
+
+ r = sd_netlink_message_close_container(req);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_INFO_DATA attribute: %m");
+
+ r = netlink_call_async(link->manager->rtnl, NULL, req, set_flags_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ return r;
+}
+
+static int link_lldp_save(Link *link) {
+ _cleanup_free_ char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ sd_lldp_neighbor **l = NULL;
+ int n = 0, r, i;
+
+ assert(link);
+ assert(link->lldp_file);
+
+ if (!link->lldp) {
+ (void) unlink(link->lldp_file);
+ return 0;
+ }
+
+ r = sd_lldp_get_neighbors(link->lldp, &l);
+ if (r < 0)
+ goto finish;
+ if (r == 0) {
+ (void) unlink(link->lldp_file);
+ goto finish;
+ }
+
+ n = r;
+
+ r = fopen_temporary(link->lldp_file, &f, &temp_path);
+ if (r < 0)
+ goto finish;
+
+ 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;
+
+ if (rename(temp_path, link->lldp_file) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+finish:
+ if (r < 0) {
+ (void) unlink(link->lldp_file);
+ if (temp_path)
+ (void) unlink(temp_path);
+
+ 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;
+}
+
+static void lldp_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata) {
+ Link *link = userdata;
+ int r;
+
+ assert(link);
+
+ (void) link_lldp_save(link);
+
+ if (link_lldp_emit_enabled(link) && event == SD_LLDP_EVENT_ADDED) {
+ /* If we received information about a new neighbor, restart the LLDP "fast" logic */
+
+ log_link_debug(link, "Received LLDP datagram from previously unknown neighbor, restarting 'fast' LLDP transmission.");
+
+ r = link_lldp_emit_start(link);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Failed to restart LLDP transmission: %m");
+ }
+}
+
+static int link_acquire_ipv6_conf(Link *link) {
+ int r;
+
+ assert(link);
+
+ if (link_ipv6_accept_ra_enabled(link)) {
+ assert(link->ndisc);
+
+ log_link_debug(link, "Discovering IPv6 routers");
+
+ r = sd_ndisc_start(link->ndisc);
+ if (r < 0 && r != -EBUSY)
+ return log_link_warning_errno(link, r, "Could not start IPv6 Router Discovery: %m");
+ }
+
+ if (link_radv_enabled(link)) {
+ assert(link->radv);
+ assert(in_addr_is_link_local(AF_INET6, (const union in_addr_union*)&link->ipv6ll_address) > 0);
+
+ log_link_debug(link, "Starting IPv6 Router Advertisements");
+
+ r = sd_radv_start(link->radv);
+ if (r < 0 && r != -EBUSY)
+ return log_link_warning_errno(link, r, "Could not start IPv6 Router Advertisement: %m");
+ }
+
+ (void) dhcp6_request_prefix_delegation(link);
+
+ return 0;
+}
+
+static int link_acquire_ipv4_conf(Link *link) {
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(link->manager);
+ assert(link->manager->event);
+
+ if (link_ipv4ll_enabled(link)) {
+ assert(link->ipv4ll);
+
+ log_link_debug(link, "Acquiring IPv4 link-local address");
+
+ r = sd_ipv4ll_start(link->ipv4ll);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not acquire IPv4 link-local address: %m");
+ }
+
+ if (link_dhcp4_enabled(link)) {
+ assert(link->dhcp_client);
+
+ log_link_debug(link, "Acquiring DHCPv4 lease");
+
+ r = sd_dhcp_client_start(link->dhcp_client);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not acquire DHCPv4 lease: %m");
+ }
+
+ return 0;
+}
+
+static int link_acquire_conf(Link *link) {
+ int r;
+
+ assert(link);
+
+ r = link_acquire_ipv4_conf(link);
+ if (r < 0)
+ return r;
+
+ if (in_addr_is_null(AF_INET6, (const union in_addr_union*) &link->ipv6ll_address) == 0) {
+ r = link_acquire_ipv6_conf(link);
+ if (r < 0)
+ return r;
+ }
+
+ if (link_lldp_emit_enabled(link)) {
+ r = link_lldp_emit_start(link);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to start LLDP transmission: %m");
+ }
+
+ return 0;
+}
+
+bool link_has_carrier(Link *link) {
+ /* see Documentation/networking/operstates.txt in the kernel sources */
+
+ if (link->kernel_operstate == IF_OPER_UP)
+ return true;
+
+ if (link->kernel_operstate == IF_OPER_UNKNOWN)
+ /* operstate may not be implemented, so fall back to flags */
+ if ((link->flags & IFF_LOWER_UP) && !(link->flags & IFF_DORMANT))
+ return true;
+
+ return false;
+}
+
+static int link_address_genmode_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Could not set address genmode for interface: %m");
+
+ return 1;
+}
+
+static int link_configure_addrgen_mode(Link *link) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ uint8_t ipv6ll_mode;
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(link->manager);
+ assert(link->manager->rtnl);
+
+ log_link_debug(link, "Setting address genmode for link");
+
+ r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m");
+
+ r = sd_netlink_message_open_container(req, IFLA_AF_SPEC);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not open IFLA_AF_SPEC container: %m");
+
+ r = sd_netlink_message_open_container(req, AF_INET6);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not open AF_INET6 container: %m");
+
+ if (!link_ipv6ll_enabled(link))
+ ipv6ll_mode = IN6_ADDR_GEN_MODE_NONE;
+ else {
+ const char *p = NULL;
+ _cleanup_free_ char *stable_secret = NULL;
+
+ p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/stable_secret");
+
+ /* The file may not exist. And event if it exists, when stable_secret is unset,
+ * then reading the file fails and EIO is returned. */
+ r = read_one_line_file(p, &stable_secret);
+ if (r < 0)
+ ipv6ll_mode = IN6_ADDR_GEN_MODE_EUI64;
+ else
+ ipv6ll_mode = IN6_ADDR_GEN_MODE_STABLE_PRIVACY;
+ }
+
+ r = sd_netlink_message_append_u8(req, IFLA_INET6_ADDR_GEN_MODE, ipv6ll_mode);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_INET6_ADDR_GEN_MODE: %m");
+
+ r = sd_netlink_message_close_container(req);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not close AF_INET6 container: %m");
+
+ r = sd_netlink_message_close_container(req);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not close IFLA_AF_SPEC container: %m");
+
+ r = netlink_call_async(link->manager->rtnl, NULL, req, link_address_genmode_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ return 0;
+}
+
+static int link_up_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0)
+ /* we warn but don't fail the link, as it may be brought up later */
+ log_link_warning_errno(link, r, "Could not bring up interface: %m");
+
+ return 1;
+}
+
+static int link_up(Link *link) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(link->manager);
+ assert(link->manager->rtnl);
+
+ log_link_debug(link, "Bringing link up");
+
+ r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m");
+
+ /* set it free if not enslaved with networkd */
+ if (!link->network->bridge && !link->network->bond && !link->network->vrf) {
+ r = sd_netlink_message_append_u32(req, IFLA_MASTER, 0);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_MASTER attribute: %m");
+ }
+
+ r = sd_rtnl_message_link_set_flags(req, IFF_UP, IFF_UP);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not set link flags: %m");
+
+ if (link->network->mac) {
+ r = sd_netlink_message_append_ether_addr(req, IFLA_ADDRESS, link->network->mac);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not set MAC address: %m");
+ }
+
+ if (link_ipv6_enabled(link)) {
+ r = sd_netlink_message_open_container(req, IFLA_AF_SPEC);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not open IFLA_AF_SPEC container: %m");
+
+ /* if the kernel lacks ipv6 support setting IFF_UP fails if any ipv6 options are passed */
+ r = sd_netlink_message_open_container(req, AF_INET6);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not open AF_INET6 container: %m");
+
+ if (!in_addr_is_null(AF_INET6, &link->network->ipv6_token)) {
+ r = sd_netlink_message_append_in6_addr(req, IFLA_INET6_TOKEN, &link->network->ipv6_token.in6);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_INET6_TOKEN: %m");
+ }
+
+ r = sd_netlink_message_close_container(req);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not close AF_INET6 container: %m");
+
+ r = sd_netlink_message_close_container(req);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not close IFLA_AF_SPEC container: %m");
+ }
+
+ r = netlink_call_async(link->manager->rtnl, NULL, req, link_up_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ return 0;
+}
+
+static int link_up_can(Link *link) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ int r;
+
+ assert(link);
+
+ log_link_debug(link, "Bringing CAN link up");
+
+ r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_SETLINK, link->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m");
+
+ r = sd_rtnl_message_link_set_flags(req, IFF_UP, IFF_UP);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not set link flags: %m");
+
+ r = netlink_call_async(link->manager->rtnl, NULL, req, link_up_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ return 0;
+}
+
+static int link_set_can(Link *link) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(link->manager);
+ assert(link->manager->rtnl);
+
+ log_link_debug(link, "link_set_can");
+
+ r = sd_rtnl_message_new_link(link->manager->rtnl, &m, RTM_NEWLINK, link->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to allocate netlink message: %m");
+
+ r = sd_netlink_message_set_flags(m, NLM_F_REQUEST | NLM_F_ACK);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not set netlink flags: %m");
+
+ r = sd_netlink_message_open_container(m, IFLA_LINKINFO);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to open netlink container: %m");
+
+ r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, link->kind);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_INFO_DATA attribute: %m");
+
+ if (link->network->can_bitrate > 0 || link->network->can_sample_point > 0) {
+ struct can_bittiming bt = {
+ .bitrate = link->network->can_bitrate,
+ .sample_point = link->network->can_sample_point,
+ };
+
+ if (link->network->can_bitrate > UINT32_MAX) {
+ log_link_error(link, "bitrate (%zu) too big.", link->network->can_bitrate);
+ return -ERANGE;
+ }
+
+ log_link_debug(link, "Setting bitrate = %d bit/s", bt.bitrate);
+ if (link->network->can_sample_point > 0)
+ log_link_debug(link, "Setting sample point = %d.%d%%", bt.sample_point / 10, bt.sample_point % 10);
+ else
+ log_link_debug(link, "Using default sample point");
+
+ r = sd_netlink_message_append_data(m, IFLA_CAN_BITTIMING, &bt, sizeof(bt));
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_CAN_BITTIMING attribute: %m");
+ }
+
+ if (link->network->can_restart_us > 0) {
+ char time_string[FORMAT_TIMESPAN_MAX];
+ uint64_t restart_ms;
+
+ if (link->network->can_restart_us == USEC_INFINITY)
+ restart_ms = 0;
+ else
+ restart_ms = DIV_ROUND_UP(link->network->can_restart_us, USEC_PER_MSEC);
+
+ format_timespan(time_string, FORMAT_TIMESPAN_MAX, restart_ms * 1000, MSEC_PER_SEC);
+
+ if (restart_ms > UINT32_MAX) {
+ log_link_error(link, "restart timeout (%s) too big.", time_string);
+ return -ERANGE;
+ }
+
+ log_link_debug(link, "Setting restart = %s", time_string);
+
+ r = sd_netlink_message_append_u32(m, IFLA_CAN_RESTART_MS, restart_ms);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not append IFLA_CAN_RESTART_MS attribute: %m");
+ }
+
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to close netlink container: %m");
+
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to close netlink container: %m");
+
+ r = netlink_call_async(link->manager->rtnl, NULL, m, link_set_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ if (!(link->flags & IFF_UP)) {
+ r = link_up_can(link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return r;
+ }
+ }
+
+ log_link_debug(link, "link_set_can done");
+
+ return r;
+}
+
+static int link_down_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Could not bring down interface: %m");
+
+ if (streq_ptr(link->kind, "can"))
+ link_set_can(link);
+
+ return 1;
+}
+
+int link_down(Link *link) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ int r;
+
+ assert(link);
+ assert(link->manager);
+ assert(link->manager->rtnl);
+
+ log_link_debug(link, "Bringing link down");
+
+ r = sd_rtnl_message_new_link(link->manager->rtnl, &req,
+ RTM_SETLINK, link->ifindex);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate RTM_SETLINK message: %m");
+
+ r = sd_rtnl_message_link_set_flags(req, 0, IFF_UP);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not set link flags: %m");
+
+ r = netlink_call_async(link->manager->rtnl, NULL, req, link_down_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ return 0;
+}
+
+static int link_handle_bound_to_list(Link *link) {
+ Link *l;
+ Iterator i;
+ int r;
+ bool required_up = false;
+ bool link_is_up = false;
+
+ assert(link);
+
+ if (hashmap_isempty(link->bound_to_links))
+ return 0;
+
+ if (link->flags & IFF_UP)
+ link_is_up = true;
+
+ HASHMAP_FOREACH (l, link->bound_to_links, i)
+ if (link_has_carrier(l)) {
+ required_up = true;
+ break;
+ }
+
+ if (!required_up && link_is_up) {
+ r = link_down(link);
+ if (r < 0)
+ return r;
+ } else if (required_up && !link_is_up) {
+ r = link_up(link);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int link_handle_bound_by_list(Link *link) {
+ Iterator i;
+ Link *l;
+ int r;
+
+ assert(link);
+
+ if (hashmap_isempty(link->bound_by_links))
+ return 0;
+
+ HASHMAP_FOREACH (l, link->bound_by_links, i) {
+ r = link_handle_bound_to_list(l);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int link_put_carrier(Link *link, Link *carrier, Hashmap **h) {
+ int r;
+
+ assert(link);
+ assert(carrier);
+
+ if (link == carrier)
+ return 0;
+
+ if (hashmap_get(*h, INT_TO_PTR(carrier->ifindex)))
+ return 0;
+
+ r = hashmap_ensure_allocated(h, NULL);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(*h, INT_TO_PTR(carrier->ifindex), carrier);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int link_new_bound_by_list(Link *link) {
+ Manager *m;
+ Link *carrier;
+ Iterator i;
+ int r;
+ bool list_updated = false;
+
+ assert(link);
+ assert(link->manager);
+
+ m = link->manager;
+
+ HASHMAP_FOREACH(carrier, m->links, i) {
+ if (!carrier->network)
+ continue;
+
+ if (strv_isempty(carrier->network->bind_carrier))
+ continue;
+
+ if (strv_fnmatch(carrier->network->bind_carrier, link->ifname, 0)) {
+ r = link_put_carrier(link, carrier, &link->bound_by_links);
+ if (r < 0)
+ return r;
+
+ list_updated = true;
+ }
+ }
+
+ if (list_updated)
+ link_dirty(link);
+
+ HASHMAP_FOREACH(carrier, link->bound_by_links, i) {
+ r = link_put_carrier(carrier, link, &carrier->bound_to_links);
+ if (r < 0)
+ return r;
+
+ link_dirty(carrier);
+ }
+
+ return 0;
+}
+
+static int link_new_bound_to_list(Link *link) {
+ Manager *m;
+ Link *carrier;
+ Iterator i;
+ int r;
+ bool list_updated = false;
+
+ assert(link);
+ assert(link->manager);
+
+ if (!link->network)
+ return 0;
+
+ if (strv_isempty(link->network->bind_carrier))
+ return 0;
+
+ m = link->manager;
+
+ HASHMAP_FOREACH (carrier, m->links, i) {
+ if (strv_fnmatch(link->network->bind_carrier, carrier->ifname, 0)) {
+ r = link_put_carrier(link, carrier, &link->bound_to_links);
+ if (r < 0)
+ return r;
+
+ list_updated = true;
+ }
+ }
+
+ if (list_updated)
+ link_dirty(link);
+
+ HASHMAP_FOREACH (carrier, link->bound_to_links, i) {
+ r = link_put_carrier(carrier, link, &carrier->bound_by_links);
+ if (r < 0)
+ return r;
+
+ link_dirty(carrier);
+ }
+
+ return 0;
+}
+
+static int link_new_carrier_maps(Link *link) {
+ int r;
+
+ r = link_new_bound_by_list(link);
+ if (r < 0)
+ return r;
+
+ r = link_handle_bound_by_list(link);
+ if (r < 0)
+ return r;
+
+ r = link_new_bound_to_list(link);
+ if (r < 0)
+ return r;
+
+ r = link_handle_bound_to_list(link);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static void link_free_bound_to_list(Link *link) {
+ Link *bound_to;
+ Iterator i;
+
+ HASHMAP_FOREACH (bound_to, link->bound_to_links, i) {
+ hashmap_remove(link->bound_to_links, INT_TO_PTR(bound_to->ifindex));
+
+ if (hashmap_remove(bound_to->bound_by_links, INT_TO_PTR(link->ifindex)))
+ link_dirty(bound_to);
+ }
+
+ return;
+}
+
+static void link_free_bound_by_list(Link *link) {
+ Link *bound_by;
+ Iterator i;
+
+ HASHMAP_FOREACH (bound_by, link->bound_by_links, i) {
+ hashmap_remove(link->bound_by_links, INT_TO_PTR(bound_by->ifindex));
+
+ if (hashmap_remove(bound_by->bound_to_links, INT_TO_PTR(link->ifindex))) {
+ link_dirty(bound_by);
+ link_handle_bound_to_list(bound_by);
+ }
+ }
+
+ return;
+}
+
+static void link_free_carrier_maps(Link *link) {
+ bool list_updated = false;
+
+ assert(link);
+
+ if (!hashmap_isempty(link->bound_to_links)) {
+ link_free_bound_to_list(link);
+ list_updated = true;
+ }
+
+ if (!hashmap_isempty(link->bound_by_links)) {
+ link_free_bound_by_list(link);
+ list_updated = true;
+ }
+
+ if (list_updated)
+ link_dirty(link);
+
+ return;
+}
+
+void link_drop(Link *link) {
+ if (!link || link->state == LINK_STATE_LINGER)
+ return;
+
+ link_set_state(link, LINK_STATE_LINGER);
+
+ link_free_carrier_maps(link);
+
+ log_link_debug(link, "Link removed");
+
+ (void) unlink(link->state_file);
+
+ link_detach_from_manager(link);
+
+ link_unref(link);
+
+ return;
+}
+
+static int link_joined(Link *link) {
+ int r;
+
+ assert(link);
+ assert(link->network);
+
+ if (!hashmap_isempty(link->bound_to_links)) {
+ r = link_handle_bound_to_list(link);
+ if (r < 0)
+ return r;
+ } else if (!(link->flags & IFF_UP)) {
+ r = link_up(link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return r;
+ }
+ }
+
+ if (link->network->bridge) {
+ r = link_set_bridge(link);
+ if (r < 0)
+ log_link_error_errno(link, r, "Could not set bridge message: %m");
+ }
+
+ if (link->network->bond) {
+ r = link_bond_set(link);
+ if (r < 0)
+ log_link_error_errno(link, r, "Could not set bond message: %m");
+ }
+
+ if (link->network->use_br_vlan &&
+ (link->network->bridge || streq_ptr("bridge", link->kind))) {
+ r = link_set_bridge_vlan(link);
+ if (r < 0)
+ log_link_error_errno(link, r, "Could not set bridge vlan: %m");
+ }
+
+ /* Skip setting up addresses until it gets carrier,
+ or it would try to set addresses twice,
+ which is bad for non-idempotent steps. */
+ if (!link_has_carrier(link) && !link->network->configure_without_carrier)
+ return 0;
+
+ return link_request_set_addresses(link);
+}
+
+static int netdev_join_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+ assert(link->network);
+
+ link->enslaving--;
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST) {
+ log_link_error_errno(link, r, "Could not join netdev: %m");
+ link_enter_failed(link);
+ return 1;
+ } else
+ log_link_debug(link, "Joined netdev");
+
+ if (link->enslaving <= 0)
+ link_joined(link);
+
+ return 1;
+}
+
+static int link_enter_join_netdev(Link *link) {
+ NetDev *netdev;
+ Iterator i;
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(link->state == LINK_STATE_PENDING);
+
+ link_set_state(link, LINK_STATE_CONFIGURING);
+
+ link_dirty(link);
+
+ if (!link->network->bridge &&
+ !link->network->bond &&
+ !link->network->vrf &&
+ hashmap_isempty(link->network->stacked_netdevs))
+ return link_joined(link);
+
+ if (link->network->bond) {
+ if (link->network->bond->state == NETDEV_STATE_READY &&
+ link->network->bond->ifindex == link->master_ifindex)
+ return link_joined(link);
+
+ log_struct(LOG_DEBUG,
+ LOG_LINK_INTERFACE(link),
+ LOG_NETDEV_INTERFACE(link->network->bond),
+ LOG_LINK_MESSAGE(link, "Enslaving by '%s'", link->network->bond->ifname));
+
+ r = netdev_join(link->network->bond, link, netdev_join_handler);
+ if (r < 0) {
+ log_struct_errno(LOG_WARNING, r,
+ LOG_LINK_INTERFACE(link),
+ LOG_NETDEV_INTERFACE(link->network->bond),
+ LOG_LINK_MESSAGE(link, "Could not join netdev '%s': %m", link->network->bond->ifname));
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->enslaving++;
+ }
+
+ if (link->network->bridge) {
+ log_struct(LOG_DEBUG,
+ LOG_LINK_INTERFACE(link),
+ LOG_NETDEV_INTERFACE(link->network->bridge),
+ LOG_LINK_MESSAGE(link, "Enslaving by '%s'", link->network->bridge->ifname));
+
+ r = netdev_join(link->network->bridge, link, netdev_join_handler);
+ if (r < 0) {
+ log_struct_errno(LOG_WARNING, r,
+ LOG_LINK_INTERFACE(link),
+ LOG_NETDEV_INTERFACE(link->network->bridge),
+ LOG_LINK_MESSAGE(link, "Could not join netdev '%s': %m", link->network->bridge->ifname));
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->enslaving++;
+ }
+
+ if (link->network->vrf) {
+ log_struct(LOG_DEBUG,
+ LOG_LINK_INTERFACE(link),
+ LOG_NETDEV_INTERFACE(link->network->vrf),
+ LOG_LINK_MESSAGE(link, "Enslaving by '%s'", link->network->vrf->ifname));
+
+ r = netdev_join(link->network->vrf, link, netdev_join_handler);
+ if (r < 0) {
+ log_struct_errno(LOG_WARNING, r,
+ LOG_LINK_INTERFACE(link),
+ LOG_NETDEV_INTERFACE(link->network->vrf),
+ LOG_LINK_MESSAGE(link, "Could not join netdev '%s': %m", link->network->vrf->ifname));
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->enslaving++;
+ }
+
+ HASHMAP_FOREACH(netdev, link->network->stacked_netdevs, i) {
+
+ if (netdev->ifindex > 0) {
+ link_joined(link);
+ continue;
+ }
+
+ log_struct(LOG_DEBUG,
+ LOG_LINK_INTERFACE(link),
+ LOG_NETDEV_INTERFACE(netdev),
+ LOG_LINK_MESSAGE(link, "Enslaving by '%s'", netdev->ifname));
+
+ r = netdev_join(netdev, link, netdev_join_handler);
+ if (r < 0) {
+ log_struct_errno(LOG_WARNING, r,
+ LOG_LINK_INTERFACE(link),
+ LOG_NETDEV_INTERFACE(netdev),
+ LOG_LINK_MESSAGE(link, "Could not join netdev '%s': %m", netdev->ifname));
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->enslaving++;
+ }
+
+ return 0;
+}
+
+static int link_set_ipv4_forward(Link *link) {
+ int r;
+
+ if (!link_ipv4_forward_enabled(link))
+ return 0;
+
+ /* 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). */
+
+ r = write_string_file("/proc/sys/net/ipv4/ip_forward", "1", WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Cannot turn on IPv4 packet forwarding, ignoring: %m");
+
+ return 0;
+}
+
+static int link_set_ipv6_forward(Link *link) {
+ int r;
+
+ if (!link_ipv6_forward_enabled(link))
+ 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). */
+
+ r = write_string_file("/proc/sys/net/ipv6/conf/all/forwarding", "1", WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Cannot configure IPv6 packet forwarding, ignoring: %m");
+
+ return 0;
+}
+
+static int link_set_ipv6_privacy_extensions(Link *link) {
+ char buf[DECIMAL_STR_MAX(unsigned) + 1];
+ IPv6PrivacyExtensions s;
+ const char *p = NULL;
+ int r;
+
+ s = link_ipv6_privacy_extensions(link);
+ if (s < 0)
+ return 0;
+
+ p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/use_tempaddr");
+ xsprintf(buf, "%u", (unsigned) link->network->ipv6_privacy_extensions);
+
+ r = write_string_file(p, buf, WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Cannot configure IPv6 privacy extension for interface: %m");
+
+ return 0;
+}
+
+static int link_set_ipv6_accept_ra(Link *link) {
+ const char *p = NULL;
+ int r;
+
+ /* Make this a NOP if IPv6 is not available */
+ if (!socket_ipv6_is_supported())
+ return 0;
+
+ if (link->flags & IFF_LOOPBACK)
+ return 0;
+
+ if (!link->network)
+ return 0;
+
+ p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/accept_ra");
+
+ /* We handle router advertisements ourselves, tell the kernel to GTFO */
+ r = write_string_file(p, "0", WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Cannot disable kernel IPv6 accept_ra for interface: %m");
+
+ return 0;
+}
+
+static int link_set_ipv6_dad_transmits(Link *link) {
+ char buf[DECIMAL_STR_MAX(int) + 1];
+ const char *p = NULL;
+ int r;
+
+ /* Make this a NOP if IPv6 is not available */
+ if (!socket_ipv6_is_supported())
+ return 0;
+
+ if (link->flags & IFF_LOOPBACK)
+ return 0;
+
+ if (!link->network)
+ return 0;
+
+ if (link->network->ipv6_dad_transmits < 0)
+ return 0;
+
+ p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/dad_transmits");
+ xsprintf(buf, "%i", link->network->ipv6_dad_transmits);
+
+ r = write_string_file(p, buf, WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Cannot set IPv6 dad transmits for interface: %m");
+
+ return 0;
+}
+
+static int link_set_ipv6_hop_limit(Link *link) {
+ char buf[DECIMAL_STR_MAX(int) + 1];
+ const char *p = NULL;
+ int r;
+
+ /* Make this a NOP if IPv6 is not available */
+ if (!socket_ipv6_is_supported())
+ return 0;
+
+ if (link->flags & IFF_LOOPBACK)
+ return 0;
+
+ if (!link->network)
+ return 0;
+
+ if (link->network->ipv6_hop_limit < 0)
+ return 0;
+
+ p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/hop_limit");
+ xsprintf(buf, "%i", link->network->ipv6_hop_limit);
+
+ r = write_string_file(p, buf, WRITE_STRING_FILE_VERIFY_ON_FAILURE | WRITE_STRING_FILE_DISABLE_BUFFER);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Cannot set IPv6 hop limit for interface: %m");
+
+ return 0;
+}
+
+static int link_set_ipv6_mtu(Link *link) {
+ char buf[DECIMAL_STR_MAX(unsigned) + 1];
+ const char *p = NULL;
+ int r;
+
+ /* Make this a NOP if IPv6 is not available */
+ if (!socket_ipv6_is_supported())
+ return 0;
+
+ if (link->flags & IFF_LOOPBACK)
+ return 0;
+
+ if (link->network->ipv6_mtu == 0)
+ return 0;
+
+ p = strjoina("/proc/sys/net/ipv6/conf/", link->ifname, "/mtu");
+
+ xsprintf(buf, "%" PRIu32, link->network->ipv6_mtu);
+
+ r = write_string_file(p, buf, WRITE_STRING_FILE_DISABLE_BUFFER);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Cannot set IPv6 MTU for interface: %m");
+
+ return 0;
+}
+
+static bool link_is_static_address_configured(Link *link, Address *address) {
+ Address *net_address;
+
+ assert(link);
+ assert(address);
+
+ if (!link->network)
+ return false;
+
+ LIST_FOREACH(addresses, net_address, link->network->static_addresses)
+ if (address_equal(net_address, address))
+ return true;
+
+ return false;
+}
+
+static bool link_is_static_route_configured(Link *link, Route *route) {
+ Route *net_route;
+
+ assert(link);
+ assert(route);
+
+ if (!link->network)
+ return false;
+
+ LIST_FOREACH(routes, net_route, link->network->static_routes)
+ if (route_equal(net_route, route))
+ return true;
+
+ return false;
+}
+
+static int link_drop_foreign_config(Link *link) {
+ Address *address;
+ Route *route;
+ Iterator i;
+ int r;
+
+ SET_FOREACH(address, link->addresses_foreign, i) {
+ /* we consider IPv6LL addresses to be managed by the kernel */
+ if (address->family == AF_INET6 && in_addr_is_link_local(AF_INET6, &address->in_addr) == 1)
+ continue;
+
+ if (link_is_static_address_configured(link, address)) {
+ r = address_add(link, address->family, &address->in_addr, address->prefixlen, NULL);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to add address: %m");
+ } else {
+ r = address_remove(address, link, NULL);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ SET_FOREACH(route, link->routes_foreign, i) {
+ /* do not touch routes managed by the kernel */
+ if (route->protocol == RTPROT_KERNEL)
+ continue;
+
+ if (link_is_static_route_configured(link, route)) {
+ r = route_add(link, route->family, &route->dst, route->dst_prefixlen, route->tos, route->priority, route->table, NULL);
+ if (r < 0)
+ return r;
+ } else {
+ r = route_remove(route, link, NULL);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+static int link_drop_config(Link *link) {
+ Address *address, *pool_address;
+ Route *route;
+ Iterator i;
+ int r;
+
+ SET_FOREACH(address, link->addresses, i) {
+ /* we consider IPv6LL addresses to be managed by the kernel */
+ if (address->family == AF_INET6 && in_addr_is_link_local(AF_INET6, &address->in_addr) == 1)
+ continue;
+
+ r = address_remove(address, link, NULL);
+ if (r < 0)
+ return r;
+
+ /* If this address came from an address pool, clean up the pool */
+ LIST_FOREACH(addresses, pool_address, link->pool_addresses) {
+ if (address_equal(address, pool_address)) {
+ LIST_REMOVE(addresses, link->pool_addresses, pool_address);
+ address_free(pool_address);
+ break;
+ }
+ }
+ }
+
+ SET_FOREACH(route, link->routes, i) {
+ /* do not touch routes managed by the kernel */
+ if (route->protocol == RTPROT_KERNEL)
+ continue;
+
+ r = route_remove(route, link, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ ndisc_flush(link);
+
+ return 0;
+}
+
+static int link_update_lldp(Link *link) {
+ int r;
+
+ assert(link);
+
+ if (!link->lldp)
+ return 0;
+
+ if (link->flags & IFF_UP) {
+ r = sd_lldp_start(link->lldp);
+ if (r > 0)
+ log_link_debug(link, "Started LLDP.");
+ } else {
+ r = sd_lldp_stop(link->lldp);
+ if (r > 0)
+ log_link_debug(link, "Stopped LLDP.");
+ }
+
+ return r;
+}
+
+static int link_configure_can(Link *link) {
+ int r;
+
+ if (streq_ptr(link->kind, "can")) {
+ /* The CAN interface must be down to configure bitrate, etc... */
+ if ((link->flags & IFF_UP)) {
+ r = link_down(link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return r;
+ }
+
+ return 0;
+ }
+
+ return link_set_can(link);
+ }
+
+ if (!(link->flags & IFF_UP)) {
+ r = link_up_can(link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+static int link_configure(Link *link) {
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(link->state == LINK_STATE_PENDING);
+
+ if (STRPTR_IN_SET(link->kind, "can", "vcan"))
+ return link_configure_can(link);
+
+ /* Drop foreign config, but ignore loopback or critical devices.
+ * We do not want to remove loopback address or addresses used for root NFS. */
+ if (!(link->flags & IFF_LOOPBACK) && !(link->network->dhcp_critical)) {
+ r = link_drop_foreign_config(link);
+ if (r < 0)
+ return r;
+ }
+
+ r = link_set_proxy_arp(link);
+ if (r < 0)
+ return r;
+
+ r = ipv6_proxy_ndp_addresses_configure(link);
+ if (r < 0)
+ return r;
+
+ r = link_set_ipv4_forward(link);
+ if (r < 0)
+ return r;
+
+ r = link_set_ipv6_forward(link);
+ if (r < 0)
+ return r;
+
+ r = link_set_ipv6_privacy_extensions(link);
+ if (r < 0)
+ return r;
+
+ r = link_set_ipv6_accept_ra(link);
+ if (r < 0)
+ return r;
+
+ r = link_set_ipv6_dad_transmits(link);
+ if (r < 0)
+ return r;
+
+ r = link_set_ipv6_hop_limit(link);
+ if (r < 0)
+ return r;
+
+ r = link_set_flags(link);
+ if (r < 0)
+ return r;
+
+ r = link_set_ipv6_mtu(link);
+ if (r < 0)
+ return r;
+
+ if (link_ipv4ll_enabled(link)) {
+ r = ipv4ll_configure(link);
+ if (r < 0)
+ return r;
+ }
+
+ if (link_dhcp4_enabled(link)) {
+ r = dhcp4_set_promote_secondaries(link);
+ if (r < 0)
+ return r;
+
+ r = dhcp4_configure(link);
+ if (r < 0)
+ return r;
+ }
+
+ if (link_dhcp4_server_enabled(link)) {
+ r = sd_dhcp_server_new(&link->dhcp_server, link->ifindex);
+ if (r < 0)
+ return r;
+
+ r = sd_dhcp_server_attach_event(link->dhcp_server, NULL, 0);
+ if (r < 0)
+ return r;
+ }
+
+ if (link_dhcp6_enabled(link) ||
+ link_ipv6_accept_ra_enabled(link)) {
+ r = dhcp6_configure(link);
+ if (r < 0)
+ return r;
+ }
+
+ if (link_ipv6_accept_ra_enabled(link)) {
+ r = ndisc_configure(link);
+ if (r < 0)
+ return r;
+ }
+
+ if (link_radv_enabled(link)) {
+ r = radv_configure(link);
+ if (r < 0)
+ return r;
+ }
+
+ if (link_lldp_rx_enabled(link)) {
+ r = sd_lldp_new(&link->lldp);
+ if (r < 0)
+ return r;
+
+ r = sd_lldp_set_ifindex(link->lldp, link->ifindex);
+ if (r < 0)
+ return r;
+
+ r = sd_lldp_match_capabilities(link->lldp,
+ link->network->lldp_mode == LLDP_MODE_ROUTERS_ONLY ?
+ SD_LLDP_SYSTEM_CAPABILITIES_ALL_ROUTERS :
+ SD_LLDP_SYSTEM_CAPABILITIES_ALL);
+ if (r < 0)
+ return r;
+
+ r = sd_lldp_set_filter_address(link->lldp, &link->mac);
+ if (r < 0)
+ return r;
+
+ r = sd_lldp_attach_event(link->lldp, NULL, 0);
+ if (r < 0)
+ return r;
+
+ r = sd_lldp_set_callback(link->lldp, lldp_handler, link);
+ if (r < 0)
+ return r;
+
+ r = link_update_lldp(link);
+ if (r < 0)
+ return r;
+ }
+
+ if (link->network->mtu > 0) {
+ r = link_set_mtu(link, link->network->mtu);
+ if (r < 0)
+ return r;
+ }
+
+ if (socket_ipv6_is_supported()) {
+ r = link_configure_addrgen_mode(link);
+ if (r < 0)
+ return r;
+ }
+
+ return link_configure_after_setting_mtu(link);
+}
+
+static int link_configure_after_setting_mtu(Link *link) {
+ int r;
+
+ assert(link);
+ assert(link->network);
+ assert(link->state == LINK_STATE_PENDING);
+
+ if (link->setting_mtu)
+ return 0;
+
+ if (link_has_carrier(link) || link->network->configure_without_carrier) {
+ r = link_acquire_conf(link);
+ if (r < 0)
+ return r;
+ }
+
+ return link_enter_join_netdev(link);
+}
+
+static int duid_set_uuid(DUID *duid, sd_id128_t uuid) {
+ assert(duid);
+
+ if (duid->raw_data_len > 0)
+ return 0;
+
+ if (duid->type != DUID_TYPE_UUID)
+ return -EINVAL;
+
+ memcpy(&duid->raw_data, &uuid, sizeof(sd_id128_t));
+ duid->raw_data_len = sizeof(sd_id128_t);
+
+ return 1;
+}
+
+int get_product_uuid_handler(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+ Manager *manager = userdata;
+ const sd_bus_error *e;
+ const void *a;
+ size_t sz;
+ DUID *duid;
+ Link *link;
+ int r;
+
+ assert(m);
+ assert(manager);
+
+ e = sd_bus_message_get_error(m);
+ if (e) {
+ log_error_errno(sd_bus_error_get_errno(e),
+ "Could not get product UUID. Falling back to use machine-app-specific ID as DUID-UUID: %s",
+ e->message);
+ goto configure;
+ }
+
+ r = sd_bus_message_read_array(m, 'y', &a, &sz);
+ if (r < 0)
+ goto configure;
+
+ if (sz != sizeof(sd_id128_t)) {
+ log_error("Invalid product UUID. Falling back to use machine-app-specific ID as DUID-UUID.");
+ goto configure;
+ }
+
+ memcpy(&manager->product_uuid, a, sz);
+ while ((duid = set_steal_first(manager->duids_requesting_uuid)))
+ (void) duid_set_uuid(duid, manager->product_uuid);
+
+ manager->duids_requesting_uuid = set_free(manager->duids_requesting_uuid);
+
+configure:
+ while ((link = set_steal_first(manager->links_requesting_uuid))) {
+ r = link_configure(link);
+ if (r < 0)
+ log_link_error_errno(link, r, "Failed to configure link: %m");
+ }
+
+ manager->links_requesting_uuid = set_free(manager->links_requesting_uuid);
+
+ /* To avoid calling GetProductUUID() bus method so frequently, set the flag below
+ * even if the method fails. */
+ manager->has_product_uuid = true;
+
+ return 1;
+}
+
+static bool link_requires_uuid(Link *link) {
+ const DUID *duid;
+
+ assert(link);
+ assert(link->manager);
+ assert(link->network);
+
+ duid = link_get_duid(link);
+ if (duid->type != DUID_TYPE_UUID || duid->raw_data_len != 0)
+ return false;
+
+ if (link_dhcp4_enabled(link) && IN_SET(link->network->dhcp_client_identifier, DHCP_CLIENT_ID_DUID, DHCP_CLIENT_ID_DUID_ONLY))
+ return true;
+
+ if (link_dhcp6_enabled(link) || link_ipv6_accept_ra_enabled(link))
+ return true;
+
+ return false;
+}
+
+static int link_configure_duid(Link *link) {
+ Manager *m;
+ DUID *duid;
+ int r;
+
+ assert(link);
+ assert(link->manager);
+ assert(link->network);
+
+ m = link->manager;
+ duid = link_get_duid(link);
+
+ if (!link_requires_uuid(link))
+ return 1;
+
+ if (m->has_product_uuid) {
+ (void) duid_set_uuid(duid, m->product_uuid);
+ return 1;
+ }
+
+ if (!m->links_requesting_uuid) {
+ r = manager_request_product_uuid(m, link);
+ if (r < 0) {
+ if (r == -ENOMEM)
+ return r;
+
+ log_link_warning_errno(link, r,
+ "Failed to get product UUID. Falling back to use machine-app-specific ID as DUID-UUID: %m");
+ return 1;
+ }
+ } else {
+ r = set_put(m->links_requesting_uuid, link);
+ if (r < 0)
+ return log_oom();
+
+ r = set_put(m->duids_requesting_uuid, duid);
+ if (r < 0)
+ return log_oom();
+ }
+
+ return 0;
+}
+
+static int link_initialized_and_synced(Link *link) {
+ Network *network;
+ int r;
+
+ assert(link);
+ assert(link->ifname);
+ assert(link->manager);
+
+ if (link->state != LINK_STATE_PENDING)
+ return 1;
+
+ log_link_debug(link, "Link state is up-to-date");
+
+ r = link_new_bound_by_list(link);
+ if (r < 0)
+ return r;
+
+ r = link_handle_bound_by_list(link);
+ if (r < 0)
+ return r;
+
+ if (!link->network) {
+ r = network_get(link->manager, link->sd_device, link->ifname,
+ &link->mac, &network);
+ if (r == -ENOENT) {
+ link_enter_unmanaged(link);
+ return 1;
+ } else if (r == 0 && network->unmanaged) {
+ link_enter_unmanaged(link);
+ return 0;
+ } else if (r < 0)
+ return r;
+
+ if (link->flags & IFF_LOOPBACK) {
+ if (network->link_local != ADDRESS_FAMILY_NO)
+ log_link_debug(link, "Ignoring link-local autoconfiguration for loopback link");
+
+ if (network->dhcp != ADDRESS_FAMILY_NO)
+ log_link_debug(link, "Ignoring DHCP clients for loopback link");
+
+ if (network->dhcp_server)
+ log_link_debug(link, "Ignoring DHCP server for loopback link");
+ }
+
+ r = network_apply(network, link);
+ if (r < 0)
+ return r;
+ }
+
+ r = link_new_bound_to_list(link);
+ if (r < 0)
+ return r;
+
+ /* link_configure_duid() returns 0 if it requests product UUID. In that case,
+ * link_configure() is called later asynchronously. */
+ r = link_configure_duid(link);
+ if (r <= 0)
+ return r;
+
+ r = link_configure(link);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static int link_initialized_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ (void) link_initialized_and_synced(link);
+ return 1;
+}
+
+int link_initialized(Link *link, sd_device *device) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ int r;
+
+ assert(link);
+ assert(link->manager);
+ assert(link->manager->rtnl);
+ assert(device);
+
+ if (link->state != LINK_STATE_PENDING)
+ return 0;
+
+ if (link->sd_device)
+ return 0;
+
+ log_link_debug(link, "udev initialized link");
+
+ link->sd_device = sd_device_ref(device);
+
+ /* udev has initialized the link, but we don't know if we have yet
+ * processed the NEWLINK messages with the latest state. Do a GETLINK,
+ * when it returns we know that the pending NEWLINKs have already been
+ * processed and that we are up-to-date */
+
+ r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_GETLINK,
+ link->ifindex);
+ if (r < 0)
+ return r;
+
+ r = netlink_call_async(link->manager->rtnl, NULL, req, link_initialized_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return r;
+
+ link_ref(link);
+
+ return 0;
+}
+
+static int link_load(Link *link) {
+ _cleanup_free_ char *network_file = NULL,
+ *addresses = NULL,
+ *routes = NULL,
+ *dhcp4_address = NULL,
+ *ipv4ll_address = NULL;
+ union in_addr_union address;
+ union in_addr_union route_dst;
+ const char *p;
+ int r;
+
+ assert(link);
+
+ r = parse_env_file(NULL, link->state_file,
+ "NETWORK_FILE", &network_file,
+ "ADDRESSES", &addresses,
+ "ROUTES", &routes,
+ "DHCP4_ADDRESS", &dhcp4_address,
+ "IPV4LL_ADDRESS", &ipv4ll_address);
+ if (r < 0 && r != -ENOENT)
+ return log_link_error_errno(link, r, "Failed to read %s: %m", link->state_file);
+
+ if (network_file) {
+ Network *network;
+ char *suffix;
+
+ /* drop suffix */
+ suffix = strrchr(network_file, '.');
+ if (!suffix) {
+ log_link_debug(link, "Failed to get network name from %s", network_file);
+ goto network_file_fail;
+ }
+ *suffix = '\0';
+
+ r = network_get_by_name(link->manager, basename(network_file), &network);
+ if (r < 0) {
+ log_link_debug_errno(link, r, "Failed to get network %s: %m", basename(network_file));
+ goto network_file_fail;
+ }
+
+ r = network_apply(network, link);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to apply network %s: %m", basename(network_file));
+ }
+
+network_file_fail:
+
+ if (addresses) {
+ p = addresses;
+
+ for (;;) {
+ _cleanup_free_ char *address_str = NULL;
+ char *prefixlen_str;
+ int family;
+ unsigned char prefixlen;
+
+ r = extract_first_word(&p, &address_str, NULL, 0);
+ if (r < 0) {
+ log_link_debug_errno(link, r, "Failed to extract next address string: %m");
+ continue;
+ }
+ if (r == 0)
+ break;
+
+ prefixlen_str = strchr(address_str, '/');
+ if (!prefixlen_str) {
+ log_link_debug(link, "Failed to parse address and prefix length %s", address_str);
+ continue;
+ }
+
+ *prefixlen_str++ = '\0';
+
+ r = sscanf(prefixlen_str, "%hhu", &prefixlen);
+ if (r != 1) {
+ log_link_error(link, "Failed to parse prefixlen %s", prefixlen_str);
+ continue;
+ }
+
+ r = in_addr_from_string_auto(address_str, &family, &address);
+ if (r < 0) {
+ log_link_debug_errno(link, r, "Failed to parse address %s: %m", address_str);
+ continue;
+ }
+
+ r = address_add(link, family, &address, prefixlen, NULL);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to add address: %m");
+ }
+ }
+
+ if (routes) {
+ p = routes;
+
+ for (;;) {
+ Route *route;
+ _cleanup_free_ char *route_str = NULL;
+ _cleanup_(sd_event_source_unrefp) sd_event_source *expire = NULL;
+ usec_t lifetime;
+ char *prefixlen_str;
+ int family;
+ unsigned char prefixlen, tos, table;
+ uint32_t priority;
+
+ r = extract_first_word(&p, &route_str, NULL, 0);
+ if (r < 0) {
+ log_link_debug_errno(link, r, "Failed to extract next route string: %m");
+ continue;
+ }
+ if (r == 0)
+ break;
+
+ prefixlen_str = strchr(route_str, '/');
+ if (!prefixlen_str) {
+ log_link_debug(link, "Failed to parse route %s", route_str);
+ continue;
+ }
+
+ *prefixlen_str++ = '\0';
+
+ r = sscanf(prefixlen_str, "%hhu/%hhu/%"SCNu32"/%hhu/"USEC_FMT, &prefixlen, &tos, &priority, &table, &lifetime);
+ if (r != 5) {
+ log_link_debug(link,
+ "Failed to parse destination prefix length, tos, priority, table or expiration %s",
+ prefixlen_str);
+ continue;
+ }
+
+ r = in_addr_from_string_auto(route_str, &family, &route_dst);
+ if (r < 0) {
+ log_link_debug_errno(link, r, "Failed to parse route destination %s: %m", route_str);
+ continue;
+ }
+
+ r = route_add(link, family, &route_dst, prefixlen, tos, priority, table, &route);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to add route: %m");
+
+ if (lifetime != USEC_INFINITY && !kernel_route_expiration_supported()) {
+ r = sd_event_add_time(link->manager->event, &expire, clock_boottime_or_monotonic(), lifetime,
+ 0, route_expire_handler, route);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Could not arm route expiration handler: %m");
+ }
+
+ route->lifetime = lifetime;
+ sd_event_source_unref(route->expire);
+ route->expire = TAKE_PTR(expire);
+ }
+ }
+
+ if (dhcp4_address) {
+ r = in_addr_from_string(AF_INET, dhcp4_address, &address);
+ if (r < 0) {
+ log_link_debug_errno(link, r, "Failed to parse DHCPv4 address %s: %m", dhcp4_address);
+ goto dhcp4_address_fail;
+ }
+
+ r = sd_dhcp_client_new(&link->dhcp_client, link->network ? link->network->dhcp_anonymize : 0);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to create DHCPv4 client: %m");
+
+ r = sd_dhcp_client_set_request_address(link->dhcp_client, &address.in);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to set initial DHCPv4 address %s: %m", dhcp4_address);
+ }
+
+dhcp4_address_fail:
+
+ if (ipv4ll_address) {
+ r = in_addr_from_string(AF_INET, ipv4ll_address, &address);
+ if (r < 0) {
+ log_link_debug_errno(link, r, "Failed to parse IPv4LL address %s: %m", ipv4ll_address);
+ goto ipv4ll_address_fail;
+ }
+
+ r = sd_ipv4ll_new(&link->ipv4ll);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to create IPv4LL client: %m");
+
+ r = sd_ipv4ll_set_address(link->ipv4ll, &address.in);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to set initial IPv4LL address %s: %m", ipv4ll_address);
+ }
+
+ipv4ll_address_fail:
+
+ return 0;
+}
+
+int link_add(Manager *m, sd_netlink_message *message, Link **ret) {
+ _cleanup_(sd_device_unrefp) sd_device *device = NULL;
+ char ifindex_str[2 + DECIMAL_STR_MAX(int)];
+ Link *link;
+ int r;
+
+ assert(m);
+ assert(m->rtnl);
+ assert(message);
+ assert(ret);
+
+ r = link_new(m, message, ret);
+ if (r < 0)
+ return r;
+
+ link = *ret;
+
+ log_link_debug(link, "Link %d added", link->ifindex);
+
+ r = link_load(link);
+ if (r < 0)
+ return r;
+
+ if (detect_container() <= 0) {
+ /* not in a container, udev will be around */
+ sprintf(ifindex_str, "n%d", link->ifindex);
+ r = sd_device_new_from_device_id(&device, ifindex_str);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Could not find device: %m");
+ goto failed;
+ }
+
+ r = sd_device_get_is_initialized(device);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Could not determine whether the device is initialized or not: %m");
+ goto failed;
+ }
+ if (r == 0) {
+ /* not yet ready */
+ log_link_debug(link, "link pending udev initialization...");
+ return 0;
+ }
+
+ r = link_initialized(link, device);
+ if (r < 0)
+ goto failed;
+ } else {
+ r = link_initialized_and_synced(link);
+ if (r < 0)
+ goto failed;
+ }
+
+ return 0;
+failed:
+ link_enter_failed(link);
+ return r;
+}
+
+int link_ipv6ll_gained(Link *link, const struct in6_addr *address) {
+ int r;
+
+ assert(link);
+
+ log_link_info(link, "Gained IPv6LL");
+
+ link->ipv6ll_address = *address;
+ link_check_ready(link);
+
+ if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_UNMANAGED, LINK_STATE_FAILED)) {
+ r = link_acquire_ipv6_conf(link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return r;
+ }
+ }
+
+ return 0;
+}
+
+static int link_carrier_gained(Link *link) {
+ int r;
+
+ assert(link);
+
+ if (!IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_UNMANAGED, LINK_STATE_FAILED)) {
+ r = link_acquire_conf(link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return r;
+ }
+
+ r = link_request_set_addresses(link);
+ if (r < 0)
+ return r;
+ }
+
+ r = link_handle_bound_by_list(link);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int link_carrier_lost(Link *link) {
+ int r;
+
+ assert(link);
+
+ /* Some devices reset itself while setting the MTU. This causes the DHCP client fall into a loop.
+ * setting_mtu keep track whether the device got reset because of setting MTU and does not drop the
+ * configuration and stop the clients as well. */
+ if (link->setting_mtu)
+ return 0;
+
+ r = link_stop_clients(link);
+ if (r < 0) {
+ link_enter_failed(link);
+ return r;
+ }
+
+ if (link_dhcp4_server_enabled(link))
+ (void) sd_dhcp_server_stop(link->dhcp_server);
+
+ r = link_drop_config(link);
+ if (r < 0)
+ return r;
+
+ if (!IN_SET(link->state, LINK_STATE_UNMANAGED, LINK_STATE_PENDING)) {
+ log_link_debug(link, "State is %s, dropping config", link_state_to_string(link->state));
+ r = link_drop_foreign_config(link);
+ if (r < 0)
+ return r;
+ }
+
+ r = link_handle_bound_by_list(link);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int link_carrier_reset(Link *link) {
+ int r;
+
+ assert(link);
+
+ if (link_has_carrier(link)) {
+ r = link_carrier_lost(link);
+ if (r < 0)
+ return r;
+
+ r = link_carrier_gained(link);
+ if (r < 0)
+ return r;
+
+ log_link_info(link, "Reset carrier");
+ }
+
+ return 0;
+}
+
+int link_update(Link *link, sd_netlink_message *m) {
+ struct ether_addr mac;
+ const char *ifname;
+ uint32_t mtu;
+ bool had_carrier, carrier_gained, carrier_lost;
+ int r;
+
+ assert(link);
+ assert(link->ifname);
+ assert(m);
+
+ if (link->state == LINK_STATE_LINGER) {
+ log_link_info(link, "Link readded");
+ link_set_state(link, LINK_STATE_CONFIGURING);
+
+ r = link_new_carrier_maps(link);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_netlink_message_read_string(m, IFLA_IFNAME, &ifname);
+ if (r >= 0 && !streq(ifname, link->ifname)) {
+ log_link_info(link, "Interface name change detected, %s has been renamed to %s.", link->ifname, ifname);
+
+ if (link->state == LINK_STATE_PENDING) {
+ r = free_and_strdup(&link->ifname, ifname);
+ if (r < 0)
+ return r;
+ } else {
+ Manager *manager = link->manager;
+
+ link_drop(link);
+ r = link_add(manager, m, &link);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ r = sd_netlink_message_read_u32(m, IFLA_MTU, &mtu);
+ if (r >= 0 && mtu > 0) {
+ link->mtu = mtu;
+ if (link->original_mtu == 0) {
+ link->original_mtu = mtu;
+ log_link_debug(link, "Saved original MTU: %" PRIu32, link->original_mtu);
+ }
+
+ if (link->dhcp_client) {
+ r = sd_dhcp_client_set_mtu(link->dhcp_client,
+ link->mtu);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not update MTU in DHCP client: %m");
+ }
+
+ if (link->radv) {
+ r = sd_radv_set_mtu(link->radv, link->mtu);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not set MTU for Router Advertisement: %m");
+ }
+ }
+
+ /* The kernel may broadcast NEWLINK messages without the MAC address
+ set, simply ignore them. */
+ r = sd_netlink_message_read_ether_addr(m, IFLA_ADDRESS, &mac);
+ if (r >= 0) {
+ if (memcmp(link->mac.ether_addr_octet, mac.ether_addr_octet,
+ ETH_ALEN)) {
+
+ memcpy(link->mac.ether_addr_octet, mac.ether_addr_octet,
+ ETH_ALEN);
+
+ log_link_debug(link, "MAC address: "
+ "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+ mac.ether_addr_octet[0],
+ mac.ether_addr_octet[1],
+ mac.ether_addr_octet[2],
+ mac.ether_addr_octet[3],
+ mac.ether_addr_octet[4],
+ mac.ether_addr_octet[5]);
+
+ if (link->ipv4ll) {
+ r = sd_ipv4ll_set_mac(link->ipv4ll, &link->mac);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not update MAC address in IPv4LL client: %m");
+ }
+
+ if (link->dhcp_client) {
+ r = sd_dhcp_client_set_mac(link->dhcp_client,
+ (const uint8_t *) &link->mac,
+ sizeof (link->mac),
+ ARPHRD_ETHER);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not update MAC address in DHCP client: %m");
+
+ r = dhcp4_set_client_identifier(link);
+ if (r < 0)
+ return r;
+ }
+
+ if (link->dhcp6_client) {
+ const DUID* duid = link_get_duid(link);
+
+ r = sd_dhcp6_client_set_mac(link->dhcp6_client,
+ (const uint8_t *) &link->mac,
+ sizeof (link->mac),
+ ARPHRD_ETHER);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not update MAC address in DHCPv6 client: %m");
+
+ if (link->network->iaid_set) {
+ r = sd_dhcp6_client_set_iaid(link->dhcp6_client,
+ link->network->iaid);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not update DHCPv6 IAID: %m");
+ }
+
+ r = sd_dhcp6_client_set_duid(link->dhcp6_client,
+ duid->type,
+ duid->raw_data_len > 0 ? duid->raw_data : NULL,
+ duid->raw_data_len);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not update DHCPv6 DUID: %m");
+ }
+
+ if (link->radv) {
+ r = sd_radv_set_mac(link->radv, &link->mac);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not update MAC for Router Advertisement: %m");
+ }
+
+ if (link->ndisc) {
+ r = sd_ndisc_set_mac(link->ndisc, &link->mac);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Could not update MAC for ndisc: %m");
+ }
+ }
+ }
+
+ had_carrier = link_has_carrier(link);
+
+ r = link_update_flags(link, m);
+ if (r < 0)
+ return r;
+
+ r = link_update_lldp(link);
+ if (r < 0)
+ return r;
+
+ carrier_gained = !had_carrier && link_has_carrier(link);
+ carrier_lost = had_carrier && !link_has_carrier(link);
+
+ if (carrier_gained) {
+ log_link_info(link, "Gained carrier");
+
+ r = link_carrier_gained(link);
+ if (r < 0)
+ return r;
+ } else if (carrier_lost) {
+ log_link_info(link, "Lost carrier");
+
+ r = link_carrier_lost(link);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static void print_link_hashmap(FILE *f, const char *prefix, Hashmap* h) {
+ bool space = false;
+ Iterator i;
+ Link *link;
+
+ assert(f);
+ assert(prefix);
+
+ if (hashmap_isempty(h))
+ return;
+
+ fputs(prefix, f);
+ HASHMAP_FOREACH(link, h, i) {
+ if (space)
+ fputc(' ', f);
+
+ fprintf(f, "%i", link->ifindex);
+ space = true;
+ }
+
+ fputc('\n', f);
+}
+
+int link_save(Link *link) {
+ _cleanup_free_ char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ const char *admin_state, *oper_state;
+ Address *a;
+ Route *route;
+ Iterator i;
+ int r;
+
+ assert(link);
+ assert(link->state_file);
+ assert(link->lease_file);
+ assert(link->manager);
+
+ if (link->state == LINK_STATE_LINGER) {
+ unlink(link->state_file);
+ return 0;
+ }
+
+ link_lldp_save(link);
+
+ admin_state = link_state_to_string(link->state);
+ assert(admin_state);
+
+ oper_state = link_operstate_to_string(link->operstate);
+ assert(oper_state);
+
+ r = fopen_temporary(link->state_file, &f, &temp_path);
+ if (r < 0)
+ goto fail;
+
+ (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+ (void) fchmod(fileno(f), 0644);
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n"
+ "ADMIN_STATE=%s\n"
+ "OPER_STATE=%s\n",
+ admin_state, oper_state);
+
+ if (link->network) {
+ bool space;
+ sd_dhcp6_lease *dhcp6_lease = NULL;
+ const char *dhcp_domainname = NULL;
+ char **dhcp6_domains = NULL;
+ char **dhcp_domains = NULL;
+ unsigned j;
+
+ fprintf(f, "REQUIRED_FOR_ONLINE=%s\n",
+ yes_no(link->network->required_for_online));
+
+ if (link->dhcp6_client) {
+ r = sd_dhcp6_client_get_lease(link->dhcp6_client, &dhcp6_lease);
+ if (r < 0 && r != -ENOMSG)
+ log_link_debug(link, "No DHCPv6 lease");
+ }
+
+ fprintf(f, "NETWORK_FILE=%s\n", link->network->filename);
+
+ fputs("DNS=", f);
+ space = false;
+
+ for (j = 0; j < link->network->n_dns; j++) {
+ _cleanup_free_ char *b = NULL;
+
+ r = in_addr_to_string(link->network->dns[j].family,
+ &link->network->dns[j].address, &b);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to format address, ignoring: %m");
+ continue;
+ }
+
+ if (space)
+ fputc(' ', f);
+ fputs(b, f);
+ space = true;
+ }
+
+ if (link->network->dhcp_use_dns &&
+ link->dhcp_lease) {
+ const struct in_addr *addresses;
+
+ r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses);
+ if (r > 0) {
+ if (space)
+ fputc(' ', f);
+ serialize_in_addrs(f, addresses, r);
+ space = true;
+ }
+ }
+
+ if (link->network->dhcp_use_dns && dhcp6_lease) {
+ struct in6_addr *in6_addrs;
+
+ r = sd_dhcp6_lease_get_dns(dhcp6_lease, &in6_addrs);
+ if (r > 0) {
+ if (space)
+ fputc(' ', f);
+ serialize_in6_addrs(f, in6_addrs, r);
+ space = true;
+ }
+ }
+
+ /* Make sure to flush out old entries before we use the NDISC data */
+ ndisc_vacuum(link);
+
+ if (link->network->ipv6_accept_ra_use_dns && link->ndisc_rdnss) {
+ NDiscRDNSS *dd;
+
+ SET_FOREACH(dd, link->ndisc_rdnss, i) {
+ if (space)
+ fputc(' ', f);
+
+ serialize_in6_addrs(f, &dd->address, 1);
+ space = true;
+ }
+ }
+
+ fputc('\n', f);
+
+ fputs("NTP=", f);
+ space = false;
+ fputstrv(f, link->network->ntp, NULL, &space);
+
+ if (link->network->dhcp_use_ntp &&
+ link->dhcp_lease) {
+ const struct in_addr *addresses;
+
+ r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses);
+ if (r > 0) {
+ if (space)
+ fputc(' ', f);
+ serialize_in_addrs(f, addresses, r);
+ space = true;
+ }
+ }
+
+ if (link->network->dhcp_use_ntp && dhcp6_lease) {
+ struct in6_addr *in6_addrs;
+ char **hosts;
+
+ r = sd_dhcp6_lease_get_ntp_addrs(dhcp6_lease,
+ &in6_addrs);
+ if (r > 0) {
+ if (space)
+ fputc(' ', f);
+ serialize_in6_addrs(f, in6_addrs, r);
+ space = true;
+ }
+
+ r = sd_dhcp6_lease_get_ntp_fqdn(dhcp6_lease, &hosts);
+ if (r > 0)
+ fputstrv(f, hosts, NULL, &space);
+ }
+
+ fputc('\n', f);
+
+ if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) {
+ if (link->dhcp_lease) {
+ (void) sd_dhcp_lease_get_domainname(link->dhcp_lease, &dhcp_domainname);
+ (void) sd_dhcp_lease_get_search_domains(link->dhcp_lease, &dhcp_domains);
+ }
+ if (dhcp6_lease)
+ (void) sd_dhcp6_lease_get_domains(dhcp6_lease, &dhcp6_domains);
+ }
+
+ fputs("DOMAINS=", f);
+ space = false;
+ fputstrv(f, link->network->search_domains, NULL, &space);
+
+ if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES) {
+ NDiscDNSSL *dd;
+
+ if (dhcp_domainname)
+ fputs_with_space(f, dhcp_domainname, NULL, &space);
+ if (dhcp_domains)
+ fputstrv(f, dhcp_domains, NULL, &space);
+ if (dhcp6_domains)
+ fputstrv(f, dhcp6_domains, NULL, &space);
+
+ SET_FOREACH(dd, link->ndisc_dnssl, i)
+ fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space);
+ }
+
+ fputc('\n', f);
+
+ fputs("ROUTE_DOMAINS=", f);
+ space = false;
+ fputstrv(f, link->network->route_domains, NULL, &space);
+
+ if (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_ROUTE) {
+ NDiscDNSSL *dd;
+
+ if (dhcp_domainname)
+ fputs_with_space(f, dhcp_domainname, NULL, &space);
+ if (dhcp_domains)
+ fputstrv(f, dhcp_domains, NULL, &space);
+ if (dhcp6_domains)
+ fputstrv(f, dhcp6_domains, NULL, &space);
+
+ SET_FOREACH(dd, link->ndisc_dnssl, i)
+ fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space);
+ }
+
+ fputc('\n', f);
+
+ fprintf(f, "LLMNR=%s\n",
+ resolve_support_to_string(link->network->llmnr));
+ fprintf(f, "MDNS=%s\n",
+ resolve_support_to_string(link->network->mdns));
+ if (link->network->dns_default_route >= 0)
+ fprintf(f, "DNS_DEFAULT_ROUTE=%s\n", yes_no(link->network->dns_default_route));
+
+ if (link->network->dns_over_tls_mode != _DNS_OVER_TLS_MODE_INVALID)
+ fprintf(f, "DNS_OVER_TLS=%s\n",
+ dns_over_tls_mode_to_string(link->network->dns_over_tls_mode));
+
+ if (link->network->dnssec_mode != _DNSSEC_MODE_INVALID)
+ fprintf(f, "DNSSEC=%s\n",
+ dnssec_mode_to_string(link->network->dnssec_mode));
+
+ if (!set_isempty(link->network->dnssec_negative_trust_anchors)) {
+ const char *n;
+
+ fputs("DNSSEC_NTA=", f);
+ space = false;
+ SET_FOREACH(n, link->network->dnssec_negative_trust_anchors, i)
+ fputs_with_space(f, n, NULL, &space);
+ fputc('\n', f);
+ }
+
+ fputs("ADDRESSES=", f);
+ space = false;
+ SET_FOREACH(a, link->addresses, i) {
+ _cleanup_free_ char *address_str = NULL;
+
+ r = in_addr_to_string(a->family, &a->in_addr, &address_str);
+ if (r < 0)
+ goto fail;
+
+ fprintf(f, "%s%s/%u", space ? " " : "", address_str, a->prefixlen);
+ space = true;
+ }
+ fputc('\n', f);
+
+ fputs("ROUTES=", f);
+ space = false;
+ SET_FOREACH(route, link->routes, i) {
+ _cleanup_free_ char *route_str = NULL;
+
+ r = in_addr_to_string(route->family, &route->dst, &route_str);
+ if (r < 0)
+ goto fail;
+
+ fprintf(f, "%s%s/%hhu/%hhu/%"PRIu32"/%"PRIu32"/"USEC_FMT,
+ space ? " " : "", route_str,
+ route->dst_prefixlen, route->tos, route->priority, route->table, route->lifetime);
+ space = true;
+ }
+
+ fputc('\n', f);
+ }
+
+ print_link_hashmap(f, "CARRIER_BOUND_TO=", link->bound_to_links);
+ print_link_hashmap(f, "CARRIER_BOUND_BY=", link->bound_by_links);
+
+ if (link->dhcp_lease) {
+ struct in_addr address;
+ const char *tz = NULL;
+
+ assert(link->network);
+
+ r = sd_dhcp_lease_get_timezone(link->dhcp_lease, &tz);
+ if (r >= 0)
+ fprintf(f, "TIMEZONE=%s\n", tz);
+
+ r = sd_dhcp_lease_get_address(link->dhcp_lease, &address);
+ if (r >= 0) {
+ fputs("DHCP4_ADDRESS=", f);
+ serialize_in_addrs(f, &address, 1);
+ fputc('\n', f);
+ }
+
+ r = dhcp_lease_save(link->dhcp_lease, link->lease_file);
+ if (r < 0)
+ goto fail;
+
+ fprintf(f,
+ "DHCP_LEASE=%s\n",
+ link->lease_file);
+ } else
+ unlink(link->lease_file);
+
+ if (link->ipv4ll) {
+ struct in_addr address;
+
+ r = sd_ipv4ll_get_address(link->ipv4ll, &address);
+ if (r >= 0) {
+ fputs("IPV4LL_ADDRESS=", f);
+ serialize_in_addrs(f, &address, 1);
+ fputc('\n', f);
+ }
+ }
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto fail;
+
+ if (rename(temp_path, link->state_file) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ (void) unlink(link->state_file);
+ if (temp_path)
+ (void) unlink(temp_path);
+
+ return log_link_error_errno(link, r, "Failed to save link data to %s: %m", link->state_file);
+}
+
+/* The serialized state in /run is no longer up-to-date. */
+void link_dirty(Link *link) {
+ int r;
+
+ assert(link);
+
+ /* mark manager dirty as link is dirty */
+ manager_dirty(link->manager);
+
+ r = set_ensure_allocated(&link->manager->dirty_links, NULL);
+ if (r < 0)
+ /* allocation errors are ignored */
+ return;
+
+ r = set_put(link->manager->dirty_links, link);
+ if (r <= 0)
+ /* don't take another ref if the link was already dirty */
+ return;
+
+ link_ref(link);
+}
+
+/* The serialized state in /run is up-to-date */
+void link_clean(Link *link) {
+ assert(link);
+ assert(link->manager);
+
+ link_unref(set_remove(link->manager->dirty_links, link));
+}
+
+static const char* const link_state_table[_LINK_STATE_MAX] = {
+ [LINK_STATE_PENDING] = "pending",
+ [LINK_STATE_CONFIGURING] = "configuring",
+ [LINK_STATE_CONFIGURED] = "configured",
+ [LINK_STATE_UNMANAGED] = "unmanaged",
+ [LINK_STATE_FAILED] = "failed",
+ [LINK_STATE_LINGER] = "linger",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(link_state, LinkState);
+
+static const char* const link_operstate_table[_LINK_OPERSTATE_MAX] = {
+ [LINK_OPERSTATE_OFF] = "off",
+ [LINK_OPERSTATE_NO_CARRIER] = "no-carrier",
+ [LINK_OPERSTATE_DORMANT] = "dormant",
+ [LINK_OPERSTATE_CARRIER] = "carrier",
+ [LINK_OPERSTATE_DEGRADED] = "degraded",
+ [LINK_OPERSTATE_ROUTABLE] = "routable",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(link_operstate, LinkOperationalState);
diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h
new file mode 100644
index 0000000..dcb1ea6
--- /dev/null
+++ b/src/network/networkd-link.h
@@ -0,0 +1,216 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <endian.h>
+
+#include "sd-bus.h"
+#include "sd-device.h"
+#include "sd-dhcp-client.h"
+#include "sd-dhcp-server.h"
+#include "sd-dhcp6-client.h"
+#include "sd-ipv4ll.h"
+#include "sd-lldp.h"
+#include "sd-ndisc.h"
+#include "sd-radv.h"
+#include "sd-netlink.h"
+
+#include "list.h"
+#include "set.h"
+
+typedef enum LinkState {
+ LINK_STATE_PENDING,
+ LINK_STATE_CONFIGURING,
+ LINK_STATE_CONFIGURED,
+ LINK_STATE_UNMANAGED,
+ LINK_STATE_FAILED,
+ LINK_STATE_LINGER,
+ _LINK_STATE_MAX,
+ _LINK_STATE_INVALID = -1
+} LinkState;
+
+typedef enum LinkOperationalState {
+ LINK_OPERSTATE_OFF,
+ LINK_OPERSTATE_NO_CARRIER,
+ LINK_OPERSTATE_DORMANT,
+ LINK_OPERSTATE_CARRIER,
+ LINK_OPERSTATE_DEGRADED,
+ LINK_OPERSTATE_ROUTABLE,
+ _LINK_OPERSTATE_MAX,
+ _LINK_OPERSTATE_INVALID = -1
+} LinkOperationalState;
+
+typedef struct Manager Manager;
+typedef struct Network Network;
+typedef struct Address Address;
+typedef struct DUID DUID;
+
+typedef struct Link {
+ Manager *manager;
+
+ unsigned n_ref;
+
+ int ifindex;
+ int master_ifindex;
+ char *ifname;
+ char *kind;
+ unsigned short iftype;
+ char *state_file;
+ struct ether_addr mac;
+ struct in6_addr ipv6ll_address;
+ uint32_t mtu;
+ sd_device *sd_device;
+
+ unsigned flags;
+ uint8_t kernel_operstate;
+
+ Network *network;
+
+ LinkState state;
+ LinkOperationalState operstate;
+
+ unsigned address_messages;
+ unsigned address_label_messages;
+ unsigned neighbor_messages;
+ unsigned route_messages;
+ unsigned routing_policy_rule_messages;
+ unsigned routing_policy_rule_remove_messages;
+ unsigned enslaving;
+
+ Set *addresses;
+ Set *addresses_foreign;
+ Set *routes;
+ Set *routes_foreign;
+
+ bool addresses_configured;
+ bool addresses_ready;
+
+ sd_dhcp_client *dhcp_client;
+ sd_dhcp_lease *dhcp_lease;
+ char *lease_file;
+ uint32_t original_mtu;
+ unsigned dhcp4_messages;
+ bool dhcp4_configured;
+ bool dhcp6_configured;
+
+ unsigned ndisc_messages;
+ bool ndisc_configured;
+
+ sd_ipv4ll *ipv4ll;
+ bool ipv4ll_address:1;
+ bool ipv4ll_route:1;
+
+ bool neighbors_configured;
+
+ bool static_routes_configured;
+ bool routing_policy_rules_configured;
+ bool setting_mtu;
+
+ LIST_HEAD(Address, pool_addresses);
+
+ sd_dhcp_server *dhcp_server;
+
+ sd_ndisc *ndisc;
+ Set *ndisc_rdnss;
+ Set *ndisc_dnssl;
+
+ sd_radv *radv;
+
+ sd_dhcp6_client *dhcp6_client;
+ bool rtnl_extended_attrs;
+
+ /* This is about LLDP reception */
+ sd_lldp *lldp;
+ char *lldp_file;
+
+ /* This is about LLDP transmission */
+ unsigned lldp_tx_fast; /* The LLDP txFast counter (See 802.1ab-2009, section 9.2.5.18) */
+ sd_event_source *lldp_emit_event_source;
+
+ Hashmap *bound_by_links;
+ Hashmap *bound_to_links;
+} Link;
+
+typedef int (*link_netlink_message_handler_t)(sd_netlink*, sd_netlink_message*, Link*);
+
+DUID *link_get_duid(Link *link);
+int get_product_uuid_handler(sd_bus_message *m, void *userdata, sd_bus_error *ret_error);
+
+Link *link_unref(Link *link);
+Link *link_ref(Link *link);
+DEFINE_TRIVIAL_DESTRUCTOR(link_netlink_destroy_callback, Link, link_unref);
+
+int link_get(Manager *m, int ifindex, Link **ret);
+int link_add(Manager *manager, sd_netlink_message *message, Link **ret);
+void link_drop(Link *link);
+
+int link_down(Link *link);
+
+void link_enter_failed(Link *link);
+int link_initialized(Link *link, sd_device *device);
+
+void link_check_ready(Link *link);
+
+void link_update_operstate(Link *link);
+int link_update(Link *link, sd_netlink_message *message);
+
+void link_dirty(Link *link);
+void link_clean(Link *link);
+int link_save(Link *link);
+
+int link_carrier_reset(Link *link);
+bool link_has_carrier(Link *link);
+
+int link_ipv6ll_gained(Link *link, const struct in6_addr *address);
+
+int link_set_mtu(Link *link, uint32_t mtu);
+
+int ipv4ll_configure(Link *link);
+int dhcp4_configure(Link *link);
+int dhcp4_set_client_identifier(Link *link);
+int dhcp4_set_promote_secondaries(Link *link);
+int dhcp6_request_prefix_delegation(Link *link);
+int dhcp6_configure(Link *link);
+int dhcp6_request_address(Link *link, int ir);
+int dhcp6_lease_pd_prefix_lost(sd_dhcp6_client *client, Link* link);
+
+const char* link_state_to_string(LinkState s) _const_;
+LinkState link_state_from_string(const char *s) _pure_;
+
+const char* link_operstate_to_string(LinkOperationalState s) _const_;
+LinkOperationalState link_operstate_from_string(const char *s) _pure_;
+
+extern const sd_bus_vtable link_vtable[];
+
+int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error);
+int link_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error);
+int link_send_changed(Link *link, const char *property, ...) _sentinel_;
+
+/* Macros which append INTERFACE= to the message */
+
+#define log_link_full(link, level, error, ...) \
+ ({ \
+ const Link *_l = (link); \
+ _l ? log_object_internal(level, error, __FILE__, __LINE__, __func__, "INTERFACE=", _l->ifname, NULL, NULL, ##__VA_ARGS__) : \
+ log_internal(level, error, __FILE__, __LINE__, __func__, ##__VA_ARGS__); \
+ }) \
+
+#define log_link_debug(link, ...) log_link_full(link, LOG_DEBUG, 0, ##__VA_ARGS__)
+#define log_link_info(link, ...) log_link_full(link, LOG_INFO, 0, ##__VA_ARGS__)
+#define log_link_notice(link, ...) log_link_full(link, LOG_NOTICE, 0, ##__VA_ARGS__)
+#define log_link_warning(link, ...) log_link_full(link, LOG_WARNING, 0, ##__VA_ARGS__)
+#define log_link_error(link, ...) log_link_full(link, LOG_ERR, 0, ##__VA_ARGS__)
+
+#define log_link_debug_errno(link, error, ...) log_link_full(link, LOG_DEBUG, error, ##__VA_ARGS__)
+#define log_link_info_errno(link, error, ...) log_link_full(link, LOG_INFO, error, ##__VA_ARGS__)
+#define log_link_notice_errno(link, error, ...) log_link_full(link, LOG_NOTICE, error, ##__VA_ARGS__)
+#define log_link_warning_errno(link, error, ...) log_link_full(link, LOG_WARNING, error, ##__VA_ARGS__)
+#define log_link_error_errno(link, error, ...) log_link_full(link, LOG_ERR, error, ##__VA_ARGS__)
+
+#define LOG_LINK_MESSAGE(link, fmt, ...) "MESSAGE=%s: " fmt, (link)->ifname, ##__VA_ARGS__
+#define LOG_LINK_INTERFACE(link) "INTERFACE=%s", (link)->ifname
+
+#define ADDRESS_FMT_VAL(address) \
+ be32toh((address).s_addr) >> 24, \
+ (be32toh((address).s_addr) >> 16) & 0xFFu, \
+ (be32toh((address).s_addr) >> 8) & 0xFFu, \
+ be32toh((address).s_addr) & 0xFFu
diff --git a/src/network/networkd-lldp-tx.c b/src/network/networkd-lldp-tx.c
new file mode 100644
index 0000000..8fd6365
--- /dev/null
+++ b/src/network/networkd-lldp-tx.c
@@ -0,0 +1,399 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <endian.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include "alloc-util.h"
+#include "env-file.h"
+#include "fd-util.h"
+#include "hostname-util.h"
+#include "missing_network.h"
+#include "networkd-lldp-tx.h"
+#include "networkd-manager.h"
+#include "parse-util.h"
+#include "random-util.h"
+#include "socket-util.h"
+#include "string-util.h"
+#include "unaligned.h"
+
+/* The LLDP spec calls this "txFastInit", see 9.2.5.19 */
+#define LLDP_TX_FAST_INIT 4U
+
+/* The LLDP spec calls this "msgTxHold", see 9.2.5.6 */
+#define LLDP_TX_HOLD 4U
+
+/* The jitter range to add, see 9.2.2. */
+#define LLDP_JITTER_USEC (400U * USEC_PER_MSEC)
+
+/* The LLDP spec calls this msgTxInterval, but we subtract half the jitter off it. */
+#define LLDP_TX_INTERVAL_USEC (30U * USEC_PER_SEC - LLDP_JITTER_USEC / 2)
+
+/* The LLDP spec calls this msgFastTx, but we subtract half the jitter off it. */
+#define LLDP_FAST_TX_USEC (1U * USEC_PER_SEC - LLDP_JITTER_USEC / 2)
+
+static const struct ether_addr lldp_multicast_addr[_LLDP_EMIT_MAX] = {
+ [LLDP_EMIT_NEAREST_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e }},
+ [LLDP_EMIT_NON_TPMR_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 }},
+ [LLDP_EMIT_CUSTOMER_BRIDGE] = {{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 }},
+};
+
+static int lldp_write_tlv_header(uint8_t **p, uint8_t id, size_t sz) {
+ assert(p);
+
+ if (id > 127)
+ return -EBADMSG;
+ if (sz > 511)
+ return -ENOBUFS;
+
+ (*p)[0] = (id << 1) | !!(sz & 256);
+ (*p)[1] = sz & 255;
+
+ *p = *p + 2;
+ return 0;
+}
+
+static int lldp_make_packet(
+ LLDPEmit mode,
+ const struct ether_addr *hwaddr,
+ const char *machine_id,
+ const char *ifname,
+ uint16_t ttl,
+ const char *port_description,
+ const char *hostname,
+ const char *pretty_hostname,
+ uint16_t system_capabilities,
+ uint16_t enabled_capabilities,
+ void **ret, size_t *sz) {
+
+ size_t machine_id_length, ifname_length, port_description_length = 0, hostname_length = 0, pretty_hostname_length = 0;
+ _cleanup_free_ void *packet = NULL;
+ struct ether_header *h;
+ uint8_t *p;
+ size_t l;
+ int r;
+
+ assert(mode > LLDP_EMIT_NO);
+ assert(mode < _LLDP_EMIT_MAX);
+ assert(hwaddr);
+ assert(machine_id);
+ assert(ifname);
+ assert(ret);
+ assert(sz);
+
+ machine_id_length = strlen(machine_id);
+ ifname_length = strlen(ifname);
+
+ if (port_description)
+ port_description_length = strlen(port_description);
+
+ if (hostname)
+ hostname_length = strlen(hostname);
+
+ if (pretty_hostname)
+ pretty_hostname_length = strlen(pretty_hostname);
+
+ l = sizeof(struct ether_header) +
+ /* Chassis ID */
+ 2 + 1 + machine_id_length +
+ /* Port ID */
+ 2 + 1 + ifname_length +
+ /* TTL */
+ 2 + 2 +
+ /* System Capabilities */
+ 2 + 4 +
+ /* End */
+ 2;
+
+ /* Port Description */
+ if (port_description)
+ l += 2 + port_description_length;
+
+ /* System Name */
+ if (hostname)
+ l += 2 + hostname_length;
+
+ /* System Description */
+ if (pretty_hostname)
+ l += 2 + pretty_hostname_length;
+
+ packet = malloc(l);
+ if (!packet)
+ return -ENOMEM;
+
+ h = (struct ether_header*) packet;
+ h->ether_type = htobe16(ETHERTYPE_LLDP);
+ memcpy(h->ether_dhost, lldp_multicast_addr + mode, ETH_ALEN);
+ memcpy(h->ether_shost, hwaddr, ETH_ALEN);
+
+ p = (uint8_t*) packet + sizeof(struct ether_header);
+
+ r = lldp_write_tlv_header(&p, SD_LLDP_TYPE_CHASSIS_ID, 1 + machine_id_length);
+ if (r < 0)
+ return r;
+ *(p++) = SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED;
+ p = mempcpy(p, machine_id, machine_id_length);
+
+ r = lldp_write_tlv_header(&p, SD_LLDP_TYPE_PORT_ID, 1 + ifname_length);
+ if (r < 0)
+ return r;
+ *(p++) = SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME;
+ p = mempcpy(p, ifname, ifname_length);
+
+ r = lldp_write_tlv_header(&p, SD_LLDP_TYPE_TTL, 2);
+ if (r < 0)
+ return r;
+ unaligned_write_be16(p, ttl);
+ p += 2;
+
+ if (port_description) {
+ r = lldp_write_tlv_header(&p, SD_LLDP_TYPE_PORT_DESCRIPTION, port_description_length);
+ if (r < 0)
+ return r;
+ p = mempcpy(p, port_description, port_description_length);
+ }
+
+ if (hostname) {
+ r = lldp_write_tlv_header(&p, SD_LLDP_TYPE_SYSTEM_NAME, hostname_length);
+ if (r < 0)
+ return r;
+ p = mempcpy(p, hostname, hostname_length);
+ }
+
+ if (pretty_hostname) {
+ r = lldp_write_tlv_header(&p, SD_LLDP_TYPE_SYSTEM_DESCRIPTION, pretty_hostname_length);
+ if (r < 0)
+ return r;
+ p = mempcpy(p, pretty_hostname, pretty_hostname_length);
+ }
+
+ r = lldp_write_tlv_header(&p, SD_LLDP_TYPE_SYSTEM_CAPABILITIES, 4);
+ if (r < 0)
+ return r;
+ unaligned_write_be16(p, system_capabilities);
+ p += 2;
+ unaligned_write_be16(p, enabled_capabilities);
+ p += 2;
+
+ r = lldp_write_tlv_header(&p, SD_LLDP_TYPE_END, 0);
+ if (r < 0)
+ return r;
+
+ assert(p == (uint8_t*) packet + l);
+
+ *ret = TAKE_PTR(packet);
+ *sz = l;
+
+ return 0;
+}
+
+static int lldp_send_packet(
+ int ifindex,
+ const struct ether_addr *address,
+ const void *packet,
+ size_t packet_size) {
+
+ union sockaddr_union sa = {
+ .ll.sll_family = AF_PACKET,
+ .ll.sll_protocol = htobe16(ETHERTYPE_LLDP),
+ .ll.sll_ifindex = ifindex,
+ .ll.sll_halen = ETH_ALEN,
+ };
+
+ _cleanup_close_ int fd = -1;
+ ssize_t l;
+
+ assert(ifindex > 0);
+ assert(address);
+ assert(packet || packet_size <= 0);
+
+ memcpy(sa.ll.sll_addr, address, ETH_ALEN);
+
+ fd = socket(PF_PACKET, SOCK_RAW|SOCK_CLOEXEC, IPPROTO_RAW);
+ if (fd < 0)
+ return -errno;
+
+ l = sendto(fd, packet, packet_size, MSG_NOSIGNAL, &sa.sa, sizeof(sa.ll));
+ if (l < 0)
+ return -errno;
+
+ if ((size_t) l != packet_size)
+ return -EIO;
+
+ return 0;
+}
+
+static int link_send_lldp(Link *link) {
+ char machine_id_string[SD_ID128_STRING_MAX];
+ _cleanup_free_ char *hostname = NULL, *pretty_hostname = NULL;
+ _cleanup_free_ void *packet = NULL;
+ size_t packet_size = 0;
+ sd_id128_t machine_id;
+ uint16_t caps;
+ usec_t ttl;
+ int r;
+
+ assert(link);
+
+ if (!link->network || link->network->lldp_emit == LLDP_EMIT_NO)
+ return 0;
+
+ assert(link->network->lldp_emit < _LLDP_EMIT_MAX);
+
+ r = sd_id128_get_machine(&machine_id);
+ if (r < 0)
+ return r;
+
+ (void) gethostname_strict(&hostname);
+ (void) parse_env_file(NULL, "/etc/machine-info", "PRETTY_HOSTNAME", &pretty_hostname);
+
+ assert_cc(LLDP_TX_INTERVAL_USEC * LLDP_TX_HOLD + 1 <= (UINT16_MAX - 1) * USEC_PER_SEC);
+ ttl = DIV_ROUND_UP(LLDP_TX_INTERVAL_USEC * LLDP_TX_HOLD + 1, USEC_PER_SEC);
+
+ caps = (link->network && link->network->ip_forward != ADDRESS_FAMILY_NO) ?
+ SD_LLDP_SYSTEM_CAPABILITIES_ROUTER :
+ SD_LLDP_SYSTEM_CAPABILITIES_STATION;
+
+ r = lldp_make_packet(link->network->lldp_emit,
+ &link->mac,
+ sd_id128_to_string(machine_id, machine_id_string),
+ link->ifname,
+ (uint16_t) ttl,
+ link->network ? link->network->description : NULL,
+ hostname,
+ pretty_hostname,
+ SD_LLDP_SYSTEM_CAPABILITIES_STATION|SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE|SD_LLDP_SYSTEM_CAPABILITIES_ROUTER,
+ caps,
+ &packet, &packet_size);
+ if (r < 0)
+ return r;
+
+ return lldp_send_packet(link->ifindex, lldp_multicast_addr + link->network->lldp_emit, packet, packet_size);
+}
+
+static int on_lldp_timer(sd_event_source *s, usec_t t, void *userdata) {
+ Link *link = userdata;
+ usec_t current, delay, next;
+ int r;
+
+ assert(s);
+ assert(userdata);
+
+ log_link_debug(link, "Sending LLDP packet...");
+
+ r = link_send_lldp(link);
+ if (r < 0)
+ log_link_debug_errno(link, r, "Failed to send LLDP packet, ignoring: %m");
+
+ if (link->lldp_tx_fast > 0)
+ link->lldp_tx_fast--;
+
+ assert_se(sd_event_now(sd_event_source_get_event(s), clock_boottime_or_monotonic(), &current) >= 0);
+
+ delay = link->lldp_tx_fast > 0 ? LLDP_FAST_TX_USEC : LLDP_TX_INTERVAL_USEC;
+ next = usec_add(usec_add(current, delay), (usec_t) random_u64() % LLDP_JITTER_USEC);
+
+ r = sd_event_source_set_time(s, next);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to restart LLDP timer: %m");
+
+ r = sd_event_source_set_enabled(s, SD_EVENT_ONESHOT);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to enable LLDP timer: %m");
+
+ return 0;
+}
+
+int link_lldp_emit_start(Link *link) {
+ usec_t next;
+ int r;
+
+ assert(link);
+
+ if (!link->network || link->network->lldp_emit == LLDP_EMIT_NO) {
+ link_lldp_emit_stop(link);
+ return 0;
+ }
+
+ /* Starts the LLDP transmission in "fast" mode. If it is already started, turns "fast" mode back on again. */
+
+ link->lldp_tx_fast = LLDP_TX_FAST_INIT;
+
+ next = usec_add(usec_add(now(clock_boottime_or_monotonic()), LLDP_FAST_TX_USEC),
+ (usec_t) random_u64() % LLDP_JITTER_USEC);
+
+ if (link->lldp_emit_event_source) {
+ usec_t old;
+
+ /* Lower the timeout, maybe */
+ r = sd_event_source_get_time(link->lldp_emit_event_source, &old);
+ if (r < 0)
+ return r;
+
+ if (old <= next)
+ return 0;
+
+ return sd_event_source_set_time(link->lldp_emit_event_source, next);
+ } else {
+ r = sd_event_add_time(
+ link->manager->event,
+ &link->lldp_emit_event_source,
+ clock_boottime_or_monotonic(),
+ next,
+ 0,
+ on_lldp_timer,
+ link);
+ if (r < 0)
+ return r;
+
+ (void) sd_event_source_set_description(link->lldp_emit_event_source, "lldp-tx");
+ }
+
+ return 0;
+}
+
+void link_lldp_emit_stop(Link *link) {
+ assert(link);
+
+ link->lldp_emit_event_source = sd_event_source_unref(link->lldp_emit_event_source);
+}
+
+int config_parse_lldp_emit(
+ 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) {
+
+ LLDPEmit *emit = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue))
+ *emit = LLDP_EMIT_NO;
+ else if (streq(rvalue, "nearest-bridge"))
+ *emit = LLDP_EMIT_NEAREST_BRIDGE;
+ else if (streq(rvalue, "non-tpmr-bridge"))
+ *emit = LLDP_EMIT_NON_TPMR_BRIDGE;
+ else if (streq(rvalue, "customer-bridge"))
+ *emit = LLDP_EMIT_CUSTOMER_BRIDGE;
+ else {
+ r = parse_boolean(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse LLDP emission setting, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ *emit = r ? LLDP_EMIT_NEAREST_BRIDGE : LLDP_EMIT_NO;
+ }
+
+ return 0;
+}
diff --git a/src/network/networkd-lldp-tx.h b/src/network/networkd-lldp-tx.h
new file mode 100644
index 0000000..6842804
--- /dev/null
+++ b/src/network/networkd-lldp-tx.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "conf-parser.h"
+#include "networkd-link.h"
+
+typedef enum LLDPEmit {
+ LLDP_EMIT_NO,
+ LLDP_EMIT_NEAREST_BRIDGE,
+ LLDP_EMIT_NON_TPMR_BRIDGE,
+ LLDP_EMIT_CUSTOMER_BRIDGE,
+ _LLDP_EMIT_MAX,
+} LLDPEmit;
+
+int link_lldp_emit_start(Link *link);
+void link_lldp_emit_stop(Link *link);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_lldp_emit);
diff --git a/src/network/networkd-manager-bus.c b/src/network/networkd-manager-bus.c
new file mode 100644
index 0000000..8c52783
--- /dev/null
+++ b/src/network/networkd-manager-bus.c
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "alloc-util.h"
+#include "bus-util.h"
+#include "networkd-manager.h"
+#include "strv.h"
+
+static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_operational_state, link_operstate, LinkOperationalState);
+
+const sd_bus_vtable manager_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+
+ SD_BUS_PROPERTY("OperationalState", "s", property_get_operational_state, offsetof(Manager, operational_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+
+ SD_BUS_VTABLE_END
+};
+
+int manager_send_changed(Manager *manager, const char *property, ...) {
+ char **l;
+
+ assert(manager);
+
+ if (!manager->bus)
+ return 0; /* replace by assert when we have kdbus */
+
+ l = strv_from_stdarg_alloca(property);
+
+ return sd_bus_emit_properties_changed_strv(
+ manager->bus,
+ "/org/freedesktop/network1",
+ "org.freedesktop.network1.Manager",
+ l);
+}
diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c
new file mode 100644
index 0000000..c8d369e
--- /dev/null
+++ b/src/network/networkd-manager.c
@@ -0,0 +1,1860 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <sys/socket.h>
+#include <linux/if.h>
+#include <linux/fib_rules.h>
+#include <stdio_ext.h>
+
+#include "sd-daemon.h"
+#include "sd-netlink.h"
+
+#include "alloc-util.h"
+#include "bus-util.h"
+#include "conf-parser.h"
+#include "def.h"
+#include "device-util.h"
+#include "dns-domain.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "local-addresses.h"
+#include "netlink-util.h"
+#include "networkd-manager.h"
+#include "ordered-set.h"
+#include "path-util.h"
+#include "set.h"
+#include "strv.h"
+#include "tmpfile-util.h"
+#include "virt.h"
+
+/* use 8 MB for receive socket kernel queue. */
+#define RCVBUF_SIZE (8*1024*1024)
+
+const char* const network_dirs[] = {
+ "/etc/systemd/network",
+ "/run/systemd/network",
+ "/usr/lib/systemd/network",
+#if HAVE_SPLIT_USR
+ "/lib/systemd/network",
+#endif
+ NULL};
+
+static int setup_default_address_pool(Manager *m) {
+ AddressPool *p;
+ int r;
+
+ assert(m);
+
+ /* Add in the well-known private address ranges. */
+
+ r = address_pool_new_from_string(m, &p, AF_INET6, "fc00::", 7);
+ if (r < 0)
+ return r;
+
+ r = address_pool_new_from_string(m, &p, AF_INET, "192.168.0.0", 16);
+ if (r < 0)
+ return r;
+
+ r = address_pool_new_from_string(m, &p, AF_INET, "172.16.0.0", 12);
+ if (r < 0)
+ return r;
+
+ r = address_pool_new_from_string(m, &p, AF_INET, "10.0.0.0", 8);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int manager_reset_all(Manager *m) {
+ Link *link;
+ Iterator i;
+ int r;
+
+ assert(m);
+
+ HASHMAP_FOREACH(link, m->links, i) {
+ r = link_carrier_reset(link);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Could not reset carrier: %m");
+ }
+
+ return 0;
+}
+
+static int match_prepare_for_sleep(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) {
+ Manager *m = userdata;
+ int b, r;
+
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "b", &b);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse PrepareForSleep signal: %m");
+ return 0;
+ }
+
+ if (b)
+ return 0;
+
+ log_debug("Coming back from suspend, resetting all connections...");
+
+ (void) manager_reset_all(m);
+
+ return 0;
+}
+
+static int on_connected(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) {
+ Manager *m = userdata;
+
+ assert(message);
+ assert(m);
+
+ /* Did we get a timezone or transient hostname from DHCP while D-Bus wasn't up yet? */
+ if (m->dynamic_hostname)
+ (void) manager_set_hostname(m, m->dynamic_hostname);
+ if (m->dynamic_timezone)
+ (void) manager_set_timezone(m, m->dynamic_timezone);
+ if (m->links_requesting_uuid)
+ (void) manager_request_product_uuid(m, NULL);
+
+ return 0;
+}
+
+int manager_connect_bus(Manager *m) {
+ int r;
+
+ assert(m);
+
+ if (m->bus)
+ return 0;
+
+ r = bus_open_system_watch_bind_with_description(&m->bus, "bus-api-network");
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to bus: %m");
+
+ r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/network1", "org.freedesktop.network1.Manager", manager_vtable, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add manager object vtable: %m");
+
+ r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/network1/link", "org.freedesktop.network1.Link", link_vtable, link_object_find, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add link object vtable: %m");
+
+ r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/network1/link", link_node_enumerator, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add link enumerator: %m");
+
+ r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/network1/network", "org.freedesktop.network1.Network", network_vtable, network_object_find, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add network object vtable: %m");
+
+ r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/network1/network", network_node_enumerator, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add network enumerator: %m");
+
+ r = sd_bus_request_name_async(m->bus, NULL, "org.freedesktop.network1", 0, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to request name: %m");
+
+ r = sd_bus_attach_event(m->bus, m->event, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to attach bus to event loop: %m");
+
+ r = sd_bus_match_signal_async(
+ m->bus,
+ NULL,
+ "org.freedesktop.DBus.Local",
+ NULL,
+ "org.freedesktop.DBus.Local",
+ "Connected",
+ on_connected, NULL, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to request match on Connected signal: %m");
+
+ r = sd_bus_match_signal_async(
+ m->bus,
+ NULL,
+ "org.freedesktop.login1",
+ "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager",
+ "PrepareForSleep",
+ match_prepare_for_sleep, NULL, m);
+ if (r < 0)
+ log_warning_errno(r, "Failed to request match for PrepareForSleep, ignoring: %m");
+
+ return 0;
+}
+
+static int manager_udev_process_link(sd_device_monitor *monitor, sd_device *device, void *userdata) {
+ Manager *m = userdata;
+ const char *action;
+ Link *link = NULL;
+ int r, ifindex;
+
+ assert(m);
+ assert(device);
+
+ r = sd_device_get_property_value(device, "ACTION", &action);
+ if (r < 0) {
+ log_device_debug_errno(device, r, "Failed to get 'ACTION' property, ignoring device: %m");
+ return 0;
+ }
+
+ if (!STR_IN_SET(action, "add", "change")) {
+ log_device_debug(device, "Ignoring udev %s event for device.", action);
+ return 0;
+ }
+
+ r = sd_device_get_ifindex(device, &ifindex);
+ if (r < 0) {
+ log_device_debug_errno(device, r, "Ignoring udev ADD event for device without ifindex or with invalid ifindex: %m");
+ return 0;
+ }
+
+ r = link_get(m, ifindex, &link);
+ if (r < 0) {
+ if (r != -ENODEV)
+ log_debug_errno(r, "Failed to get link from ifindex %i, ignoring: %m", ifindex);
+ return 0;
+ }
+
+ (void) link_initialized(link, device);
+
+ return 0;
+}
+
+static int manager_connect_udev(Manager *m) {
+ int r;
+
+ /* udev does not initialize devices inside containers,
+ * so we rely on them being already initialized before
+ * entering the container */
+ if (detect_container() > 0)
+ return 0;
+
+ r = sd_device_monitor_new(&m->device_monitor);
+ if (r < 0)
+ return log_error_errno(r, "Failed to initialize device monitor: %m");
+
+ r = sd_device_monitor_filter_add_match_subsystem_devtype(m->device_monitor, "net", NULL);
+ if (r < 0)
+ return log_error_errno(r, "Could not add device monitor filter: %m");
+
+ r = sd_device_monitor_attach_event(m->device_monitor, m->event);
+ if (r < 0)
+ return log_error_errno(r, "Failed to attach event to device monitor: %m");
+
+ r = sd_device_monitor_start(m->device_monitor, manager_udev_process_link, m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to start device monitor: %m");
+
+ return 0;
+}
+
+int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, void *userdata) {
+ Manager *m = userdata;
+ Link *link = NULL;
+ uint16_t type;
+ uint32_t ifindex, priority = 0;
+ unsigned char protocol, scope, tos, table, rt_type;
+ int family;
+ unsigned char dst_prefixlen, src_prefixlen;
+ union in_addr_union dst = {}, gw = {}, src = {}, prefsrc = {};
+ Route *route = NULL;
+ int r;
+
+ assert(rtnl);
+ assert(message);
+ assert(m);
+
+ if (sd_netlink_message_is_error(message)) {
+ r = sd_netlink_message_get_errno(message);
+ if (r < 0)
+ log_warning_errno(r, "rtnl: failed to receive route, ignoring: %m");
+
+ return 0;
+ }
+
+ r = sd_netlink_message_get_type(message, &type);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: could not get message type, ignoring: %m");
+ return 0;
+ } else if (!IN_SET(type, RTM_NEWROUTE, RTM_DELROUTE)) {
+ log_warning("rtnl: received unexpected message type when processing route, ignoring");
+ return 0;
+ }
+
+ r = sd_netlink_message_read_u32(message, RTA_OIF, &ifindex);
+ if (r == -ENODATA) {
+ log_debug("rtnl: received route without ifindex, ignoring");
+ return 0;
+ } else if (r < 0) {
+ log_warning_errno(r, "rtnl: could not get ifindex from route, ignoring: %m");
+ return 0;
+ } else if (ifindex <= 0) {
+ log_warning("rtnl: received route message with invalid ifindex, ignoring: %d", ifindex);
+ return 0;
+ } else {
+ r = link_get(m, ifindex, &link);
+ if (r < 0 || !link) {
+ /* 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 for nonexistent link (%d), ignoring", ifindex);
+ return 0;
+ }
+ }
+
+ r = sd_rtnl_message_route_get_family(message, &family);
+ if (r < 0 || !IN_SET(family, AF_INET, AF_INET6)) {
+ log_link_warning(link, "rtnl: received address with invalid family, ignoring");
+ return 0;
+ }
+
+ r = sd_rtnl_message_route_get_protocol(message, &protocol);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: could not get route protocol: %m");
+ return 0;
+ }
+
+ switch (family) {
+ case AF_INET:
+ r = sd_netlink_message_read_in_addr(message, RTA_DST, &dst.in);
+ if (r < 0 && r != -ENODATA) {
+ log_link_warning_errno(link, r, "rtnl: received route without valid destination, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_netlink_message_read_in_addr(message, RTA_GATEWAY, &gw.in);
+ if (r < 0 && r != -ENODATA) {
+ log_link_warning_errno(link, r, "rtnl: received route with invalid gateway, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_netlink_message_read_in_addr(message, RTA_SRC, &src.in);
+ if (r < 0 && r != -ENODATA) {
+ log_link_warning_errno(link, r, "rtnl: received route with invalid source, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_netlink_message_read_in_addr(message, RTA_PREFSRC, &prefsrc.in);
+ if (r < 0 && r != -ENODATA) {
+ log_link_warning_errno(link, r, "rtnl: received route with invalid preferred source, ignoring: %m");
+ return 0;
+ }
+
+ break;
+
+ case AF_INET6:
+ r = sd_netlink_message_read_in6_addr(message, RTA_DST, &dst.in6);
+ if (r < 0 && r != -ENODATA) {
+ log_link_warning_errno(link, r, "rtnl: received route without valid destination, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_netlink_message_read_in6_addr(message, RTA_GATEWAY, &gw.in6);
+ if (r < 0 && r != -ENODATA) {
+ log_link_warning_errno(link, r, "rtnl: received route with invalid gateway, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_netlink_message_read_in6_addr(message, RTA_SRC, &src.in6);
+ if (r < 0 && r != -ENODATA) {
+ log_link_warning_errno(link, r, "rtnl: received route with invalid source, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_netlink_message_read_in6_addr(message, RTA_PREFSRC, &prefsrc.in6);
+ if (r < 0 && r != -ENODATA) {
+ log_link_warning_errno(link, r, "rtnl: received route with invalid preferred source, ignoring: %m");
+ return 0;
+ }
+
+ break;
+
+ default:
+ assert_not_reached("Received unsupported address family");
+ return 0;
+ }
+
+ r = sd_rtnl_message_route_get_dst_prefixlen(message, &dst_prefixlen);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: received route with invalid destination prefixlen, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_rtnl_message_route_get_src_prefixlen(message, &src_prefixlen);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: received route with invalid source prefixlen, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_rtnl_message_route_get_scope(message, &scope);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: received route with invalid scope, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_rtnl_message_route_get_tos(message, &tos);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: received route with invalid tos, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_rtnl_message_route_get_type(message, &rt_type);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: received route with invalid type, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_rtnl_message_route_get_table(message, &table);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: received route with invalid table, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_netlink_message_read_u32(message, RTA_PRIORITY, &priority);
+ if (r < 0 && r != -ENODATA) {
+ log_link_warning_errno(link, r, "rtnl: received route with invalid priority, ignoring: %m");
+ return 0;
+ }
+
+ (void) route_get(link, family, &dst, dst_prefixlen, tos, priority, table, &route);
+
+ switch (type) {
+ case RTM_NEWROUTE:
+ if (!route) {
+ /* A route appeared that we did not request */
+ r = route_add_foreign(link, family, &dst, dst_prefixlen, tos, priority, table, &route);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to add route, ignoring: %m");
+ return 0;
+ }
+ }
+
+ route_update(route, &src, src_prefixlen, &gw, &prefsrc, scope, protocol, rt_type);
+
+ break;
+
+ case RTM_DELROUTE:
+ route_free(route);
+ break;
+
+ default:
+ assert_not_reached("Received invalid RTNL message type");
+ }
+
+ return 1;
+}
+
+int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, void *userdata) {
+ Manager *m = userdata;
+ Link *link = NULL;
+ uint16_t type;
+ unsigned char flags;
+ int family;
+ unsigned char prefixlen;
+ unsigned char scope;
+ union in_addr_union in_addr;
+ struct ifa_cacheinfo cinfo;
+ Address *address = NULL;
+ char buf[INET6_ADDRSTRLEN], valid_buf[FORMAT_TIMESPAN_MAX];
+ const char *valid_str = NULL;
+ int r, ifindex;
+
+ assert(rtnl);
+ assert(message);
+ assert(m);
+
+ if (sd_netlink_message_is_error(message)) {
+ r = sd_netlink_message_get_errno(message);
+ if (r < 0)
+ log_warning_errno(r, "rtnl: failed to receive address, ignoring: %m");
+
+ return 0;
+ }
+
+ r = sd_netlink_message_get_type(message, &type);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: could not get message type, ignoring: %m");
+ return 0;
+ } else if (!IN_SET(type, RTM_NEWADDR, RTM_DELADDR)) {
+ log_warning("rtnl: received unexpected message type when processing address, ignoring");
+ return 0;
+ }
+
+ r = sd_rtnl_message_addr_get_ifindex(message, &ifindex);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: could not get ifindex from address, ignoring: %m");
+ return 0;
+ } else if (ifindex <= 0) {
+ log_warning("rtnl: received address message with invalid ifindex, ignoring: %d", ifindex);
+ return 0;
+ } else {
+ r = link_get(m, ifindex, &link);
+ if (r < 0 || !link) {
+ /* when enumerating we might be out of sync, but we will
+ * get the address again, so just ignore it */
+ if (!m->enumerating)
+ log_warning("rtnl: received address for nonexistent link (%d), ignoring", ifindex);
+ return 0;
+ }
+ }
+
+ r = sd_rtnl_message_addr_get_family(message, &family);
+ if (r < 0 || !IN_SET(family, AF_INET, AF_INET6)) {
+ log_link_warning(link, "rtnl: received address with invalid family, ignoring");
+ return 0;
+ }
+
+ r = sd_rtnl_message_addr_get_prefixlen(message, &prefixlen);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: received address with invalid prefixlen, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_rtnl_message_addr_get_scope(message, &scope);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: received address with invalid scope, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_rtnl_message_addr_get_flags(message, &flags);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: received address with invalid flags, ignoring: %m");
+ return 0;
+ }
+
+ switch (family) {
+ case AF_INET:
+ r = sd_netlink_message_read_in_addr(message, IFA_LOCAL, &in_addr.in);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: received address without valid address, ignoring: %m");
+ return 0;
+ }
+
+ break;
+
+ case AF_INET6:
+ r = sd_netlink_message_read_in6_addr(message, IFA_ADDRESS, &in_addr.in6);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "rtnl: received address without valid address, ignoring: %m");
+ return 0;
+ }
+
+ break;
+
+ default:
+ assert_not_reached("Received unsupported address family");
+ }
+
+ if (!inet_ntop(family, &in_addr, buf, INET6_ADDRSTRLEN)) {
+ log_link_warning(link, "Could not print address, ignoring");
+ return 0;
+ }
+
+ r = sd_netlink_message_read_cache_info(message, IFA_CACHEINFO, &cinfo);
+ if (r < 0 && r != -ENODATA) {
+ log_link_warning_errno(link, r, "rtnl: cannot get IFA_CACHEINFO attribute, ignoring: %m");
+ return 0;
+ } else if (r >= 0) {
+ if (cinfo.ifa_valid != CACHE_INFO_INFINITY_LIFE_TIME)
+ valid_str = format_timespan(valid_buf, FORMAT_TIMESPAN_MAX,
+ cinfo.ifa_valid * USEC_PER_SEC,
+ USEC_PER_SEC);
+ }
+
+ (void) address_get(link, family, &in_addr, prefixlen, &address);
+
+ switch (type) {
+ case RTM_NEWADDR:
+ if (address)
+ log_link_debug(link, "Updating address: %s/%u (valid %s%s)", buf, prefixlen,
+ valid_str ? "for " : "forever", strempty(valid_str));
+ else {
+ /* An address appeared that we did not request */
+ r = address_add_foreign(link, family, &in_addr, prefixlen, &address);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to add address %s/%u, ignoring: %m", buf, prefixlen);
+ return 0;
+ } else
+ log_link_debug(link, "Adding address: %s/%u (valid %s%s)", buf, prefixlen,
+ valid_str ? "for " : "forever", strempty(valid_str));
+ }
+
+ r = address_update(address, flags, scope, &cinfo);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to update address %s/%u, ignoring: %m", buf, prefixlen);
+ return 0;
+ }
+
+ break;
+
+ case RTM_DELADDR:
+
+ if (address) {
+ log_link_debug(link, "Removing address: %s/%u (valid %s%s)", buf, prefixlen,
+ valid_str ? "for " : "forever", strempty(valid_str));
+ (void) address_drop(address);
+ } else
+ log_link_warning(link, "Removing non-existent address: %s/%u (valid %s%s), ignoring", buf, prefixlen,
+ valid_str ? "for " : "forever", strempty(valid_str));
+
+ break;
+ default:
+ assert_not_reached("Received invalid RTNL message type");
+ }
+
+ return 1;
+}
+
+static int manager_rtnl_process_link(sd_netlink *rtnl, sd_netlink_message *message, void *userdata) {
+ Manager *m = userdata;
+ Link *link = NULL;
+ NetDev *netdev = NULL;
+ uint16_t type;
+ const char *name;
+ int r, ifindex;
+
+ assert(rtnl);
+ assert(message);
+ assert(m);
+
+ if (sd_netlink_message_is_error(message)) {
+ r = sd_netlink_message_get_errno(message);
+ if (r < 0)
+ log_warning_errno(r, "rtnl: Could not receive link, ignoring: %m");
+
+ return 0;
+ }
+
+ r = sd_netlink_message_get_type(message, &type);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: Could not get message type, ignoring: %m");
+ return 0;
+ } else if (!IN_SET(type, RTM_NEWLINK, RTM_DELLINK)) {
+ log_warning("rtnl: Received unexpected message type when processing link, ignoring");
+ return 0;
+ }
+
+ r = sd_rtnl_message_link_get_ifindex(message, &ifindex);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: Could not get ifindex from link, ignoring: %m");
+ return 0;
+ } else if (ifindex <= 0) {
+ log_warning("rtnl: received link message with invalid ifindex %d, ignoring", ifindex);
+ return 0;
+ }
+
+ r = sd_netlink_message_read_string(message, IFLA_IFNAME, &name);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: Received link message without ifname, ignoring: %m");
+ return 0;
+ }
+
+ (void) link_get(m, ifindex, &link);
+ (void) netdev_get(m, name, &netdev);
+
+ switch (type) {
+ case RTM_NEWLINK:
+ if (!link) {
+ /* link is new, so add it */
+ r = link_add(m, message, &link);
+ if (r < 0) {
+ log_warning_errno(r, "Could not add new link, ignoring: %m");
+ return 0;
+ }
+ }
+
+ if (netdev) {
+ /* netdev exists, so make sure the ifindex matches */
+ r = netdev_set_ifindex(netdev, message);
+ if (r < 0) {
+ log_warning_errno(r, "Could not set ifindex on netdev, ignoring: %m");
+ return 0;
+ }
+ }
+
+ r = link_update(link, message);
+ if (r < 0) {
+ log_warning_errno(r, "Could not update link, ignoring: %m");
+ return 0;
+ }
+
+ break;
+
+ case RTM_DELLINK:
+ link_drop(link);
+ netdev_drop(netdev);
+
+ break;
+
+ default:
+ assert_not_reached("Received invalid RTNL message type.");
+ }
+
+ return 1;
+}
+
+int manager_rtnl_process_rule(sd_netlink *rtnl, sd_netlink_message *message, void *userdata) {
+ uint8_t tos = 0, to_prefixlen = 0, from_prefixlen = 0, protocol = 0;
+ struct fib_rule_port_range sport = {}, dport = {};
+ union in_addr_union to = {}, from = {};
+ RoutingPolicyRule *rule = NULL;
+ uint32_t fwmark = 0, table = 0;
+ const char *iif = NULL, *oif = NULL;
+ Manager *m = userdata;
+ uint16_t type;
+ int family;
+ int r;
+
+ assert(rtnl);
+ assert(message);
+ assert(m);
+
+ if (sd_netlink_message_is_error(message)) {
+ r = sd_netlink_message_get_errno(message);
+ if (r < 0)
+ log_warning_errno(r, "rtnl: failed to receive rule, ignoring: %m");
+
+ return 0;
+ }
+
+ r = sd_netlink_message_get_type(message, &type);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: could not get message type, ignoring: %m");
+ return 0;
+ } else if (!IN_SET(type, RTM_NEWRULE, RTM_DELRULE)) {
+ log_warning("rtnl: received unexpected message type '%u' when processing rule, ignoring", type);
+ return 0;
+ }
+
+ r = sd_rtnl_message_get_family(message, &family);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: could not get rule family, ignoring: %m");
+ return 0;
+ } else if (!IN_SET(family, AF_INET, AF_INET6)) {
+ log_debug("rtnl: received address with invalid family %u, ignoring", family);
+ return 0;
+ }
+
+ switch (family) {
+ case AF_INET:
+ r = sd_netlink_message_read_in_addr(message, FRA_SRC, &from.in);
+ if (r < 0 && r != -ENODATA) {
+ log_warning_errno(r, "rtnl: could not get FRA_SRC attribute, ignoring: %m");
+ return 0;
+ } else if (r >= 0) {
+ r = sd_rtnl_message_routing_policy_rule_get_rtm_src_prefixlen(message, &from_prefixlen);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: failed to retrieve rule from prefix length, ignoring: %m");
+ return 0;
+ }
+ }
+
+ r = sd_netlink_message_read_in_addr(message, FRA_DST, &to.in);
+ if (r < 0 && r != -ENODATA) {
+ log_warning_errno(r, "rtnl: could not get FRA_DST attribute, ignoring: %m");
+ return 0;
+ } else if (r >= 0) {
+ r = sd_rtnl_message_routing_policy_rule_get_rtm_dst_prefixlen(message, &to_prefixlen);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: failed to retrieve rule to prefix length, ignoring: %m");
+ return 0;
+ }
+ }
+
+ break;
+
+ case AF_INET6:
+ r = sd_netlink_message_read_in6_addr(message, FRA_SRC, &from.in6);
+ if (r < 0 && r != -ENODATA) {
+ log_warning_errno(r, "rtnl: could not get FRA_SRC attribute, ignoring: %m");
+ return 0;
+ } else if (r >= 0) {
+ r = sd_rtnl_message_routing_policy_rule_get_rtm_src_prefixlen(message, &from_prefixlen);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: failed to retrieve rule from prefix length, ignoring: %m");
+ return 0;
+ }
+ }
+
+ r = sd_netlink_message_read_in6_addr(message, FRA_DST, &to.in6);
+ if (r < 0 && r != -ENODATA) {
+ log_warning_errno(r, "rtnl: could not get FRA_DST attribute, ignoring: %m");
+ return 0;
+ } else if (r >= 0) {
+ r = sd_rtnl_message_routing_policy_rule_get_rtm_dst_prefixlen(message, &to_prefixlen);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: failed to retrieve rule to prefix length, ignoring: %m");
+ return 0;
+ }
+ }
+
+ break;
+
+ default:
+ assert_not_reached("Received unsupported address family");
+ }
+
+ if (from_prefixlen == 0 && to_prefixlen == 0)
+ return 0;
+
+ r = sd_netlink_message_read_u32(message, FRA_FWMARK, &fwmark);
+ if (r < 0 && r != -ENODATA) {
+ log_warning_errno(r, "rtnl: could not get FRA_FWMARK attribute, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_netlink_message_read_u32(message, FRA_TABLE, &table);
+ if (r < 0 && r != -ENODATA) {
+ log_warning_errno(r, "rtnl: could not get FRA_TABLE attribute, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_rtnl_message_routing_policy_rule_get_tos(message, &tos);
+ if (r < 0 && r != -ENODATA) {
+ log_warning_errno(r, "rtnl: could not get ip rule TOS, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_netlink_message_read_string(message, FRA_IIFNAME, &iif);
+ if (r < 0 && r != -ENODATA) {
+ log_warning_errno(r, "rtnl: could not get FRA_IIFNAME attribute, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_netlink_message_read_string(message, FRA_OIFNAME, &oif);
+ if (r < 0 && r != -ENODATA) {
+ log_warning_errno(r, "rtnl: could not get FRA_OIFNAME attribute, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_netlink_message_read_u8(message, FRA_IP_PROTO, &protocol);
+ if (r < 0 && r != -ENODATA) {
+ log_warning_errno(r, "rtnl: could not get FRA_IP_PROTO attribute, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_netlink_message_read(message, FRA_SPORT_RANGE, sizeof(sport), (void *) &sport);
+ if (r < 0 && r != -ENODATA) {
+ log_warning_errno(r, "rtnl: could not get FRA_SPORT_RANGE attribute, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_netlink_message_read(message, FRA_DPORT_RANGE, sizeof(dport), (void *) &dport);
+ if (r < 0 && r != -ENODATA) {
+ log_warning_errno(r, "rtnl: could not get FRA_DPORT_RANGE attribute, ignoring: %m");
+ return 0;
+ }
+
+ (void) routing_policy_rule_get(m, family, &from, from_prefixlen, &to, to_prefixlen, tos, fwmark, table, iif, oif, protocol, &sport, &dport, &rule);
+
+ switch (type) {
+ case RTM_NEWRULE:
+ if (!rule) {
+ r = routing_policy_rule_add_foreign(m, family, &from, from_prefixlen, &to, to_prefixlen, tos, fwmark, table, iif, oif, protocol, &sport, &dport, &rule);
+ if (r < 0) {
+ log_warning_errno(r, "Could not add rule, ignoring: %m");
+ return 0;
+ }
+ }
+ break;
+ case RTM_DELRULE:
+ routing_policy_rule_free(rule);
+
+ break;
+
+ default:
+ assert_not_reached("Received invalid RTNL message type");
+ }
+
+ return 1;
+}
+
+static int systemd_netlink_fd(void) {
+ int n, fd, rtnl_fd = -EINVAL;
+
+ n = sd_listen_fds(true);
+ if (n <= 0)
+ return -EINVAL;
+
+ for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) {
+ if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1) > 0) {
+ if (rtnl_fd >= 0)
+ return -EINVAL;
+
+ rtnl_fd = fd;
+ }
+ }
+
+ return rtnl_fd;
+}
+
+static int manager_connect_genl(Manager *m) {
+ int r;
+
+ assert(m);
+
+ r = sd_genl_socket_open(&m->genl);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_inc_rcvbuf(m->genl, RCVBUF_SIZE);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_attach_event(m->genl, m->event, 0);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int manager_connect_rtnl(Manager *m) {
+ int fd, r;
+
+ assert(m);
+
+ fd = systemd_netlink_fd();
+ if (fd < 0)
+ r = sd_netlink_open(&m->rtnl);
+ else
+ r = sd_netlink_open_fd(&m->rtnl, fd);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_inc_rcvbuf(m->rtnl, RCVBUF_SIZE);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_attach_event(m->rtnl, m->event, 0);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_add_match(m->rtnl, NULL, RTM_NEWLINK, &manager_rtnl_process_link, NULL, m, "network-rtnl_process_link");
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_add_match(m->rtnl, NULL, RTM_DELLINK, &manager_rtnl_process_link, NULL, m, "network-rtnl_process_link");
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_add_match(m->rtnl, NULL, RTM_NEWADDR, &manager_rtnl_process_address, NULL, m, "network-rtnl_process_address");
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_add_match(m->rtnl, NULL, RTM_DELADDR, &manager_rtnl_process_address, NULL, m, "network-rtnl_process_address");
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_add_match(m->rtnl, NULL, RTM_NEWROUTE, &manager_rtnl_process_route, NULL, m, "network-rtnl_process_route");
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_add_match(m->rtnl, NULL, RTM_DELROUTE, &manager_rtnl_process_route, NULL, m, "network-rtnl_process_route");
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_add_match(m->rtnl, NULL, RTM_NEWRULE, &manager_rtnl_process_rule, NULL, m, "network-rtnl_process_rule");
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_add_match(m->rtnl, NULL, RTM_DELRULE, &manager_rtnl_process_rule, NULL, m, "network-rtnl_process_rule");
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int ordered_set_put_in_addr_data(OrderedSet *s, const struct in_addr_data *address) {
+ char *p;
+ int r;
+
+ assert(s);
+ assert(address);
+
+ r = in_addr_to_string(address->family, &address->address, &p);
+ if (r < 0)
+ return r;
+
+ r = ordered_set_consume(s, p);
+ if (r == -EEXIST)
+ return 0;
+
+ return r;
+}
+
+static int ordered_set_put_in_addr_datav(OrderedSet *s, const struct in_addr_data *addresses, unsigned n) {
+ int r, c = 0;
+ unsigned i;
+
+ assert(s);
+ assert(addresses || n == 0);
+
+ for (i = 0; i < n; i++) {
+ r = ordered_set_put_in_addr_data(s, addresses+i);
+ if (r < 0)
+ return r;
+
+ c += r;
+ }
+
+ return c;
+}
+
+static int ordered_set_put_in4_addr(OrderedSet *s, const struct in_addr *address) {
+ char *p;
+ int r;
+
+ assert(s);
+ assert(address);
+
+ r = in_addr_to_string(AF_INET, (const union in_addr_union*) address, &p);
+ if (r < 0)
+ return r;
+
+ r = ordered_set_consume(s, p);
+ if (r == -EEXIST)
+ return 0;
+
+ return r;
+}
+
+static int ordered_set_put_in4_addrv(OrderedSet *s, const struct in_addr *addresses, unsigned n) {
+ int r, c = 0;
+ unsigned i;
+
+ assert(s);
+ assert(n == 0 || addresses);
+
+ for (i = 0; i < n; i++) {
+ r = ordered_set_put_in4_addr(s, addresses+i);
+ if (r < 0)
+ return r;
+
+ c += r;
+ }
+
+ return c;
+}
+
+static void print_string_set(FILE *f, const char *field, OrderedSet *s) {
+ bool space = false;
+ Iterator i;
+ char *p;
+
+ if (ordered_set_isempty(s))
+ return;
+
+ fputs(field, f);
+
+ ORDERED_SET_FOREACH(p, s, i)
+ fputs_with_space(f, p, NULL, &space);
+
+ fputc('\n', f);
+}
+
+static int manager_save(Manager *m) {
+ _cleanup_ordered_set_free_free_ OrderedSet *dns = NULL, *ntp = NULL, *search_domains = NULL, *route_domains = NULL;
+ Link *link;
+ Iterator i;
+ _cleanup_free_ char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ LinkOperationalState operstate = LINK_OPERSTATE_OFF;
+ const char *operstate_str;
+ int r;
+
+ assert(m);
+ assert(m->state_file);
+
+ /* We add all NTP and DNS server to a set, to filter out duplicates */
+ dns = ordered_set_new(&string_hash_ops);
+ if (!dns)
+ return -ENOMEM;
+
+ ntp = ordered_set_new(&string_hash_ops);
+ if (!ntp)
+ return -ENOMEM;
+
+ search_domains = ordered_set_new(&dns_name_hash_ops);
+ if (!search_domains)
+ return -ENOMEM;
+
+ route_domains = ordered_set_new(&dns_name_hash_ops);
+ if (!route_domains)
+ return -ENOMEM;
+
+ HASHMAP_FOREACH(link, m->links, i) {
+ if (link->flags & IFF_LOOPBACK)
+ continue;
+
+ if (link->operstate > operstate)
+ operstate = link->operstate;
+
+ if (!link->network)
+ continue;
+
+ /* First add the static configured entries */
+ r = ordered_set_put_in_addr_datav(dns, link->network->dns, link->network->n_dns);
+ if (r < 0)
+ return r;
+
+ r = ordered_set_put_strdupv(ntp, link->network->ntp);
+ if (r < 0)
+ return r;
+
+ r = ordered_set_put_strdupv(search_domains, link->network->search_domains);
+ if (r < 0)
+ return r;
+
+ r = ordered_set_put_strdupv(route_domains, link->network->route_domains);
+ if (r < 0)
+ return r;
+
+ if (!link->dhcp_lease)
+ continue;
+
+ /* Secondly, add the entries acquired via DHCP */
+ if (link->network->dhcp_use_dns) {
+ const struct in_addr *addresses;
+
+ r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses);
+ if (r > 0) {
+ r = ordered_set_put_in4_addrv(dns, addresses, r);
+ if (r < 0)
+ return r;
+ } else if (r < 0 && r != -ENODATA)
+ return r;
+ }
+
+ if (link->network->dhcp_use_ntp) {
+ const struct in_addr *addresses;
+
+ r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses);
+ if (r > 0) {
+ r = ordered_set_put_in4_addrv(ntp, addresses, r);
+ if (r < 0)
+ return r;
+ } else if (r < 0 && r != -ENODATA)
+ return r;
+ }
+
+ if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) {
+ const char *domainname;
+ char **domains = NULL;
+
+ OrderedSet *target_domains = (link->network->dhcp_use_domains == DHCP_USE_DOMAINS_YES) ? search_domains : route_domains;
+ r = sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname);
+ if (r >= 0) {
+ r = ordered_set_put_strdup(target_domains, domainname);
+ if (r < 0)
+ return r;
+ } else if (r != -ENODATA)
+ return r;
+
+ r = sd_dhcp_lease_get_search_domains(link->dhcp_lease, &domains);
+ if (r >= 0) {
+ r = ordered_set_put_strdupv(target_domains, domains);
+ if (r < 0)
+ return r;
+ } else if (r != -ENODATA)
+ return r;
+ }
+ }
+
+ operstate_str = link_operstate_to_string(operstate);
+ assert(operstate_str);
+
+ r = fopen_temporary(m->state_file, &f, &temp_path);
+ if (r < 0)
+ return r;
+
+ (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+ (void) fchmod(fileno(f), 0644);
+
+ fprintf(f,
+ "# This is private data. Do not parse.\n"
+ "OPER_STATE=%s\n", operstate_str);
+
+ print_string_set(f, "DNS=", dns);
+ print_string_set(f, "NTP=", ntp);
+ print_string_set(f, "DOMAINS=", search_domains);
+ print_string_set(f, "ROUTE_DOMAINS=", route_domains);
+
+ r = routing_policy_serialize_rules(m->rules, f);
+ if (r < 0)
+ goto fail;
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto fail;
+
+ if (rename(temp_path, m->state_file) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ if (m->operational_state != operstate) {
+ m->operational_state = operstate;
+ r = manager_send_changed(m, "OperationalState", NULL);
+ if (r < 0)
+ log_error_errno(r, "Could not emit changed OperationalState: %m");
+ }
+
+ m->dirty = false;
+
+ return 0;
+
+fail:
+ (void) unlink(m->state_file);
+ (void) unlink(temp_path);
+
+ return log_error_errno(r, "Failed to save network state to %s: %m", m->state_file);
+}
+
+static int manager_dirty_handler(sd_event_source *s, void *userdata) {
+ Manager *m = userdata;
+ Link *link;
+ Iterator i;
+
+ assert(m);
+
+ if (m->dirty)
+ manager_save(m);
+
+ SET_FOREACH(link, m->dirty_links, i)
+ if (link_save(link) >= 0)
+ link_clean(link);
+
+ return 1;
+}
+
+Link *manager_dhcp6_prefix_get(Manager *m, struct in6_addr *addr) {
+ assert_return(m, NULL);
+ assert_return(addr, NULL);
+
+ return hashmap_get(m->dhcp6_prefixes, addr);
+}
+
+static int dhcp6_route_add_handler(sd_netlink *nl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST)
+ log_link_debug_errno(link, r, "Received error adding DHCPv6 Prefix Delegation route: %m");
+
+ return 0;
+}
+
+static void dhcp6_prefixes_hash_func(const struct in6_addr *addr, struct siphash *state) {
+ assert(addr);
+
+ siphash24_compress(addr, sizeof(*addr), state);
+}
+
+static int dhcp6_prefixes_compare_func(const struct in6_addr *a, const struct in6_addr *b) {
+ return memcmp(a, b, sizeof(*a));
+}
+
+DEFINE_PRIVATE_HASH_OPS(dhcp6_prefixes_hash_ops, struct in6_addr, dhcp6_prefixes_hash_func, dhcp6_prefixes_compare_func);
+
+int manager_dhcp6_prefix_add(Manager *m, struct in6_addr *addr, Link *link) {
+ _cleanup_free_ char *buf = NULL;
+ Route *route;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(addr, -EINVAL);
+
+ r = route_add(link, AF_INET6, (union in_addr_union *) addr, 64,
+ 0, 0, 0, &route);
+ if (r < 0)
+ return r;
+
+ r = route_configure(route, link, dhcp6_route_add_handler);
+ if (r < 0)
+ return r;
+
+ (void) in_addr_to_string(AF_INET6, (union in_addr_union *) addr, &buf);
+ log_link_debug(link, "Adding prefix route %s/64", strnull(buf));
+
+ r = hashmap_ensure_allocated(&m->dhcp6_prefixes, &dhcp6_prefixes_hash_ops);
+ if (r < 0)
+ return r;
+
+ return hashmap_put(m->dhcp6_prefixes, addr, link);
+}
+
+static int dhcp6_route_remove_handler(sd_netlink *nl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0)
+ log_link_debug_errno(link, r, "Received error on DHCPv6 Prefix Delegation route removal: %m");
+
+ return 1;
+}
+
+static int manager_dhcp6_prefix_remove(Manager *m, struct in6_addr *addr) {
+ _cleanup_free_ char *buf = NULL;
+ Route *route;
+ Link *l;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(addr, -EINVAL);
+
+ l = hashmap_remove(m->dhcp6_prefixes, addr);
+ if (!l)
+ return -EINVAL;
+
+ (void) sd_radv_remove_prefix(l->radv, addr, 64);
+ r = route_get(l, AF_INET6, (union in_addr_union *) addr, 64,
+ 0, 0, 0, &route);
+ if (r < 0)
+ return r;
+
+ r = route_remove(route, l, dhcp6_route_remove_handler);
+ if (r < 0)
+ return r;
+
+ (void) in_addr_to_string(AF_INET6, (union in_addr_union *) addr, &buf);
+ log_link_debug(l, "Removing prefix route %s/64", strnull(buf));
+
+ return 0;
+}
+
+int manager_dhcp6_prefix_remove_all(Manager *m, Link *link) {
+ struct in6_addr *addr;
+ Iterator i;
+ Link *l;
+
+ assert_return(m, -EINVAL);
+ assert_return(link, -EINVAL);
+
+ HASHMAP_FOREACH_KEY(l, addr, m->dhcp6_prefixes, i) {
+ if (l != link)
+ continue;
+
+ (void) manager_dhcp6_prefix_remove(m, addr);
+ }
+
+ return 0;
+}
+
+int manager_new(Manager **ret) {
+ _cleanup_(manager_freep) Manager *m = NULL;
+ int r;
+
+ m = new0(Manager, 1);
+ if (!m)
+ return -ENOMEM;
+
+ m->state_file = strdup("/run/systemd/netif/state");
+ if (!m->state_file)
+ return -ENOMEM;
+
+ r = sd_event_default(&m->event);
+ if (r < 0)
+ return r;
+
+ (void) sd_event_set_watchdog(m->event, true);
+ (void) sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
+ (void) sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
+
+ r = sd_event_add_post(m->event, NULL, manager_dirty_handler, m);
+ if (r < 0)
+ return r;
+
+ r = manager_connect_rtnl(m);
+ if (r < 0)
+ return r;
+
+ r = manager_connect_genl(m);
+ if (r < 0)
+ return r;
+
+ r = manager_connect_udev(m);
+ if (r < 0)
+ return r;
+
+ LIST_HEAD_INIT(m->networks);
+
+ r = sd_resolve_default(&m->resolve);
+ if (r < 0)
+ return r;
+
+ r = sd_resolve_attach_event(m->resolve, m->event, 0);
+ if (r < 0)
+ return r;
+
+ r = setup_default_address_pool(m);
+ if (r < 0)
+ return r;
+
+ m->duid.type = DUID_TYPE_EN;
+
+ (void) routing_policy_load_rules(m->state_file, &m->rules_saved);
+
+ *ret = TAKE_PTR(m);
+
+ return 0;
+}
+
+void manager_free(Manager *m) {
+ AddressPool *pool;
+ Network *network;
+ Link *link;
+
+ if (!m)
+ return;
+
+ free(m->state_file);
+
+ sd_netlink_unref(m->rtnl);
+ sd_netlink_unref(m->genl);
+ sd_resolve_unref(m->resolve);
+
+ while ((network = m->networks))
+ network_free(network);
+
+ while ((link = hashmap_first(m->dhcp6_prefixes)))
+ manager_dhcp6_prefix_remove_all(m, link);
+ hashmap_free(m->dhcp6_prefixes);
+
+ while ((link = hashmap_steal_first(m->links))) {
+ if (link->dhcp6_client)
+ (void) dhcp6_lease_pd_prefix_lost(link->dhcp6_client, link);
+ link_unref(link);
+ }
+
+ m->dirty_links = set_free_with_destructor(m->dirty_links, link_unref);
+ m->links = hashmap_free(m->links);
+ m->links_requesting_uuid = set_free(m->links_requesting_uuid);
+ set_free(m->duids_requesting_uuid);
+
+ hashmap_free(m->networks_by_name);
+
+ m->netdevs = hashmap_free_with_destructor(m->netdevs, netdev_unref);
+
+ while ((pool = m->address_pools))
+ address_pool_free(pool);
+
+ /* routing_policy_rule_free() access m->rules and m->rules_foreign.
+ * So, it is necessary to set NULL after the sets are freed. */
+ m->rules = set_free_with_destructor(m->rules, routing_policy_rule_free);
+ m->rules_foreign = set_free_with_destructor(m->rules_foreign, routing_policy_rule_free);
+ set_free_with_destructor(m->rules_saved, routing_policy_rule_free);
+
+ sd_event_unref(m->event);
+
+ sd_device_monitor_unref(m->device_monitor);
+
+ sd_bus_flush_close_unref(m->bus);
+
+ free(m->dynamic_timezone);
+ free(m->dynamic_hostname);
+
+ free(m);
+}
+
+int manager_start(Manager *m) {
+ Link *link;
+ Iterator i;
+
+ assert(m);
+
+ /* The dirty handler will deal with future serialization, but the first one
+ must be done explicitly. */
+
+ manager_save(m);
+
+ HASHMAP_FOREACH(link, m->links, i)
+ link_save(link);
+
+ return 0;
+}
+
+int manager_load_config(Manager *m) {
+ int r;
+
+ /* update timestamp */
+ paths_check_timestamp(network_dirs, &m->network_dirs_ts_usec, true);
+
+ r = netdev_load(m);
+ if (r < 0)
+ return r;
+
+ r = network_load(m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+bool manager_should_reload(Manager *m) {
+ return paths_check_timestamp(network_dirs, &m->network_dirs_ts_usec, false);
+}
+
+int manager_rtnl_enumerate_links(Manager *m) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
+ sd_netlink_message *link;
+ int r;
+
+ assert(m);
+ assert(m->rtnl);
+
+ r = sd_rtnl_message_new_link(m->rtnl, &req, RTM_GETLINK, 0);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_request_dump(req, true);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_call(m->rtnl, req, 0, &reply);
+ if (r < 0)
+ return r;
+
+ for (link = reply; link; link = sd_netlink_message_next(link)) {
+ int k;
+
+ m->enumerating = true;
+
+ k = manager_rtnl_process_link(m->rtnl, link, m);
+ if (k < 0)
+ r = k;
+
+ m->enumerating = false;
+ }
+
+ return r;
+}
+
+int manager_rtnl_enumerate_addresses(Manager *m) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
+ sd_netlink_message *addr;
+ int r;
+
+ assert(m);
+ assert(m->rtnl);
+
+ r = sd_rtnl_message_new_addr(m->rtnl, &req, RTM_GETADDR, 0, 0);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_request_dump(req, true);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_call(m->rtnl, req, 0, &reply);
+ if (r < 0)
+ return r;
+
+ for (addr = reply; addr; addr = sd_netlink_message_next(addr)) {
+ int k;
+
+ m->enumerating = true;
+
+ k = manager_rtnl_process_address(m->rtnl, addr, m);
+ if (k < 0)
+ r = k;
+
+ m->enumerating = false;
+ }
+
+ return r;
+}
+
+int manager_rtnl_enumerate_routes(Manager *m) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
+ sd_netlink_message *route;
+ int r;
+
+ assert(m);
+ assert(m->rtnl);
+
+ r = sd_rtnl_message_new_route(m->rtnl, &req, RTM_GETROUTE, 0, 0);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_request_dump(req, true);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_call(m->rtnl, req, 0, &reply);
+ if (r < 0)
+ return r;
+
+ for (route = reply; route; route = sd_netlink_message_next(route)) {
+ int k;
+
+ m->enumerating = true;
+
+ k = manager_rtnl_process_route(m->rtnl, route, m);
+ if (k < 0)
+ r = k;
+
+ m->enumerating = false;
+ }
+
+ return r;
+}
+
+int manager_rtnl_enumerate_rules(Manager *m) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
+ sd_netlink_message *rule;
+ int r;
+
+ assert(m);
+ assert(m->rtnl);
+
+ r = sd_rtnl_message_new_routing_policy_rule(m->rtnl, &req, RTM_GETRULE, 0);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_request_dump(req, true);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_call(m->rtnl, req, 0, &reply);
+ if (r < 0) {
+ if (r == -EOPNOTSUPP) {
+ log_debug("FIB Rules are not supported by the kernel. Ignoring.");
+ return 0;
+ }
+
+ return r;
+ }
+
+ for (rule = reply; rule; rule = sd_netlink_message_next(rule)) {
+ int k;
+
+ m->enumerating = true;
+
+ k = manager_rtnl_process_rule(m->rtnl, rule, m);
+ if (k < 0)
+ r = k;
+
+ m->enumerating = false;
+ }
+
+ return r;
+}
+
+int manager_address_pool_acquire(Manager *m, int family, unsigned prefixlen, union in_addr_union *found) {
+ AddressPool *p;
+ int r;
+
+ assert(m);
+ assert(prefixlen > 0);
+ assert(found);
+
+ LIST_FOREACH(address_pools, p, m->address_pools) {
+ if (p->family != family)
+ continue;
+
+ r = address_pool_acquire(p, prefixlen, found);
+ if (r != 0)
+ return r;
+ }
+
+ return 0;
+}
+
+Link* manager_find_uplink(Manager *m, Link *exclude) {
+ _cleanup_free_ struct local_address *gateways = NULL;
+ int n, i;
+
+ assert(m);
+
+ /* Looks for a suitable "uplink", via black magic: an
+ * interface that is up and where the default route with the
+ * highest priority points to. */
+
+ n = local_gateways(m->rtnl, 0, AF_UNSPEC, &gateways);
+ if (n < 0) {
+ log_warning_errno(n, "Failed to determine list of default gateways: %m");
+ return NULL;
+ }
+
+ for (i = 0; i < n; i++) {
+ Link *link;
+
+ link = hashmap_get(m->links, INT_TO_PTR(gateways[i].ifindex));
+ if (!link) {
+ log_debug("Weird, found a gateway for a link we don't know. Ignoring.");
+ continue;
+ }
+
+ if (link == exclude)
+ continue;
+
+ if (link->operstate < LINK_OPERSTATE_ROUTABLE)
+ continue;
+
+ return link;
+ }
+
+ return NULL;
+}
+
+void manager_dirty(Manager *manager) {
+ assert(manager);
+
+ /* the serialized state in /run is no longer up-to-date */
+ manager->dirty = true;
+}
+
+static int set_hostname_handler(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+ Manager *manager = userdata;
+ const sd_bus_error *e;
+
+ assert(m);
+ assert(manager);
+
+ e = sd_bus_message_get_error(m);
+ if (e)
+ log_warning_errno(sd_bus_error_get_errno(e), "Could not set hostname: %s", e->message);
+
+ return 1;
+}
+
+int manager_set_hostname(Manager *m, const char *hostname) {
+ int r;
+
+ log_debug("Setting transient hostname: '%s'", strna(hostname));
+
+ if (free_and_strdup(&m->dynamic_hostname, hostname) < 0)
+ return log_oom();
+
+ if (!m->bus || sd_bus_is_ready(m->bus) <= 0) {
+ log_debug("Not connected to system bus, setting hostname later.");
+ return 0;
+ }
+
+ r = sd_bus_call_method_async(
+ m->bus,
+ NULL,
+ "org.freedesktop.hostname1",
+ "/org/freedesktop/hostname1",
+ "org.freedesktop.hostname1",
+ "SetHostname",
+ set_hostname_handler,
+ m,
+ "sb",
+ hostname,
+ false);
+
+ if (r < 0)
+ return log_error_errno(r, "Could not set transient hostname: %m");
+
+ return 0;
+}
+
+static int set_timezone_handler(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+ Manager *manager = userdata;
+ const sd_bus_error *e;
+
+ assert(m);
+ assert(manager);
+
+ e = sd_bus_message_get_error(m);
+ if (e)
+ log_warning_errno(sd_bus_error_get_errno(e), "Could not set timezone: %s", e->message);
+
+ return 1;
+}
+
+int manager_set_timezone(Manager *m, const char *tz) {
+ int r;
+
+ assert(m);
+ assert(tz);
+
+ log_debug("Setting system timezone: '%s'", tz);
+ if (free_and_strdup(&m->dynamic_timezone, tz) < 0)
+ return log_oom();
+
+ if (!m->bus || sd_bus_is_ready(m->bus) <= 0) {
+ log_debug("Not connected to system bus, setting timezone later.");
+ return 0;
+ }
+
+ r = sd_bus_call_method_async(
+ m->bus,
+ NULL,
+ "org.freedesktop.timedate1",
+ "/org/freedesktop/timedate1",
+ "org.freedesktop.timedate1",
+ "SetTimezone",
+ set_timezone_handler,
+ m,
+ "sb",
+ tz,
+ false);
+ if (r < 0)
+ return log_error_errno(r, "Could not set timezone: %m");
+
+ return 0;
+}
+
+int manager_request_product_uuid(Manager *m, Link *link) {
+ int r;
+
+ assert(m);
+
+ if (m->has_product_uuid)
+ return 0;
+
+ log_debug("Requesting product UUID");
+
+ if (link) {
+ DUID *duid;
+
+ assert_se(duid = link_get_duid(link));
+
+ r = set_ensure_allocated(&m->links_requesting_uuid, NULL);
+ if (r < 0)
+ return log_oom();
+
+ r = set_ensure_allocated(&m->duids_requesting_uuid, NULL);
+ if (r < 0)
+ return log_oom();
+
+ r = set_put(m->links_requesting_uuid, link);
+ if (r < 0)
+ return log_oom();
+
+ r = set_put(m->duids_requesting_uuid, duid);
+ if (r < 0)
+ return log_oom();
+ }
+
+ if (!m->bus || sd_bus_is_ready(m->bus) <= 0) {
+ log_debug("Not connected to system bus, requesting product UUID later.");
+ return 0;
+ }
+
+ r = sd_bus_call_method_async(
+ m->bus,
+ NULL,
+ "org.freedesktop.hostname1",
+ "/org/freedesktop/hostname1",
+ "org.freedesktop.hostname1",
+ "GetProductUUID",
+ get_product_uuid_handler,
+ m,
+ "b",
+ false);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to get product UUID: %m");
+
+ return 0;
+}
diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h
new file mode 100644
index 0000000..289ca96
--- /dev/null
+++ b/src/network/networkd-manager.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <arpa/inet.h>
+
+#include "sd-bus.h"
+#include "sd-device.h"
+#include "sd-event.h"
+#include "sd-id128.h"
+#include "sd-netlink.h"
+#include "sd-resolve.h"
+
+#include "dhcp-identifier.h"
+#include "hashmap.h"
+#include "list.h"
+
+#include "networkd-address-pool.h"
+#include "networkd-link.h"
+#include "networkd-network.h"
+
+extern const char* const network_dirs[];
+
+struct Manager {
+ sd_netlink *rtnl;
+ /* lazy initialized */
+ sd_netlink *genl;
+ sd_event *event;
+ sd_resolve *resolve;
+ sd_bus *bus;
+ sd_device_monitor *device_monitor;
+
+ bool enumerating:1;
+ bool dirty:1;
+
+ Set *dirty_links;
+
+ char *state_file;
+ LinkOperationalState operational_state;
+
+ Hashmap *links;
+ Hashmap *netdevs;
+ Hashmap *networks_by_name;
+ Hashmap *dhcp6_prefixes;
+ LIST_HEAD(Network, networks);
+ LIST_HEAD(AddressPool, address_pools);
+
+ usec_t network_dirs_ts_usec;
+
+ DUID duid;
+ sd_id128_t product_uuid;
+ bool has_product_uuid;
+ Set *links_requesting_uuid;
+ Set *duids_requesting_uuid;
+
+ char* dynamic_hostname;
+ char* dynamic_timezone;
+
+ Set *rules;
+ Set *rules_foreign;
+ Set *rules_saved;
+};
+
+extern const sd_bus_vtable manager_vtable[];
+
+int manager_new(Manager **ret);
+void manager_free(Manager *m);
+
+int manager_connect_bus(Manager *m);
+int manager_start(Manager *m);
+
+int manager_load_config(Manager *m);
+bool manager_should_reload(Manager *m);
+
+int manager_rtnl_enumerate_links(Manager *m);
+int manager_rtnl_enumerate_addresses(Manager *m);
+int manager_rtnl_enumerate_routes(Manager *m);
+int manager_rtnl_enumerate_rules(Manager *m);
+
+int manager_rtnl_process_address(sd_netlink *nl, sd_netlink_message *message, void *userdata);
+int manager_rtnl_process_route(sd_netlink *nl, sd_netlink_message *message, void *userdata);
+int manager_rtnl_process_rule(sd_netlink *nl, sd_netlink_message *message, void *userdata);
+
+int manager_send_changed(Manager *m, const char *property, ...) _sentinel_;
+void manager_dirty(Manager *m);
+
+int manager_address_pool_acquire(Manager *m, int family, unsigned prefixlen, union in_addr_union *found);
+
+Link* manager_find_uplink(Manager *m, Link *exclude);
+
+int manager_set_hostname(Manager *m, const char *hostname);
+int manager_set_timezone(Manager *m, const char *timezone);
+int manager_request_product_uuid(Manager *m, Link *link);
+
+Link *manager_dhcp6_prefix_get(Manager *m, struct in6_addr *addr);
+int manager_dhcp6_prefix_add(Manager *m, struct in6_addr *addr, Link *link);
+int manager_dhcp6_prefix_remove_all(Manager *m, Link *link);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c
new file mode 100644
index 0000000..e5b8d11
--- /dev/null
+++ b/src/network/networkd-ndisc.c
@@ -0,0 +1,671 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/***
+ Copyright © 2014 Intel Corporation. All rights reserved.
+***/
+
+#include <netinet/icmp6.h>
+#include <arpa/inet.h>
+
+#include "sd-ndisc.h"
+
+#include "missing_network.h"
+#include "networkd-ndisc.h"
+#include "networkd-route.h"
+#include "strv.h"
+
+#define NDISC_DNSSL_MAX 64U
+#define NDISC_RDNSS_MAX 64U
+#define NDISC_PREFIX_LFT_MIN 7200U
+
+static int ndisc_netlink_message_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+ assert(link->ndisc_messages > 0);
+
+ link->ndisc_messages--;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST)
+ log_link_error_errno(link, r, "Could not set NDisc route or address: %m");
+
+ if (link->ndisc_messages == 0) {
+ link->ndisc_configured = true;
+ link_check_ready(link);
+ }
+
+ return 1;
+}
+
+static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) {
+ _cleanup_(route_freep) Route *route = NULL;
+ struct in6_addr gateway;
+ uint16_t lifetime;
+ unsigned preference;
+ uint32_t mtu;
+ usec_t time_now;
+ int r;
+ Address *address;
+ Iterator i;
+
+ assert(link);
+ assert(rt);
+
+ r = sd_ndisc_router_get_lifetime(rt, &lifetime);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
+
+ if (lifetime == 0) /* not a default router */
+ return 0;
+
+ r = sd_ndisc_router_get_address(rt, &gateway);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
+
+ SET_FOREACH(address, link->addresses, i)
+ if (!memcmp(&gateway, &address->in_addr.in6, sizeof(address->in_addr.in6))) {
+ char buffer[INET6_ADDRSTRLEN];
+
+ log_link_debug(link, "No NDisc route added, gateway %s matches local address",
+ inet_ntop(AF_INET6,
+ &address->in_addr.in6,
+ buffer, sizeof(buffer)));
+ return 0;
+ }
+
+ SET_FOREACH(address, link->addresses_foreign, i)
+ if (!memcmp(&gateway, &address->in_addr.in6, sizeof(address->in_addr.in6))) {
+ char buffer[INET6_ADDRSTRLEN];
+
+ log_link_debug(link, "No NDisc route added, gateway %s matches local address",
+ inet_ntop(AF_INET6,
+ &address->in_addr.in6,
+ buffer, sizeof(buffer)));
+ 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");
+
+ r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
+
+ r = sd_ndisc_router_get_mtu(rt, &mtu);
+ if (r == -ENODATA)
+ mtu = 0;
+ else if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get default router MTU from RA: %m");
+
+ r = route_new(&route);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate route: %m");
+
+ route->family = AF_INET6;
+ route->table = link->network->ipv6_accept_ra_route_table;
+ route->priority = link->network->dhcp_route_metric;
+ route->protocol = RTPROT_RA;
+ route->pref = preference;
+ route->gw.in6 = gateway;
+ route->lifetime = time_now + lifetime * USEC_PER_SEC;
+ route->mtu = mtu;
+
+ r = route_configure(route, link, ndisc_netlink_message_handler);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Could not set default route: %m");
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->ndisc_messages++;
+
+ return 0;
+}
+
+static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) {
+ _cleanup_(address_freep) Address *address = NULL;
+ Address *existing_address;
+ uint32_t lifetime_valid, lifetime_preferred, lifetime_remaining;
+ usec_t time_now;
+ unsigned prefixlen;
+ int r;
+
+ assert(link);
+ assert(rt);
+
+ r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
+
+ r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to get prefix length: %m");
+
+ r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime_valid);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to get prefix valid lifetime: %m");
+
+ r = sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime_preferred);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to get prefix preferred lifetime: %m");
+
+ /* The preferred lifetime is never greater than the valid lifetime */
+ if (lifetime_preferred > lifetime_valid)
+ return 0;
+
+ r = address_new(&address);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate address: %m");
+
+ address->family = AF_INET6;
+ r = sd_ndisc_router_prefix_get_address(rt, &address->in_addr.in6);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to get prefix address: %m");
+
+ if (in_addr_is_null(AF_INET6, (const union in_addr_union *) &link->network->ipv6_token) == 0)
+ memcpy(((char *)&address->in_addr.in6) + 8, ((char *)&link->network->ipv6_token) + 8, 8);
+ else {
+ /* see RFC4291 section 2.5.1 */
+ address->in_addr.in6.s6_addr[8] = link->mac.ether_addr_octet[0];
+ address->in_addr.in6.s6_addr[8] ^= 1 << 1;
+ address->in_addr.in6.s6_addr[9] = link->mac.ether_addr_octet[1];
+ address->in_addr.in6.s6_addr[10] = link->mac.ether_addr_octet[2];
+ address->in_addr.in6.s6_addr[11] = 0xff;
+ address->in_addr.in6.s6_addr[12] = 0xfe;
+ address->in_addr.in6.s6_addr[13] = link->mac.ether_addr_octet[3];
+ address->in_addr.in6.s6_addr[14] = link->mac.ether_addr_octet[4];
+ address->in_addr.in6.s6_addr[15] = link->mac.ether_addr_octet[5];
+ }
+ address->prefixlen = prefixlen;
+ address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR;
+ address->cinfo.ifa_prefered = lifetime_preferred;
+
+ /* see RFC4862 section 5.5.3.e */
+ r = address_get(link, address->family, &address->in_addr, address->prefixlen, &existing_address);
+ if (r > 0) {
+ lifetime_remaining = existing_address->cinfo.tstamp / 100 + existing_address->cinfo.ifa_valid - time_now / USEC_PER_SEC;
+ if (lifetime_valid > NDISC_PREFIX_LFT_MIN || lifetime_valid > lifetime_remaining)
+ address->cinfo.ifa_valid = lifetime_valid;
+ else if (lifetime_remaining <= NDISC_PREFIX_LFT_MIN)
+ address->cinfo.ifa_valid = lifetime_remaining;
+ else
+ address->cinfo.ifa_valid = NDISC_PREFIX_LFT_MIN;
+ } else if (lifetime_valid > 0)
+ address->cinfo.ifa_valid = lifetime_valid;
+ else
+ return 0; /* see RFC4862 section 5.5.3.d */
+
+ if (address->cinfo.ifa_valid == 0)
+ return 0;
+
+ r = address_configure(address, link, ndisc_netlink_message_handler, true);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Could not set SLAAC address: %m");
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->ndisc_messages++;
+
+ return 0;
+}
+
+static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) {
+ _cleanup_(route_freep) Route *route = NULL;
+ usec_t time_now;
+ uint32_t lifetime;
+ unsigned prefixlen;
+ int r;
+
+ assert(link);
+ assert(rt);
+
+ r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
+
+ r = sd_ndisc_router_prefix_get_prefixlen(rt, &prefixlen);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to get prefix length: %m");
+
+ r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to get prefix lifetime: %m");
+
+ r = route_new(&route);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate route: %m");
+
+ route->family = AF_INET6;
+ route->table = link->network->ipv6_accept_ra_route_table;
+ route->priority = link->network->dhcp_route_metric;
+ route->protocol = RTPROT_RA;
+ route->flags = RTM_F_PREFIX;
+ route->dst_prefixlen = prefixlen;
+ route->lifetime = time_now + lifetime * USEC_PER_SEC;
+
+ r = sd_ndisc_router_prefix_get_address(rt, &route->dst.in6);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to get prefix address: %m");
+
+ r = route_configure(route, link, ndisc_netlink_message_handler);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Could not set prefix route: %m");
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->ndisc_messages++;
+
+ return 0;
+}
+
+static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) {
+ _cleanup_(route_freep) Route *route = NULL;
+ struct in6_addr gateway;
+ uint32_t lifetime;
+ unsigned preference, prefixlen;
+ usec_t time_now;
+ int r;
+
+ assert(link);
+
+ r = sd_ndisc_router_route_get_lifetime(rt, &lifetime);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
+
+ if (lifetime == 0)
+ return 0;
+
+ r = sd_ndisc_router_get_address(rt, &gateway);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
+
+ r = sd_ndisc_router_route_get_prefixlen(rt, &prefixlen);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get route prefix length: %m");
+
+ r = sd_ndisc_router_route_get_preference(rt, &preference);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m");
+
+ r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
+
+ r = route_new(&route);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Could not allocate route: %m");
+
+ route->family = AF_INET6;
+ route->table = link->network->ipv6_accept_ra_route_table;
+ route->protocol = RTPROT_RA;
+ route->pref = preference;
+ route->gw.in6 = gateway;
+ route->dst_prefixlen = prefixlen;
+ route->lifetime = time_now + lifetime * USEC_PER_SEC;
+
+ r = sd_ndisc_router_route_get_address(rt, &route->dst.in6);
+ if (r < 0)
+ return log_link_error_errno(link, r, "Failed to get route address: %m");
+
+ r = route_configure(route, link, ndisc_netlink_message_handler);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Could not set additional route: %m");
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->ndisc_messages++;
+
+ return 0;
+}
+
+static void ndisc_rdnss_hash_func(const NDiscRDNSS *x, struct siphash *state) {
+ siphash24_compress(&x->address, sizeof(x->address), state);
+}
+
+static int ndisc_rdnss_compare_func(const NDiscRDNSS *a, const NDiscRDNSS *b) {
+ return memcmp(&a->address, &b->address, sizeof(a->address));
+}
+
+DEFINE_PRIVATE_HASH_OPS(ndisc_rdnss_hash_ops, NDiscRDNSS, ndisc_rdnss_hash_func, ndisc_rdnss_compare_func);
+
+static int ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) {
+ uint32_t lifetime;
+ const struct in6_addr *a;
+ usec_t time_now;
+ int i, n, r;
+
+ assert(link);
+ assert(rt);
+
+ r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
+
+ r = sd_ndisc_router_rdnss_get_lifetime(rt, &lifetime);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get RDNSS lifetime: %m");
+
+ n = sd_ndisc_router_rdnss_get_addresses(rt, &a);
+ if (n < 0)
+ return log_link_warning_errno(link, n, "Failed to get RDNSS addresses: %m");
+
+ for (i = 0; i < n; i++) {
+ _cleanup_free_ NDiscRDNSS *x = NULL;
+ NDiscRDNSS d = {
+ .address = a[i],
+ }, *y;
+
+ if (lifetime == 0) {
+ (void) set_remove(link->ndisc_rdnss, &d);
+ link_dirty(link);
+ continue;
+ }
+
+ y = set_get(link->ndisc_rdnss, &d);
+ if (y) {
+ y->valid_until = time_now + lifetime * USEC_PER_SEC;
+ continue;
+ }
+
+ ndisc_vacuum(link);
+
+ if (set_size(link->ndisc_rdnss) >= NDISC_RDNSS_MAX) {
+ log_link_warning(link, "Too many RDNSS records per link, ignoring.");
+ continue;
+ }
+
+ r = set_ensure_allocated(&link->ndisc_rdnss, &ndisc_rdnss_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ x = new(NDiscRDNSS, 1);
+ if (!x)
+ return log_oom();
+
+ *x = (NDiscRDNSS) {
+ .address = a[i],
+ .valid_until = time_now + lifetime * USEC_PER_SEC,
+ };
+
+ r = set_put(link->ndisc_rdnss, x);
+ if (r < 0)
+ return log_oom();
+
+ TAKE_PTR(x);
+
+ assert(r > 0);
+ link_dirty(link);
+ }
+
+ return 0;
+}
+
+static void ndisc_dnssl_hash_func(const NDiscDNSSL *x, struct siphash *state) {
+ siphash24_compress(NDISC_DNSSL_DOMAIN(x), strlen(NDISC_DNSSL_DOMAIN(x)), state);
+}
+
+static int ndisc_dnssl_compare_func(const NDiscDNSSL *a, const NDiscDNSSL *b) {
+ return strcmp(NDISC_DNSSL_DOMAIN(a), NDISC_DNSSL_DOMAIN(b));
+}
+
+DEFINE_PRIVATE_HASH_OPS(ndisc_dnssl_hash_ops, NDiscDNSSL, ndisc_dnssl_hash_func, ndisc_dnssl_compare_func);
+
+static void ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) {
+ _cleanup_strv_free_ char **l = NULL;
+ uint32_t lifetime;
+ usec_t time_now;
+ char **i;
+ int r;
+
+ assert(link);
+ assert(rt);
+
+ r = sd_ndisc_router_get_timestamp(rt, clock_boottime_or_monotonic(), &time_now);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get RA timestamp: %m");
+ return;
+ }
+
+ r = sd_ndisc_router_dnssl_get_lifetime(rt, &lifetime);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get RDNSS lifetime: %m");
+ return;
+ }
+
+ r = sd_ndisc_router_dnssl_get_domains(rt, &l);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get RDNSS addresses: %m");
+ return;
+ }
+
+ STRV_FOREACH(i, l) {
+ _cleanup_free_ NDiscDNSSL *s;
+ NDiscDNSSL *x;
+
+ s = malloc0(ALIGN(sizeof(NDiscDNSSL)) + strlen(*i) + 1);
+ if (!s) {
+ log_oom();
+ return;
+ }
+
+ strcpy(NDISC_DNSSL_DOMAIN(s), *i);
+
+ if (lifetime == 0) {
+ (void) set_remove(link->ndisc_dnssl, s);
+ link_dirty(link);
+ continue;
+ }
+
+ x = set_get(link->ndisc_dnssl, s);
+ if (x) {
+ x->valid_until = time_now + lifetime * USEC_PER_SEC;
+ continue;
+ }
+
+ ndisc_vacuum(link);
+
+ if (set_size(link->ndisc_dnssl) >= NDISC_DNSSL_MAX) {
+ log_link_warning(link, "Too many DNSSL records per link, ignoring.");
+ continue;
+ }
+
+ r = set_ensure_allocated(&link->ndisc_dnssl, &ndisc_dnssl_hash_ops);
+ if (r < 0) {
+ log_oom();
+ return;
+ }
+
+ s->valid_until = time_now + lifetime * USEC_PER_SEC;
+
+ r = set_put(link->ndisc_dnssl, s);
+ if (r < 0) {
+ log_oom();
+ return;
+ }
+
+ s = NULL;
+ assert(r > 0);
+ link_dirty(link);
+ }
+}
+
+static void ndisc_router_process_options(Link *link, sd_ndisc_router *rt) {
+ int r;
+
+ assert(link);
+ assert(rt);
+
+ r = sd_ndisc_router_option_rewind(rt);
+ for (;;) {
+ uint8_t type;
+
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to iterate through options: %m");
+ return;
+ }
+ if (r == 0) /* EOF */
+ break;
+
+ r = sd_ndisc_router_option_get_type(rt, &type);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get RA option type: %m");
+ return;
+ }
+
+ switch (type) {
+
+ case SD_NDISC_OPTION_PREFIX_INFORMATION: {
+ uint8_t flags;
+
+ r = sd_ndisc_router_prefix_get_flags(rt, &flags);
+ if (r < 0) {
+ log_link_warning_errno(link, r, "Failed to get RA prefix flags: %m");
+ return;
+ }
+
+ if (flags & ND_OPT_PI_FLAG_ONLINK)
+ (void) ndisc_router_process_onlink_prefix(link, rt);
+ if (flags & ND_OPT_PI_FLAG_AUTO)
+ (void) ndisc_router_process_autonomous_prefix(link, rt);
+
+ break;
+ }
+
+ case SD_NDISC_OPTION_ROUTE_INFORMATION:
+ (void) ndisc_router_process_route(link, rt);
+ break;
+
+ case SD_NDISC_OPTION_RDNSS:
+ if (link->network->ipv6_accept_ra_use_dns)
+ (void) ndisc_router_process_rdnss(link, rt);
+ break;
+
+ case SD_NDISC_OPTION_DNSSL:
+ if (link->network->ipv6_accept_ra_use_dns)
+ (void) ndisc_router_process_dnssl(link, rt);
+ break;
+ }
+
+ r = sd_ndisc_router_option_next(rt);
+ }
+}
+
+static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) {
+ uint64_t flags;
+ int r = 0;
+
+ assert(link);
+ assert(link->network);
+ assert(link->manager);
+ assert(rt);
+
+ r = sd_ndisc_router_get_flags(rt, &flags);
+ if (r < 0)
+ return log_link_warning_errno(link, r, "Failed to get RA flags: %m");
+
+ if (flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)) {
+ /* (re)start DHCPv6 client in stateful or stateless mode according to RA flags */
+ r = dhcp6_request_address(link, !(flags & ND_RA_FLAG_MANAGED));
+ if (r < 0 && r != -EBUSY)
+ log_link_warning_errno(link, r, "Could not acquire DHCPv6 lease on NDisc request: %m");
+ else {
+ log_link_debug(link, "Acquiring DHCPv6 lease on NDisc request");
+ r = 0;
+ }
+ }
+
+ ndisc_router_process_default(link, rt);
+ ndisc_router_process_options(link, rt);
+
+ return r;
+}
+
+static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata) {
+ Link *link = userdata;
+
+ assert(link);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return;
+
+ switch (event) {
+
+ case SD_NDISC_EVENT_ROUTER:
+ (void) ndisc_router_handler(link, rt);
+ break;
+
+ case SD_NDISC_EVENT_TIMEOUT:
+ link->ndisc_configured = true;
+ link_check_ready(link);
+
+ break;
+ default:
+ log_link_warning(link, "IPv6 Neighbor Discovery unknown event: %d", event);
+ }
+}
+
+int ndisc_configure(Link *link) {
+ int r;
+
+ assert(link);
+
+ r = sd_ndisc_new(&link->ndisc);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_attach_event(link->ndisc, NULL, 0);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_set_mac(link->ndisc, &link->mac);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_set_ifindex(link->ndisc, link->ifindex);
+ if (r < 0)
+ return r;
+
+ r = sd_ndisc_set_callback(link->ndisc, ndisc_handler, link);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+void ndisc_vacuum(Link *link) {
+ NDiscRDNSS *r;
+ NDiscDNSSL *d;
+ Iterator i;
+ usec_t time_now;
+
+ assert(link);
+
+ /* Removes all RDNSS and DNSSL entries whose validity time has passed */
+
+ time_now = now(clock_boottime_or_monotonic());
+
+ SET_FOREACH(r, link->ndisc_rdnss, i)
+ if (r->valid_until < time_now) {
+ free(set_remove(link->ndisc_rdnss, r));
+ link_dirty(link);
+ }
+
+ SET_FOREACH(d, link->ndisc_dnssl, i)
+ if (d->valid_until < time_now) {
+ free(set_remove(link->ndisc_dnssl, d));
+ link_dirty(link);
+ }
+}
+
+void ndisc_flush(Link *link) {
+ assert(link);
+
+ /* Removes all RDNSS and DNSSL entries, without exception */
+
+ link->ndisc_rdnss = set_free_free(link->ndisc_rdnss);
+ link->ndisc_dnssl = set_free_free(link->ndisc_dnssl);
+}
diff --git a/src/network/networkd-ndisc.h b/src/network/networkd-ndisc.h
new file mode 100644
index 0000000..762f42d
--- /dev/null
+++ b/src/network/networkd-ndisc.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "networkd-link.h"
+
+typedef struct NDiscRDNSS {
+ usec_t valid_until;
+ struct in6_addr address;
+} NDiscRDNSS;
+
+typedef struct NDiscDNSSL {
+ usec_t valid_until;
+ /* The domain name follows immediately. */
+} NDiscDNSSL;
+
+static inline char* NDISC_DNSSL_DOMAIN(const NDiscDNSSL *n) {
+ return ((char*) n) + ALIGN(sizeof(NDiscDNSSL));
+}
+
+int ndisc_configure(Link *link);
+void ndisc_vacuum(Link *link);
+void ndisc_flush(Link *link);
diff --git a/src/network/networkd-neighbor.c b/src/network/networkd-neighbor.c
new file mode 100644
index 0000000..254a60b
--- /dev/null
+++ b/src/network/networkd-neighbor.c
@@ -0,0 +1,237 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "sd-netlink.h"
+
+#include "alloc-util.h"
+#include "conf-parser.h"
+#include "ether-addr-util.h"
+#include "hashmap.h"
+#include "in-addr-util.h"
+#include "netlink-util.h"
+#include "networkd-link.h"
+#include "networkd-manager.h"
+#include "networkd-neighbor.h"
+
+void neighbor_free(Neighbor *neighbor) {
+ if (!neighbor)
+ return;
+
+ if (neighbor->network) {
+ LIST_REMOVE(neighbors, neighbor->network->neighbors, neighbor);
+ assert(neighbor->network->n_neighbors > 0);
+ neighbor->network->n_neighbors--;
+
+ if (neighbor->section) {
+ hashmap_remove(neighbor->network->neighbors_by_section, neighbor->section);
+ network_config_section_free(neighbor->section);
+ }
+ }
+
+ free(neighbor);
+}
+
+static int neighbor_new_static(Network *network, const char *filename, unsigned section_line, Neighbor **ret) {
+ _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
+ _cleanup_(neighbor_freep) Neighbor *neighbor = NULL;
+ int r;
+
+ assert(network);
+ assert(ret);
+ assert(!!filename == (section_line > 0));
+
+ if (filename) {
+ r = network_config_section_new(filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ neighbor = hashmap_get(network->neighbors_by_section, n);
+ if (neighbor) {
+ *ret = TAKE_PTR(neighbor);
+
+ return 0;
+ }
+ }
+
+ neighbor = new(Neighbor, 1);
+ if (!neighbor)
+ return -ENOMEM;
+
+ *neighbor = (Neighbor) {
+ .network = network,
+ .family = AF_UNSPEC,
+ };
+
+ LIST_APPEND(neighbors, network->neighbors, neighbor);
+ network->n_neighbors++;
+
+ if (filename) {
+ neighbor->section = TAKE_PTR(n);
+
+ r = hashmap_ensure_allocated(&network->neighbors_by_section, &network_config_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(network->neighbors_by_section, neighbor->section, neighbor);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(neighbor);
+
+ return 0;
+}
+
+static int neighbor_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(link);
+ assert(link->neighbor_messages > 0);
+
+ link->neighbor_messages--;
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST)
+ log_link_warning_errno(link, r, "Could not set neighbor: %m");
+
+ if (link->neighbor_messages == 0) {
+ log_link_debug(link, "Neighbors set");
+ link->neighbors_configured = true;
+ link_check_ready(link);
+ }
+
+ return 1;
+}
+
+int neighbor_configure(Neighbor *neighbor, Link *link, link_netlink_message_handler_t callback) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ int r;
+
+ assert(neighbor);
+ assert(link);
+ assert(link->ifindex > 0);
+ assert(link->manager);
+ assert(link->manager->rtnl);
+
+ if (neighbor->family == AF_UNSPEC)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Neighbor without Address= configured");
+ if (!neighbor->mac_configured)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Neighbor without MACAddress= configured");
+
+ r = sd_rtnl_message_new_neigh(link->manager->rtnl, &req, RTM_NEWNEIGH,
+ link->ifindex, neighbor->family);
+ if (r < 0)
+ return log_error_errno(r, "Could not allocate RTM_NEWNEIGH message: %m");
+
+ r = sd_rtnl_message_neigh_set_state(req, NUD_PERMANENT);
+ if (r < 0)
+ return log_error_errno(r, "Could not set state: %m");
+
+ r = sd_netlink_message_set_flags(req, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE);
+ if (r < 0)
+ return log_error_errno(r, "Could not set flags: %m");
+
+ r = sd_netlink_message_append_ether_addr(req, NDA_LLADDR, &neighbor->mac);
+ if (r < 0)
+ return log_error_errno(r, "Could not append NDA_LLADDR attribute: %m");
+
+ switch (neighbor->family) {
+ case AF_INET6:
+ r = sd_netlink_message_append_in6_addr(req, NDA_DST, &neighbor->in_addr.in6);
+ if (r < 0)
+ return log_error_errno(r, "Could not append NDA_DST attribute: %m");
+ break;
+ case AF_INET:
+ r = sd_netlink_message_append_in_addr(req, NDA_DST, &neighbor->in_addr.in);
+ if (r < 0)
+ return log_error_errno(r, "Could not append NDA_DST attribute: %m");
+ break;
+ default:
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Neighbor with invalid address family");
+ }
+
+ r = netlink_call_async(link->manager->rtnl, NULL, req, callback ?: neighbor_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_error_errno(r, "Could not send rtnetlink message: %m");
+
+ link->neighbor_messages++;
+ link_ref(link);
+
+ return 0;
+}
+
+int config_parse_neighbor_address(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Network *network = userdata;
+ _cleanup_(neighbor_freep) Neighbor *n = NULL;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = neighbor_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ r = in_addr_from_string_auto(rvalue, &n->family, &n->in_addr);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Neighbor Address is invalid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ TAKE_PTR(n);
+
+ return 0;
+}
+
+int config_parse_neighbor_hwaddr(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_(neighbor_freep) Neighbor *n = NULL;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = neighbor_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ r = ether_addr_from_string(rvalue, &n->mac);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Neighbor MACAddress is invalid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ n->mac_configured = true;
+ TAKE_PTR(n);
+
+ return 0;
+}
diff --git a/src/network/networkd-neighbor.h b/src/network/networkd-neighbor.h
new file mode 100644
index 0000000..094bf79
--- /dev/null
+++ b/src/network/networkd-neighbor.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "sd-netlink.h"
+
+#include "conf-parser.h"
+#include "ether-addr-util.h"
+#include "in-addr-util.h"
+#include "list.h"
+#include "macro.h"
+
+typedef struct Neighbor Neighbor;
+
+#include "networkd-link.h"
+#include "networkd-network.h"
+
+struct Neighbor {
+ Network *network;
+ Link *link;
+ NetworkConfigSection *section;
+
+ int family;
+ union in_addr_union in_addr;
+ bool mac_configured;
+ struct ether_addr mac;
+
+ LIST_FIELDS(Neighbor, neighbors);
+};
+
+void neighbor_free(Neighbor *neighbor);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Neighbor*, neighbor_free);
+
+int neighbor_configure(Neighbor *neighbor, Link *link, link_netlink_message_handler_t callback);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_neighbor_address);
+CONFIG_PARSER_PROTOTYPE(config_parse_neighbor_hwaddr);
diff --git a/src/network/networkd-network-bus.c b/src/network/networkd-network-bus.c
new file mode 100644
index 0000000..07e453c
--- /dev/null
+++ b/src/network/networkd-network-bus.c
@@ -0,0 +1,137 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "alloc-util.h"
+#include "ether-addr-util.h"
+#include "networkd-manager.h"
+#include "string-util.h"
+#include "strv.h"
+
+static int property_get_ether_addrs(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+
+ char buf[ETHER_ADDR_TO_STRING_MAX];
+ const struct ether_addr *p;
+ Iterator i;
+ Set *s;
+ int r;
+
+ assert(bus);
+ assert(reply);
+ assert(userdata);
+
+ s = *(Set **) userdata;
+
+ r = sd_bus_message_open_container(reply, 'a', "s");
+ if (r < 0)
+ return r;
+
+ SET_FOREACH(p, s, i) {
+ r = sd_bus_message_append(reply, "s", ether_addr_to_string(p, buf));
+ if (r < 0)
+ return r;
+ }
+
+ return sd_bus_message_close_container(reply);
+}
+
+const sd_bus_vtable network_vtable[] = {
+ SD_BUS_VTABLE_START(0),
+
+ SD_BUS_PROPERTY("Description", "s", NULL, offsetof(Network, description), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("SourcePath", "s", NULL, offsetof(Network, filename), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("MatchMAC", "as", property_get_ether_addrs, offsetof(Network, match_mac), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("MatchPath", "as", NULL, offsetof(Network, match_path), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("MatchDriver", "as", NULL, offsetof(Network, match_driver), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("MatchType", "as", NULL, offsetof(Network, match_type), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("MatchName", "as", NULL, offsetof(Network, match_name), SD_BUS_VTABLE_PROPERTY_CONST),
+
+ SD_BUS_VTABLE_END
+};
+
+static char *network_bus_path(Network *network) {
+ _cleanup_free_ char *name = NULL;
+ char *networkname, *d, *path;
+ int r;
+
+ assert(network);
+ assert(network->filename);
+
+ name = strdup(network->filename);
+ if (!name)
+ return NULL;
+
+ networkname = basename(name);
+
+ d = strrchr(networkname, '.');
+ if (!d)
+ return NULL;
+
+ assert(streq(d, ".network"));
+
+ *d = '\0';
+
+ r = sd_bus_path_encode("/org/freedesktop/network1/network", networkname, &path);
+ if (r < 0)
+ return NULL;
+
+ return path;
+}
+
+int network_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) {
+ _cleanup_strv_free_ char **l = NULL;
+ Manager *m = userdata;
+ Network *network;
+ int r;
+
+ assert(bus);
+ assert(path);
+ assert(m);
+ assert(nodes);
+
+ LIST_FOREACH(networks, network, m->networks) {
+ char *p;
+
+ p = network_bus_path(network);
+ if (!p)
+ return -ENOMEM;
+
+ r = strv_consume(&l, p);
+ if (r < 0)
+ return r;
+ }
+
+ *nodes = TAKE_PTR(l);
+
+ return 1;
+}
+
+int network_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) {
+ Manager *m = userdata;
+ Network *network;
+ _cleanup_free_ char *name = NULL;
+ int r;
+
+ assert(bus);
+ assert(path);
+ assert(interface);
+ assert(m);
+ assert(found);
+
+ r = sd_bus_path_decode(path, "/org/freedesktop/network1/network", &name);
+ if (r < 0)
+ return 0;
+
+ r = network_get_by_name(m, name, &network);
+ if (r < 0)
+ return 0;
+
+ *found = network;
+
+ return 1;
+}
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
new file mode 100644
index 0000000..5d8aede
--- /dev/null
+++ b/src/network/networkd-network-gperf.gperf
@@ -0,0 +1,201 @@
+%{
+#if __GNUC__ >= 7
+_Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"")
+#endif
+#include <stddef.h>
+#include "conf-parser.h"
+#include "network-internal.h"
+#include "networkd-conf.h"
+#include "networkd-network.h"
+#include "vlan-util.h"
+%}
+struct ConfigPerfItem;
+%null_strings
+%language=ANSI-C
+%define slot-name section_and_lvalue
+%define hash-function-name network_network_gperf_hash
+%define lookup-function-name network_network_gperf_lookup
+%readonly-tables
+%omit-struct-type
+%struct-type
+%includes
+%%
+Match.MACAddress, config_parse_hwaddrs, 0, offsetof(Network, match_mac)
+Match.Path, config_parse_strv, 0, offsetof(Network, match_path)
+Match.Driver, config_parse_strv, 0, offsetof(Network, match_driver)
+Match.Type, config_parse_strv, 0, offsetof(Network, match_type)
+Match.Name, config_parse_ifnames, 0, offsetof(Network, match_name)
+Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(Network, match_host)
+Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(Network, match_virt)
+Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(Network, match_kernel_cmdline)
+Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(Network, match_kernel_version)
+Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(Network, match_arch)
+Link.MACAddress, config_parse_hwaddr, 0, offsetof(Network, mac)
+Link.MTUBytes, config_parse_mtu, AF_UNSPEC, offsetof(Network, mtu)
+Link.ARP, config_parse_tristate, 0, offsetof(Network, arp)
+Link.Multicast, config_parse_tristate, 0, offsetof(Network, multicast)
+Link.AllMulticast, config_parse_tristate, 0, offsetof(Network, allmulticast)
+Link.Unmanaged, config_parse_bool, 0, offsetof(Network, unmanaged)
+Link.RequiredForOnline, config_parse_bool, 0, offsetof(Network, required_for_online)
+Network.Description, config_parse_string, 0, offsetof(Network, description)
+Network.Bridge, config_parse_netdev, 0, 0
+Network.Bond, config_parse_netdev, 0, 0
+Network.VLAN, config_parse_netdev, 0, 0
+Network.MACVLAN, config_parse_netdev, 0, 0
+Network.MACVTAP, config_parse_netdev, 0, 0
+Network.IPVLAN, config_parse_netdev, 0, 0
+Network.VXLAN, config_parse_netdev, 0, 0
+Network.Tunnel, config_parse_tunnel, 0, 0
+Network.VRF, config_parse_netdev, 0, 0
+Network.DHCP, config_parse_dhcp, 0, offsetof(Network, dhcp)
+Network.DHCPServer, config_parse_bool, 0, offsetof(Network, dhcp_server)
+Network.LinkLocalAddressing, config_parse_address_family_boolean, 0, offsetof(Network, link_local)
+Network.IPv4LLRoute, config_parse_bool, 0, offsetof(Network, ipv4ll_route)
+Network.IPv6Token, config_parse_ipv6token, 0, offsetof(Network, ipv6_token)
+Network.LLDP, config_parse_lldp_mode, 0, offsetof(Network, lldp_mode)
+Network.EmitLLDP, config_parse_lldp_emit, 0, offsetof(Network, lldp_emit)
+Network.Address, config_parse_address, 0, 0
+Network.Gateway, config_parse_gateway, 0, 0
+Network.Domains, config_parse_domains, 0, 0
+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)
+Network.MulticastDNS, config_parse_resolve_support, 0, offsetof(Network, mdns)
+Network.DNSOverTLS, config_parse_dns_over_tls_mode, 0, offsetof(Network, dns_over_tls_mode)
+Network.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Network, dnssec_mode)
+Network.DNSSECNegativeTrustAnchors, config_parse_dnssec_negative_trust_anchors, 0, 0
+Network.NTP, config_parse_ntp, 0, offsetof(Network, ntp)
+Network.IPForward, config_parse_address_family_boolean_with_kernel,0, offsetof(Network, ip_forward)
+Network.IPMasquerade, config_parse_bool, 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.IPv6DuplicateAddressDetection, config_parse_int, 0, offsetof(Network, ipv6_dad_transmits)
+Network.IPv6HopLimit, config_parse_int, 0, offsetof(Network, ipv6_hop_limit)
+Network.IPv6ProxyNDP, config_parse_tristate, 0, offsetof(Network, ipv6_proxy_ndp)
+Network.IPv6MTUBytes, config_parse_mtu, AF_INET6, offsetof(Network, ipv6_mtu)
+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.ProxyARP, config_parse_tristate, 0, offsetof(Network, proxy_arp)
+Network.IPv6ProxyNDPAddress, config_parse_ipv6_proxy_ndp_address, 0, 0
+Network.BindCarrier, config_parse_strv, 0, offsetof(Network, bind_carrier)
+Network.ConfigureWithoutCarrier, config_parse_bool, 0, offsetof(Network, configure_without_carrier)
+Address.Address, config_parse_address, 0, 0
+Address.Peer, config_parse_address, 0, 0
+Address.Broadcast, config_parse_broadcast, 0, 0
+Address.Label, config_parse_label, 0, 0
+Address.PreferredLifetime, config_parse_lifetime, 0, 0
+Address.HomeAddress, config_parse_address_flags, 0, 0
+Address.DuplicateAddressDetection, config_parse_address_flags, 0, 0
+Address.ManageTemporaryAddress, config_parse_address_flags, 0, 0
+Address.PrefixRoute, config_parse_address_flags, 0, 0
+Address.AutoJoin, config_parse_address_flags, 0, 0
+Address.Scope, config_parse_address_scope, 0, 0
+IPv6AddressLabel.Prefix, config_parse_address_label_prefix, 0, 0
+IPv6AddressLabel.Label, config_parse_address_label, 0, 0
+Neighbor.Address, config_parse_neighbor_address, 0, 0
+Neighbor.MACAddress, config_parse_neighbor_hwaddr, 0, 0
+RoutingPolicyRule.TypeOfService, config_parse_routing_policy_rule_tos, 0, 0
+RoutingPolicyRule.Priority, config_parse_routing_policy_rule_priority, 0, 0
+RoutingPolicyRule.Table, config_parse_routing_policy_rule_table, 0, 0
+RoutingPolicyRule.FirewallMark, config_parse_routing_policy_rule_fwmark_mask, 0, 0
+RoutingPolicyRule.From, config_parse_routing_policy_rule_prefix, 0, 0
+RoutingPolicyRule.To, config_parse_routing_policy_rule_prefix, 0, 0
+RoutingPolicyRule.IncomingInterface, config_parse_routing_policy_rule_device, 0, 0
+RoutingPolicyRule.OutgoingInterface, config_parse_routing_policy_rule_device, 0, 0
+RoutingPolicyRule.IPProtocol, config_parse_routing_policy_rule_ip_protocol, 0, 0
+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
+Route.Gateway, config_parse_gateway, 0, 0
+Route.Destination, config_parse_destination, 0, 0
+Route.Source, config_parse_destination, 0, 0
+Route.Metric, config_parse_route_priority, 0, 0
+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_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.InitialCongestionWindow, config_parse_tcp_window, 0, 0
+Route.InitialAdvertisedReceiveWindow, config_parse_tcp_window, 0, 0
+Route.QuickAck, config_parse_quickack, 0, 0
+DHCP.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier)
+DHCP.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns)
+DHCP.UseNTP, config_parse_bool, 0, offsetof(Network, 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, 0, offsetof(Network, 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_bool, 0, offsetof(Network, dhcp_send_hostname)
+DHCP.Hostname, config_parse_hostname, 0, offsetof(Network, dhcp_hostname)
+DHCP.RequestBroadcast, config_parse_bool, 0, offsetof(Network, dhcp_broadcast)
+DHCP.CriticalConnection, config_parse_bool, 0, offsetof(Network, dhcp_critical)
+DHCP.VendorClassIdentifier, config_parse_string, 0, offsetof(Network, dhcp_vendor_class_identifier)
+DHCP.UserClass, config_parse_dhcp_user_class, 0, offsetof(Network, dhcp_user_class)
+DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Network, duid)
+DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, duid)
+DHCP.RouteMetric, config_parse_unsigned, 0, offsetof(Network, dhcp_route_metric)
+DHCP.RouteTable, config_parse_dhcp_route_table, 0, 0
+DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone)
+DHCP.IAID, config_parse_iaid, 0, 0
+DHCP.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port)
+DHCP.RapidCommit, config_parse_bool, 0, offsetof(Network, rapid_commit)
+DHCP.ForceDHCPv6PDOtherInformation, config_parse_bool, 0, offsetof(Network, dhcp6_force_pd_other_information)
+IPv6AcceptRA.UseDNS, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_dns)
+IPv6AcceptRA.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, ipv6_accept_ra_use_domains)
+IPv6AcceptRA.RouteTable, config_parse_uint32, 0, offsetof(Network, ipv6_accept_ra_route_table)
+DHCPServer.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec)
+DHCPServer.DefaultLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_default_lease_time_usec)
+DHCPServer.EmitDNS, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_dns)
+DHCPServer.DNS, config_parse_dhcp_server_dns, 0, 0
+DHCPServer.EmitNTP, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_ntp)
+DHCPServer.NTP, config_parse_dhcp_server_ntp, 0, 0
+DHCPServer.EmitRouter, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_router)
+DHCPServer.EmitTimezone, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_timezone)
+DHCPServer.Timezone, config_parse_timezone, 0, offsetof(Network, dhcp_server_timezone)
+DHCPServer.PoolOffset, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_offset)
+DHCPServer.PoolSize, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_size)
+Bridge.Cost, config_parse_uint32, 0, offsetof(Network, cost)
+Bridge.UseBPDU, config_parse_tristate, 0, offsetof(Network, use_bpdu)
+Bridge.HairPin, config_parse_tristate, 0, offsetof(Network, hairpin)
+Bridge.FastLeave, config_parse_tristate, 0, offsetof(Network, fast_leave)
+Bridge.AllowPortToBeRoot, config_parse_tristate, 0, offsetof(Network, allow_port_to_be_root)
+Bridge.UnicastFlood, config_parse_tristate, 0, offsetof(Network, unicast_flood)
+Bridge.MulticastToUnicast, config_parse_tristate, 0, offsetof(Network, multicast_to_unicast)
+Bridge.Priority, config_parse_bridge_port_priority, 0, offsetof(Network, priority)
+BridgeFDB.MACAddress, config_parse_fdb_hwaddr, 0, 0
+BridgeFDB.VLANId, config_parse_fdb_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
+Network.IPv6PrefixDelegation, config_parse_router_prefix_delegation, 0, 0
+IPv6PrefixDelegation.RouterLifetimeSec, config_parse_sec, 0, offsetof(Network, router_lifetime_usec)
+IPv6PrefixDelegation.Managed, config_parse_bool, 0, offsetof(Network, router_managed)
+IPv6PrefixDelegation.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information)
+IPv6PrefixDelegation.RouterPreference, config_parse_router_preference, 0, 0
+IPv6PrefixDelegation.EmitDNS, config_parse_bool, 0, offsetof(Network, router_emit_dns)
+IPv6PrefixDelegation.DNS, config_parse_radv_dns, 0, 0
+IPv6PrefixDelegation.EmitDomains, config_parse_bool, 0, offsetof(Network, router_emit_domains)
+IPv6PrefixDelegation.Domains, config_parse_radv_search_domains, 0, 0
+IPv6PrefixDelegation.DNSLifetimeSec, config_parse_sec, 0, offsetof(Network, router_dns_lifetime_usec)
+IPv6Prefix.Prefix, config_parse_prefix, 0, 0
+IPv6Prefix.OnLink, config_parse_prefix_flags, 0, 0
+IPv6Prefix.AddressAutoconfiguration, config_parse_prefix_flags, 0, 0
+IPv6Prefix.ValidLifetimeSec, config_parse_prefix_lifetime, 0, 0
+IPv6Prefix.PreferredLifetimeSec, config_parse_prefix_lifetime, 0, 0
+CAN.BitRate, config_parse_si_size, 0, offsetof(Network, can_bitrate)
+CAN.SamplePoint, config_parse_permille, 0, offsetof(Network, can_sample_point)
+CAN.RestartSec, config_parse_sec, 0, offsetof(Network, can_restart_us)
+/* backwards compatibility: do not add new entries to this section */
+Network.IPv4LL, config_parse_ipv4ll, 0, offsetof(Network, link_local)
+DHCPv4.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns)
+DHCPv4.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu)
+DHCPv4.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname)
+DHCP.UseDomainName, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains)
+DHCPv4.UseDomainName, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains)
+DHCPv4.CriticalConnection, config_parse_bool, 0, offsetof(Network, dhcp_critical)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
new file mode 100644
index 0000000..12344ec
--- /dev/null
+++ b/src/network/networkd-network.c
@@ -0,0 +1,1507 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <ctype.h>
+#include <net/if.h>
+
+#include "alloc-util.h"
+#include "conf-files.h"
+#include "conf-parser.h"
+#include "dns-domain.h"
+#include "fd-util.h"
+#include "hostname-util.h"
+#include "in-addr-util.h"
+#include "missing_network.h"
+#include "network-internal.h"
+#include "networkd-manager.h"
+#include "networkd-network.h"
+#include "parse-util.h"
+#include "set.h"
+#include "stat-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "strv.h"
+#include "util.h"
+
+static void network_config_hash_func(const NetworkConfigSection *c, struct siphash *state) {
+ siphash24_compress(c->filename, strlen(c->filename), state);
+ siphash24_compress(&c->line, sizeof(c->line), state);
+}
+
+static int network_config_compare_func(const NetworkConfigSection *x, const NetworkConfigSection *y) {
+ int r;
+
+ r = strcmp(x->filename, y->filename);
+ if (r != 0)
+ return r;
+
+ return CMP(x->line, y->line);
+}
+
+DEFINE_HASH_OPS(network_config_hash_ops, NetworkConfigSection, network_config_hash_func, network_config_compare_func);
+
+int network_config_section_new(const char *filename, unsigned line, NetworkConfigSection **s) {
+ NetworkConfigSection *cs;
+
+ cs = malloc0(offsetof(NetworkConfigSection, filename) + strlen(filename) + 1);
+ if (!cs)
+ return -ENOMEM;
+
+ strcpy(cs->filename, filename);
+ cs->line = line;
+
+ *s = TAKE_PTR(cs);
+
+ return 0;
+}
+
+void network_config_section_free(NetworkConfigSection *cs) {
+ free(cs);
+}
+
+/* Set defaults following RFC7844 */
+void network_apply_anonymize_if_set(Network *network) {
+ if (!network->dhcp_anonymize)
+ return;
+ /* RFC7844 3.7
+ SHOULD NOT send the Host Name option */
+ network->dhcp_send_hostname = false;
+ /* RFC7844 section 3.:
+ MAY contain the Client Identifier option
+ Section 3.5:
+ clients MUST use client identifiers based solely
+ on the link-layer address */
+ /* NOTE: Using MAC, as it does not reveal extra information,
+ * and some servers might not answer if this option is not sent */
+ network->dhcp_client_identifier = DHCP_CLIENT_ID_MAC;
+ /* RFC 7844 3.10:
+ SHOULD NOT use the Vendor Class Identifier option */
+ network->dhcp_vendor_class_identifier = mfree(network->dhcp_vendor_class_identifier);
+ /* RFC7844 section 3.6.:
+ The client intending to protect its privacy SHOULD only request a
+ minimal number of options in the PRL and SHOULD also randomly shuffle
+ the ordering of option codes in the PRL. If this random ordering
+ cannot be implemented, the client MAY order the option codes in the
+ PRL by option code number (lowest to highest).
+ */
+ /* NOTE: dhcp_use_mtu is false by default,
+ * though it was not initiallized to any value in network_load_one.
+ * Maybe there should be another var called *send*?
+ * (to use the MTU sent by the server but to do not send
+ * the option in the PRL). */
+ network->dhcp_use_mtu = false;
+ /* NOTE: when Anonymize=yes, the PRL route options are sent by default,
+ * but this is needed to use them. */
+ network->dhcp_use_routes = true;
+ /* RFC7844 section 3.6.
+ * same comments as previous option */
+ network->dhcp_use_timezone = false;
+}
+
+int network_load_one(Manager *manager, const char *filename) {
+ _cleanup_free_ char *fname = NULL, *name = NULL;
+ _cleanup_(network_freep) Network *network = NULL;
+ _cleanup_fclose_ FILE *file = NULL;
+ const char *dropin_dirname;
+ Address *address;
+ Route *route;
+ char *d;
+ int r;
+
+ assert(manager);
+ assert(filename);
+
+ file = fopen(filename, "re");
+ if (!file) {
+ if (errno == ENOENT)
+ return 0;
+
+ return -errno;
+ }
+
+ if (null_or_empty_fd(fileno(file))) {
+ log_debug("Skipping empty file: %s", filename);
+ return 0;
+ }
+
+ fname = strdup(filename);
+ if (!fname)
+ return log_oom();
+
+ name = strdup(basename(filename));
+ if (!name)
+ return log_oom();
+
+ d = strrchr(name, '.');
+ if (!d)
+ return -EINVAL;
+
+ *d = '\0';
+
+ dropin_dirname = strjoina(name, ".network.d");
+
+ network = new(Network, 1);
+ if (!network)
+ return log_oom();
+
+ *network = (Network) {
+ .manager = manager,
+ .filename = TAKE_PTR(fname),
+ .name = TAKE_PTR(name),
+
+ .required_for_online = true,
+ .dhcp = ADDRESS_FAMILY_NO,
+ .dhcp_use_ntp = true,
+ .dhcp_use_dns = true,
+ .dhcp_use_hostname = true,
+ .dhcp_use_routes = true,
+ /* NOTE: this var might be overwriten by network_apply_anonymize_if_set */
+ .dhcp_send_hostname = true,
+ /* To enable/disable RFC7844 Anonymity Profiles */
+ .dhcp_anonymize = false,
+ .dhcp_route_metric = DHCP_ROUTE_METRIC,
+ /* NOTE: this var might be overwrite by network_apply_anonymize_if_set */
+ .dhcp_client_identifier = DHCP_CLIENT_ID_DUID,
+ .dhcp_route_table = RT_TABLE_MAIN,
+ .dhcp_route_table_set = false,
+ /* NOTE: from man: UseMTU=... Defaults to false*/
+ .dhcp_use_mtu = false,
+ /* NOTE: from man: UseTimezone=... Defaults to "no".*/
+ .dhcp_use_timezone = false,
+ .rapid_commit = true,
+
+ .dhcp_server_emit_dns = true,
+ .dhcp_server_emit_ntp = true,
+ .dhcp_server_emit_router = true,
+ .dhcp_server_emit_timezone = true,
+
+ .router_emit_dns = true,
+ .router_emit_domains = true,
+
+ .use_bpdu = -1,
+ .hairpin = -1,
+ .fast_leave = -1,
+ .allow_port_to_be_root = -1,
+ .unicast_flood = -1,
+ .multicast_to_unicast = -1,
+ .priority = LINK_BRIDGE_PORT_PRIORITY_INVALID,
+
+ .lldp_mode = LLDP_MODE_ROUTERS_ONLY,
+
+ .dns_default_route = -1,
+ .llmnr = RESOLVE_SUPPORT_YES,
+ .mdns = RESOLVE_SUPPORT_NO,
+ .dnssec_mode = _DNSSEC_MODE_INVALID,
+ .dns_over_tls_mode = _DNS_OVER_TLS_MODE_INVALID,
+
+ .link_local = ADDRESS_FAMILY_IPV6,
+
+ .ipv6_privacy_extensions = IPV6_PRIVACY_EXTENSIONS_NO,
+ .ipv6_accept_ra = -1,
+ .ipv6_dad_transmits = -1,
+ .ipv6_hop_limit = -1,
+ .ipv6_proxy_ndp = -1,
+ .duid.type = _DUID_TYPE_INVALID,
+ .proxy_arp = -1,
+ .arp = -1,
+ .multicast = -1,
+ .allmulticast = -1,
+ .ipv6_accept_ra_use_dns = true,
+ .ipv6_accept_ra_route_table = RT_TABLE_MAIN,
+ };
+
+ r = config_parse_many(filename, network_dirs, dropin_dirname,
+ "Match\0"
+ "Link\0"
+ "Network\0"
+ "Address\0"
+ "Neighbor\0"
+ "IPv6AddressLabel\0"
+ "RoutingPolicyRule\0"
+ "Route\0"
+ "DHCP\0"
+ "DHCPv4\0" /* compat */
+ "DHCPServer\0"
+ "IPv6AcceptRA\0"
+ "IPv6NDPProxyAddress\0"
+ "Bridge\0"
+ "BridgeFDB\0"
+ "BridgeVLAN\0"
+ "IPv6PrefixDelegation\0"
+ "IPv6Prefix\0"
+ "CAN\0",
+ config_item_perf_lookup, network_network_gperf_lookup,
+ CONFIG_PARSE_WARN, network);
+ if (r < 0) {
+ /* Unset manager here. Otherwise, LIST_REMOVE() in network_free() fails. */
+ network->manager = NULL;
+ return r;
+ }
+
+ network_apply_anonymize_if_set(network);
+
+ /* IPMasquerade=yes implies IPForward=yes */
+ if (network->ip_masquerade)
+ network->ip_forward |= ADDRESS_FAMILY_IPV4;
+
+ if (network->mtu > 0 && network->dhcp_use_mtu) {
+ log_warning("MTUBytes= in [Link] section and UseMTU= in [DHCP] section are set in %s. "
+ "Disabling UseMTU=.", filename);
+ network->dhcp_use_mtu = false;
+ }
+
+ LIST_PREPEND(networks, manager->networks, network);
+
+ r = hashmap_ensure_allocated(&manager->networks_by_name, &string_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(manager->networks_by_name, network->name, network);
+ if (r < 0)
+ return r;
+
+ LIST_FOREACH(routes, route, network->static_routes)
+ if (!route->family) {
+ log_warning("Route section without Gateway field configured in %s. "
+ "Ignoring", filename);
+ return 0;
+ }
+
+ LIST_FOREACH(addresses, address, network->static_addresses)
+ if (!address->family) {
+ log_warning("Address section without Address field configured in %s. "
+ "Ignoring", filename);
+ return 0;
+ }
+
+ network = NULL;
+
+ return 0;
+}
+
+int network_load(Manager *manager) {
+ Network *network;
+ _cleanup_strv_free_ char **files = NULL;
+ char **f;
+ int r;
+
+ assert(manager);
+
+ while ((network = manager->networks))
+ network_free(network);
+
+ r = conf_files_list_strv(&files, ".network", NULL, 0, network_dirs);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enumerate network files: %m");
+
+ STRV_FOREACH_BACKWARDS(f, files) {
+ r = network_load_one(manager, *f);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+void network_free(Network *network) {
+ IPv6ProxyNDPAddress *ipv6_proxy_ndp_address;
+ RoutingPolicyRule *rule;
+ FdbEntry *fdb_entry;
+ Neighbor *neighbor;
+ AddressLabel *label;
+ Prefix *prefix;
+ Address *address;
+ Route *route;
+
+ if (!network)
+ return;
+
+ free(network->filename);
+
+ set_free_free(network->match_mac);
+ strv_free(network->match_path);
+ strv_free(network->match_driver);
+ strv_free(network->match_type);
+ strv_free(network->match_name);
+
+ free(network->description);
+ free(network->dhcp_vendor_class_identifier);
+ strv_free(network->dhcp_user_class);
+ free(network->dhcp_hostname);
+
+ free(network->mac);
+
+ strv_free(network->ntp);
+ free(network->dns);
+ strv_free(network->search_domains);
+ strv_free(network->route_domains);
+ strv_free(network->bind_carrier);
+
+ strv_free(network->router_search_domains);
+ free(network->router_dns);
+
+ netdev_unref(network->bridge);
+ netdev_unref(network->bond);
+ netdev_unref(network->vrf);
+
+ hashmap_free_with_destructor(network->stacked_netdevs, netdev_unref);
+
+ while ((route = network->static_routes))
+ route_free(route);
+
+ while ((address = network->static_addresses))
+ address_free(address);
+
+ while ((fdb_entry = network->static_fdb_entries))
+ fdb_entry_free(fdb_entry);
+
+ while ((ipv6_proxy_ndp_address = network->ipv6_proxy_ndp_addresses))
+ ipv6_proxy_ndp_address_free(ipv6_proxy_ndp_address);
+
+ while ((neighbor = network->neighbors))
+ neighbor_free(neighbor);
+
+ while ((label = network->address_labels))
+ address_label_free(label);
+
+ while ((prefix = network->static_prefixes))
+ prefix_free(prefix);
+
+ while ((rule = network->rules))
+ routing_policy_rule_free(rule);
+
+ hashmap_free(network->addresses_by_section);
+ hashmap_free(network->routes_by_section);
+ hashmap_free(network->fdb_entries_by_section);
+ hashmap_free(network->neighbors_by_section);
+ hashmap_free(network->address_labels_by_section);
+ hashmap_free(network->prefixes_by_section);
+ hashmap_free(network->rules_by_section);
+
+ if (network->manager) {
+ if (network->manager->networks)
+ LIST_REMOVE(networks, network->manager->networks, network);
+
+ if (network->manager->networks_by_name && network->name)
+ hashmap_remove(network->manager->networks_by_name, network->name);
+
+ if (network->manager->duids_requesting_uuid)
+ set_remove(network->manager->duids_requesting_uuid, &network->duid);
+ }
+
+ free(network->name);
+
+ condition_free_list(network->match_host);
+ condition_free_list(network->match_virt);
+ condition_free_list(network->match_kernel_cmdline);
+ condition_free_list(network->match_kernel_version);
+ condition_free_list(network->match_arch);
+
+ free(network->dhcp_server_timezone);
+ free(network->dhcp_server_dns);
+ free(network->dhcp_server_ntp);
+
+ set_free_free(network->dnssec_negative_trust_anchors);
+
+ free(network);
+}
+
+int network_get_by_name(Manager *manager, const char *name, Network **ret) {
+ Network *network;
+
+ assert(manager);
+ assert(name);
+ assert(ret);
+
+ network = hashmap_get(manager->networks_by_name, name);
+ if (!network)
+ return -ENOENT;
+
+ *ret = network;
+
+ return 0;
+}
+
+int network_get(Manager *manager, sd_device *device,
+ const char *ifname, const struct ether_addr *address,
+ Network **ret) {
+ const char *path = NULL, *parent_driver = NULL, *driver = NULL, *devtype = NULL;
+ sd_device *parent;
+ Network *network;
+
+ assert(manager);
+ assert(ret);
+
+ if (device) {
+ (void) sd_device_get_property_value(device, "ID_PATH", &path);
+
+ if (sd_device_get_parent(device, &parent) >= 0)
+ (void) sd_device_get_driver(parent, &parent_driver);
+
+ (void) sd_device_get_property_value(device, "ID_NET_DRIVER", &driver);
+
+ (void) sd_device_get_devtype(device, &devtype);
+ }
+
+ LIST_FOREACH(networks, network, manager->networks) {
+ if (net_match_config(network->match_mac, network->match_path,
+ network->match_driver, network->match_type,
+ network->match_name, network->match_host,
+ network->match_virt, network->match_kernel_cmdline,
+ network->match_kernel_version, network->match_arch,
+ address, path, parent_driver, driver,
+ devtype, ifname)) {
+ if (network->match_name && device) {
+ const char *attr;
+ uint8_t name_assign_type = NET_NAME_UNKNOWN;
+
+ if (sd_device_get_sysattr_value(device, "name_assign_type", &attr) >= 0)
+ (void) safe_atou8(attr, &name_assign_type);
+
+ if (name_assign_type == NET_NAME_ENUM)
+ log_warning("%s: found matching network '%s', based on potentially unpredictable ifname",
+ ifname, network->filename);
+ else
+ log_debug("%s: found matching network '%s'", ifname, network->filename);
+ } else
+ log_debug("%s: found matching network '%s'", ifname, network->filename);
+
+ *ret = network;
+ return 0;
+ }
+ }
+
+ *ret = NULL;
+
+ return -ENOENT;
+}
+
+int network_apply(Network *network, Link *link) {
+ int r;
+
+ assert(network);
+ assert(link);
+
+ link->network = network;
+
+ if (network->ipv4ll_route) {
+ Route *route;
+
+ r = route_new_static(network, NULL, 0, &route);
+ if (r < 0)
+ return r;
+
+ r = inet_pton(AF_INET, "169.254.0.0", &route->dst.in);
+ if (r == 0)
+ return -EINVAL;
+ if (r < 0)
+ return -errno;
+
+ route->family = AF_INET;
+ route->dst_prefixlen = 16;
+ route->scope = RT_SCOPE_LINK;
+ route->priority = IPV4LL_ROUTE_METRIC;
+ route->protocol = RTPROT_STATIC;
+ }
+
+ if (network->n_dns > 0 ||
+ !strv_isempty(network->ntp) ||
+ !strv_isempty(network->search_domains) ||
+ !strv_isempty(network->route_domains))
+ link_dirty(link);
+
+ return 0;
+}
+
+bool network_has_static_ipv6_addresses(Network *network) {
+ Address *address;
+
+ assert(network);
+
+ LIST_FOREACH(addresses, address, network->static_addresses) {
+ if (address->family == AF_INET6)
+ return true;
+ }
+
+ return false;
+}
+
+int config_parse_netdev(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_free_ char *kind_string = NULL;
+ char *p;
+ NetDev *netdev;
+ NetDevKind kind;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ kind_string = strdup(lvalue);
+ if (!kind_string)
+ return log_oom();
+
+ /* the keys are CamelCase versions of the kind */
+ for (p = kind_string; *p; p++)
+ *p = tolower(*p);
+
+ kind = netdev_kind_from_string(kind_string);
+ if (kind == _NETDEV_KIND_INVALID) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid NetDev kind: %s", lvalue);
+ return 0;
+ }
+
+ r = netdev_get(network->manager, rvalue, &netdev);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "%s could not be found, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ if (netdev->kind != kind) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "NetDev is not a %s, ignoring assignment: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ switch (kind) {
+ case NETDEV_KIND_BRIDGE:
+ network->bridge = netdev_unref(network->bridge);
+ network->bridge = netdev;
+
+ break;
+ case NETDEV_KIND_BOND:
+ network->bond = netdev_unref(network->bond);
+ network->bond = netdev;
+
+ break;
+ case NETDEV_KIND_VRF:
+ network->vrf = netdev_unref(network->vrf);
+ network->vrf = netdev;
+
+ break;
+ case NETDEV_KIND_VLAN:
+ case NETDEV_KIND_MACVLAN:
+ case NETDEV_KIND_MACVTAP:
+ case NETDEV_KIND_IPVLAN:
+ case NETDEV_KIND_VXLAN:
+ case NETDEV_KIND_VCAN:
+ r = hashmap_ensure_allocated(&network->stacked_netdevs, &string_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ r = hashmap_put(network->stacked_netdevs, netdev->ifname, netdev);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Cannot add NetDev '%s' to network: %m", rvalue);
+ return 0;
+ }
+
+ break;
+ default:
+ assert_not_reached("Cannot parse NetDev");
+ }
+
+ netdev_ref(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) {
+
+ const char *p;
+ Network *n = data;
+ int r;
+
+ assert(n);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ n->search_domains = strv_free(n->search_domains);
+ n->route_domains = strv_free(n->route_domains);
+ return 0;
+ }
+
+ p = rvalue;
+ for (;;) {
+ _cleanup_free_ char *w = NULL, *normalized = NULL;
+ const char *domain;
+ bool is_route;
+
+ r = extract_first_word(&p, &w, NULL, 0);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract search or route domain, ignoring: %s", rvalue);
+ break;
+ }
+ if (r == 0)
+ break;
+
+ 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_ERR, filename, line, r, "'%s' is not a valid domain name, ignoring.", domain);
+ continue;
+ }
+
+ domain = normalized;
+
+ if (is_localhost(domain)) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "'localhost' domain names may not be configure as search or route domains, ignoring assignment: %s", domain);
+ continue;
+ }
+ }
+
+ if (is_route)
+ r = strv_extend(&n->route_domains, domain);
+ else
+ r = strv_extend(&n->search_domains, domain);
+ if (r < 0)
+ return log_oom();
+ }
+
+ strv_uniq(n->route_domains);
+ strv_uniq(n->search_domains);
+
+ return 0;
+}
+
+int config_parse_tunnel(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;
+ NetDev *netdev;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = netdev_get(network->manager, rvalue, &netdev);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Tunnel is invalid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ if (!IN_SET(netdev->kind,
+ NETDEV_KIND_IPIP,
+ NETDEV_KIND_SIT,
+ NETDEV_KIND_GRE,
+ NETDEV_KIND_GRETAP,
+ NETDEV_KIND_IP6GRE,
+ NETDEV_KIND_IP6GRETAP,
+ NETDEV_KIND_VTI,
+ NETDEV_KIND_VTI6,
+ NETDEV_KIND_IP6TNL)) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "NetDev is not a tunnel, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ r = hashmap_ensure_allocated(&network->stacked_netdevs, &string_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ r = hashmap_put(network->stacked_netdevs, netdev->ifname, netdev);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Cannot add VLAN '%s' to network, ignoring: %m", rvalue);
+ return 0;
+ }
+
+ netdev_ref(netdev);
+
+ return 0;
+}
+
+int config_parse_ipv4ll(
+ 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) {
+
+ AddressFamilyBoolean *link_local = data;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ /* Note that this is mostly like
+ * config_parse_address_family_boolean(), except that it
+ * applies only to IPv4 */
+
+ SET_FLAG(*link_local, ADDRESS_FAMILY_IPV4, parse_boolean(rvalue));
+
+ return 0;
+}
+
+int config_parse_dhcp(
+ 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) {
+
+ AddressFamilyBoolean *dhcp = data, s;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ /* Note that this is mostly like
+ * config_parse_address_family_boolean(), except that it
+ * understands some old names for the enum values */
+
+ s = address_family_boolean_from_string(rvalue);
+ if (s < 0) {
+
+ /* Previously, we had a slightly different enum here,
+ * support its values for compatbility. */
+
+ if (streq(rvalue, "none"))
+ s = ADDRESS_FAMILY_NO;
+ else if (streq(rvalue, "v4"))
+ s = ADDRESS_FAMILY_IPV4;
+ else if (streq(rvalue, "v6"))
+ s = ADDRESS_FAMILY_IPV6;
+ else if (streq(rvalue, "both"))
+ s = ADDRESS_FAMILY_YES;
+ else {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse DHCP option, ignoring: %s", rvalue);
+ return 0;
+ }
+ }
+
+ *dhcp = s;
+ return 0;
+}
+
+static const char* const dhcp_client_identifier_table[_DHCP_CLIENT_ID_MAX] = {
+ [DHCP_CLIENT_ID_MAC] = "mac",
+ [DHCP_CLIENT_ID_DUID] = "duid",
+ [DHCP_CLIENT_ID_DUID_ONLY] = "duid-only",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(dhcp_client_identifier, DHCPClientIdentifier);
+DEFINE_CONFIG_PARSE_ENUM(config_parse_dhcp_client_identifier, dhcp_client_identifier, DHCPClientIdentifier, "Failed to parse client identifier type");
+
+int config_parse_ipv6token(
+ 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) {
+
+ union in_addr_union buffer;
+ struct in6_addr *token = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(token);
+
+ r = in_addr_from_string(AF_INET6, rvalue, &buffer);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse IPv6 token, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ r = in_addr_is_null(AF_INET6, &buffer);
+ if (r != 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "IPv6 token cannot be the ANY address, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ if ((buffer.in6.s6_addr32[0] | buffer.in6.s6_addr32[1]) != 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "IPv6 token cannot be longer than 64 bits, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ *token = buffer.in6;
+
+ return 0;
+}
+
+static const char* const ipv6_privacy_extensions_table[_IPV6_PRIVACY_EXTENSIONS_MAX] = {
+ [IPV6_PRIVACY_EXTENSIONS_NO] = "no",
+ [IPV6_PRIVACY_EXTENSIONS_PREFER_PUBLIC] = "prefer-public",
+ [IPV6_PRIVACY_EXTENSIONS_YES] = "yes",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(ipv6_privacy_extensions, IPv6PrivacyExtensions);
+
+int config_parse_ipv6_privacy_extensions(
+ 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) {
+
+ IPv6PrivacyExtensions *ipv6_privacy_extensions = data;
+ int k;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(ipv6_privacy_extensions);
+
+ /* Our enum shall be a superset of booleans, hence first try
+ * to parse as boolean, and then as enum */
+
+ k = parse_boolean(rvalue);
+ if (k > 0)
+ *ipv6_privacy_extensions = IPV6_PRIVACY_EXTENSIONS_YES;
+ else if (k == 0)
+ *ipv6_privacy_extensions = IPV6_PRIVACY_EXTENSIONS_NO;
+ else {
+ IPv6PrivacyExtensions s;
+
+ s = ipv6_privacy_extensions_from_string(rvalue);
+ if (s < 0) {
+
+ if (streq(rvalue, "kernel"))
+ s = _IPV6_PRIVACY_EXTENSIONS_INVALID;
+ else {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse IPv6 privacy extensions option, ignoring: %s", rvalue);
+ return 0;
+ }
+ }
+
+ *ipv6_privacy_extensions = s;
+ }
+
+ return 0;
+}
+
+int config_parse_hostname(
+ 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_free_ char *hn = NULL;
+ char **hostname = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ r = config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &hn, userdata);
+ if (r < 0)
+ return r;
+
+ if (!hostname_is_valid(hn, false)) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Hostname is not valid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ r = dns_name_is_valid(hn);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to check validity of hostname '%s', ignoring assignment: %m", rvalue);
+ return 0;
+ }
+ if (r == 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Hostname is not a valid DNS domain name, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ return free_and_replace(*hostname, hn);
+}
+
+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) {
+
+ _cleanup_free_ char *tz = NULL;
+ char **datap = data;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ r = config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &tz, userdata);
+ if (r < 0)
+ return r;
+
+ if (!timezone_is_valid(tz, LOG_ERR)) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Timezone is not valid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ return free_and_replace(*datap, tz);
+}
+
+int config_parse_dhcp_server_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 = data;
+ const char *p = rvalue;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ for (;;) {
+ _cleanup_free_ char *w = NULL;
+ struct in_addr a, *m;
+
+ r = extract_first_word(&p, &w, NULL, 0);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract word, ignoring: %s", rvalue);
+ return 0;
+ }
+ if (r == 0)
+ break;
+
+ if (inet_pton(AF_INET, w, &a) <= 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse DNS server address, ignoring: %s", w);
+ continue;
+ }
+
+ m = reallocarray(n->dhcp_server_dns, n->n_dhcp_server_dns + 1, sizeof(struct in_addr));
+ if (!m)
+ return log_oom();
+
+ m[n->n_dhcp_server_dns++] = a;
+ n->dhcp_server_dns = m;
+ }
+
+ return 0;
+}
+
+int config_parse_radv_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 = data;
+ const char *p = rvalue;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ for (;;) {
+ _cleanup_free_ char *w = NULL;
+ union in_addr_union a;
+
+ r = extract_first_word(&p, &w, NULL, 0);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract word, ignoring: %s", rvalue);
+ return 0;
+ }
+ if (r == 0)
+ break;
+
+ if (in_addr_from_string(AF_INET6, w, &a) >= 0) {
+ struct in6_addr *m;
+
+ m = reallocarray(n->router_dns, n->n_router_dns + 1, sizeof(struct in6_addr));
+ if (!m)
+ return log_oom();
+
+ m[n->n_router_dns++] = a.in6;
+ n->router_dns = m;
+
+ } else
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse DNS server address, ignoring: %s", w);
+
+ }
+
+ return 0;
+}
+
+int config_parse_radv_search_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 = data;
+ const char *p = rvalue;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ for (;;) {
+ _cleanup_free_ char *w = NULL, *idna = NULL;
+
+ r = extract_first_word(&p, &w, NULL, 0);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract word, ignoring: %s", rvalue);
+ return 0;
+ }
+ if (r == 0)
+ break;
+
+ r = dns_name_apply_idna(w, &idna);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to apply IDNA to domain name '%s', ignoring: %m", w);
+ continue;
+ }
+ if (r > 0) {
+ r = strv_push(&n->router_search_domains, idna);
+ if (r >= 0)
+ idna = NULL;
+ } else {
+ r = strv_push(&n->router_search_domains, w);
+ if (r >= 0)
+ w = NULL;
+ }
+ }
+
+ return 0;
+}
+
+int config_parse_dhcp_server_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 *n = data;
+ const char *p = rvalue;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ for (;;) {
+ _cleanup_free_ char *w = NULL;
+ struct in_addr a, *m;
+
+ r = extract_first_word(&p, &w, NULL, 0);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract word, ignoring: %s", rvalue);
+ return 0;
+ }
+ if (r == 0)
+ return 0;
+
+ if (inet_pton(AF_INET, w, &a) <= 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse NTP server address, ignoring: %s", w);
+ continue;
+ }
+
+ m = reallocarray(n->dhcp_server_ntp, n->n_dhcp_server_ntp + 1, sizeof(struct in_addr));
+ if (!m)
+ return log_oom();
+
+ m[n->n_dhcp_server_ntp++] = a;
+ n->dhcp_server_ntp = m;
+ }
+}
+
+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 = userdata;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+
+ for (;;) {
+ _cleanup_free_ char *w = NULL;
+ union in_addr_union a;
+ struct in_addr_data *m;
+ int family;
+
+ r = extract_first_word(&rvalue, &w, NULL, 0);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
+ break;
+ }
+ if (r == 0)
+ break;
+
+ r = in_addr_from_string_auto(w, &family, &a);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse dns server address, ignoring: %s", w);
+ continue;
+ }
+
+ m = reallocarray(n->dns, n->n_dns + 1, sizeof(struct in_addr_data));
+ if (!m)
+ return log_oom();
+
+ m[n->n_dns++] = (struct in_addr_data) {
+ .family = family,
+ .address = a,
+ };
+
+ n->dns = m;
+ }
+
+ return 0;
+}
+
+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) {
+
+ const char *p = rvalue;
+ Network *n = data;
+ int r;
+
+ assert(n);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ n->dnssec_negative_trust_anchors = set_free_free(n->dnssec_negative_trust_anchors);
+ return 0;
+ }
+
+ for (;;) {
+ _cleanup_free_ char *w = NULL;
+
+ r = extract_first_word(&p, &w, NULL, 0);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract negative trust anchor domain, ignoring: %s", rvalue);
+ break;
+ }
+ if (r == 0)
+ break;
+
+ r = dns_name_is_valid(w);
+ if (r <= 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "%s is not a valid domain name, ignoring.", w);
+ continue;
+ }
+
+ r = set_ensure_allocated(&n->dnssec_negative_trust_anchors, &dns_name_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ r = set_put(n->dnssec_negative_trust_anchors, w);
+ if (r < 0)
+ return log_oom();
+ if (r > 0)
+ w = NULL;
+ }
+
+ return 0;
+}
+
+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 = data;
+ int r;
+
+ assert(l);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ *l = strv_free(*l);
+ return 0;
+ }
+
+ for (;;) {
+ _cleanup_free_ char *w = NULL;
+
+ r = extract_first_word(&rvalue, &w, NULL, 0);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract NTP server name, ignoring: %s", rvalue);
+ break;
+ }
+ if (r == 0)
+ break;
+
+ r = dns_name_is_valid_or_address(w);
+ if (r <= 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "%s is not a valid domain name or IP address, ignoring.", w);
+ continue;
+ }
+
+ r = strv_push(l, w);
+ if (r < 0)
+ return log_oom();
+
+ w = NULL;
+ }
+
+ return 0;
+}
+
+int config_parse_dhcp_user_class(
+ 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 = data;
+ int r;
+
+ assert(l);
+ assert(lvalue);
+ assert(rvalue);
+
+ if (isempty(rvalue)) {
+ *l = strv_free(*l);
+ return 0;
+ }
+
+ for (;;) {
+ _cleanup_free_ char *w = NULL;
+
+ r = extract_first_word(&rvalue, &w, NULL, 0);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to split user classes option, ignoring: %s", rvalue);
+ break;
+ }
+ if (r == 0)
+ break;
+
+ if (strlen(w) > 255) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "%s length is not in the range 1-255, ignoring.", w);
+ continue;
+ }
+
+ r = strv_push(l, w);
+ if (r < 0)
+ return log_oom();
+
+ w = NULL;
+ }
+
+ return 0;
+}
+
+int config_parse_dhcp_route_table(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 = data;
+ uint32_t rt;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = safe_atou32(rvalue, &rt);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Unable to read RouteTable, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ network->dhcp_route_table = rt;
+ network->dhcp_route_table_set = true;
+
+ return 0;
+}
+
+DEFINE_CONFIG_PARSE_ENUM(config_parse_dhcp_use_domains, dhcp_use_domains, DHCPUseDomains, "Failed to parse DHCP use domains setting");
+
+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);
+
+DEFINE_CONFIG_PARSE_ENUM(config_parse_lldp_mode, lldp_mode, LLDPMode, "Failed to parse LLDP= setting.");
+
+static const char* const lldp_mode_table[_LLDP_MODE_MAX] = {
+ [LLDP_MODE_NO] = "no",
+ [LLDP_MODE_YES] = "yes",
+ [LLDP_MODE_ROUTERS_ONLY] = "routers-only",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(lldp_mode, LLDPMode, LLDP_MODE_YES);
+
+int config_parse_iaid(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 = data;
+ uint32_t iaid;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(network);
+
+ r = safe_atou32(rvalue, &iaid);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Unable to read IAID, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ network->iaid = iaid;
+ network->iaid_set = true;
+
+ return 0;
+}
diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h
new file mode 100644
index 0000000..f6e62cd
--- /dev/null
+++ b/src/network/networkd-network.h
@@ -0,0 +1,335 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "sd-bus.h"
+#include "sd-device.h"
+
+#include "condition.h"
+#include "conf-parser.h"
+#include "dhcp-identifier.h"
+#include "hashmap.h"
+#include "netdev/netdev.h"
+#include "networkd-address-label.h"
+#include "networkd-address.h"
+#include "networkd-brvlan.h"
+#include "networkd-fdb.h"
+#include "networkd-ipv6-proxy-ndp.h"
+#include "networkd-lldp-tx.h"
+#include "networkd-neighbor.h"
+#include "networkd-radv.h"
+#include "networkd-route.h"
+#include "networkd-routing-policy-rule.h"
+#include "networkd-util.h"
+#include "resolve-util.h"
+
+#define DHCP_ROUTE_METRIC 1024
+#define IPV4LL_ROUTE_METRIC 2048
+
+#define BRIDGE_VLAN_BITMAP_MAX 4096
+#define BRIDGE_VLAN_BITMAP_LEN (BRIDGE_VLAN_BITMAP_MAX / 32)
+
+typedef enum DHCPClientIdentifier {
+ DHCP_CLIENT_ID_MAC,
+ DHCP_CLIENT_ID_DUID,
+ /* The following option may not be good for RFC regarding DHCP (3315 and 4361).
+ * But some setups require this. E.g., Sky Broadband, the second largest provider in the UK
+ * requires the client id to be set to a custom string, reported at
+ * https://github.com/systemd/systemd/issues/7828 */
+ DHCP_CLIENT_ID_DUID_ONLY,
+ _DHCP_CLIENT_ID_MAX,
+ _DHCP_CLIENT_ID_INVALID = -1,
+} DHCPClientIdentifier;
+
+typedef enum IPv6PrivacyExtensions {
+ /* The values map to the kernel's /proc/sys/net/ipv6/conf/xxx/use_tempaddr values */
+ IPV6_PRIVACY_EXTENSIONS_NO,
+ IPV6_PRIVACY_EXTENSIONS_PREFER_PUBLIC,
+ IPV6_PRIVACY_EXTENSIONS_YES, /* aka prefer-temporary */
+ _IPV6_PRIVACY_EXTENSIONS_MAX,
+ _IPV6_PRIVACY_EXTENSIONS_INVALID = -1,
+} IPv6PrivacyExtensions;
+
+typedef enum DHCPUseDomains {
+ DHCP_USE_DOMAINS_NO,
+ DHCP_USE_DOMAINS_YES,
+ DHCP_USE_DOMAINS_ROUTE,
+ _DHCP_USE_DOMAINS_MAX,
+ _DHCP_USE_DOMAINS_INVALID = -1,
+} DHCPUseDomains;
+
+typedef enum LLDPMode {
+ LLDP_MODE_NO = 0,
+ LLDP_MODE_YES = 1,
+ LLDP_MODE_ROUTERS_ONLY = 2,
+ _LLDP_MODE_MAX,
+ _LLDP_MODE_INVALID = -1,
+} LLDPMode;
+
+typedef struct DUID {
+ /* Value of Type in [DHCP] section */
+ DUIDType type;
+
+ uint8_t raw_data_len;
+ uint8_t raw_data[MAX_DUID_LEN];
+ usec_t llt_time;
+} DUID;
+
+typedef enum RADVPrefixDelegation {
+ RADV_PREFIX_DELEGATION_NONE,
+ RADV_PREFIX_DELEGATION_STATIC,
+ RADV_PREFIX_DELEGATION_DHCP6,
+ RADV_PREFIX_DELEGATION_BOTH,
+ _RADV_PREFIX_DELEGATION_MAX,
+ _RADV_PREFIX_DELEGATION_INVALID = -1,
+} RADVPrefixDelegation;
+
+typedef struct NetworkConfigSection {
+ unsigned line;
+ char filename[];
+} NetworkConfigSection;
+
+int network_config_section_new(const char *filename, unsigned line, NetworkConfigSection **s);
+void network_config_section_free(NetworkConfigSection *network);
+DEFINE_TRIVIAL_CLEANUP_FUNC(NetworkConfigSection*, network_config_section_free);
+extern const struct hash_ops network_config_hash_ops;
+
+typedef struct Manager Manager;
+
+struct Network {
+ Manager *manager;
+
+ char *filename;
+ char *name;
+
+ Set *match_mac;
+ char **match_path;
+ char **match_driver;
+ char **match_type;
+ char **match_name;
+
+ Condition *match_host;
+ Condition *match_virt;
+ Condition *match_kernel_cmdline;
+ Condition *match_kernel_version;
+ Condition *match_arch;
+
+ char *description;
+
+ NetDev *bridge;
+ NetDev *bond;
+ NetDev *vrf;
+ Hashmap *stacked_netdevs;
+
+ /* DHCP Client Support */
+ AddressFamilyBoolean dhcp;
+ DHCPClientIdentifier dhcp_client_identifier;
+ char *dhcp_vendor_class_identifier;
+ char **dhcp_user_class;
+ char *dhcp_hostname;
+ unsigned dhcp_route_metric;
+ uint32_t dhcp_route_table;
+ uint16_t dhcp_client_port;
+ bool dhcp_anonymize;
+ bool dhcp_send_hostname;
+ bool dhcp_broadcast;
+ bool dhcp_critical;
+ bool dhcp_use_dns;
+ bool dhcp_use_ntp;
+ bool dhcp_use_mtu;
+ bool dhcp_use_routes;
+ bool dhcp_use_timezone;
+ bool rapid_commit;
+ bool dhcp_use_hostname;
+ bool dhcp_route_table_set;
+ DHCPUseDomains dhcp_use_domains;
+
+ /* DHCP Server Support */
+ bool dhcp_server;
+ bool dhcp_server_emit_dns;
+ struct in_addr *dhcp_server_dns;
+ unsigned n_dhcp_server_dns;
+ bool dhcp_server_emit_ntp;
+ struct in_addr *dhcp_server_ntp;
+ unsigned n_dhcp_server_ntp;
+ bool dhcp_server_emit_router;
+ bool dhcp_server_emit_timezone;
+ char *dhcp_server_timezone;
+ usec_t dhcp_server_default_lease_time_usec, dhcp_server_max_lease_time_usec;
+ uint32_t dhcp_server_pool_offset;
+ uint32_t dhcp_server_pool_size;
+
+ /* IPV4LL Support */
+ AddressFamilyBoolean link_local;
+ bool ipv4ll_route;
+
+ /* IPv6 prefix delegation support */
+ RADVPrefixDelegation router_prefix_delegation;
+ usec_t router_lifetime_usec;
+ uint8_t router_preference;
+ bool router_managed;
+ bool router_other_information;
+ bool router_emit_dns;
+ bool router_emit_domains;
+ usec_t router_dns_lifetime_usec;
+ struct in6_addr *router_dns;
+ unsigned n_router_dns;
+ char **router_search_domains;
+ bool dhcp6_force_pd_other_information; /* Start DHCPv6 PD also when 'O'
+ RA flag is set, see RFC 7084,
+ WPD-4 */
+
+ /* Bridge Support */
+ int use_bpdu;
+ int hairpin;
+ int fast_leave;
+ int allow_port_to_be_root;
+ int unicast_flood;
+ int multicast_to_unicast;
+ uint32_t cost;
+ uint16_t priority;
+
+ 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];
+
+ /* CAN support */
+ size_t can_bitrate;
+ unsigned can_sample_point;
+ usec_t can_restart_us;
+
+ AddressFamilyBoolean ip_forward;
+ bool ip_masquerade;
+
+ int ipv6_accept_ra;
+ int ipv6_dad_transmits;
+ int ipv6_hop_limit;
+ int ipv6_proxy_ndp;
+ int proxy_arp;
+ uint32_t ipv6_mtu;
+
+ bool ipv6_accept_ra_use_dns;
+ bool active_slave;
+ bool primary_slave;
+ DHCPUseDomains ipv6_accept_ra_use_domains;
+ uint32_t ipv6_accept_ra_route_table;
+
+ union in_addr_union ipv6_token;
+ IPv6PrivacyExtensions ipv6_privacy_extensions;
+
+ struct ether_addr *mac;
+ uint32_t mtu;
+ int arp;
+ int multicast;
+ int allmulticast;
+ bool unmanaged;
+ bool configure_without_carrier;
+ uint32_t iaid;
+ DUID duid;
+
+ bool iaid_set;
+
+ bool required_for_online; /* Is this network required to be considered online? */
+
+ LLDPMode lldp_mode; /* LLDP reception */
+ LLDPEmit lldp_emit; /* LLDP transmission */
+
+ LIST_HEAD(Address, static_addresses);
+ LIST_HEAD(Route, static_routes);
+ LIST_HEAD(FdbEntry, static_fdb_entries);
+ LIST_HEAD(IPv6ProxyNDPAddress, ipv6_proxy_ndp_addresses);
+ LIST_HEAD(Neighbor, neighbors);
+ LIST_HEAD(AddressLabel, address_labels);
+ LIST_HEAD(Prefix, static_prefixes);
+ LIST_HEAD(RoutingPolicyRule, rules);
+
+ unsigned n_static_addresses;
+ unsigned n_static_routes;
+ unsigned n_static_fdb_entries;
+ unsigned n_ipv6_proxy_ndp_addresses;
+ unsigned n_neighbors;
+ unsigned n_address_labels;
+ unsigned n_static_prefixes;
+ unsigned n_rules;
+
+ Hashmap *addresses_by_section;
+ Hashmap *routes_by_section;
+ Hashmap *fdb_entries_by_section;
+ Hashmap *neighbors_by_section;
+ Hashmap *address_labels_by_section;
+ Hashmap *prefixes_by_section;
+ Hashmap *rules_by_section;
+
+ /* All kinds of DNS configuration */
+ struct in_addr_data *dns;
+ unsigned n_dns;
+ char **search_domains, **route_domains;
+ int dns_default_route;
+ ResolveSupport llmnr;
+ ResolveSupport mdns;
+ DnssecMode dnssec_mode;
+ DnsOverTlsMode dns_over_tls_mode;
+ Set *dnssec_negative_trust_anchors;
+
+ char **ntp;
+ char **bind_carrier;
+
+ LIST_FIELDS(Network, networks);
+};
+
+void network_free(Network *network);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Network*, network_free);
+
+int network_load(Manager *manager);
+int network_load_one(Manager *manager, const char *filename);
+
+int network_get_by_name(Manager *manager, const char *name, Network **ret);
+int network_get(Manager *manager, sd_device *device, const char *ifname, const struct ether_addr *mac, Network **ret);
+int network_apply(Network *network, Link *link);
+void network_apply_anonymize_if_set(Network *network);
+
+bool network_has_static_ipv6_addresses(Network *network);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_netdev);
+CONFIG_PARSER_PROTOTYPE(config_parse_domains);
+CONFIG_PARSER_PROTOTYPE(config_parse_tunnel);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp);
+CONFIG_PARSER_PROTOTYPE(config_parse_dns);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_client_identifier);
+CONFIG_PARSER_PROTOTYPE(config_parse_ipv6token);
+CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_privacy_extensions);
+CONFIG_PARSER_PROTOTYPE(config_parse_hostname);
+CONFIG_PARSER_PROTOTYPE(config_parse_timezone);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_dns);
+CONFIG_PARSER_PROTOTYPE(config_parse_radv_dns);
+CONFIG_PARSER_PROTOTYPE(config_parse_radv_search_domains);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_ntp);
+CONFIG_PARSER_PROTOTYPE(config_parse_dnssec_negative_trust_anchors);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_domains);
+CONFIG_PARSER_PROTOTYPE(config_parse_lldp_mode);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_route_table);
+CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_user_class);
+CONFIG_PARSER_PROTOTYPE(config_parse_ntp);
+CONFIG_PARSER_PROTOTYPE(config_parse_iaid);
+/* Legacy IPv4LL support */
+CONFIG_PARSER_PROTOTYPE(config_parse_ipv4ll);
+
+const struct ConfigPerfItem* network_network_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
+
+extern const sd_bus_vtable network_vtable[];
+
+int network_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error);
+int network_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error);
+
+const char* ipv6_privacy_extensions_to_string(IPv6PrivacyExtensions i) _const_;
+IPv6PrivacyExtensions ipv6_privacy_extensions_from_string(const char *s) _pure_;
+
+const char* dhcp_use_domains_to_string(DHCPUseDomains p) _const_;
+DHCPUseDomains dhcp_use_domains_from_string(const char *s) _pure_;
+
+const char* lldp_mode_to_string(LLDPMode m) _const_;
+LLDPMode lldp_mode_from_string(const char *s) _pure_;
+
+const char* radv_prefix_delegation_to_string(RADVPrefixDelegation i) _const_;
+RADVPrefixDelegation radv_prefix_delegation_from_string(const char *s) _pure_;
diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c
new file mode 100644
index 0000000..92cead0
--- /dev/null
+++ b/src/network/networkd-radv.c
@@ -0,0 +1,512 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/***
+ Copyright © 2017 Intel Corporation. All rights reserved.
+***/
+
+#include <netinet/icmp6.h>
+#include <arpa/inet.h>
+
+#include "networkd-address.h"
+#include "networkd-manager.h"
+#include "networkd-radv.h"
+#include "parse-util.h"
+#include "sd-radv.h"
+#include "string-util.h"
+#include "string-table.h"
+#include "strv.h"
+
+static const char * const radv_prefix_delegation_table[_RADV_PREFIX_DELEGATION_MAX] = {
+ [RADV_PREFIX_DELEGATION_NONE] = "no",
+ [RADV_PREFIX_DELEGATION_STATIC] = "static",
+ [RADV_PREFIX_DELEGATION_DHCP6] = "dhcpv6",
+ [RADV_PREFIX_DELEGATION_BOTH] = "yes",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(
+ radv_prefix_delegation,
+ RADVPrefixDelegation,
+ RADV_PREFIX_DELEGATION_BOTH);
+
+int config_parse_router_prefix_delegation(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Network *network = userdata;
+ RADVPrefixDelegation d;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ d = radv_prefix_delegation_from_string(rvalue);
+ if (d < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Invalid router prefix delegation '%s', ignoring assignment.", rvalue);
+ return 0;
+ }
+
+ network->router_prefix_delegation = d;
+
+ return 0;
+}
+
+int config_parse_router_preference(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Network *network = userdata;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (streq(rvalue, "high"))
+ network->router_preference = SD_NDISC_PREFERENCE_HIGH;
+ else if (STR_IN_SET(rvalue, "medium", "normal", "default"))
+ network->router_preference = SD_NDISC_PREFERENCE_MEDIUM;
+ else if (streq(rvalue, "low"))
+ network->router_preference = SD_NDISC_PREFERENCE_LOW;
+ else
+ log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Router preference '%s' is invalid, ignoring assignment: %m", rvalue);
+
+ return 0;
+}
+
+void prefix_free(Prefix *prefix) {
+ if (!prefix)
+ return;
+
+ if (prefix->network) {
+ LIST_REMOVE(prefixes, prefix->network->static_prefixes, prefix);
+ assert(prefix->network->n_static_prefixes > 0);
+ prefix->network->n_static_prefixes--;
+
+ if (prefix->section)
+ hashmap_remove(prefix->network->prefixes_by_section,
+ prefix->section);
+ }
+
+ network_config_section_free(prefix->section);
+ prefix->radv_prefix = sd_radv_prefix_unref(prefix->radv_prefix);
+
+ free(prefix);
+}
+
+int prefix_new(Prefix **ret) {
+ _cleanup_(prefix_freep) Prefix *prefix = NULL;
+
+ prefix = new0(Prefix, 1);
+ if (!prefix)
+ return -ENOMEM;
+
+ if (sd_radv_prefix_new(&prefix->radv_prefix) < 0)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(prefix);
+
+ return 0;
+}
+
+int prefix_new_static(Network *network, const char *filename,
+ unsigned section_line, Prefix **ret) {
+ _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
+ _cleanup_(prefix_freep) Prefix *prefix = NULL;
+ int r;
+
+ assert(network);
+ assert(ret);
+ assert(!!filename == (section_line > 0));
+
+ if (filename) {
+ r = network_config_section_new(filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ if (section_line) {
+ prefix = hashmap_get(network->prefixes_by_section, n);
+ if (prefix) {
+ *ret = TAKE_PTR(prefix);
+
+ return 0;
+ }
+ }
+ }
+
+ r = prefix_new(&prefix);
+ if (r < 0)
+ return r;
+
+ prefix->network = network;
+ LIST_APPEND(prefixes, network->static_prefixes, prefix);
+ network->n_static_prefixes++;
+
+ if (filename) {
+ prefix->section = TAKE_PTR(n);
+
+ r = hashmap_ensure_allocated(&network->prefixes_by_section, &network_config_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(network->prefixes_by_section, prefix->section, prefix);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(prefix);
+
+ return 0;
+}
+
+int config_parse_prefix(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Network *network = userdata;
+ _cleanup_(prefix_freep) Prefix *p = NULL;
+ uint8_t prefixlen = 64;
+ union in_addr_union in6addr;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = prefix_new_static(network, filename, section_line, &p);
+ if (r < 0)
+ return r;
+
+ r = in_addr_prefix_from_string(rvalue, AF_INET6, &in6addr, &prefixlen);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Prefix is invalid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ if (sd_radv_prefix_set_prefix(p->radv_prefix, &in6addr.in6, prefixlen) < 0)
+ return -EADDRNOTAVAIL;
+
+ log_syntax(unit, LOG_INFO, filename, line, r, "Found prefix %s", rvalue);
+
+ p = NULL;
+
+ return 0;
+}
+
+int config_parse_prefix_flags(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Network *network = userdata;
+ _cleanup_(prefix_freep) Prefix *p = NULL;
+ int r, val;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = prefix_new_static(network, filename, section_line, &p);
+ if (r < 0)
+ return r;
+
+ r = parse_boolean(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address flag, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ val = r;
+
+ if (streq(lvalue, "OnLink"))
+ r = sd_radv_prefix_set_onlink(p->radv_prefix, val);
+ else if (streq(lvalue, "AddressAutoconfiguration"))
+ r = sd_radv_prefix_set_address_autoconfiguration(p->radv_prefix, val);
+ if (r < 0)
+ return r;
+
+ p = NULL;
+
+ return 0;
+}
+
+int config_parse_prefix_lifetime(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ Network *network = userdata;
+ _cleanup_(prefix_freep) Prefix *p = NULL;
+ usec_t usec;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = prefix_new_static(network, filename, section_line, &p);
+ if (r < 0)
+ return r;
+
+ r = parse_sec(rvalue, &usec);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Lifetime is invalid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ /* a value of 0xffffffff represents infinity */
+ if (streq(lvalue, "PreferredLifetimeSec"))
+ r = sd_radv_prefix_set_preferred_lifetime(p->radv_prefix,
+ DIV_ROUND_UP(usec, USEC_PER_SEC));
+ else if (streq(lvalue, "ValidLifetimeSec"))
+ r = sd_radv_prefix_set_valid_lifetime(p->radv_prefix,
+ DIV_ROUND_UP(usec, USEC_PER_SEC));
+ if (r < 0)
+ return r;
+
+ p = NULL;
+
+ return 0;
+}
+
+static int radv_get_ip6dns(Network *network, struct in6_addr **dns,
+ size_t *n_dns) {
+ _cleanup_free_ struct in6_addr *addresses = NULL;
+ size_t i, n_addresses = 0, n_allocated = 0;
+
+ assert(network);
+ assert(dns);
+ assert(n_dns);
+
+ for (i = 0; i < network->n_dns; i++) {
+ union in_addr_union *addr;
+
+ if (network->dns[i].family != AF_INET6)
+ continue;
+
+ addr = &network->dns[i].address;
+
+ if (in_addr_is_null(AF_INET6, addr) ||
+ in_addr_is_link_local(AF_INET6, addr) ||
+ in_addr_is_localhost(AF_INET6, addr))
+ continue;
+
+ if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + 1))
+ return -ENOMEM;
+
+ addresses[n_addresses++] = addr->in6;
+ }
+
+ if (addresses) {
+ *dns = TAKE_PTR(addresses);
+
+ *n_dns = n_addresses;
+ }
+
+ return n_addresses;
+}
+
+static int radv_set_dns(Link *link, Link *uplink) {
+ _cleanup_free_ struct in6_addr *dns = NULL;
+ size_t n_dns;
+ usec_t lifetime_usec;
+ int r;
+
+ if (!link->network->router_emit_dns)
+ return 0;
+
+ if (link->network->router_dns) {
+ dns = newdup(struct in6_addr, link->network->router_dns,
+ link->network->n_router_dns);
+ if (dns == NULL)
+ return -ENOMEM;
+
+ n_dns = link->network->n_router_dns;
+ lifetime_usec = link->network->router_dns_lifetime_usec;
+
+ goto set_dns;
+ }
+
+ lifetime_usec = SD_RADV_DEFAULT_DNS_LIFETIME_USEC;
+
+ r = radv_get_ip6dns(link->network, &dns, &n_dns);
+ if (r > 0)
+ goto set_dns;
+
+ if (uplink) {
+ if (uplink->network == NULL) {
+ log_link_debug(uplink, "Cannot fetch DNS servers as uplink interface is not managed by us");
+ return 0;
+ }
+
+ r = radv_get_ip6dns(uplink->network, &dns, &n_dns);
+ if (r > 0)
+ goto set_dns;
+ }
+
+ return 0;
+
+ set_dns:
+ return sd_radv_set_rdnss(link->radv,
+ DIV_ROUND_UP(lifetime_usec, USEC_PER_SEC),
+ dns, n_dns);
+}
+
+static int radv_set_domains(Link *link, Link *uplink) {
+ char **search_domains;
+ usec_t lifetime_usec;
+
+ if (!link->network->router_emit_domains)
+ return 0;
+
+ search_domains = link->network->router_search_domains;
+ lifetime_usec = link->network->router_dns_lifetime_usec;
+
+ if (search_domains)
+ goto set_domains;
+
+ lifetime_usec = SD_RADV_DEFAULT_DNS_LIFETIME_USEC;
+
+ search_domains = link->network->search_domains;
+ if (search_domains)
+ goto set_domains;
+
+ if (uplink) {
+ if (uplink->network == NULL) {
+ log_link_debug(uplink, "Cannot fetch DNS search domains as uplink interface is not managed by us");
+ return 0;
+ }
+
+ search_domains = uplink->network->search_domains;
+ if (search_domains)
+ goto set_domains;
+ }
+
+ return 0;
+
+ set_domains:
+ return sd_radv_set_dnssl(link->radv,
+ DIV_ROUND_UP(lifetime_usec, USEC_PER_SEC),
+ search_domains);
+
+}
+
+int radv_emit_dns(Link *link) {
+ Link *uplink;
+ int r;
+
+ uplink = manager_find_uplink(link->manager, link);
+
+ r = radv_set_dns(link, uplink);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Could not set RA DNS: %m");
+
+ r = radv_set_domains(link, uplink);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Could not set RA Domains: %m");
+
+ return 0;
+}
+
+int radv_configure(Link *link) {
+ int r;
+ Prefix *p;
+
+ assert(link);
+ assert(link->network);
+
+ r = sd_radv_new(&link->radv);
+ if (r < 0)
+ return r;
+
+ r = sd_radv_attach_event(link->radv, NULL, 0);
+ if (r < 0)
+ return r;
+
+ r = sd_radv_set_mac(link->radv, &link->mac);
+ if (r < 0)
+ return r;
+
+ r = sd_radv_set_ifindex(link->radv, link->ifindex);
+ if (r < 0)
+ return r;
+
+ r = sd_radv_set_managed_information(link->radv, link->network->router_managed);
+ if (r < 0)
+ return r;
+
+ r = sd_radv_set_other_information(link->radv, link->network->router_other_information);
+ if (r < 0)
+ return r;
+
+ /* a value of 0xffffffff represents infinity, 0x0 means this host is
+ not a router */
+ r = sd_radv_set_router_lifetime(link->radv,
+ DIV_ROUND_UP(link->network->router_lifetime_usec, USEC_PER_SEC));
+ 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;
+ }
+
+ if (IN_SET(link->network->router_prefix_delegation,
+ RADV_PREFIX_DELEGATION_STATIC,
+ RADV_PREFIX_DELEGATION_BOTH)) {
+
+ LIST_FOREACH(prefixes, p, link->network->static_prefixes) {
+ r = sd_radv_add_prefix(link->radv, p->radv_prefix, false);
+ if (r == -EEXIST)
+ continue;
+ if (r == -ENOEXEC) {
+ log_link_warning_errno(link, r, "[IPv6Prefix] section configured without Prefix= setting, ignoring section.");
+ continue;
+ }
+ if (r < 0)
+ return r;
+ }
+ }
+
+ return radv_emit_dns(link);
+}
diff --git a/src/network/networkd-radv.h b/src/network/networkd-radv.h
new file mode 100644
index 0000000..bb88f8a
--- /dev/null
+++ b/src/network/networkd-radv.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+/***
+ Copyright © 2017 Intel Corporation. All rights reserved.
+***/
+
+#include "conf-parser.h"
+#include "networkd-address.h"
+#include "networkd-link.h"
+
+typedef struct Prefix Prefix;
+
+struct Prefix {
+ Network *network;
+ NetworkConfigSection *section;
+
+ sd_radv_prefix *radv_prefix;
+
+ LIST_FIELDS(Prefix, prefixes);
+};
+
+int prefix_new(Prefix **ret);
+void prefix_free(Prefix *prefix);
+int prefix_new_static(Network *network, const char *filename, unsigned section, Prefix **ret);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Prefix*, prefix_free);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_router_prefix_delegation);
+CONFIG_PARSER_PROTOTYPE(config_parse_router_preference);
+CONFIG_PARSER_PROTOTYPE(config_parse_prefix);
+CONFIG_PARSER_PROTOTYPE(config_parse_prefix_flags);
+CONFIG_PARSER_PROTOTYPE(config_parse_prefix_lifetime);
+
+int radv_emit_dns(Link *link);
+int radv_configure(Link *link);
diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c
new file mode 100644
index 0000000..5553a7e
--- /dev/null
+++ b/src/network/networkd-route.c
@@ -0,0 +1,1192 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <linux/icmpv6.h>
+
+#include "alloc-util.h"
+#include "conf-parser.h"
+#include "in-addr-util.h"
+#include "missing_network.h"
+#include "netlink-util.h"
+#include "networkd-manager.h"
+#include "networkd-route.h"
+#include "parse-util.h"
+#include "set.h"
+#include "string-util.h"
+#include "sysctl-util.h"
+#include "util.h"
+
+#define ROUTES_DEFAULT_MAX_PER_FAMILY 4096U
+
+static unsigned routes_max(void) {
+ static thread_local unsigned cached = 0;
+
+ _cleanup_free_ char *s4 = NULL, *s6 = NULL;
+ unsigned val4 = ROUTES_DEFAULT_MAX_PER_FAMILY, val6 = ROUTES_DEFAULT_MAX_PER_FAMILY;
+
+ if (cached > 0)
+ return cached;
+
+ if (sysctl_read("net/ipv4/route/max_size", &s4) >= 0) {
+ truncate_nl(s4);
+ if (safe_atou(s4, &val4) >= 0 &&
+ val4 == 2147483647U)
+ /* This is the default "no limit" value in the kernel */
+ val4 = ROUTES_DEFAULT_MAX_PER_FAMILY;
+ }
+
+ if (sysctl_read("net/ipv6/route/max_size", &s6) >= 0) {
+ truncate_nl(s6);
+ (void) safe_atou(s6, &val6);
+ }
+
+ cached = MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val4) +
+ MAX(ROUTES_DEFAULT_MAX_PER_FAMILY, val6);
+ return cached;
+}
+
+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_INFINITY,
+ .quickack = -1,
+ };
+
+ *ret = TAKE_PTR(route);
+
+ return 0;
+}
+
+int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret) {
+ _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
+ _cleanup_(route_freep) Route *route = NULL;
+ int r;
+
+ assert(network);
+ assert(ret);
+ assert(!!filename == (section_line > 0));
+
+ if (filename) {
+ r = network_config_section_new(filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ route = hashmap_get(network->routes_by_section, n);
+ if (route) {
+ *ret = TAKE_PTR(route);
+
+ return 0;
+ }
+ }
+
+ if (network->n_static_routes >= routes_max())
+ return -E2BIG;
+
+ r = route_new(&route);
+ if (r < 0)
+ return r;
+
+ route->protocol = RTPROT_STATIC;
+ route->network = network;
+ LIST_PREPEND(routes, network->static_routes, route);
+ network->n_static_routes++;
+
+ if (filename) {
+ route->section = TAKE_PTR(n);
+
+ r = hashmap_ensure_allocated(&network->routes_by_section, &network_config_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(network->routes_by_section, route->section, route);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(route);
+
+ return 0;
+}
+
+void route_free(Route *route) {
+ if (!route)
+ return;
+
+ if (route->network) {
+ LIST_REMOVE(routes, route->network->static_routes, route);
+
+ assert(route->network->n_static_routes > 0);
+ route->network->n_static_routes--;
+
+ if (route->section)
+ hashmap_remove(route->network->routes_by_section, route->section);
+ }
+
+ network_config_section_free(route->section);
+
+ if (route->link) {
+ set_remove(route->link->routes, route);
+ set_remove(route->link->routes_foreign, route);
+ }
+
+ sd_event_source_unref(route->expire);
+
+ free(route);
+}
+
+static void route_hash_func(const Route *route, struct siphash *state) {
+ assert(route);
+
+ siphash24_compress(&route->family, sizeof(route->family), state);
+
+ switch (route->family) {
+ case AF_INET:
+ case AF_INET6:
+ /* Equality of routes are given by the 4-touple
+ (dst_prefix,dst_prefixlen,tos,priority,table) */
+ siphash24_compress(&route->dst, FAMILY_ADDRESS_SIZE(route->family), state);
+ siphash24_compress(&route->dst_prefixlen, sizeof(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);
+
+ break;
+ default:
+ /* treat any other address family as AF_UNSPEC */
+ break;
+ }
+}
+
+static int route_compare_func(const Route *a, const Route *b) {
+ int r;
+
+ r = CMP(a->family, b->family);
+ if (r != 0)
+ return r;
+
+ switch (a->family) {
+ case AF_INET:
+ case AF_INET6:
+ r = CMP(a->dst_prefixlen, b->dst_prefixlen);
+ if (r != 0)
+ return r;
+
+ r = CMP(a->tos, b->tos);
+ if (r != 0)
+ return r;
+
+ r = CMP(a->priority, b->priority);
+ if (r != 0)
+ return r;
+
+ r = CMP(a->table, b->table);
+ if (r != 0)
+ return r;
+
+ return memcmp(&a->dst, &b->dst, FAMILY_ADDRESS_SIZE(a->family));
+ default:
+ /* treat any other address family as AF_UNSPEC */
+ return 0;
+ }
+}
+
+DEFINE_PRIVATE_HASH_OPS(route_hash_ops, Route, route_hash_func, route_compare_func);
+
+bool route_equal(Route *r1, Route *r2) {
+ if (r1 == r2)
+ return true;
+
+ if (!r1 || !r2)
+ return false;
+
+ return route_compare_func(r1, r2) == 0;
+}
+
+int route_get(Link *link,
+ int family,
+ const union in_addr_union *dst,
+ unsigned char dst_prefixlen,
+ unsigned char tos,
+ uint32_t priority,
+ uint32_t table,
+ Route **ret) {
+
+ Route route, *existing;
+
+ assert(link);
+ assert(dst);
+
+ route = (Route) {
+ .family = family,
+ .dst = *dst,
+ .dst_prefixlen = dst_prefixlen,
+ .tos = tos,
+ .priority = priority,
+ .table = table,
+ };
+
+ existing = set_get(link->routes, &route);
+ if (existing) {
+ if (ret)
+ *ret = existing;
+ return 1;
+ }
+
+ existing = set_get(link->routes_foreign, &route);
+ if (existing) {
+ if (ret)
+ *ret = existing;
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+static int route_add_internal(
+ Link *link,
+ Set **routes,
+ int family,
+ const union in_addr_union *dst,
+ unsigned char dst_prefixlen,
+ unsigned char tos,
+ uint32_t priority,
+ uint32_t table,
+ Route **ret) {
+
+ _cleanup_(route_freep) Route *route = NULL;
+ int r;
+
+ assert(link);
+ assert(routes);
+ assert(dst);
+
+ r = route_new(&route);
+ if (r < 0)
+ return r;
+
+ route->family = family;
+ route->dst = *dst;
+ route->dst_prefixlen = dst_prefixlen;
+ route->tos = tos;
+ route->priority = priority;
+ route->table = table;
+
+ r = set_ensure_allocated(routes, &route_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = set_put(*routes, route);
+ if (r < 0)
+ return r;
+
+ route->link = link;
+
+ if (ret)
+ *ret = route;
+
+ route = NULL;
+
+ return 0;
+}
+
+int route_add_foreign(
+ Link *link,
+ int family,
+ const union in_addr_union *dst,
+ unsigned char dst_prefixlen,
+ unsigned char tos,
+ uint32_t priority,
+ uint32_t table,
+ Route **ret) {
+
+ return route_add_internal(link, &link->routes_foreign, family, dst, dst_prefixlen, tos, priority, table, ret);
+}
+
+int route_add(Link *link,
+ int family,
+ const union in_addr_union *dst,
+ unsigned char dst_prefixlen,
+ unsigned char tos,
+ uint32_t priority,
+ uint32_t table,
+ Route **ret) {
+
+ Route *route;
+ int r;
+
+ r = route_get(link, family, dst, dst_prefixlen, tos, priority, table, &route);
+ if (r == -ENOENT) {
+ /* Route does not exist, create a new one */
+ r = route_add_internal(link, &link->routes, family, dst, dst_prefixlen, tos, priority, table, &route);
+ if (r < 0)
+ return r;
+ } else if (r == 0) {
+ /* Take over a foreign route */
+ r = set_ensure_allocated(&link->routes, &route_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = set_put(link->routes, route);
+ if (r < 0)
+ return r;
+
+ set_remove(link->routes_foreign, route);
+ } else if (r == 1) {
+ /* Route exists, do nothing */
+ ;
+ } else
+ return r;
+
+ if (ret)
+ *ret = route;
+
+ return 0;
+}
+
+void route_update(Route *route,
+ const union in_addr_union *src,
+ unsigned char src_prefixlen,
+ const union in_addr_union *gw,
+ const union in_addr_union *prefsrc,
+ unsigned char scope,
+ unsigned char protocol,
+ unsigned char type) {
+
+ assert(route);
+ assert(src || src_prefixlen == 0);
+
+ route->src = src ? *src : IN_ADDR_NULL;
+ route->src_prefixlen = src_prefixlen;
+ route->gw = gw ? *gw : IN_ADDR_NULL;
+ route->prefsrc = prefsrc ? *prefsrc : IN_ADDR_NULL;
+ route->scope = scope;
+ route->protocol = protocol;
+ route->type = type;
+}
+
+static int route_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(m);
+ assert(link);
+ assert(link->ifname);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -ESRCH)
+ log_link_warning_errno(link, r, "Could not drop route: %m");
+
+ return 1;
+}
+
+int route_remove(Route *route, Link *link,
+ link_netlink_message_handler_t callback) {
+
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ int r;
+
+ assert(link);
+ assert(link->manager);
+ assert(link->manager->rtnl);
+ assert(link->ifindex > 0);
+ assert(IN_SET(route->family, AF_INET, AF_INET6));
+
+ r = sd_rtnl_message_new_route(link->manager->rtnl, &req,
+ RTM_DELROUTE, route->family,
+ route->protocol);
+ if (r < 0)
+ return log_error_errno(r, "Could not create RTM_DELROUTE message: %m");
+
+ if (!in_addr_is_null(route->family, &route->gw)) {
+ if (route->family == AF_INET)
+ r = sd_netlink_message_append_in_addr(req, RTA_GATEWAY, &route->gw.in);
+ else if (route->family == AF_INET6)
+ r = sd_netlink_message_append_in6_addr(req, RTA_GATEWAY, &route->gw.in6);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_GATEWAY attribute: %m");
+ }
+
+ if (route->dst_prefixlen) {
+ if (route->family == AF_INET)
+ r = sd_netlink_message_append_in_addr(req, RTA_DST, &route->dst.in);
+ else if (route->family == AF_INET6)
+ r = sd_netlink_message_append_in6_addr(req, RTA_DST, &route->dst.in6);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_DST attribute: %m");
+
+ r = sd_rtnl_message_route_set_dst_prefixlen(req, route->dst_prefixlen);
+ if (r < 0)
+ return log_error_errno(r, "Could not set destination prefix length: %m");
+ }
+
+ if (route->src_prefixlen) {
+ if (route->family == AF_INET)
+ r = sd_netlink_message_append_in_addr(req, RTA_SRC, &route->src.in);
+ else if (route->family == AF_INET6)
+ r = sd_netlink_message_append_in6_addr(req, RTA_SRC, &route->src.in6);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_SRC attribute: %m");
+
+ r = sd_rtnl_message_route_set_src_prefixlen(req, route->src_prefixlen);
+ if (r < 0)
+ return log_error_errno(r, "Could not set source prefix length: %m");
+ }
+
+ if (!in_addr_is_null(route->family, &route->prefsrc)) {
+ if (route->family == AF_INET)
+ r = sd_netlink_message_append_in_addr(req, RTA_PREFSRC, &route->prefsrc.in);
+ else if (route->family == AF_INET6)
+ r = sd_netlink_message_append_in6_addr(req, RTA_PREFSRC, &route->prefsrc.in6);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_PREFSRC attribute: %m");
+ }
+
+ r = sd_rtnl_message_route_set_scope(req, route->scope);
+ if (r < 0)
+ return log_error_errno(r, "Could not set scope: %m");
+
+ r = sd_netlink_message_append_u32(req, RTA_PRIORITY, route->priority);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_PRIORITY attribute: %m");
+
+ if (!IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW)) {
+ r = sd_netlink_message_append_u32(req, RTA_OIF, link->ifindex);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_OIF attribute: %m");
+ }
+
+ r = netlink_call_async(link->manager->rtnl, NULL, req,
+ callback ?: route_remove_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_error_errno(r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ return 0;
+}
+
+int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdata) {
+ Route *route = userdata;
+ int r;
+
+ assert(route);
+
+ r = route_remove(route, route->link, NULL);
+ if (r < 0)
+ log_warning_errno(r, "Could not remove route: %m");
+ else
+ route_free(route);
+
+ return 1;
+}
+
+int route_configure(
+ Route *route,
+ Link *link,
+ link_netlink_message_handler_t callback) {
+
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
+ _cleanup_(sd_event_source_unrefp) sd_event_source *expire = NULL;
+ usec_t lifetime;
+ int r;
+
+ assert(link);
+ assert(link->manager);
+ assert(link->manager->rtnl);
+ assert(link->ifindex > 0);
+ assert(IN_SET(route->family, AF_INET, AF_INET6));
+ assert(callback);
+
+ if (route_get(link, route->family, &route->dst, route->dst_prefixlen, route->tos, route->priority, route->table, NULL) <= 0 &&
+ set_size(link->routes) >= routes_max())
+ return -E2BIG;
+
+ r = sd_rtnl_message_new_route(link->manager->rtnl, &req,
+ RTM_NEWROUTE, route->family,
+ route->protocol);
+ if (r < 0)
+ return log_error_errno(r, "Could not create RTM_NEWROUTE message: %m");
+
+ if (!in_addr_is_null(route->family, &route->gw)) {
+ if (route->family == AF_INET)
+ r = sd_netlink_message_append_in_addr(req, RTA_GATEWAY, &route->gw.in);
+ else if (route->family == AF_INET6)
+ r = sd_netlink_message_append_in6_addr(req, RTA_GATEWAY, &route->gw.in6);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_GATEWAY attribute: %m");
+
+ r = sd_rtnl_message_route_set_family(req, route->family);
+ if (r < 0)
+ return log_error_errno(r, "Could not set route family: %m");
+ }
+
+ if (route->dst_prefixlen) {
+ if (route->family == AF_INET)
+ r = sd_netlink_message_append_in_addr(req, RTA_DST, &route->dst.in);
+ else if (route->family == AF_INET6)
+ r = sd_netlink_message_append_in6_addr(req, RTA_DST, &route->dst.in6);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_DST attribute: %m");
+
+ r = sd_rtnl_message_route_set_dst_prefixlen(req, route->dst_prefixlen);
+ if (r < 0)
+ return log_error_errno(r, "Could not set destination prefix length: %m");
+ }
+
+ if (route->src_prefixlen) {
+ if (route->family == AF_INET)
+ r = sd_netlink_message_append_in_addr(req, RTA_SRC, &route->src.in);
+ else if (route->family == AF_INET6)
+ r = sd_netlink_message_append_in6_addr(req, RTA_SRC, &route->src.in6);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_SRC attribute: %m");
+
+ r = sd_rtnl_message_route_set_src_prefixlen(req, route->src_prefixlen);
+ if (r < 0)
+ return log_error_errno(r, "Could not set source prefix length: %m");
+ }
+
+ if (!in_addr_is_null(route->family, &route->prefsrc)) {
+ if (route->family == AF_INET)
+ r = sd_netlink_message_append_in_addr(req, RTA_PREFSRC, &route->prefsrc.in);
+ else if (route->family == AF_INET6)
+ r = sd_netlink_message_append_in6_addr(req, RTA_PREFSRC, &route->prefsrc.in6);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_PREFSRC attribute: %m");
+ }
+
+ r = sd_rtnl_message_route_set_scope(req, route->scope);
+ if (r < 0)
+ return log_error_errno(r, "Could not set scope: %m");
+
+ r = sd_rtnl_message_route_set_flags(req, route->flags);
+ if (r < 0)
+ return log_error_errno(r, "Could not set flags: %m");
+
+ if (route->table != RT_TABLE_MAIN) {
+ if (route->table < 256) {
+ r = sd_rtnl_message_route_set_table(req, route->table);
+ if (r < 0)
+ return log_error_errno(r, "Could not set route table: %m");
+ } else {
+ r = sd_rtnl_message_route_set_table(req, RT_TABLE_UNSPEC);
+ if (r < 0)
+ return log_error_errno(r, "Could not set route table: %m");
+
+ /* Table attribute to allow more than 256. */
+ r = sd_netlink_message_append_data(req, RTA_TABLE, &route->table, sizeof(route->table));
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_TABLE attribute: %m");
+ }
+ }
+
+ r = sd_netlink_message_append_u32(req, RTA_PRIORITY, route->priority);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_PRIORITY attribute: %m");
+
+ r = sd_netlink_message_append_u8(req, RTA_PREF, route->pref);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_PREF attribute: %m");
+
+ if (route->lifetime != USEC_INFINITY && kernel_route_expiration_supported()) {
+ r = sd_netlink_message_append_u32(req, RTA_EXPIRES,
+ DIV_ROUND_UP(usec_sub_unsigned(route->lifetime, now(clock_boottime_or_monotonic())), USEC_PER_SEC));
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_EXPIRES attribute: %m");
+ }
+
+ r = sd_rtnl_message_route_set_type(req, route->type);
+ if (r < 0)
+ return log_error_errno(r, "Could not set route type: %m");
+
+ if (!IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW)) {
+ r = sd_netlink_message_append_u32(req, RTA_OIF, link->ifindex);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_OIF attribute: %m");
+ }
+
+ r = sd_netlink_message_open_container(req, RTA_METRICS);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_METRICS attribute: %m");
+
+ if (route->mtu > 0) {
+ r = sd_netlink_message_append_u32(req, RTAX_MTU, route->mtu);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTAX_MTU attribute: %m");
+ }
+
+ if (route->initcwnd > 0) {
+ r = sd_netlink_message_append_u32(req, RTAX_INITCWND, route->initcwnd);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTAX_INITCWND attribute: %m");
+ }
+
+ if (route->initrwnd > 0) {
+ r = sd_netlink_message_append_u32(req, RTAX_INITRWND, route->initrwnd);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTAX_INITRWND attribute: %m");
+ }
+
+ if (route->quickack != -1) {
+ r = sd_netlink_message_append_u32(req, RTAX_QUICKACK, route->quickack);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTAX_QUICKACK attribute: %m");
+ }
+
+ r = sd_netlink_message_close_container(req);
+ if (r < 0)
+ return log_error_errno(r, "Could not append RTA_METRICS attribute: %m");
+
+ r = netlink_call_async(link->manager->rtnl, NULL, req, callback,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_error_errno(r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ lifetime = route->lifetime;
+
+ r = route_add(link, route->family, &route->dst, route->dst_prefixlen, route->tos, route->priority, route->table, &route);
+ if (r < 0)
+ return log_error_errno(r, "Could not add route: %m");
+
+ /* TODO: drop expiration handling once it can be pushed into the kernel */
+ route->lifetime = lifetime;
+
+ if (route->lifetime != USEC_INFINITY && !kernel_route_expiration_supported()) {
+ r = sd_event_add_time(link->manager->event, &expire, clock_boottime_or_monotonic(),
+ route->lifetime, 0, route_expire_handler, route);
+ if (r < 0)
+ return log_error_errno(r, "Could not arm expiration timer: %m");
+ }
+
+ sd_event_source_unref(route->expire);
+ route->expire = TAKE_PTR(expire);
+
+ 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_freep) 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 treat
+ * this as the special '0' section */
+ r = route_new_static(network, NULL, 0, &n);
+ } else
+ r = route_new_static(network, filename, section_line, &n);
+
+ if (r < 0)
+ return r;
+
+ r = in_addr_from_string_auto(rvalue, &n->family, &n->gw);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Route is invalid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ TAKE_PTR(n);
+
+ return 0;
+}
+
+int config_parse_preferred_src(
+ 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_freep) 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 < 0)
+ return r;
+
+ r = in_addr_from_string_auto(rvalue, &n->family, &n->prefsrc);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, EINVAL,
+ "Preferred source is invalid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ TAKE_PTR(n);
+
+ return 0;
+}
+
+int config_parse_destination(
+ 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_freep) Route *n = NULL;
+ union in_addr_union *buffer;
+ unsigned char *prefixlen;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = route_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ if (streq(lvalue, "Destination")) {
+ buffer = &n->dst;
+ prefixlen = &n->dst_prefixlen;
+ } else if (streq(lvalue, "Source")) {
+ buffer = &n->src;
+ prefixlen = &n->src_prefixlen;
+ } else
+ assert_not_reached(lvalue);
+
+ r = in_addr_prefix_from_string_auto(rvalue, &n->family, buffer, prefixlen);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Route %s= prefix is invalid, ignoring assignment: %s",
+ lvalue, rvalue);
+ return 0;
+ }
+
+ TAKE_PTR(n);
+ return 0;
+}
+
+int config_parse_route_priority(
+ 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_freep) 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 < 0)
+ return r;
+
+ r = safe_atou32(rvalue, &n->priority);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Could not parse route priority \"%s\", ignoring assignment: %m", rvalue);
+ return 0;
+ }
+
+ TAKE_PTR(n);
+ return 0;
+}
+
+int config_parse_route_scope(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Network *network = userdata;
+ _cleanup_(route_freep) 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 < 0)
+ return r;
+
+ if (streq(rvalue, "host"))
+ n->scope = RT_SCOPE_HOST;
+ else if (streq(rvalue, "link"))
+ n->scope = RT_SCOPE_LINK;
+ else if (streq(rvalue, "global"))
+ n->scope = RT_SCOPE_UNIVERSE;
+ else {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Unknown route scope: %s", rvalue);
+ return 0;
+ }
+
+ TAKE_PTR(n);
+ return 0;
+}
+
+int config_parse_route_table(
+ 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_freep) Route *n = NULL;
+ Network *network = userdata;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = route_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ r = safe_atou32(rvalue, &n->table);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Could not parse route table number \"%s\", ignoring assignment: %m", rvalue);
+ return 0;
+ }
+
+ TAKE_PTR(n);
+ return 0;
+}
+
+int config_parse_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_freep) 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 < 0)
+ return r;
+
+ r = parse_boolean(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Could not parse gateway onlink \"%s\", ignoring assignment: %m", rvalue);
+ return 0;
+ }
+
+ SET_FLAG(n->flags, RTNH_F_ONLINK, r);
+ TAKE_PTR(n);
+ return 0;
+}
+
+int config_parse_ipv6_route_preference(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ Network *network = userdata;
+ _cleanup_(route_freep) Route *n = NULL;
+ int r;
+
+ r = route_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ if (streq(rvalue, "low"))
+ n->pref = ICMPV6_ROUTER_PREF_LOW;
+ else if (streq(rvalue, "medium"))
+ n->pref = ICMPV6_ROUTER_PREF_MEDIUM;
+ else if (streq(rvalue, "high"))
+ n->pref = ICMPV6_ROUTER_PREF_HIGH;
+ else {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Unknown route preference: %s", rvalue);
+ return 0;
+ }
+
+ TAKE_PTR(n);
+ return 0;
+}
+
+int config_parse_route_protocol(
+ 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_freep) Route *n = NULL;
+ int r;
+
+ r = route_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ if (streq(rvalue, "kernel"))
+ n->protocol = RTPROT_KERNEL;
+ else if (streq(rvalue, "boot"))
+ n->protocol = RTPROT_BOOT;
+ else if (streq(rvalue, "static"))
+ n->protocol = RTPROT_STATIC;
+ else {
+ r = safe_atou8(rvalue , &n->protocol);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Could not parse route protocol \"%s\", ignoring assignment: %m", rvalue);
+ return 0;
+ }
+ }
+
+ TAKE_PTR(n);
+ return 0;
+}
+
+int config_parse_route_type(
+ 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_freep) Route *n = NULL;
+ int r;
+
+ r = route_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ if (streq(rvalue, "unicast"))
+ n->type = RTN_UNICAST;
+ else if (streq(rvalue, "blackhole"))
+ n->type = RTN_BLACKHOLE;
+ else if (streq(rvalue, "unreachable"))
+ n->type = RTN_UNREACHABLE;
+ else if (streq(rvalue, "prohibit"))
+ n->type = RTN_PROHIBIT;
+ else if (streq(rvalue, "throw"))
+ n->type = RTN_THROW;
+ else {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Could not parse route type \"%s\", ignoring assignment: %m", rvalue);
+ return 0;
+ }
+
+ 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) {
+
+ _cleanup_(route_freep) Route *n = NULL;
+ Network *network = userdata;
+ uint64_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 < 0)
+ return r;
+
+ r = parse_size(rvalue, 1024, &k);
+ if (r < 0 || k > UINT32_MAX) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Could not parse TCP %s \"%s\" bytes, ignoring assignment: %m", rvalue, lvalue);
+ return 0;
+ }
+
+ if (streq(lvalue, "InitialCongestionWindow"))
+ n->initcwnd = k;
+ else if (streq(lvalue, "InitialAdvertisedReceiveWindow"))
+ n->initrwnd = k;
+ else {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse TCP %s: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ TAKE_PTR(n);
+ return 0;
+}
+
+int config_parse_quickack(
+ 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_freep) Route *n = NULL;
+ Network *network = userdata;
+ int k, r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = route_new_static(network, filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ k = parse_boolean(rvalue);
+ if (k < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse TCP quickack, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ n->quickack = !!k;
+ TAKE_PTR(n);
+ 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_freep) 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 < 0)
+ return r;
+
+ 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;
+}
diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h
new file mode 100644
index 0000000..4eddf51
--- /dev/null
+++ b/src/network/networkd-route.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "conf-parser.h"
+
+typedef struct Route Route;
+typedef struct NetworkConfigSection NetworkConfigSection;
+
+#include "networkd-network.h"
+
+struct Route {
+ Network *network;
+ NetworkConfigSection *section;
+
+ Link *link;
+
+ int family;
+ int quickack;
+
+ unsigned char dst_prefixlen;
+ unsigned char src_prefixlen;
+ unsigned char scope;
+ 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;
+ unsigned char pref;
+ unsigned flags;
+
+ union in_addr_union gw;
+ union in_addr_union dst;
+ union in_addr_union src;
+ union in_addr_union prefsrc;
+
+ usec_t lifetime;
+ sd_event_source *expire;
+
+ LIST_FIELDS(Route, routes);
+};
+
+int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret);
+int route_new(Route **ret);
+void route_free(Route *route);
+int route_configure(Route *route, Link *link, link_netlink_message_handler_t callback);
+int route_remove(Route *route, Link *link, link_netlink_message_handler_t callback);
+
+int route_get(Link *link, int family, const union in_addr_union *dst, unsigned char dst_prefixlen, unsigned char tos, uint32_t priority, uint32_t table, Route **ret);
+int route_add(Link *link, int family, const union in_addr_union *dst, unsigned char dst_prefixlen, unsigned char tos, uint32_t priority, uint32_t table, Route **ret);
+int route_add_foreign(Link *link, int family, const union in_addr_union *dst, unsigned char dst_prefixlen, unsigned char tos, uint32_t priority, uint32_t table, Route **ret);
+void route_update(Route *route, const union in_addr_union *src, unsigned char src_prefixlen, const union in_addr_union *gw, const union in_addr_union *prefsrc, unsigned char scope, unsigned char protocol, unsigned char type);
+bool route_equal(Route *r1, Route *r2);
+
+int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdata);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Route*, route_free);
+
+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_gateway_onlink);
+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_tcp_window);
+CONFIG_PARSER_PROTOTYPE(config_parse_quickack);
+CONFIG_PARSER_PROTOTYPE(config_parse_route_mtu);
diff --git a/src/network/networkd-routing-policy-rule.c b/src/network/networkd-routing-policy-rule.c
new file mode 100644
index 0000000..2dc7862
--- /dev/null
+++ b/src/network/networkd-routing-policy-rule.c
@@ -0,0 +1,1274 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <net/if.h>
+#include <linux/fib_rules.h>
+
+#include "alloc-util.h"
+#include "conf-parser.h"
+#include "fileio.h"
+#include "ip-protocol-list.h"
+#include "networkd-routing-policy-rule.h"
+#include "netlink-util.h"
+#include "networkd-manager.h"
+#include "parse-util.h"
+#include "socket-util.h"
+#include "string-util.h"
+#include "strv.h"
+
+int routing_policy_rule_new(RoutingPolicyRule **ret) {
+ RoutingPolicyRule *rule;
+
+ rule = new(RoutingPolicyRule, 1);
+ if (!rule)
+ return -ENOMEM;
+
+ *rule = (RoutingPolicyRule) {
+ .family = AF_INET,
+ .table = RT_TABLE_MAIN,
+ };
+
+ *ret = rule;
+ return 0;
+}
+
+void routing_policy_rule_free(RoutingPolicyRule *rule) {
+
+ if (!rule)
+ return;
+
+ if (rule->network) {
+ LIST_REMOVE(rules, rule->network->rules, rule);
+ assert(rule->network->n_rules > 0);
+ rule->network->n_rules--;
+
+ if (rule->section)
+ hashmap_remove(rule->network->rules_by_section, rule->section);
+ }
+
+ if (rule->manager) {
+ set_remove(rule->manager->rules, rule);
+ set_remove(rule->manager->rules_foreign, rule);
+ }
+
+ network_config_section_free(rule->section);
+ free(rule->iif);
+ free(rule->oif);
+ free(rule);
+}
+
+static void routing_policy_rule_hash_func(const RoutingPolicyRule *rule, struct siphash *state) {
+ assert(rule);
+
+ siphash24_compress(&rule->family, sizeof(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);
+
+ siphash24_compress(&rule->to, FAMILY_ADDRESS_SIZE(rule->family), state);
+ siphash24_compress(&rule->to_prefixlen, sizeof(rule->to_prefixlen), state);
+
+ siphash24_compress(&rule->tos, sizeof(rule->tos), state);
+ siphash24_compress(&rule->fwmark, sizeof(rule->fwmark), state);
+ siphash24_compress(&rule->table, sizeof(rule->table), 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);
+
+ if (rule->iif)
+ siphash24_compress(rule->iif, strlen(rule->iif), state);
+
+ if (rule->oif)
+ siphash24_compress(rule->oif, strlen(rule->oif), state);
+
+ break;
+ default:
+ /* treat any other address family as AF_UNSPEC */
+ break;
+ }
+}
+
+static int routing_policy_rule_compare_func(const RoutingPolicyRule *a, const RoutingPolicyRule *b) {
+ int r;
+
+ r = CMP(a->family, b->family);
+ if (r != 0)
+ return r;
+
+ switch (a->family) {
+ case AF_INET:
+ case AF_INET6:
+ r = CMP(a->from_prefixlen, b->from_prefixlen);
+ if (r != 0)
+ return r;
+
+ r = CMP(a->to_prefixlen, b->to_prefixlen);
+ if (r != 0)
+ return r;
+
+ r = CMP(a->tos, b->tos);
+ if (r != 0)
+ return r;
+
+ r = CMP(a->fwmask, b->fwmask);
+ if (r != 0)
+ return r;
+
+ r = CMP(a->table, b->table);
+ if (r != 0)
+ return r;
+
+ r = strcmp_ptr(a->iif, b->iif);
+ if (!r)
+ return r;
+
+ r = strcmp_ptr(a->oif, b->oif);
+ if (!r)
+ return r;
+
+ r = CMP(a->protocol, b->protocol);
+ if (r != 0)
+ return r;
+
+ r = memcmp(&a->sport, &b->sport, sizeof(a->sport));
+ if (r != 0)
+ return r;
+
+ r = memcmp(&a->dport, &b->dport, sizeof(a->dport));
+ if (r != 0)
+ return r;
+
+ r = memcmp(&a->from, &b->from, FAMILY_ADDRESS_SIZE(a->family));
+ if (r != 0)
+ return r;
+
+ return memcmp(&a->to, &b->to, FAMILY_ADDRESS_SIZE(a->family));
+
+ default:
+ /* treat any other address family as AF_UNSPEC */
+ return 0;
+ }
+}
+
+DEFINE_PRIVATE_HASH_OPS(routing_policy_rule_hash_ops, RoutingPolicyRule, routing_policy_rule_hash_func, routing_policy_rule_compare_func);
+
+int routing_policy_rule_get(Manager *m,
+ int family,
+ const union in_addr_union *from,
+ uint8_t from_prefixlen,
+ const union in_addr_union *to,
+ uint8_t to_prefixlen,
+ uint8_t tos,
+ uint32_t fwmark,
+ uint32_t table,
+ const char *iif,
+ const char *oif,
+ uint8_t protocol,
+ struct fib_rule_port_range *sport,
+ struct fib_rule_port_range *dport,
+ RoutingPolicyRule **ret) {
+
+ RoutingPolicyRule rule, *existing;
+
+ assert_return(m, -1);
+
+ rule = (RoutingPolicyRule) {
+ .family = family,
+ .from = *from,
+ .from_prefixlen = from_prefixlen,
+ .to = *to,
+ .to_prefixlen = to_prefixlen,
+ .tos = tos,
+ .fwmark = fwmark,
+ .table = table,
+ .iif = (char*) iif,
+ .oif = (char*) oif,
+ .protocol = protocol,
+ .sport = *sport,
+ .dport = *dport,
+ };
+
+ existing = set_get(m->rules, &rule);
+ if (existing) {
+ if (ret)
+ *ret = existing;
+ return 1;
+ }
+
+ existing = set_get(m->rules_foreign, &rule);
+ if (existing) {
+ if (ret)
+ *ret = existing;
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+int routing_policy_rule_make_local(Manager *m, RoutingPolicyRule *rule) {
+ int r;
+
+ assert(m);
+
+ if (set_contains(m->rules_foreign, rule)) {
+ set_remove(m->rules_foreign, rule);
+
+ r = set_ensure_allocated(&m->rules, &routing_policy_rule_hash_ops);
+ if (r < 0)
+ return r;
+
+ return set_put(m->rules, rule);
+ }
+
+ return -ENOENT;
+}
+
+static int routing_policy_rule_add_internal(Manager *m,
+ Set **rules,
+ int family,
+ const union in_addr_union *from,
+ uint8_t from_prefixlen,
+ const union in_addr_union *to,
+ uint8_t to_prefixlen,
+ uint8_t tos,
+ uint32_t fwmark,
+ uint32_t table,
+ const char *_iif,
+ const char *_oif,
+ uint8_t protocol,
+ const struct fib_rule_port_range *sport,
+ const struct fib_rule_port_range *dport,
+ RoutingPolicyRule **ret) {
+
+ _cleanup_(routing_policy_rule_freep) RoutingPolicyRule *rule = NULL;
+ _cleanup_free_ char *iif = NULL, *oif = NULL;
+ int r;
+
+ assert_return(rules, -EINVAL);
+
+ if (_iif) {
+ iif = strdup(_iif);
+ if (!iif)
+ return -ENOMEM;
+ }
+
+ if (_oif) {
+ oif = strdup(_oif);
+ if (!oif)
+ return -ENOMEM;
+ }
+
+ r = routing_policy_rule_new(&rule);
+ if (r < 0)
+ return r;
+
+ rule->manager = m;
+ rule->family = family;
+ rule->from = *from;
+ rule->from_prefixlen = from_prefixlen;
+ rule->to = *to;
+ rule->to_prefixlen = to_prefixlen;
+ rule->tos = tos;
+ rule->fwmark = fwmark;
+ rule->table = table;
+ rule->iif = iif;
+ rule->oif = oif;
+ rule->protocol = protocol;
+ rule->sport = *sport;
+ rule->dport = *dport;
+
+ r = set_ensure_allocated(rules, &routing_policy_rule_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = set_put(*rules, rule);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = rule;
+
+ rule = NULL;
+ iif = oif = NULL;
+
+ return 0;
+}
+
+int routing_policy_rule_add(Manager *m,
+ int family,
+ const union in_addr_union *from,
+ uint8_t from_prefixlen,
+ const union in_addr_union *to,
+ uint8_t to_prefixlen,
+ uint8_t tos,
+ uint32_t fwmark,
+ uint32_t table,
+ const char *iif,
+ const char *oif,
+ uint8_t protocol,
+ const struct fib_rule_port_range *sport,
+ const struct fib_rule_port_range *dport,
+ RoutingPolicyRule **ret) {
+
+ return routing_policy_rule_add_internal(m, &m->rules, family, from, from_prefixlen, to, to_prefixlen, tos, fwmark, table, iif, oif, protocol, sport, dport, ret);
+}
+
+int routing_policy_rule_add_foreign(Manager *m,
+ int family,
+ const union in_addr_union *from,
+ uint8_t from_prefixlen,
+ const union in_addr_union *to,
+ uint8_t to_prefixlen,
+ uint8_t tos,
+ uint32_t fwmark,
+ uint32_t table,
+ const char *iif,
+ const char *oif,
+ uint8_t protocol,
+ const struct fib_rule_port_range *sport,
+ const struct fib_rule_port_range *dport,
+ RoutingPolicyRule **ret) {
+ return routing_policy_rule_add_internal(m, &m->rules_foreign, family, from, from_prefixlen, to, to_prefixlen, tos, fwmark, table, iif, oif, protocol, sport, dport, ret);
+}
+
+static int routing_policy_rule_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(m);
+ assert(link);
+ assert(link->ifname);
+
+ link->routing_policy_rule_remove_messages--;
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0)
+ log_link_warning_errno(link, r, "Could not drop routing policy rule: %m");
+
+ return 1;
+}
+
+int routing_policy_rule_remove(RoutingPolicyRule *routing_policy_rule, Link *link, link_netlink_message_handler_t callback) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+ int r;
+
+ assert(routing_policy_rule);
+ assert(link);
+ assert(link->manager);
+ assert(link->manager->rtnl);
+ assert(link->ifindex > 0);
+ assert(IN_SET(routing_policy_rule->family, AF_INET, AF_INET6));
+
+ r = sd_rtnl_message_new_routing_policy_rule(link->manager->rtnl, &m, RTM_DELRULE, routing_policy_rule->family);
+ if (r < 0)
+ return log_error_errno(r, "Could not allocate RTM_DELRULE message: %m");
+
+ if (!in_addr_is_null(routing_policy_rule->family, &routing_policy_rule->from)) {
+ if (routing_policy_rule->family == AF_INET)
+ r = sd_netlink_message_append_in_addr(m, FRA_SRC, &routing_policy_rule->from.in);
+ else
+ r = sd_netlink_message_append_in6_addr(m, FRA_SRC, &routing_policy_rule->from.in6);
+
+ if (r < 0)
+ return log_error_errno(r, "Could not append FRA_SRC attribute: %m");
+
+ r = sd_rtnl_message_routing_policy_rule_set_rtm_src_prefixlen(m, routing_policy_rule->from_prefixlen);
+ if (r < 0)
+ return log_error_errno(r, "Could not set source prefix length: %m");
+ }
+
+ if (!in_addr_is_null(routing_policy_rule->family, &routing_policy_rule->to)) {
+ if (routing_policy_rule->family == AF_INET)
+ r = sd_netlink_message_append_in_addr(m, FRA_DST, &routing_policy_rule->to.in);
+ else
+ r = sd_netlink_message_append_in6_addr(m, FRA_DST, &routing_policy_rule->to.in6);
+
+ if (r < 0)
+ return log_error_errno(r, "Could not append FRA_DST attribute: %m");
+
+ r = sd_rtnl_message_routing_policy_rule_set_rtm_dst_prefixlen(m, routing_policy_rule->to_prefixlen);
+ if (r < 0)
+ return log_error_errno(r, "Could not set destination prefix length: %m");
+ }
+
+ r = netlink_call_async(link->manager->rtnl, NULL, m,
+ callback ?: routing_policy_rule_remove_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_error_errno(r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ return 0;
+}
+
+static int routing_policy_rule_new_static(Network *network, const char *filename, unsigned section_line, RoutingPolicyRule **ret) {
+ _cleanup_(routing_policy_rule_freep) RoutingPolicyRule *rule = NULL;
+ _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
+ int r;
+
+ assert(network);
+ assert(ret);
+ assert(!!filename == (section_line > 0));
+
+ if (filename) {
+ r = network_config_section_new(filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ rule = hashmap_get(network->rules_by_section, n);
+ if (rule) {
+ *ret = TAKE_PTR(rule);
+
+ return 0;
+ }
+ }
+
+ r = routing_policy_rule_new(&rule);
+ if (r < 0)
+ return r;
+
+ rule->network = network;
+ LIST_APPEND(rules, network->rules, rule);
+ network->n_rules++;
+
+ if (filename) {
+ rule->section = TAKE_PTR(n);
+
+ r = hashmap_ensure_allocated(&network->rules_by_section, &network_config_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(network->rules_by_section, rule->section, rule);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(rule);
+
+ return 0;
+}
+
+static int routing_policy_rule_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) {
+ int r;
+
+ assert(rtnl);
+ assert(m);
+ assert(link);
+ assert(link->ifname);
+ assert(link->routing_policy_rule_messages > 0);
+
+ link->routing_policy_rule_messages--;
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_netlink_message_get_errno(m);
+ if (r < 0 && r != -EEXIST)
+ log_link_warning_errno(link, r, "Could not add routing policy rule: %m");
+
+ if (link->routing_policy_rule_messages == 0) {
+ log_link_debug(link, "Routing policy rule configured");
+ link->routing_policy_rules_configured = true;
+ link_check_ready(link);
+ }
+
+ return 1;
+}
+
+int routing_policy_rule_configure(RoutingPolicyRule *rule, Link *link, link_netlink_message_handler_t callback, bool update) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+ int r;
+
+ assert(rule);
+ assert(link);
+ assert(link->ifindex > 0);
+ assert(link->manager);
+ assert(link->manager->rtnl);
+
+ r = sd_rtnl_message_new_routing_policy_rule(link->manager->rtnl, &m, RTM_NEWRULE, rule->family);
+ if (r < 0)
+ return log_error_errno(r, "Could not allocate RTM_NEWRULE message: %m");
+
+ if (!in_addr_is_null(rule->family, &rule->from)) {
+ if (rule->family == AF_INET)
+ r = sd_netlink_message_append_in_addr(m, FRA_SRC, &rule->from.in);
+ else
+ r = sd_netlink_message_append_in6_addr(m, FRA_SRC, &rule->from.in6);
+
+ if (r < 0)
+ return log_error_errno(r, "Could not append FRA_SRC attribute: %m");
+
+ r = sd_rtnl_message_routing_policy_rule_set_rtm_src_prefixlen(m, rule->from_prefixlen);
+ if (r < 0)
+ return log_error_errno(r, "Could not set source prefix length: %m");
+ }
+
+ if (!in_addr_is_null(rule->family, &rule->to)) {
+ if (rule->family == AF_INET)
+ r = sd_netlink_message_append_in_addr(m, FRA_DST, &rule->to.in);
+ else
+ r = sd_netlink_message_append_in6_addr(m, FRA_DST, &rule->to.in6);
+
+ if (r < 0)
+ return log_error_errno(r, "Could not append FRA_DST attribute: %m");
+
+ r = sd_rtnl_message_routing_policy_rule_set_rtm_dst_prefixlen(m, rule->to_prefixlen);
+ if (r < 0)
+ return log_error_errno(r, "Could not set destination prefix length: %m");
+ }
+
+ r = sd_netlink_message_append_u32(m, FRA_PRIORITY, rule->priority);
+ if (r < 0)
+ return log_error_errno(r, "Could not append FRA_PRIORITY attribute: %m");
+
+ if (rule->tos > 0) {
+ r = sd_rtnl_message_routing_policy_rule_set_tos(m, rule->tos);
+ if (r < 0)
+ return log_error_errno(r, "Could not set ip rule tos: %m");
+ }
+
+ if (rule->table < 256) {
+ r = sd_rtnl_message_routing_policy_rule_set_table(m, rule->table);
+ if (r < 0)
+ return log_error_errno(r, "Could not set ip rule table: %m");
+ } else {
+ r = sd_rtnl_message_routing_policy_rule_set_table(m, RT_TABLE_UNSPEC);
+ if (r < 0)
+ return log_error_errno(r, "Could not set ip rule table: %m");
+
+ r = sd_netlink_message_append_u32(m, FRA_TABLE, rule->table);
+ if (r < 0)
+ return log_error_errno(r, "Could not append FRA_TABLE attribute: %m");
+ }
+
+ if (rule->fwmark > 0) {
+ r = sd_netlink_message_append_u32(m, FRA_FWMARK, rule->fwmark);
+ if (r < 0)
+ return log_error_errno(r, "Could not append FRA_FWMARK attribute: %m");
+ }
+
+ if (rule->fwmask > 0) {
+ r = sd_netlink_message_append_u32(m, FRA_FWMASK, rule->fwmask);
+ if (r < 0)
+ return log_error_errno(r, "Could not append FRA_FWMASK attribute: %m");
+ }
+
+ if (rule->iif) {
+ r = sd_netlink_message_append_string(m, FRA_IFNAME, rule->iif);
+ if (r < 0)
+ return log_error_errno(r, "Could not append FRA_IFNAME attribute: %m");
+ }
+
+ if (rule->oif) {
+ r = sd_netlink_message_append_string(m, FRA_OIFNAME, rule->oif);
+ if (r < 0)
+ return log_error_errno(r, "Could not append FRA_OIFNAME attribute: %m");
+ }
+
+ r = sd_netlink_message_append_u8(m, FRA_IP_PROTO, rule->protocol);
+ if (r < 0)
+ return log_error_errno(r, "Could not append FRA_IP_PROTO attribute: %m");
+
+ if (rule->sport.start != 0 || rule->sport.end != 0) {
+ r = sd_netlink_message_append_data(m, FRA_SPORT_RANGE, &rule->sport, sizeof(rule->sport));
+ if (r < 0)
+ return log_error_errno(r, "Could not append FRA_SPORT_RANGE attribute: %m");
+ }
+
+ if (rule->dport.start != 0 || rule->dport.end != 0) {
+ r = sd_netlink_message_append_data(m, FRA_DPORT_RANGE, &rule->dport, sizeof(rule->dport));
+ if (r < 0)
+ return log_error_errno(r, "Could not append FRA_DPORT_RANGE attribute: %m");
+ }
+
+ if (rule->invert_rule) {
+ r = sd_rtnl_message_routing_policy_rule_set_flags(m, FIB_RULE_INVERT);
+ if (r < 0)
+ return log_error_errno(r, "Could not append FIB_RULE_INVERT attribute: %m");
+ }
+
+ rule->link = link;
+
+ r = netlink_call_async(link->manager->rtnl, NULL, m,
+ callback ?: routing_policy_rule_handler,
+ link_netlink_destroy_callback, link);
+ if (r < 0)
+ return log_error_errno(r, "Could not send rtnetlink message: %m");
+
+ link_ref(link);
+
+ r = routing_policy_rule_add(link->manager, rule->family, &rule->from, rule->from_prefixlen, &rule->to,
+ rule->to_prefixlen, rule->tos, rule->fwmark, rule->table, rule->iif, rule->oif, rule->protocol, &rule->sport, &rule->dport, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Could not add rule: %m");
+
+ return 0;
+}
+
+static int parse_fwmark_fwmask(const char *s, uint32_t *fwmark, uint32_t *fwmask) {
+ _cleanup_free_ char *f = NULL;
+ char *p;
+ int r;
+
+ assert(s);
+
+ f = strdup(s);
+ if (!f)
+ return -ENOMEM;
+
+ p = strchr(f, '/');
+ if (p)
+ *p++ = '\0';
+
+ r = safe_atou32(f, fwmark);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse RPDB rule firewall mark, ignoring: %s", f);
+
+ if (p) {
+ r = safe_atou32(p, fwmask);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse RPDB rule mask, ignoring: %s", f);
+ }
+
+ return 0;
+}
+
+int config_parse_routing_policy_rule_tos(
+ 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_freep) 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 r;
+
+ r = safe_atou8(rvalue, &n->tos);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse RPDB rule tos, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ n = NULL;
+
+ return 0;
+}
+
+int config_parse_routing_policy_rule_priority(
+ 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_freep) 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 r;
+
+ r = safe_atou32(rvalue, &n->priority);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse RPDB rule priority, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ n = NULL;
+
+ return 0;
+}
+
+int config_parse_routing_policy_rule_table(
+ 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_freep) 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 r;
+
+ r = safe_atou32(rvalue, &n->table);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse RPDB rule table, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ n = NULL;
+
+ return 0;
+}
+
+int config_parse_routing_policy_rule_fwmark_mask(
+ 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_freep) 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 r;
+
+ r = parse_fwmark_fwmask(rvalue, &n->fwmark, &n->fwmask);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse RPDB rule firewall mark or mask, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ n = NULL;
+
+ return 0;
+}
+
+int config_parse_routing_policy_rule_prefix(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ _cleanup_(routing_policy_rule_freep) RoutingPolicyRule *n = NULL;
+ Network *network = userdata;
+ union in_addr_union *buffer;
+ uint8_t *prefixlen;
+ 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 r;
+
+ if (streq(lvalue, "To")) {
+ buffer = &n->to;
+ prefixlen = &n->to_prefixlen;
+ } else {
+ buffer = &n->from;
+ prefixlen = &n->from_prefixlen;
+ }
+
+ r = in_addr_prefix_from_string_auto(rvalue, &n->family, buffer, prefixlen);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "RPDB rule prefix is invalid, ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ n = NULL;
+
+ return 0;
+}
+
+int config_parse_routing_policy_rule_device(
+ 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_freep) 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 r;
+
+ if (!ifname_valid(rvalue)) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse '%s' interface name, ignoring: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ if (streq(lvalue, "IncomingInterface")) {
+ r = free_and_strdup(&n->iif, rvalue);
+ if (r < 0)
+ return log_oom();
+ } else {
+ r = free_and_strdup(&n->oif, rvalue);
+ if (r < 0)
+ return log_oom();
+ }
+
+ n = NULL;
+
+ return 0;
+}
+
+int config_parse_routing_policy_rule_port_range(
+ 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_freep) RoutingPolicyRule *n = NULL;
+ Network *network = userdata;
+ uint16_t low, high;
+ 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 r;
+
+ r = parse_ip_port_range(rvalue, &low, &high);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse routing policy rule port range '%s'", rvalue);
+ return 0;
+ }
+
+ if (streq(lvalue, "SourcePort")) {
+ n->sport.start = low;
+ n->sport.end = high;
+ } else {
+ n->dport.start = low;
+ n->dport.end = high;
+ }
+
+ n = NULL;
+
+ return 0;
+}
+
+int config_parse_routing_policy_rule_ip_protocol(
+ 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_freep) 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 r;
+
+ r = parse_ip_protocol(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse IP protocol '%s' for routing policy rule, ignoring: %m", rvalue);
+ return 0;
+ }
+
+ n->protocol = r;
+
+ n = NULL;
+
+ return 0;
+}
+
+int config_parse_routing_policy_rule_invert(
+ 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_freep) 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 r;
+
+ r = parse_boolean(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse RPDB rule invert, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ n->invert_rule = r;
+
+ n = NULL;
+
+ return 0;
+}
+
+static int routing_policy_rule_read_full_file(const char *state_file, char **ret) {
+ _cleanup_free_ char *s = NULL;
+ size_t size;
+ int r;
+
+ assert(state_file);
+
+ r = read_full_file(state_file, &s, &size);
+ if (r == -ENOENT)
+ return -ENODATA;
+ if (r < 0)
+ return r;
+ if (size <= 0)
+ return -ENODATA;
+
+ *ret = TAKE_PTR(s);
+
+ return size;
+}
+
+int routing_policy_serialize_rules(Set *rules, FILE *f) {
+ RoutingPolicyRule *rule = NULL;
+ Iterator i;
+ int r;
+
+ assert(f);
+
+ SET_FOREACH(rule, rules, i) {
+ _cleanup_free_ char *from_str = NULL, *to_str = NULL;
+ bool space = false;
+
+ fputs("RULE=", f);
+
+ if (!in_addr_is_null(rule->family, &rule->from)) {
+ r = in_addr_to_string(rule->family, &rule->from, &from_str);
+ if (r < 0)
+ return r;
+
+ fprintf(f, "from=%s/%hhu",
+ from_str, rule->from_prefixlen);
+ space = true;
+ }
+
+ if (!in_addr_is_null(rule->family, &rule->to)) {
+ r = in_addr_to_string(rule->family, &rule->to, &to_str);
+ if (r < 0)
+ return r;
+
+ fprintf(f, "%sto=%s/%hhu",
+ space ? " " : "",
+ to_str, rule->to_prefixlen);
+ space = true;
+ }
+
+ if (rule->tos != 0) {
+ fprintf(f, "%stos=%hhu",
+ space ? " " : "",
+ rule->tos);
+ space = true;
+ }
+
+ if (rule->fwmark != 0) {
+ fprintf(f, "%sfwmark=%"PRIu32"/%"PRIu32,
+ space ? " " : "",
+ rule->fwmark, rule->fwmask);
+ space = true;
+ }
+
+ if (rule->iif) {
+ fprintf(f, "%siif=%s",
+ space ? " " : "",
+ rule->iif);
+ space = true;
+ }
+
+ if (rule->oif) {
+ fprintf(f, "%soif=%s",
+ space ? " " : "",
+ rule->oif);
+ space = true;
+ }
+
+ if (rule->protocol != 0) {
+ fprintf(f, "%sprotocol=%hhu",
+ space ? " " : "",
+ rule->protocol);
+ space = true;
+ }
+
+ if (rule->sport.start != 0 || rule->sport.end != 0) {
+ fprintf(f, "%ssourcesport=%"PRIu16"-%"PRIu16,
+ space ? " " : "",
+ rule->sport.start, rule->sport.end);
+ space = true;
+ }
+
+ if (rule->dport.start != 0 || rule->dport.end != 0) {
+ fprintf(f, "%sdestinationport=%"PRIu16"-%"PRIu16,
+ space ? " " : "",
+ rule->dport.start, rule->dport.end);
+ space = true;
+ }
+
+ fprintf(f, "%stable=%"PRIu32 "\n",
+ space ? " " : "",
+ rule->table);
+ }
+
+ return 0;
+}
+
+int routing_policy_load_rules(const char *state_file, Set **rules) {
+ _cleanup_strv_free_ char **l = NULL;
+ _cleanup_free_ char *data = NULL;
+ uint16_t low = 0, high = 0;
+ const char *p;
+ char **i;
+ int r;
+
+ assert(state_file);
+ assert(rules);
+
+ r = routing_policy_rule_read_full_file(state_file, &data);
+ if (r <= 0)
+ return r;
+
+ l = strv_split_newlines(data);
+ if (!l)
+ return -ENOMEM;
+
+ r = set_ensure_allocated(rules, &routing_policy_rule_hash_ops);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(i, l) {
+ _cleanup_(routing_policy_rule_freep) RoutingPolicyRule *rule = NULL;
+
+ p = startswith(*i, "RULE=");
+ if (!p)
+ continue;
+
+ r = routing_policy_rule_new(&rule);
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ _cleanup_free_ char *word = NULL, *a = NULL, *b = NULL;
+
+ r = extract_first_word(&p, &word, NULL, 0);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ r = split_pair(word, "=", &a, &b);
+ if (r < 0)
+ continue;
+
+ if (STR_IN_SET(a, "from", "to")) {
+ union in_addr_union *buffer;
+ uint8_t *prefixlen;
+
+ if (streq(a, "to")) {
+ buffer = &rule->to;
+ prefixlen = &rule->to_prefixlen;
+ } else {
+ buffer = &rule->from;
+ prefixlen = &rule->from_prefixlen;
+ }
+
+ r = in_addr_prefix_from_string_auto(b, &rule->family, buffer, prefixlen);
+ if (r < 0) {
+ log_error_errno(r, "RPDB rule prefix is invalid, ignoring assignment: %s", b);
+ continue;
+ }
+
+ } else if (streq(a, "tos")) {
+ r = safe_atou8(b, &rule->tos);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse RPDB rule tos, ignoring: %s", b);
+ continue;
+ }
+ } else if (streq(a, "table")) {
+ r = safe_atou32(b, &rule->table);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse RPDB rule table, ignoring: %s", b);
+ continue;
+ }
+ } else if (streq(a, "fwmark")) {
+
+ r = parse_fwmark_fwmask(b, &rule->fwmark, &rule->fwmask);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse RPDB rule firewall mark or mask, ignoring: %s", a);
+ continue;
+ }
+ } else if (streq(a, "iif")) {
+
+ if (free_and_strdup(&rule->iif, b) < 0)
+ return log_oom();
+
+ } else if (streq(a, "oif")) {
+
+ if (free_and_strdup(&rule->oif, b) < 0)
+ return log_oom();
+ } else if (streq(a, "protocol")) {
+ r = safe_atou8(b, &rule->protocol);
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse RPDB rule protocol, ignoring: %s", b);
+ continue;
+ }
+ } else if (streq(a, "sourceport")) {
+
+ r = parse_ip_port_range(b, &low, &high);
+ if (r < 0) {
+ log_error_errno(r, "Invalid routing policy rule source port range, ignoring assignment:'%s'", b);
+ continue;
+ }
+
+ rule->sport.start = low;
+ rule->sport.end = high;
+
+ } else if (streq(a, "destinationport")) {
+
+ r = parse_ip_port_range(b, &low, &high);
+ if (r < 0) {
+ log_error_errno(r, "Invalid routing policy rule destination port range, ignoring assignment:'%s'", b);
+ continue;
+ }
+
+ rule->dport.start = low;
+ rule->dport.end = high;
+ }
+ }
+
+ r = set_put(*rules, rule);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to add RPDB rule to saved DB, ignoring: %s", p);
+ continue;
+ }
+
+ rule = NULL;
+ }
+
+ return 0;
+}
+
+void routing_policy_rule_purge(Manager *m, Link *link) {
+ RoutingPolicyRule *rule, *existing;
+ Iterator i;
+ int r;
+
+ assert(m);
+ assert(link);
+
+ SET_FOREACH(rule, m->rules_saved, i) {
+ existing = set_get(m->rules_foreign, rule);
+ if (existing) {
+
+ r = routing_policy_rule_remove(rule, link, NULL);
+ if (r < 0) {
+ log_warning_errno(r, "Could not remove routing policy rules: %m");
+ continue;
+ }
+
+ link->routing_policy_rule_remove_messages++;
+ }
+ }
+}
diff --git a/src/network/networkd-routing-policy-rule.h b/src/network/networkd-routing-policy-rule.h
new file mode 100644
index 0000000..b35126e
--- /dev/null
+++ b/src/network/networkd-routing-policy-rule.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <inttypes.h>
+#include <linux/fib_rules.h>
+#include <stdbool.h>
+
+#include "in-addr-util.h"
+#include "conf-parser.h"
+#include "missing_fib_rules.h"
+
+typedef struct RoutingPolicyRule RoutingPolicyRule;
+
+#include "networkd-link.h"
+#include "networkd-network.h"
+
+typedef struct Network Network;
+typedef struct Link Link;
+typedef struct NetworkConfigSection NetworkConfigSection;
+typedef struct Manager Manager;
+
+struct RoutingPolicyRule {
+ Manager *manager;
+ Network *network;
+ Link *link;
+ NetworkConfigSection *section;
+
+ bool invert_rule;
+
+ uint8_t tos;
+ uint8_t protocol;
+
+ uint32_t table;
+ uint32_t fwmark;
+ uint32_t fwmask;
+ uint32_t priority;
+
+ int family;
+ unsigned char to_prefixlen;
+ unsigned char from_prefixlen;
+
+ char *iif;
+ char *oif;
+
+ union in_addr_union to;
+ union in_addr_union from;
+
+ struct fib_rule_port_range sport;
+ struct fib_rule_port_range dport;
+
+ LIST_FIELDS(RoutingPolicyRule, rules);
+};
+
+int routing_policy_rule_new(RoutingPolicyRule **ret);
+void routing_policy_rule_free(RoutingPolicyRule *rule);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(RoutingPolicyRule*, routing_policy_rule_free);
+
+int routing_policy_rule_configure(RoutingPolicyRule *address, Link *link, link_netlink_message_handler_t callback, bool update);
+int routing_policy_rule_remove(RoutingPolicyRule *routing_policy_rule, Link *link, link_netlink_message_handler_t callback);
+
+int routing_policy_rule_add(Manager *m, int family, const union in_addr_union *from, uint8_t from_prefixlen, const union in_addr_union *to, uint8_t to_prefixlen,
+ uint8_t tos, uint32_t fwmark, uint32_t table, const char *iif, const char *oif, uint8_t protocol, const struct fib_rule_port_range *sport,
+ const struct fib_rule_port_range *dport, RoutingPolicyRule **ret);
+int routing_policy_rule_add_foreign(Manager *m, int family, const union in_addr_union *from, uint8_t from_prefixlen, const union in_addr_union *to, uint8_t to_prefixlen,
+ uint8_t tos, uint32_t fwmark, uint32_t table, const char *iif, const char *oif, uint8_t protocol, const struct fib_rule_port_range *sport,
+ const struct fib_rule_port_range *dport, RoutingPolicyRule **ret);
+int routing_policy_rule_get(Manager *m, int family, const union in_addr_union *from, uint8_t from_prefixlen, const union in_addr_union *to, uint8_t to_prefixlen, uint8_t tos,
+ uint32_t fwmark, uint32_t table, const char *iif, const char *oif, uint8_t protocol, struct fib_rule_port_range *sport,
+ struct fib_rule_port_range *dport, RoutingPolicyRule **ret);
+int routing_policy_rule_make_local(Manager *m, RoutingPolicyRule *rule);
+int routing_policy_serialize_rules(Set *rules, FILE *f);
+int routing_policy_load_rules(const char *state_file, Set **rules);
+void routing_policy_rule_purge(Manager *m, Link *link);
+
+CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_tos);
+CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_table);
+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_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-util.c b/src/network/networkd-util.c
new file mode 100644
index 0000000..9b6bc12
--- /dev/null
+++ b/src/network/networkd-util.c
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "condition.h"
+#include "conf-parser.h"
+#include "networkd-util.h"
+#include "parse-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "util.h"
+
+const char *address_family_boolean_to_string(AddressFamilyBoolean b) {
+ if (IN_SET(b, ADDRESS_FAMILY_YES, ADDRESS_FAMILY_NO))
+ return yes_no(b == ADDRESS_FAMILY_YES);
+
+ if (b == ADDRESS_FAMILY_IPV4)
+ return "ipv4";
+ if (b == ADDRESS_FAMILY_IPV6)
+ return "ipv6";
+
+ return NULL;
+}
+
+AddressFamilyBoolean address_family_boolean_from_string(const char *s) {
+ int r;
+
+ /* Make this a true superset of a boolean */
+
+ r = parse_boolean(s);
+ if (r > 0)
+ return ADDRESS_FAMILY_YES;
+ if (r == 0)
+ return ADDRESS_FAMILY_NO;
+
+ if (streq(s, "ipv4"))
+ return ADDRESS_FAMILY_IPV4;
+ if (streq(s, "ipv6"))
+ return ADDRESS_FAMILY_IPV6;
+
+ return _ADDRESS_FAMILY_BOOLEAN_INVALID;
+}
+
+DEFINE_CONFIG_PARSE_ENUM(config_parse_address_family_boolean, address_family_boolean, AddressFamilyBoolean, "Failed to parse option");
+
+int config_parse_address_family_boolean_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) {
+
+ AddressFamilyBoolean *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
+ * distuingish 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_boolean_from_string(rvalue);
+ if (s < 0) {
+ if (streq(rvalue, "kernel"))
+ s = ADDRESS_FAMILY_NO;
+ else {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse IPForward= option, ignoring: %s", rvalue);
+ return 0;
+ }
+ }
+
+ *fwd = s;
+
+ return 0;
+}
+
+/* Router lifetime can be set with netlink interface since kernel >= 4.5
+ * so for the supported kernel we dont need to expire routes in userspace */
+int kernel_route_expiration_supported(void) {
+ static int cached = -1;
+ int r;
+
+ if (cached < 0) {
+ Condition c = {
+ .type = CONDITION_KERNEL_VERSION,
+ .parameter = (char *) ">= 4.5"
+ };
+ r = condition_test(&c);
+ if (r < 0)
+ return r;
+
+ cached = r;
+ }
+ return cached;
+}
diff --git a/src/network/networkd-util.h b/src/network/networkd-util.h
new file mode 100644
index 0000000..435fdb7
--- /dev/null
+++ b/src/network/networkd-util.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "conf-parser.h"
+#include "macro.h"
+
+typedef enum AddressFamilyBoolean {
+ /* This is a bitmask, though it usually doesn't feel that way! */
+ ADDRESS_FAMILY_NO = 0,
+ ADDRESS_FAMILY_IPV4 = 1,
+ ADDRESS_FAMILY_IPV6 = 2,
+ ADDRESS_FAMILY_YES = 3,
+ _ADDRESS_FAMILY_BOOLEAN_MAX,
+ _ADDRESS_FAMILY_BOOLEAN_INVALID = -1,
+} AddressFamilyBoolean;
+
+CONFIG_PARSER_PROTOTYPE(config_parse_address_family_boolean);
+CONFIG_PARSER_PROTOTYPE(config_parse_address_family_boolean_with_kernel);
+
+const char *address_family_boolean_to_string(AddressFamilyBoolean b) _const_;
+AddressFamilyBoolean address_family_boolean_from_string(const char *s) _const_;
+
+int kernel_route_expiration_supported(void);
diff --git a/src/network/networkd.c b/src/network/networkd.c
new file mode 100644
index 0000000..fcecafe
--- /dev/null
+++ b/src/network/networkd.c
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "sd-daemon.h"
+#include "sd-event.h"
+
+#include "capability-util.h"
+#include "daemon-util.h"
+#include "main-func.h"
+#include "mkdir.h"
+#include "networkd-conf.h"
+#include "networkd-manager.h"
+#include "signal-util.h"
+#include "user-util.h"
+
+static int run(int argc, char *argv[]) {
+ _cleanup_(notify_on_cleanup) const char *notify_message = NULL;
+ _cleanup_(manager_freep) Manager *m = NULL;
+ const char *user = "systemd-network";
+ uid_t uid;
+ gid_t gid;
+ int r;
+
+ log_setup_service();
+
+ umask(0022);
+
+ if (argc != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments.");
+
+ r = get_user_creds(&user, &uid, &gid, NULL, NULL, 0);
+ if (r < 0)
+ return log_error_errno(r, "Cannot resolve user name %s: %m", user);
+
+ /* Create runtime directory. This is not necessary when networkd is
+ * started with "RuntimeDirectory=systemd/netif", or after
+ * systemd-tmpfiles-setup.service. */
+ r = mkdir_safe_label("/run/systemd/netif", 0755, uid, gid, MKDIR_WARN_MODE);
+ if (r < 0)
+ log_warning_errno(r, "Could not create runtime directory: %m");
+
+ /* Drop privileges, but only if we have been started as root. If we are not running as root we assume all
+ * privileges are already dropped. */
+ if (geteuid() == 0) {
+ r = drop_privileges(uid, gid,
+ (1ULL << CAP_NET_ADMIN) |
+ (1ULL << CAP_NET_BIND_SERVICE) |
+ (1ULL << CAP_NET_BROADCAST) |
+ (1ULL << CAP_NET_RAW));
+ if (r < 0)
+ return log_error_errno(r, "Failed to drop privileges: %m");
+ }
+
+ /* 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, gid, 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, gid, 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, gid, MKDIR_WARN_MODE);
+ if (r < 0)
+ log_warning_errno(r, "Could not create runtime directory 'lldp': %m");
+
+ assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
+
+ r = manager_new(&m);
+ if (r < 0)
+ return log_error_errno(r, "Could not create manager: %m");
+
+ r = manager_connect_bus(m);
+ if (r < 0)
+ return log_error_errno(r, "Could not connect to bus: %m");
+
+ r = manager_parse_config_file(m);
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse configuration file: %m");
+
+ r = manager_load_config(m);
+ if (r < 0)
+ return log_error_errno(r, "Could not load configuration files: %m");
+
+ r = manager_rtnl_enumerate_links(m);
+ if (r < 0)
+ return log_error_errno(r, "Could not enumerate links: %m");
+
+ r = manager_rtnl_enumerate_addresses(m);
+ if (r < 0)
+ return log_error_errno(r, "Could not enumerate addresses: %m");
+
+ r = manager_rtnl_enumerate_routes(m);
+ if (r < 0)
+ return log_error_errno(r, "Could not enumerate routes: %m");
+
+ r = manager_rtnl_enumerate_rules(m);
+ if (r < 0)
+ return log_error_errno(r, "Could not enumerate rules: %m");
+
+ r = manager_start(m);
+ if (r < 0)
+ return log_error_errno(r, "Could not start manager: %m");
+
+ log_info("Enumeration completed");
+
+ notify_message = notify_start(NOTIFY_READY, NOTIFY_STOPPING);
+
+ r = sd_event_loop(m->event);
+ if (r < 0)
+ return log_error_errno(r, "Event loop failed: %m");
+
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/network/networkd.conf b/src/network/networkd.conf
new file mode 100644
index 0000000..8dc5676
--- /dev/null
+++ b/src/network/networkd.conf
@@ -0,0 +1,16 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# Entries in this file show the compile time defaults.
+# You can change settings by editing this file.
+# Defaults can be restored by simply deleting this file.
+#
+# See networkd.conf(5) for details
+
+[DHCP]
+#DUIDType=vendor
+#DUIDRawData=
diff --git a/src/network/org.freedesktop.network1.conf b/src/network/org.freedesktop.network1.conf
new file mode 100644
index 0000000..52dad33
--- /dev/null
+++ b/src/network/org.freedesktop.network1.conf
@@ -0,0 +1,42 @@
+<?xml version="1.0"?> <!--*-nxml-*-->
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<!--
+ This file is part of systemd.
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+-->
+
+<busconfig>
+
+ <policy user="systemd-network">
+ <allow own="org.freedesktop.network1"/>
+ <allow send_destination="org.freedesktop.network1"/>
+ <allow receive_sender="org.freedesktop.network1"/>
+ </policy>
+
+ <policy context="default">
+ <deny send_destination="org.freedesktop.network1"/>
+
+ <allow send_destination="org.freedesktop.network1"
+ send_interface="org.freedesktop.DBus.Introspectable"/>
+
+ <allow send_destination="org.freedesktop.network1"
+ send_interface="org.freedesktop.DBus.Peer"/>
+
+ <allow send_destination="org.freedesktop.network1"
+ send_interface="org.freedesktop.DBus.Properties"
+ send_member="Get"/>
+
+ <allow send_destination="org.freedesktop.network1"
+ send_interface="org.freedesktop.DBus.Properties"
+ send_member="GetAll"/>
+
+ <allow receive_sender="org.freedesktop.network1"/>
+ </policy>
+
+</busconfig>
diff --git a/src/network/org.freedesktop.network1.service b/src/network/org.freedesktop.network1.service
new file mode 100644
index 0000000..641a1a5
--- /dev/null
+++ b/src/network/org.freedesktop.network1.service
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: LGPL-2.1+
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[D-BUS Service]
+Name=org.freedesktop.network1
+Exec=/bin/false
+User=root
+SystemdService=dbus-org.freedesktop.network1.service
diff --git a/src/network/systemd-networkd.pkla b/src/network/systemd-networkd.pkla
new file mode 100644
index 0000000..4d1bb45
--- /dev/null
+++ b/src/network/systemd-networkd.pkla
@@ -0,0 +1,4 @@
+[Allow systemd-networkd to set timezone and transient hostname]
+Identity=unix-user:systemd-network
+Action=org.freedesktop.hostname1.set-hostname;org.freedesktop.hostname1.get-product-uuid;org.freedesktop.timedate1.set-timezone;
+ResultAny=yes
diff --git a/src/network/systemd-networkd.rules b/src/network/systemd-networkd.rules
new file mode 100644
index 0000000..b9077c1
--- /dev/null
+++ b/src/network/systemd-networkd.rules
@@ -0,0 +1,10 @@
+// Allow systemd-networkd to set timezone, get product UUID,
+// and transient hostname
+polkit.addRule(function(action, subject) {
+ if ((action.id == "org.freedesktop.hostname1.set-hostname" ||
+ action.id == "org.freedesktop.hostname1.get-product-uuid" ||
+ action.id == "org.freedesktop.timedate1.set-timezone") &&
+ subject.user == "systemd-network") {
+ return polkit.Result.YES;
+ }
+});
diff --git a/src/network/test-network-tables.c b/src/network/test-network-tables.c
new file mode 100644
index 0000000..6b110b7
--- /dev/null
+++ b/src/network/test-network-tables.c
@@ -0,0 +1,47 @@
+#include "dhcp6-internal.h"
+#include "dhcp6-protocol.h"
+#include "ethtool-util.h"
+#include "lldp-internal.h"
+#include "ndisc-internal.h"
+#include "netdev/bond.h"
+#include "netdev/ipvlan.h"
+#include "netdev/macvlan.h"
+#include "netdev/tunnel.h"
+#include "netlink-internal.h"
+#include "networkd-link.h"
+#include "networkd-network.h"
+#include "networkd-util.h"
+#include "test-tables.h"
+
+int main(int argc, char **argv) {
+ test_table(address_family_boolean, ADDRESS_FAMILY_BOOLEAN);
+ test_table(bond_ad_select, NETDEV_BOND_AD_SELECT);
+ test_table(bond_arp_all_targets, NETDEV_BOND_ARP_ALL_TARGETS);
+ test_table(bond_arp_validate, NETDEV_BOND_ARP_VALIDATE);
+ test_table(bond_fail_over_mac, NETDEV_BOND_FAIL_OVER_MAC);
+ test_table(bond_lacp_rate, NETDEV_BOND_LACP_RATE);
+ test_table(bond_mode, NETDEV_BOND_MODE);
+ test_table(bond_primary_reselect, NETDEV_BOND_PRIMARY_RESELECT);
+ 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); /* enum starts from 1 */
+ test_table(dhcp_use_domains, DHCP_USE_DOMAINS);
+ test_table(duplex, DUP);
+ test_table(ip6tnl_mode, NETDEV_IP6_TNL_MODE);
+ test_table(ipv6_privacy_extensions, IPV6_PRIVACY_EXTENSIONS);
+ test_table(ipvlan_flags, NETDEV_IPVLAN_FLAGS);
+ test_table(link_operstate, LINK_OPERSTATE);
+ /* test_table(link_state, LINK_STATE); — not a reversible mapping */
+ test_table(lldp_mode, LLDP_MODE);
+ test_table(netdev_kind, NETDEV_KIND);
+ test_table(nl_union_link_info_data, NL_UNION_LINK_INFO_DATA);
+ test_table(radv_prefix_delegation, RADV_PREFIX_DELEGATION);
+ test_table(wol, WOL);
+ test_table(lldp_event, SD_LLDP_EVENT);
+ test_table(ndisc_event, SD_NDISC_EVENT);
+
+ test_table_sparse(ipvlan_mode, NETDEV_IPVLAN_MODE);
+ test_table_sparse(macvlan_mode, NETDEV_MACVLAN_MODE);
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/network/test-network.c b/src/network/test-network.c
new file mode 100644
index 0000000..6e169e0
--- /dev/null
+++ b/src/network/test-network.c
@@ -0,0 +1,249 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <sys/param.h>
+
+#include "sd-device.h"
+
+#include "alloc-util.h"
+#include "dhcp-lease-internal.h"
+#include "hostname-util.h"
+#include "network-internal.h"
+#include "networkd-manager.h"
+#include "string-util.h"
+#include "tests.h"
+
+static void test_deserialize_in_addr(void) {
+ _cleanup_free_ struct in_addr *addresses = NULL;
+ _cleanup_free_ struct in6_addr *addresses6 = NULL;
+ struct in_addr a, b, c;
+ struct in6_addr d, e, f;
+ int size;
+ const char *addresses_string = "192.168.0.1 0:0:0:0:0:FFFF:204.152.189.116 192.168.0.2 ::1 192.168.0.3 1:0:0:0:0:0:0:8";
+
+ assert_se(inet_pton(AF_INET, "0:0:0:0:0:FFFF:204.152.189.116", &a) == 0);
+ assert_se(inet_pton(AF_INET6, "192.168.0.1", &d) == 0);
+
+ assert_se(inet_pton(AF_INET, "192.168.0.1", &a) == 1);
+ assert_se(inet_pton(AF_INET, "192.168.0.2", &b) == 1);
+ assert_se(inet_pton(AF_INET, "192.168.0.3", &c) == 1);
+ assert_se(inet_pton(AF_INET6, "0:0:0:0:0:FFFF:204.152.189.116", &d) == 1);
+ assert_se(inet_pton(AF_INET6, "::1", &e) == 1);
+ assert_se(inet_pton(AF_INET6, "1:0:0:0:0:0:0:8", &f) == 1);
+
+ assert_se((size = deserialize_in_addrs(&addresses, addresses_string)) >= 0);
+ assert_se(size == 3);
+ assert_se(!memcmp(&a, &addresses[0], sizeof(struct in_addr)));
+ assert_se(!memcmp(&b, &addresses[1], sizeof(struct in_addr)));
+ assert_se(!memcmp(&c, &addresses[2], sizeof(struct in_addr)));
+
+ assert_se((size = deserialize_in6_addrs(&addresses6, addresses_string)) >= 0);
+ assert_se(size == 3);
+ assert_se(!memcmp(&d, &addresses6[0], sizeof(struct in6_addr)));
+ assert_se(!memcmp(&e, &addresses6[1], sizeof(struct in6_addr)));
+ assert_se(!memcmp(&f, &addresses6[2], sizeof(struct in6_addr)));
+}
+
+static void test_deserialize_dhcp_routes(void) {
+ size_t size, allocated;
+
+ {
+ _cleanup_free_ struct sd_dhcp_route *routes = NULL;
+ assert_se(deserialize_dhcp_routes(&routes, &size, &allocated, "") >= 0);
+ assert_se(size == 0);
+ }
+
+ {
+ /* no errors */
+ _cleanup_free_ struct sd_dhcp_route *routes = NULL;
+ const char *routes_string = "192.168.0.0/16,192.168.0.1 10.1.2.0/24,10.1.2.1 0.0.0.0/0,10.0.1.1";
+
+ assert_se(deserialize_dhcp_routes(&routes, &size, &allocated, routes_string) >= 0);
+
+ assert_se(size == 3);
+ assert_se(routes[0].dst_addr.s_addr == inet_addr("192.168.0.0"));
+ assert_se(routes[0].gw_addr.s_addr == inet_addr("192.168.0.1"));
+ assert_se(routes[0].dst_prefixlen == 16);
+
+ assert_se(routes[1].dst_addr.s_addr == inet_addr("10.1.2.0"));
+ assert_se(routes[1].gw_addr.s_addr == inet_addr("10.1.2.1"));
+ assert_se(routes[1].dst_prefixlen == 24);
+
+ assert_se(routes[2].dst_addr.s_addr == inet_addr("0.0.0.0"));
+ assert_se(routes[2].gw_addr.s_addr == inet_addr("10.0.1.1"));
+ assert_se(routes[2].dst_prefixlen == 0);
+ }
+
+ {
+ /* error in second word */
+ _cleanup_free_ struct sd_dhcp_route *routes = NULL;
+ const char *routes_string = "192.168.0.0/16,192.168.0.1 10.1.2.0#24,10.1.2.1 0.0.0.0/0,10.0.1.1";
+
+ assert_se(deserialize_dhcp_routes(&routes, &size, &allocated, routes_string) >= 0);
+
+ assert_se(size == 2);
+ assert_se(routes[0].dst_addr.s_addr == inet_addr("192.168.0.0"));
+ assert_se(routes[0].gw_addr.s_addr == inet_addr("192.168.0.1"));
+ assert_se(routes[0].dst_prefixlen == 16);
+
+ assert_se(routes[1].dst_addr.s_addr == inet_addr("0.0.0.0"));
+ assert_se(routes[1].gw_addr.s_addr == inet_addr("10.0.1.1"));
+ assert_se(routes[1].dst_prefixlen == 0);
+ }
+
+ {
+ /* error in every word */
+ _cleanup_free_ struct sd_dhcp_route *routes = NULL;
+ const char *routes_string = "192.168.0.0/55,192.168.0.1 10.1.2.0#24,10.1.2.1 0.0.0.0/0,10.0.1.X";
+
+ assert_se(deserialize_dhcp_routes(&routes, &size, &allocated, routes_string) >= 0);
+ assert_se(size == 0);
+ }
+}
+
+static int test_load_config(Manager *manager) {
+ int r;
+/* TODO: should_reload, is false if the config dirs do not exist, so
+ * so we can't do this test here, move it to a test for paths_check_timestamps
+ * directly
+ *
+ * assert_se(network_should_reload(manager) == true);
+*/
+
+ r = manager_load_config(manager);
+ if (r == -EPERM)
+ return r;
+ assert_se(r >= 0);
+
+ assert_se(manager_should_reload(manager) == false);
+
+ return 0;
+}
+
+static void test_network_get(Manager *manager, sd_device *loopback) {
+ Network *network;
+ const struct ether_addr mac = {};
+
+ /* let's assume that the test machine does not have a .network file
+ that applies to the loopback device... */
+ assert_se(network_get(manager, loopback, "lo", &mac, &network) == -ENOENT);
+ assert_se(!network);
+}
+
+static void test_address_equality(void) {
+ _cleanup_(address_freep) Address *a1 = NULL, *a2 = NULL;
+
+ assert_se(address_new(&a1) >= 0);
+ assert_se(address_new(&a2) >= 0);
+
+ assert_se(address_equal(NULL, NULL));
+ assert_se(!address_equal(a1, NULL));
+ assert_se(!address_equal(NULL, a2));
+ assert_se(address_equal(a1, a2));
+
+ a1->family = AF_INET;
+ assert_se(!address_equal(a1, a2));
+
+ a2->family = AF_INET;
+ assert_se(address_equal(a1, a2));
+
+ assert_se(inet_pton(AF_INET, "192.168.3.9", &a1->in_addr.in));
+ assert_se(!address_equal(a1, a2));
+ assert_se(inet_pton(AF_INET, "192.168.3.9", &a2->in_addr.in));
+ assert_se(address_equal(a1, a2));
+ assert_se(inet_pton(AF_INET, "192.168.3.10", &a1->in_addr_peer.in));
+ assert_se(address_equal(a1, a2));
+ assert_se(inet_pton(AF_INET, "192.168.3.11", &a2->in_addr_peer.in));
+ assert_se(address_equal(a1, a2));
+ a1->prefixlen = 10;
+ assert_se(!address_equal(a1, a2));
+ a2->prefixlen = 10;
+ assert_se(address_equal(a1, a2));
+
+ a1->family = AF_INET6;
+ assert_se(!address_equal(a1, a2));
+
+ a2->family = AF_INET6;
+ assert_se(inet_pton(AF_INET6, "2001:4ca0:4f01::2", &a1->in_addr.in6));
+ assert_se(inet_pton(AF_INET6, "2001:4ca0:4f01::2", &a2->in_addr.in6));
+ assert_se(address_equal(a1, a2));
+
+ a2->prefixlen = 8;
+ assert_se(address_equal(a1, a2));
+
+ assert_se(inet_pton(AF_INET6, "2001:4ca0:4f01::1", &a2->in_addr.in6));
+ assert_se(!address_equal(a1, a2));
+}
+
+static void test_dhcp_hostname_shorten_overlong(void) {
+ int r;
+
+ {
+ /* simple hostname, no actions, no errors */
+ _cleanup_free_ char *shortened = NULL;
+ r = shorten_overlong("name1", &shortened);
+ assert_se(r == 0);
+ assert_se(streq("name1", shortened));
+ }
+
+ {
+ /* simple fqdn, no actions, no errors */
+ _cleanup_free_ char *shortened = NULL;
+ r = shorten_overlong("name1.example.com", &shortened);
+ assert_se(r == 0);
+ assert_se(streq("name1.example.com", shortened));
+ }
+
+ {
+ /* overlong fqdn, cut to first dot, no errors */
+ _cleanup_free_ char *shortened = NULL;
+ r = shorten_overlong("name1.test-dhcp-this-one-here-is-a-very-very-long-domain.example.com", &shortened);
+ assert_se(r == 1);
+ assert_se(streq("name1", shortened));
+ }
+
+ {
+ /* overlong hostname, cut to HOST_MAX_LEN, no errors */
+ _cleanup_free_ char *shortened = NULL;
+ r = shorten_overlong("test-dhcp-this-one-here-is-a-very-very-long-hostname-without-domainname", &shortened);
+ assert_se(r == 1);
+ assert_se(streq("test-dhcp-this-one-here-is-a-very-very-long-hostname-without-dom", shortened));
+ }
+
+ {
+ /* overlong fqdn, cut to first dot, empty result error */
+ _cleanup_free_ char *shortened = NULL;
+ r = shorten_overlong(".test-dhcp-this-one-here-is-a-very-very-long-hostname.example.com", &shortened);
+ assert_se(r == -EDOM);
+ assert_se(shortened == NULL);
+ }
+
+}
+
+int main(void) {
+ _cleanup_(manager_freep) Manager *manager = NULL;
+ _cleanup_(sd_device_unrefp) sd_device *loopback = NULL;
+ int ifindex, r;
+
+ test_setup_logging(LOG_INFO);
+
+ test_deserialize_in_addr();
+ test_deserialize_dhcp_routes();
+ test_address_equality();
+ test_dhcp_hostname_shorten_overlong();
+
+ assert_se(manager_new(&manager) >= 0);
+
+ r = test_load_config(manager);
+ if (r == -EPERM)
+ return log_tests_skipped("Cannot load configuration");
+ assert_se(r == 0);
+
+ assert_se(sd_device_new_from_syspath(&loopback, "/sys/class/net/lo") >= 0);
+ assert_se(loopback);
+ assert_se(sd_device_get_ifindex(loopback, &ifindex) >= 0);
+ assert_se(ifindex == 1);
+
+ test_network_get(manager, loopback);
+
+ assert_se(manager_rtnl_enumerate_links(manager) >= 0);
+}
diff --git a/src/network/test-networkd-conf.c b/src/network/test-networkd-conf.c
new file mode 100644
index 0000000..86d4e7e
--- /dev/null
+++ b/src/network/test-networkd-conf.c
@@ -0,0 +1,223 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "ether-addr-util.h"
+#include "hexdecoct.h"
+#include "log.h"
+#include "macro.h"
+#include "set.h"
+#include "string-util.h"
+
+#include "network-internal.h"
+#include "networkd-conf.h"
+#include "networkd-network.h"
+
+static void test_config_parse_duid_type_one(const char *rvalue, int ret, DUIDType expected, usec_t expected_time) {
+ DUID actual = {};
+ int r;
+
+ r = config_parse_duid_type("network", "filename", 1, "section", 1, "lvalue", 0, rvalue, &actual, NULL);
+ log_info_errno(r, "\"%s\" → %d (%m)", rvalue, actual.type);
+ assert_se(r == ret);
+ assert_se(expected == actual.type);
+ if (expected == DUID_TYPE_LLT)
+ assert_se(expected_time == actual.llt_time);
+}
+
+static void test_config_parse_duid_type(void) {
+ test_config_parse_duid_type_one("", 0, 0, 0);
+ test_config_parse_duid_type_one("link-layer-time", 0, DUID_TYPE_LLT, 0);
+ test_config_parse_duid_type_one("link-layer-time:2000-01-01 00:00:00 UTC", 0, DUID_TYPE_LLT, (usec_t) 946684800000000);
+ test_config_parse_duid_type_one("vendor", 0, DUID_TYPE_EN, 0);
+ test_config_parse_duid_type_one("vendor:2000-01-01 00:00:00 UTC", 0, 0, 0);
+ test_config_parse_duid_type_one("link-layer", 0, DUID_TYPE_LL, 0);
+ test_config_parse_duid_type_one("link-layer:2000-01-01 00:00:00 UTC", 0, 0, 0);
+ test_config_parse_duid_type_one("uuid", 0, DUID_TYPE_UUID, 0);
+ test_config_parse_duid_type_one("uuid:2000-01-01 00:00:00 UTC", 0, 0, 0);
+ test_config_parse_duid_type_one("foo", 0, 0, 0);
+ test_config_parse_duid_type_one("foo:2000-01-01 00:00:00 UTC", 0, 0, 0);
+}
+
+static void test_config_parse_duid_rawdata_one(const char *rvalue, int ret, const DUID* expected) {
+ DUID actual = {};
+ int r;
+ _cleanup_free_ char *d = NULL;
+
+ r = config_parse_duid_rawdata("network", "filename", 1, "section", 1, "lvalue", 0, rvalue, &actual, NULL);
+ d = hexmem(actual.raw_data, actual.raw_data_len);
+ log_info_errno(r, "\"%s\" → \"%s\" (%m)",
+ rvalue, strnull(d));
+ assert_se(r == ret);
+ if (expected) {
+ assert_se(actual.raw_data_len == expected->raw_data_len);
+ assert_se(memcmp(actual.raw_data, expected->raw_data, expected->raw_data_len) == 0);
+ }
+}
+
+static void test_config_parse_hwaddr_one(const char *rvalue, int ret, const struct ether_addr* expected) {
+ struct ether_addr *actual = NULL;
+ int r;
+
+ r = config_parse_hwaddr("network", "filename", 1, "section", 1, "lvalue", 0, rvalue, &actual, NULL);
+ assert_se(ret == r);
+ if (expected) {
+ assert_se(actual);
+ assert_se(ether_addr_equal(expected, actual));
+ } else
+ assert_se(actual == NULL);
+
+ free(actual);
+}
+
+static void test_config_parse_hwaddrs_one(const char *rvalue, const struct ether_addr* list, size_t n) {
+ _cleanup_set_free_free_ Set *s = NULL;
+ size_t m;
+
+ assert_se(config_parse_hwaddrs("network", "filename", 1, "section", 1, "lvalue", 0, rvalue, &s, NULL) == 0);
+ assert_se(set_size(s) == n);
+
+ for (m = 0; m < n; m++) {
+ _cleanup_free_ struct ether_addr *q = NULL;
+
+ assert_se(q = set_remove(s, &list[m]));
+ }
+
+ assert_se(set_size(s) == 0);
+}
+
+#define BYTES_0_128 "0:1:2:3:4:5:6:7:8:9:a:b:c:d:e:f:10:11:12:13:14:15:16:17:18:19:1a:1b:1c:1d:1e:1f:20:21:22:23:24:25:26:27:28:29:2a:2b:2c:2d:2e:2f:30:31:32:33:34:35:36:37:38:39:3a:3b:3c:3d:3e:3f:40:41:42:43:44:45:46:47:48:49:4a:4b:4c:4d:4e:4f:50:51:52:53:54:55:56:57:58:59:5a:5b:5c:5d:5e:5f:60:61:62:63:64:65:66:67:68:69:6a:6b:6c:6d:6e:6f:70:71:72:73:74:75:76:77:78:79:7a:7b:7c:7d:7e:7f:80"
+
+#define BYTES_1_128 {0x1,0x2,0x3,0x4,0x5,0x6,0x7,0x8,0x9,0xa,0xb,0xc,0xd,0xe,0xf,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,0x80}
+
+static void test_config_parse_duid_rawdata(void) {
+ test_config_parse_duid_rawdata_one("", 0, &(DUID){});
+ test_config_parse_duid_rawdata_one("00:11:22:33:44:55:66:77", 0,
+ &(DUID){0, 8, {0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77}});
+ test_config_parse_duid_rawdata_one("00:11:22:", 0,
+ &(DUID){0, 3, {0x00,0x11,0x22}});
+ test_config_parse_duid_rawdata_one("000:11:22", 0, &(DUID){}); /* error, output is all zeros */
+ test_config_parse_duid_rawdata_one("00:111:22", 0, &(DUID){});
+ test_config_parse_duid_rawdata_one("0:1:2:3:4:5:6:7", 0,
+ &(DUID){0, 8, {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}});
+ test_config_parse_duid_rawdata_one("11::", 0, &(DUID){0, 1, {0x11}}); /* FIXME: should this be an error? */
+ test_config_parse_duid_rawdata_one("abcdef", 0, &(DUID){});
+ test_config_parse_duid_rawdata_one(BYTES_0_128, 0, &(DUID){});
+ test_config_parse_duid_rawdata_one(BYTES_0_128 + 2, 0, &(DUID){0, 128, BYTES_1_128});
+}
+
+static void test_config_parse_hwaddr(void) {
+ const struct ether_addr t[] = {
+ { .ether_addr_octet = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff } },
+ { .ether_addr_octet = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab } },
+ };
+
+ test_config_parse_hwaddr_one("", 0, NULL);
+ test_config_parse_hwaddr_one("no:ta:ma:ca:dd:re", 0, NULL);
+ test_config_parse_hwaddr_one("aa:bb:cc:dd:ee:fx", 0, NULL);
+ test_config_parse_hwaddr_one("aa:bb:cc:dd:ee:ff", 0, &t[0]);
+ test_config_parse_hwaddr_one(" aa:bb:cc:dd:ee:ff", 0, &t[0]);
+ test_config_parse_hwaddr_one("aa:bb:cc:dd:ee:ff \t\n", 0, NULL);
+ test_config_parse_hwaddr_one("aa:bb:cc:dd:ee:ff \t\nxxx", 0, NULL);
+ test_config_parse_hwaddr_one("aa:bb:cc: dd:ee:ff", 0, NULL);
+ test_config_parse_hwaddr_one("aa:bb:cc:d d:ee:ff", 0, NULL);
+ test_config_parse_hwaddr_one("aa:bb:cc:dd:ee", 0, NULL);
+ test_config_parse_hwaddr_one("9:aa:bb:cc:dd:ee:ff", 0, NULL);
+ test_config_parse_hwaddr_one("aa:bb:cc:dd:ee:ff:gg", 0, NULL);
+ test_config_parse_hwaddr_one("aa:Bb:CC:dd:ee:ff", 0, &t[0]);
+ test_config_parse_hwaddr_one("01:23:45:67:89:aB", 0, &t[1]);
+ test_config_parse_hwaddr_one("1:23:45:67:89:aB", 0, &t[1]);
+ test_config_parse_hwaddr_one("aa-bb-cc-dd-ee-ff", 0, &t[0]);
+ test_config_parse_hwaddr_one("AA-BB-CC-DD-EE-FF", 0, &t[0]);
+ test_config_parse_hwaddr_one("01-23-45-67-89-ab", 0, &t[1]);
+ test_config_parse_hwaddr_one("aabb.ccdd.eeff", 0, &t[0]);
+ test_config_parse_hwaddr_one("0123.4567.89ab", 0, &t[1]);
+ test_config_parse_hwaddr_one("123.4567.89ab.", 0, NULL);
+ test_config_parse_hwaddr_one("aabbcc.ddeeff", 0, NULL);
+ test_config_parse_hwaddr_one("aabbccddeeff", 0, NULL);
+ test_config_parse_hwaddr_one("aabbccddee:ff", 0, NULL);
+ test_config_parse_hwaddr_one("012345.6789ab", 0, NULL);
+ test_config_parse_hwaddr_one("123.4567.89ab", 0, &t[1]);
+
+ test_config_parse_hwaddrs_one("", t, 0);
+ test_config_parse_hwaddrs_one("no:ta:ma:ca:dd:re", t, 0);
+ test_config_parse_hwaddrs_one("aa:bb:cc:dd:ee:fx", t, 0);
+ test_config_parse_hwaddrs_one("aa:bb:cc:dd:ee:ff", t, 1);
+ test_config_parse_hwaddrs_one(" aa:bb:cc:dd:ee:ff", t, 1);
+ test_config_parse_hwaddrs_one("aa:bb:cc:dd:ee:ff \t\n", t, 1);
+ test_config_parse_hwaddrs_one("aa:bb:cc:dd:ee:ff \t\nxxx", t, 1);
+ test_config_parse_hwaddrs_one("aa:bb:cc: dd:ee:ff", t, 0);
+ test_config_parse_hwaddrs_one("aa:bb:cc:d d:ee:ff", t, 0);
+ test_config_parse_hwaddrs_one("aa:bb:cc:dd:ee", t, 0);
+ test_config_parse_hwaddrs_one("9:aa:bb:cc:dd:ee:ff", t, 0);
+ test_config_parse_hwaddrs_one("aa:bb:cc:dd:ee:ff:gg", t, 0);
+ test_config_parse_hwaddrs_one("aa:Bb:CC:dd:ee:ff", t, 1);
+ test_config_parse_hwaddrs_one("01:23:45:67:89:aB", &t[1], 1);
+ test_config_parse_hwaddrs_one("1:23:45:67:89:aB", &t[1], 1);
+ test_config_parse_hwaddrs_one("aa-bb-cc-dd-ee-ff", t, 1);
+ test_config_parse_hwaddrs_one("AA-BB-CC-DD-EE-FF", t, 1);
+ test_config_parse_hwaddrs_one("01-23-45-67-89-ab", &t[1], 1);
+ test_config_parse_hwaddrs_one("aabb.ccdd.eeff", t, 1);
+ test_config_parse_hwaddrs_one("0123.4567.89ab", &t[1], 1);
+ test_config_parse_hwaddrs_one("123.4567.89ab.", t, 0);
+ test_config_parse_hwaddrs_one("aabbcc.ddeeff", t, 0);
+ test_config_parse_hwaddrs_one("aabbccddeeff", t, 0);
+ test_config_parse_hwaddrs_one("aabbccddee:ff", t, 0);
+ test_config_parse_hwaddrs_one("012345.6789ab", t, 0);
+ test_config_parse_hwaddrs_one("123.4567.89ab", &t[1], 1);
+
+ test_config_parse_hwaddrs_one("123.4567.89ab aa:bb:cc:dd:ee:ff 01-23-45-67-89-ab aa:Bb:CC:dd:ee:ff", t, 2);
+ test_config_parse_hwaddrs_one("123.4567.89ab aa:bb:cc:dd:ee:fx hogehoge 01-23-45-67-89-ab aaaa aa:Bb:CC:dd:ee:ff", t, 2);
+}
+
+static void test_config_parse_address_one(const char *rvalue, int family, unsigned n_addresses, const union in_addr_union *u, unsigned char prefixlen) {
+ _cleanup_(network_freep) Network *network = NULL;
+
+ assert_se(network = new0(Network, 1));
+ assert_se(config_parse_address("network", "filename", 1, "section", 1, "Address", 0, rvalue, network, network) == 0);
+ assert_se(network->n_static_addresses == n_addresses);
+ if (n_addresses > 0) {
+ assert_se(network->static_addresses);
+ assert_se(network->static_addresses->prefixlen == prefixlen);
+ assert_se(network->static_addresses->family == family);
+ assert_se(in_addr_equal(family, &network->static_addresses->in_addr, u));
+ /* TODO: check Address.in_addr and Address.broadcast */
+ }
+}
+
+static void test_config_parse_address(void) {
+ test_config_parse_address_one("", AF_INET, 0, NULL, 0);
+ test_config_parse_address_one("/", AF_INET, 0, NULL, 0);
+ test_config_parse_address_one("/8", AF_INET, 0, NULL, 0);
+ test_config_parse_address_one("1.2.3.4", AF_INET, 1, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 8);
+ test_config_parse_address_one("1.2.3.4/0", AF_INET, 1, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 0);
+ test_config_parse_address_one("1.2.3.4/1", AF_INET, 1, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 1);
+ test_config_parse_address_one("1.2.3.4/2", AF_INET, 1, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 2);
+ test_config_parse_address_one("1.2.3.4/32", AF_INET, 1, &(union in_addr_union) { .in = (struct in_addr) { .s_addr = htobe32(0x01020304) } }, 32);
+ test_config_parse_address_one("1.2.3.4/33", AF_INET, 0, NULL, 0);
+ test_config_parse_address_one("1.2.3.4/-1", AF_INET, 0, NULL, 0);
+
+ test_config_parse_address_one("", AF_INET6, 0, NULL, 0);
+ test_config_parse_address_one("/", AF_INET6, 0, NULL, 0);
+ test_config_parse_address_one("/8", AF_INET6, 0, NULL, 0);
+ test_config_parse_address_one("::1", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 0);
+ test_config_parse_address_one("::1/0", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 0);
+ test_config_parse_address_one("::1/1", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 1);
+ test_config_parse_address_one("::1/2", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 2);
+ test_config_parse_address_one("::1/32", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 32);
+ test_config_parse_address_one("::1/33", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 33);
+ test_config_parse_address_one("::1/64", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 64);
+ test_config_parse_address_one("::1/128", AF_INET6, 1, &(union in_addr_union) { .in6 = IN6ADDR_LOOPBACK_INIT }, 128);
+ test_config_parse_address_one("::1/129", AF_INET6, 0, NULL, 0);
+ test_config_parse_address_one("::1/-1", AF_INET6, 0, NULL, 0);
+}
+
+int main(int argc, char **argv) {
+ log_parse_environment();
+ log_open();
+
+ test_config_parse_duid_type();
+ test_config_parse_duid_rawdata();
+ test_config_parse_hwaddr();
+ test_config_parse_address();
+
+ return 0;
+}
diff --git a/src/network/test-routing-policy-rule.c b/src/network/test-routing-policy-rule.c
new file mode 100644
index 0000000..57bfb6a
--- /dev/null
+++ b/src/network/test-routing-policy-rule.c
@@ -0,0 +1,92 @@
+/***
+ SPDX-License-Identifier: LGPL-2.1+
+***/
+
+#include "fd-util.h"
+#include "fileio.h"
+#include "log.h"
+#include "macro.h"
+#include "network-internal.h"
+#include "networkd-manager.h"
+#include "string-util.h"
+#include "tests.h"
+#include "tmpfile-util.h"
+
+static void test_rule_serialization(const char *title, const char *ruleset, const char *expected) {
+ char pattern[] = "/tmp/systemd-test-routing-policy-rule.XXXXXX",
+ pattern2[] = "/tmp/systemd-test-routing-policy-rule.XXXXXX",
+ pattern3[] = "/tmp/systemd-test-routing-policy-rule.XXXXXX";
+ const char *cmd;
+ int fd, fd2, fd3;
+ _cleanup_fclose_ FILE *f = NULL, *f2 = NULL, *f3 = NULL;
+ Set *rules = NULL;
+ _cleanup_free_ char *buf = NULL;
+ size_t buf_size;
+
+ log_info("========== %s ==========", title);
+ log_info("put:\n%s\n", ruleset);
+
+ fd = mkostemp_safe(pattern);
+ assert_se(fd >= 0);
+ assert_se(f = fdopen(fd, "a+"));
+ assert_se(write_string_stream(f, ruleset, 0) == 0);
+
+ assert_se(routing_policy_load_rules(pattern, &rules) == 0);
+
+ fd2 = mkostemp_safe(pattern2);
+ assert_se(fd2 >= 0);
+ assert_se(f2 = fdopen(fd2, "a+"));
+
+ assert_se(routing_policy_serialize_rules(rules, f2) == 0);
+ assert_se(fflush_and_check(f2) == 0);
+
+ assert_se(read_full_file(pattern2, &buf, &buf_size) == 0);
+
+ log_info("got:\n%s", buf);
+
+ fd3 = mkostemp_safe(pattern3);
+ assert_se(fd3 >= 0);
+ assert_se(f3 = fdopen(fd3, "w"));
+ assert_se(write_string_stream(f3, expected ?: ruleset, 0) == 0);
+
+ cmd = strjoina("diff -u ", pattern3, " ", pattern2);
+ log_info("$ %s", cmd);
+ assert_se(system(cmd) == 0);
+
+ set_free_with_destructor(rules, routing_policy_rule_free);
+}
+
+int main(int argc, char **argv) {
+ _cleanup_free_ char *p = NULL;
+
+ test_setup_logging(LOG_DEBUG);
+
+ test_rule_serialization("basic parsing",
+ "RULE=from=1.2.3.4/32 to=2.3.4.5/32 tos=5 fwmark=1/2 table=10", NULL);
+
+ test_rule_serialization("ignored values",
+ "RULE=something=to=ignore from=1.2.3.4/32 from=1.2.3.4/32"
+ " \t to=2.3.4.5/24 to=2.3.4.5/32 tos=5 fwmark=2 fwmark=1 table=10 table=20",
+ "RULE=from=1.2.3.4/32"
+ " to=2.3.4.5/32 tos=5 fwmark=1/0 table=20");
+
+ test_rule_serialization("ipv6",
+ "RULE=from=1::2/64 to=2::3/64 table=6", NULL);
+
+ assert_se(asprintf(&p, "RULE=from=1::2/64 to=2::3/64 table=%d", RT_TABLE_MAIN) >= 0);
+ test_rule_serialization("default table",
+ "RULE=from=1::2/64 to=2::3/64", p);
+
+ test_rule_serialization("incoming interface",
+ "RULE=from=1::2/64 to=2::3/64 table=1 iif=lo",
+ "RULE=from=1::2/64 to=2::3/64 iif=lo table=1");
+
+ test_rule_serialization("outgoing interface",
+ "RULE=from=1::2/64 to=2::3/64 oif=eth0 table=1", NULL);
+
+ test_rule_serialization("freeing interface names",
+ "RULE=from=1::2/64 to=2::3/64 iif=e0 iif=e1 oif=e0 oif=e1 table=1",
+ "RULE=from=1::2/64 to=2::3/64 iif=e1 oif=e1 table=1");
+
+ return 0;
+}
diff --git a/src/network/wait-online/link.c b/src/network/wait-online/link.c
new file mode 100644
index 0000000..87d54fe
--- /dev/null
+++ b/src/network/wait-online/link.c
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "sd-network.h"
+
+#include "alloc-util.h"
+#include "hashmap.h"
+#include "link.h"
+#include "manager.h"
+#include "string-util.h"
+
+int link_new(Manager *m, Link **ret, int ifindex, const char *ifname) {
+ _cleanup_(link_freep) Link *l = NULL;
+ int r;
+
+ assert(m);
+ assert(ifindex > 0);
+
+ r = hashmap_ensure_allocated(&m->links, NULL);
+ if (r < 0)
+ return r;
+
+ r = hashmap_ensure_allocated(&m->links_by_name, &string_hash_ops);
+ if (r < 0)
+ return r;
+
+ l = new0(Link, 1);
+ if (!l)
+ return -ENOMEM;
+
+ l->manager = m;
+
+ l->ifname = strdup(ifname);
+ if (!l->ifname)
+ return -ENOMEM;
+
+ r = hashmap_put(m->links_by_name, l->ifname, l);
+ if (r < 0)
+ return r;
+
+ l->ifindex = ifindex;
+
+ r = hashmap_put(m->links, INT_TO_PTR(ifindex), l);
+ if (r < 0)
+ return r;
+
+ if (ret)
+ *ret = l;
+ l = NULL;
+
+ return 0;
+}
+
+Link *link_free(Link *l) {
+
+ if (!l)
+ return NULL;
+
+ if (l->manager) {
+ hashmap_remove(l->manager->links, INT_TO_PTR(l->ifindex));
+ hashmap_remove(l->manager->links_by_name, l->ifname);
+ }
+
+ free(l->ifname);
+ return mfree(l);
+ }
+
+int link_update_rtnl(Link *l, sd_netlink_message *m) {
+ const char *ifname;
+ int r;
+
+ assert(l);
+ assert(l->manager);
+ assert(m);
+
+ r = sd_rtnl_message_link_get_flags(m, &l->flags);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_read_string(m, IFLA_IFNAME, &ifname);
+ if (r < 0)
+ return r;
+
+ if (!streq(l->ifname, ifname)) {
+ char *new_ifname;
+
+ new_ifname = strdup(ifname);
+ if (!new_ifname)
+ return -ENOMEM;
+
+ hashmap_remove(l->manager->links_by_name, l->ifname);
+ free(l->ifname);
+ l->ifname = new_ifname;
+
+ r = hashmap_put(l->manager->links_by_name, l->ifname, l);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+int link_update_monitor(Link *l) {
+ assert(l);
+
+ l->required_for_online = sd_network_link_get_required_for_online(l->ifindex) != 0;
+
+ l->operational_state = mfree(l->operational_state);
+
+ sd_network_link_get_operational_state(l->ifindex, &l->operational_state);
+
+ l->state = mfree(l->state);
+
+ sd_network_link_get_setup_state(l->ifindex, &l->state);
+
+ return 0;
+}
diff --git a/src/network/wait-online/link.h b/src/network/wait-online/link.h
new file mode 100644
index 0000000..57e8f12
--- /dev/null
+++ b/src/network/wait-online/link.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "sd-netlink.h"
+
+typedef struct Link Link;
+typedef struct Manager Manager;
+
+struct Link {
+ Manager *manager;
+
+ int ifindex;
+ char *ifname;
+ unsigned flags;
+
+ bool required_for_online;
+ char *operational_state;
+ char *state;
+};
+
+int link_new(Manager *m, Link **ret, int ifindex, const char *ifname);
+Link *link_free(Link *l);
+int link_update_rtnl(Link *l, sd_netlink_message *m);
+int link_update_monitor(Link *l);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free);
diff --git a/src/network/wait-online/manager.c b/src/network/wait-online/manager.c
new file mode 100644
index 0000000..67218b6
--- /dev/null
+++ b/src/network/wait-online/manager.c
@@ -0,0 +1,316 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <netinet/ether.h>
+#include <linux/if.h>
+#include <fnmatch.h>
+
+#include "alloc-util.h"
+#include "link.h"
+#include "manager.h"
+#include "netlink-util.h"
+#include "network-internal.h"
+#include "strv.h"
+#include "time-util.h"
+#include "util.h"
+
+bool manager_ignore_link(Manager *m, Link *link) {
+ assert(m);
+ assert(link);
+
+ /* always ignore the loopback interface */
+ if (link->flags & IFF_LOOPBACK)
+ return true;
+
+ /* if interfaces are given on the command line, ignore all others */
+ if (m->interfaces && !strv_contains(m->interfaces, link->ifname))
+ return true;
+
+ if (!link->required_for_online)
+ return true;
+
+ /* ignore interfaces we explicitly are asked to ignore */
+ return strv_fnmatch(m->ignore, link->ifname, 0);
+}
+
+bool manager_all_configured(Manager *m) {
+ Iterator i;
+ Link *l;
+ char **ifname;
+ bool one_ready = false;
+
+ /* wait for all the links given on the command line to appear */
+ STRV_FOREACH(ifname, m->interfaces) {
+ l = hashmap_get(m->links_by_name, *ifname);
+ if (!l) {
+ log_debug("still waiting for %s", *ifname);
+ return false;
+ }
+ }
+
+ /* wait for all links networkd manages to be in admin state 'configured'
+ and at least one link to gain a carrier */
+ HASHMAP_FOREACH(l, m->links, i) {
+ if (manager_ignore_link(m, l)) {
+ log_info("ignoring: %s", l->ifname);
+ continue;
+ }
+
+ if (!l->state) {
+ log_debug("link %s has not yet been processed by udev",
+ l->ifname);
+ return false;
+ }
+
+ if (STR_IN_SET(l->state, "configuring", "pending")) {
+ log_debug("link %s is being processed by networkd",
+ l->ifname);
+ return false;
+ }
+
+ if (l->operational_state &&
+ STR_IN_SET(l->operational_state, "degraded", "routable"))
+ /* we wait for at least one link to be ready,
+ regardless of who manages it */
+ one_ready = true;
+ }
+
+ return one_ready;
+}
+
+static int manager_process_link(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) {
+ Manager *m = userdata;
+ uint16_t type;
+ Link *l;
+ const char *ifname;
+ int ifindex, r;
+
+ assert(rtnl);
+ assert(m);
+ assert(mm);
+
+ r = sd_netlink_message_get_type(mm, &type);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: Could not get message type, ignoring: %m");
+ return 0;
+ }
+
+ r = sd_rtnl_message_link_get_ifindex(mm, &ifindex);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: Could not get ifindex from link, ignoring: %m");
+ return 0;
+ } else if (ifindex <= 0) {
+ log_warning("rtnl: received link message with invalid ifindex %d, ignoring", ifindex);
+ return 0;
+ }
+
+ r = sd_netlink_message_read_string(mm, IFLA_IFNAME, &ifname);
+ if (r < 0) {
+ log_warning_errno(r, "rtnl: Received link message without ifname, ignoring: %m");
+ return 0;
+ }
+
+ l = hashmap_get(m->links, INT_TO_PTR(ifindex));
+
+ switch (type) {
+
+ case RTM_NEWLINK:
+ if (!l) {
+ log_debug("Found link %i", ifindex);
+
+ r = link_new(m, &l, ifindex, ifname);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create link object: %m");
+
+ r = link_update_monitor(l);
+ if (r < 0)
+ return log_error_errno(r, "Failed to initialize link object: %m");
+ }
+
+ r = link_update_rtnl(l, mm);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to process RTNL link message: %m");;
+
+ break;
+
+ case RTM_DELLINK:
+ if (l) {
+ log_debug("Removing link %i", l->ifindex);
+ link_free(l);
+ }
+
+ break;
+ }
+
+ return 0;
+}
+
+static int on_rtnl_event(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) {
+ Manager *m = userdata;
+ int r;
+
+ r = manager_process_link(rtnl, mm, m);
+ if (r < 0)
+ return r;
+
+ if (manager_all_configured(m))
+ sd_event_exit(m->event, 0);
+
+ return 1;
+}
+
+static int manager_rtnl_listen(Manager *m) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL;
+ sd_netlink_message *i;
+ int r;
+
+ assert(m);
+
+ /* First, subscribe to interfaces coming and going */
+ r = sd_netlink_open(&m->rtnl);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_attach_event(m->rtnl, m->event, 0);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_add_match(m->rtnl, NULL, RTM_NEWLINK, on_rtnl_event, NULL, m, "wait-online-on-NEWLINK");
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_add_match(m->rtnl, NULL, RTM_DELLINK, on_rtnl_event, NULL, m, "wait-online-on-DELLINK");
+ if (r < 0)
+ return r;
+
+ /* Then, enumerate all links */
+ r = sd_rtnl_message_new_link(m->rtnl, &req, RTM_GETLINK, 0);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_message_request_dump(req, true);
+ if (r < 0)
+ return r;
+
+ r = sd_netlink_call(m->rtnl, req, 0, &reply);
+ if (r < 0)
+ return r;
+
+ for (i = reply; i; i = sd_netlink_message_next(i)) {
+ r = manager_process_link(m->rtnl, i, m);
+ if (r < 0)
+ return r;
+ }
+
+ return r;
+}
+
+static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ Manager *m = userdata;
+ Iterator i;
+ Link *l;
+ int r;
+
+ assert(m);
+
+ sd_network_monitor_flush(m->network_monitor);
+
+ HASHMAP_FOREACH(l, m->links, i) {
+ r = link_update_monitor(l);
+ if (r < 0)
+ log_warning_errno(r, "Failed to update monitor information for %i: %m", l->ifindex);
+ }
+
+ if (manager_all_configured(m))
+ sd_event_exit(m->event, 0);
+
+ return 0;
+}
+
+static int manager_network_monitor_listen(Manager *m) {
+ int r, fd, events;
+
+ assert(m);
+
+ r = sd_network_monitor_new(&m->network_monitor, NULL);
+ if (r < 0)
+ return r;
+
+ fd = sd_network_monitor_get_fd(m->network_monitor);
+ if (fd < 0)
+ return fd;
+
+ events = sd_network_monitor_get_events(m->network_monitor);
+ if (events < 0)
+ return events;
+
+ r = sd_event_add_io(m->event, &m->network_monitor_event_source,
+ fd, events, &on_network_event, m);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int manager_new(Manager **ret, char **interfaces, char **ignore, usec_t timeout) {
+ _cleanup_(manager_freep) Manager *m = NULL;
+ int r;
+
+ assert(ret);
+
+ m = new0(Manager, 1);
+ if (!m)
+ return -ENOMEM;
+
+ m->interfaces = interfaces;
+ m->ignore = ignore;
+
+ r = sd_event_default(&m->event);
+ if (r < 0)
+ return r;
+
+ (void) sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL);
+ (void) sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL);
+
+ if (timeout > 0) {
+ usec_t usec;
+
+ usec = now(clock_boottime_or_monotonic()) + timeout;
+
+ r = sd_event_add_time(m->event, NULL, clock_boottime_or_monotonic(), usec, 0, NULL, INT_TO_PTR(-ETIMEDOUT));
+ if (r < 0)
+ return r;
+ }
+
+ sd_event_set_watchdog(m->event, true);
+
+ r = manager_network_monitor_listen(m);
+ if (r < 0)
+ return r;
+
+ r = manager_rtnl_listen(m);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(m);
+
+ return 0;
+}
+
+void manager_free(Manager *m) {
+ if (!m)
+ return;
+
+ hashmap_free_with_destructor(m->links, link_free);
+ hashmap_free(m->links_by_name);
+
+ sd_event_source_unref(m->network_monitor_event_source);
+ sd_network_monitor_unref(m->network_monitor);
+
+ sd_event_source_unref(m->rtnl_event_source);
+ sd_netlink_unref(m->rtnl);
+
+ sd_event_unref(m->event);
+ free(m);
+
+ return;
+}
diff --git a/src/network/wait-online/manager.h b/src/network/wait-online/manager.h
new file mode 100644
index 0000000..0fde272
--- /dev/null
+++ b/src/network/wait-online/manager.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "sd-event.h"
+#include "sd-netlink.h"
+#include "sd-network.h"
+
+#include "hashmap.h"
+
+typedef struct Manager Manager;
+typedef struct Link Link;
+
+struct Manager {
+ Hashmap *links;
+ Hashmap *links_by_name;
+
+ char **interfaces;
+ char **ignore;
+
+ sd_netlink *rtnl;
+ sd_event_source *rtnl_event_source;
+
+ sd_network_monitor *network_monitor;
+ sd_event_source *network_monitor_event_source;
+
+ sd_event *event;
+};
+
+void manager_free(Manager *m);
+int manager_new(Manager **ret, char **interfaces, char **ignore, usec_t timeout);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
+
+bool manager_all_configured(Manager *m);
+bool manager_ignore_link(Manager *m, Link *link);
diff --git a/src/network/wait-online/wait-online.c b/src/network/wait-online/wait-online.c
new file mode 100644
index 0000000..71b6cf6
--- /dev/null
+++ b/src/network/wait-online/wait-online.c
@@ -0,0 +1,152 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <getopt.h>
+
+#include "sd-daemon.h"
+
+#include "daemon-util.h"
+#include "main-func.h"
+#include "manager.h"
+#include "pretty-print.h"
+#include "signal-util.h"
+#include "strv.h"
+
+static bool arg_quiet = false;
+static usec_t arg_timeout = 120 * USEC_PER_SEC;
+static char **arg_interfaces = NULL;
+static char **arg_ignore = NULL;
+
+STATIC_DESTRUCTOR_REGISTER(arg_interfaces, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_ignore, strv_freep);
+
+static int help(void) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("systemd-networkd-wait-online.service", "8", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%s [OPTIONS...]\n\n"
+ "Block until network is configured.\n\n"
+ " -h --help Show this help\n"
+ " --version Print version string\n"
+ " -q --quiet Do not show status information\n"
+ " -i --interface=INTERFACE Block until at least these interfaces have appeared\n"
+ " --ignore=INTERFACE Don't take these interfaces into account\n"
+ " --timeout=SECS Maximum time to wait for network connectivity\n"
+ "\nSee the %s for details.\n"
+ , program_invocation_short_name
+ , link
+ );
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_IGNORE,
+ ARG_TIMEOUT,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "quiet", no_argument, NULL, 'q' },
+ { "interface", required_argument, NULL, 'i' },
+ { "ignore", required_argument, NULL, ARG_IGNORE },
+ { "timeout", required_argument, NULL, ARG_TIMEOUT },
+ {}
+ };
+
+ int c, r;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "+hi:q", options, NULL)) >= 0)
+
+ switch (c) {
+
+ case 'h':
+ help();
+ return 0;
+
+ case 'q':
+ arg_quiet = true;
+ break;
+
+ case ARG_VERSION:
+ return version();
+
+ case 'i':
+ if (strv_extend(&arg_interfaces, optarg) < 0)
+ return log_oom();
+
+ break;
+
+ case ARG_IGNORE:
+ if (strv_extend(&arg_ignore, optarg) < 0)
+ return log_oom();
+
+ break;
+
+ case ARG_TIMEOUT:
+ r = parse_sec(optarg, &arg_timeout);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option");
+ }
+
+ return 1;
+}
+
+static int run(int argc, char *argv[]) {
+ _cleanup_(notify_on_cleanup) const char *notify_message = NULL;
+ _cleanup_(manager_freep) Manager *m = NULL;
+ int r;
+
+ log_setup_service();
+
+ umask(0022);
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ if (arg_quiet)
+ log_set_max_level(LOG_WARNING);
+
+ assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
+
+ r = manager_new(&m, arg_interfaces, arg_ignore, arg_timeout);
+ if (r < 0)
+ return log_error_errno(r, "Could not create manager: %m");
+
+ if (manager_all_configured(m))
+ goto success;
+
+ notify_message = notify_start("READY=1\n"
+ "STATUS=Waiting for network connections...",
+ "STATUS=Failed to wait for network connectivity...");
+
+ r = sd_event_loop(m->event);
+ if (r < 0)
+ return log_error_errno(r, "Event loop failed: %m");
+
+success:
+ notify_message = "STATUS=All interfaces configured...";
+
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);