diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:25:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 02:25:50 +0000 |
commit | 19f4f86bfed21c5326ed2acebe1163f3a83e832b (patch) | |
tree | d59b9989ce55ed23693e80974d94c856f1c2c8b1 /src/network | |
parent | Initial commit. (diff) | |
download | systemd-d54215faec6264b9021f699873239ee9e4d99e5b.tar.xz systemd-d54215faec6264b9021f699873239ee9e4d99e5b.zip |
Adding upstream version 241.upstream/241upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/network')
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, ÐER_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(), ¤t) >= 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); |