From 3f36b9ed477ce3b003d03e3dcca7376fbdcee1a4 Mon Sep 17 00:00:00 2001 From: Topi Miettinen Date: Sat, 2 Jul 2022 17:15:42 +0300 Subject: [PATCH 1/4] basic: generate netmasks for IPv6 and generic IP family addresses Added functions to generate netmasks for IPv6 and generic IP family addresses. --- src/basic/in-addr-util.c | 42 +++++++++++++++++++++++++++++ src/basic/in-addr-util.h | 2 ++ src/test/test-in-addr-util.c | 52 ++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c index fe356c6d15..cefe3f7661 100644 --- a/src/basic/in-addr-util.c +++ b/src/basic/in-addr-util.c @@ -586,6 +586,7 @@ unsigned char in4_addr_netmask_to_prefixlen(const struct in_addr *addr) { return 32U - u32ctz(be32toh(addr->s_addr)); } +/* Calculate an IPv4 netmask from prefix length, for example /8 -> 255.0.0.0. */ struct in_addr* in4_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen) { assert(addr); assert(prefixlen <= 32); @@ -599,6 +600,47 @@ struct in_addr* in4_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned cha return addr; } +/* Calculate an IPv6 netmask from prefix length, for example /16 -> ffff::. */ +struct in6_addr* in6_addr_prefixlen_to_netmask(struct in6_addr *addr, unsigned char prefixlen) { + assert(addr); + assert(prefixlen <= 128); + + for (unsigned i = 0; i < 16; i++) { + uint8_t mask; + + if (prefixlen >= 8) { + mask = 0xFF; + prefixlen -= 8; + } else if (prefixlen > 0) { + mask = 0xFF << (8 - prefixlen); + prefixlen = 0; + } else { + assert(prefixlen == 0); + mask = 0; + } + + addr->s6_addr[i] = mask; + } + + return addr; +} + +/* Calculate an IPv4 or IPv6 netmask from prefix length, for example /8 -> 255.0.0.0 or /16 -> ffff::. */ +int in_addr_prefixlen_to_netmask(int family, union in_addr_union *addr, unsigned char prefixlen) { + assert(addr); + + switch (family) { + case AF_INET: + in4_addr_prefixlen_to_netmask(&addr->in, prefixlen); + return 0; + case AF_INET6: + in6_addr_prefixlen_to_netmask(&addr->in6, prefixlen); + return 0; + default: + return -EAFNOSUPPORT; + } +} + int in4_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen) { uint8_t msb_octet = *(uint8_t*) addr; diff --git a/src/basic/in-addr-util.h b/src/basic/in-addr-util.h index fbc60436c7..19fa35f1d2 100644 --- a/src/basic/in-addr-util.h +++ b/src/basic/in-addr-util.h @@ -138,6 +138,8 @@ int in_addr_from_string_auto(const char *s, int *ret_family, union in_addr_union unsigned char in4_addr_netmask_to_prefixlen(const struct in_addr *addr); struct in_addr* in4_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen); +struct in6_addr* in6_addr_prefixlen_to_netmask(struct in6_addr *addr, unsigned char prefixlen); +int in_addr_prefixlen_to_netmask(int family, union in_addr_union *addr, unsigned char prefixlen); int in4_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen); int in4_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask); int in4_addr_mask(struct in_addr *addr, unsigned char prefixlen); diff --git a/src/test/test-in-addr-util.c b/src/test/test-in-addr-util.c index f5dcad65d5..31d767e41d 100644 --- a/src/test/test-in-addr-util.c +++ b/src/test/test-in-addr-util.c @@ -364,4 +364,56 @@ TEST(in_addr_to_string) { test_in_addr_to_string_one(AF_INET6, "fe80::"); } +TEST(in_addr_prefixlen_to_netmask) { + union in_addr_union addr; + static const char *const ipv4_netmasks[] = { + "0.0.0.0", "128.0.0.0", "192.0.0.0", "224.0.0.0", "240.0.0.0", + "248.0.0.0", "252.0.0.0", "254.0.0.0", "255.0.0.0", + "255.128.0.0", "255.192.0.0", "255.224.0.0", "255.240.0.0", + "255.248.0.0", "255.252.0.0", "255.254.0.0", "255.255.0.0", + "255.255.128.0", "255.255.192.0", "255.255.224.0", "255.255.240.0", + "255.255.248.0", "255.255.252.0", "255.255.254.0", "255.255.255.0", + "255.255.255.128", "255.255.255.192", "255.255.255.224", "255.255.255.240", + "255.255.255.248", "255.255.255.252", "255.255.255.254", "255.255.255.255", + }; + + static const char *const ipv6_netmasks[] = { + [0] = "::", + [1] = "8000::", + [2] = "c000::", + [7] = "fe00::", + [8] = "ff00::", + [9] = "ff80::", + [16] = "ffff::", + [17] = "ffff:8000::", + [32] = "ffff:ffff::", + [33] = "ffff:ffff:8000::", + [64] = "ffff:ffff:ffff:ffff::", + [65] = "ffff:ffff:ffff:ffff:8000::", + [96] = "ffff:ffff:ffff:ffff:ffff:ffff::", + [97] = "ffff:ffff:ffff:ffff:ffff:ffff:8000:0", + [127] = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe", + [128] = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" + }; + + for (unsigned char prefixlen = 0; prefixlen <= 32; prefixlen++) { + _cleanup_free_ char *result = NULL; + + assert_se(in_addr_prefixlen_to_netmask(AF_INET, &addr, prefixlen) >= 0); + assert_se(in_addr_to_string(AF_INET, &addr, &result) >= 0); + printf("test_in_addr_prefixlen_to_netmask: %s == %s\n", ipv4_netmasks[prefixlen], result); + assert_se(streq(ipv4_netmasks[prefixlen], result)); + } + + for (unsigned char prefixlen = 0; prefixlen <= 128; prefixlen++) { + _cleanup_free_ char *result = NULL; + + assert_se(in_addr_prefixlen_to_netmask(AF_INET6, &addr, prefixlen) >= 0); + assert_se(in_addr_to_string(AF_INET6, &addr, &result) >= 0); + printf("test_in_addr_prefixlen_to_netmask: %s\n", result); + if (ipv6_netmasks[prefixlen]) + assert_se(streq(ipv6_netmasks[prefixlen], result)); + } +} + DEFINE_TEST_MAIN(LOG_DEBUG); From 5b198025def31d8f004d74f0001a844010bd650f Mon Sep 17 00:00:00 2001 From: Topi Miettinen Date: Sun, 28 Aug 2022 07:09:55 +0900 Subject: [PATCH 2/4] sd-netlink: add NetLabel support --- src/basic/missing_network.h | 32 +++++++++++++++++++ .../sd-netlink/netlink-types-genl.c | 25 +++++++++++---- src/libsystemd/sd-netlink/test-netlink.c | 2 ++ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/basic/missing_network.h b/src/basic/missing_network.h index 6e71b26afd..776c7c8375 100644 --- a/src/basic/missing_network.h +++ b/src/basic/missing_network.h @@ -49,3 +49,35 @@ #ifndef IEEE80211_MAX_SSID_LEN #define IEEE80211_MAX_SSID_LEN 32 #endif + +/* Not exposed but defined in include/net/netlabel.h */ +#ifndef NETLBL_NLTYPE_UNLABELED_NAME +#define NETLBL_NLTYPE_UNLABELED_NAME "NLBL_UNLBL" +#endif + +/* Not exposed but defined in net/netlabel/netlabel_unlabeled.h */ +enum { + NLBL_UNLABEL_C_UNSPEC, + NLBL_UNLABEL_C_ACCEPT, + NLBL_UNLABEL_C_LIST, + NLBL_UNLABEL_C_STATICADD, + NLBL_UNLABEL_C_STATICREMOVE, + NLBL_UNLABEL_C_STATICLIST, + NLBL_UNLABEL_C_STATICADDDEF, + NLBL_UNLABEL_C_STATICREMOVEDEF, + NLBL_UNLABEL_C_STATICLISTDEF, + __NLBL_UNLABEL_C_MAX, +}; + +/* Not exposed but defined in net/netlabel/netlabel_unlabeled.h */ +enum { + NLBL_UNLABEL_A_UNSPEC, + NLBL_UNLABEL_A_ACPTFLG, + NLBL_UNLABEL_A_IPV6ADDR, + NLBL_UNLABEL_A_IPV6MASK, + NLBL_UNLABEL_A_IPV4ADDR, + NLBL_UNLABEL_A_IPV4MASK, + NLBL_UNLABEL_A_IFACE, + NLBL_UNLABEL_A_SECCTX, + __NLBL_UNLABEL_A_MAX, +}; diff --git a/src/libsystemd/sd-netlink/netlink-types-genl.c b/src/libsystemd/sd-netlink/netlink-types-genl.c index 9d6d978b45..6fe9adcdbd 100644 --- a/src/libsystemd/sd-netlink/netlink-types-genl.c +++ b/src/libsystemd/sd-netlink/netlink-types-genl.c @@ -178,6 +178,16 @@ static const NLAPolicy genl_macsec_policies[] = { [MACSEC_ATTR_SA_CONFIG] = BUILD_POLICY_NESTED(genl_macsec_sa), }; +/***************** genl NetLabel type systems *****************/ +static const NLAPolicy genl_netlabel_policies[] = { + [NLBL_UNLABEL_A_IPV4ADDR] = BUILD_POLICY(IN_ADDR), + [NLBL_UNLABEL_A_IPV4MASK] = BUILD_POLICY(IN_ADDR), + [NLBL_UNLABEL_A_IPV6ADDR] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in6_addr)), + [NLBL_UNLABEL_A_IPV6MASK] = BUILD_POLICY_WITH_SIZE(IN_ADDR, sizeof(struct in6_addr)), + [NLBL_UNLABEL_A_IFACE] = BUILD_POLICY_WITH_SIZE(STRING, IFNAMSIZ-1), + [NLBL_UNLABEL_A_SECCTX] = BUILD_POLICY(STRING), +}; + /***************** genl nl80211 type systems *****************/ static const NLAPolicy genl_nl80211_policies[] = { [NL80211_ATTR_WIPHY] = BUILD_POLICY(U32), @@ -223,13 +233,14 @@ static const NLAPolicy genl_wireguard_policies[] = { /***************** genl families *****************/ static const NLAPolicySetUnionElement genl_policy_set_union_elements[] = { - BUILD_UNION_ELEMENT_BY_STRING(CTRL_GENL_NAME, genl_ctrl), - BUILD_UNION_ELEMENT_BY_STRING(BATADV_NL_NAME, genl_batadv), - BUILD_UNION_ELEMENT_BY_STRING(FOU_GENL_NAME, genl_fou), - BUILD_UNION_ELEMENT_BY_STRING(L2TP_GENL_NAME, genl_l2tp), - BUILD_UNION_ELEMENT_BY_STRING(MACSEC_GENL_NAME, genl_macsec), - BUILD_UNION_ELEMENT_BY_STRING(NL80211_GENL_NAME, genl_nl80211), - BUILD_UNION_ELEMENT_BY_STRING(WG_GENL_NAME, genl_wireguard), + BUILD_UNION_ELEMENT_BY_STRING(CTRL_GENL_NAME, genl_ctrl), + BUILD_UNION_ELEMENT_BY_STRING(BATADV_NL_NAME, genl_batadv), + BUILD_UNION_ELEMENT_BY_STRING(FOU_GENL_NAME, genl_fou), + BUILD_UNION_ELEMENT_BY_STRING(L2TP_GENL_NAME, genl_l2tp), + BUILD_UNION_ELEMENT_BY_STRING(MACSEC_GENL_NAME, genl_macsec), + BUILD_UNION_ELEMENT_BY_STRING(NETLBL_NLTYPE_UNLABELED_NAME, genl_netlabel), + BUILD_UNION_ELEMENT_BY_STRING(NL80211_GENL_NAME, genl_nl80211), + BUILD_UNION_ELEMENT_BY_STRING(WG_GENL_NAME, genl_wireguard), }; /* This is the root type system union, so match_attribute is not necessary. */ diff --git a/src/libsystemd/sd-netlink/test-netlink.c b/src/libsystemd/sd-netlink/test-netlink.c index 5eda53b596..50e9d64663 100644 --- a/src/libsystemd/sd-netlink/test-netlink.c +++ b/src/libsystemd/sd-netlink/test-netlink.c @@ -656,6 +656,8 @@ static void test_genl(void) { (void) sd_genl_message_new(genl, MACSEC_GENL_NAME, 0, &m); m = sd_netlink_message_unref(m); (void) sd_genl_message_new(genl, NL80211_GENL_NAME, 0, &m); + m = sd_netlink_message_unref(m); + (void) sd_genl_message_new(genl, NETLBL_NLTYPE_UNLABELED_NAME, 0, &m); for (;;) { r = sd_event_run(event, 500 * USEC_PER_MSEC); From 4b3590c32457c938b0e351053069ddb344f35b31 Mon Sep 17 00:00:00 2001 From: Topi Miettinen Date: Sat, 20 Aug 2022 20:52:48 +0300 Subject: [PATCH 3/4] network: NetLabel integration New directive `NetLabel=` provides a method for integrating static and dynamic network configuration into Linux NetLabel subsystem rules, used by Linux Security Modules (LSMs) for network access control. The label, with suitable LSM rules, can be used to control connectivity of (for example) a service with peers in the local network. At least with SELinux, only the ingress can be controlled but not egress. The benefit of using this setting is that it may be possible to apply interface independent part of NetLabel configuration at very early stage of system boot sequence, at the time when the network interfaces are not available yet, with netlabelctl(8), and the per-interface configuration with systemd-networkd once the interfaces appear later. Currently this feature is only implemented for SELinux. The option expects a single NetLabel label. The label must conform to lexical restrictions of LSM labels. When an interface is configured with IP addresses, the addresses and subnetwork masks will be appended to the NetLabel Fallback Peer Labeling rules. They will be removed when the interface is deconfigured. Failures to manage the labels will be ignored. Example: ``` [DHCPv4] NetLabel=system_u:object_r:localnet_peer_t:s0 ``` With the above rules for interface `eth0`, when the interface is configured with an IPv4 address of 10.0.0.123/8, `systemd-networkd` performs the equivalent of `netlabelctl` operation ``` $ sudo netlabelctl unlbl add interface eth0 address:10.0.0.0/8 label:system_u:object_r:localnet_peer_t:s0 ``` Result: ``` $ sudo netlabelctl -p unlbl list ... interface: eth0 address: 10.0.0.0/8 label: "system_u:object_r:localnet_peer_t:s0" ... ``` --- man/systemd.network.xml | 84 ++++++++++++ src/network/meson.build | 2 + src/network/networkd-address.c | 52 +++++++ src/network/networkd-address.h | 3 +- src/network/networkd-dhcp-prefix-delegation.c | 4 + src/network/networkd-dhcp4.c | 4 + src/network/networkd-dhcp6.c | 4 + src/network/networkd-ndisc.c | 4 + src/network/networkd-netlabel.c | 128 ++++++++++++++++++ src/network/networkd-netlabel.h | 5 + src/network/networkd-network-gperf.gperf | 6 + src/network/networkd-network.c | 4 + src/network/networkd-network.h | 4 + test/fuzz/fuzz-network-parser/directives | 5 + 14 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 src/network/networkd-netlabel.c create mode 100644 src/network/networkd-netlabel.h diff --git a/man/systemd.network.xml b/man/systemd.network.xml index b9d74d6097..4f516748ed 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -1121,6 +1121,62 @@ Table=1234 Defaults to no. + + + NetLabel=label + + + This setting provides a method for integrating static and dynamic network configuration into + Linux NetLabel subsystem rules, + used by Linux Security Modules + (LSMs) for network access control. The label, with suitable LSM rules, can be used to + control connectivity of (for example) a service with peers in the local network. At least with + SELinux, only the ingress can be controlled but not egress. The benefit of using this setting is + that it may be possible to apply interface independent part of NetLabel configuration at very early + stage of system boot sequence, at the time when the network interfaces are not available yet, with + netlabelctl8, + and the per-interface configuration with systemd-networkd once the interfaces + appear later. Currently this feature is only implemented for SELinux. + + The option expects a single NetLabel label. The label must conform to lexical restrictions of + LSM labels. When an interface is configured with IP addresses, the addresses and subnetwork masks + will be appended to the NetLabel + Fallback Peer Labeling rules. They will be removed when the interface is + deconfigured. Failures to manage the labels will be ignored. + + Warning: Once labeling is enabled for network traffic, a lot of LSM access control points in + Linux networking stack go from dormant to active. Care should be taken to avoid getting into a + situation where for example remote connectivity is broken, when the security policy hasn't been + updated to consider LSM per-packet access controls and no rules would allow any network + traffic. Also note that additional configuration with netlabelctl8 + is needed. + + Example: + [Address] +NetLabel=system_u:object_r:localnet_peer_t:s0 + + With the example rules applying for interface eth0, when the interface is + configured with an IPv4 address of 10.0.0.123/8, systemd-networkd performs the + equivalent of netlabelctl operation + + netlabelctl unlbl add interface eth0 address:10.0.0.0/8 label:system_u:object_r:localnet_peer_t:s0 + + and the reverse operation when the IPv4 address is deconfigured. The configuration can be used with + LSM rules; in case of SELinux to allow a SELinux domain to receive data from objects of SELinux + peer class. For example: + + type localnet_peer_t; +allow my_server_t localnet_peer_t:peer recv; + + The effect of the above configuration and rules (in absence of other rules as may be the case) is + to only allow my_server_t (and nothing else) to receive data from local subnet + 10.0.0.0/8 of interface eth0. + + + @@ -2071,6 +2127,15 @@ Table=1234 RFC 5227. Defaults to false. + + + NetLabel= + + This applies the NetLabel for the addresses received with DHCP, like + NetLabel= in [Address] section applies it to statically configured + addresses. See NetLabel= in [Address] section for more details. + + @@ -2197,6 +2262,7 @@ Table=1234 UseNTP= UseHostname= UseDomains= + NetLabel= As in the [DHCPv4] section. @@ -2298,6 +2364,15 @@ Table=1234 + + + NetLabel= + + This applies the NetLabel for the addresses received with DHCP, like + NetLabel= in [Address] section applies it to statically configured + addresses. See NetLabel= in [Address] section for more details. + + @@ -2555,6 +2630,15 @@ Token=prefixstable:2002:da8:1:: specified. Defaults to true. + + + NetLabel= + + This applies the NetLabel for the addresses received with RA, like + NetLabel= in [Address] section applies it to statically configured + addresses. See NetLabel= in [Address] section for more details. + + diff --git a/src/network/meson.build b/src/network/meson.build index 2315b56a33..e4def6bc51 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -115,6 +115,8 @@ sources = files( 'networkd-ndisc.h', 'networkd-neighbor.c', 'networkd-neighbor.h', + 'networkd-netlabel.c', + 'networkd-netlabel.h', 'networkd-network-bus.c', 'networkd-network-bus.h', 'networkd-network.c', diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 0cd6c1498d..d7333b634e 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -12,6 +12,7 @@ #include "networkd-dhcp-server.h" #include "networkd-ipv4acd.h" #include "networkd-manager.h" +#include "networkd-netlabel.h" #include "networkd-network.h" #include "networkd-queue.h" #include "networkd-route-util.h" @@ -137,6 +138,7 @@ Address *address_free(Address *address) { config_section_free(address->section); free(address->label); + free(address->netlabel); return mfree(address); } @@ -392,6 +394,7 @@ int address_dup(const Address *src, Address **ret) { dest->link = NULL; dest->label = NULL; dest->acd = NULL; + dest->netlabel = NULL; if (src->family == AF_INET) { r = free_and_strdup(&dest->label, src->label); @@ -399,6 +402,10 @@ int address_dup(const Address *src, Address **ret) { return r; } + r = free_and_strdup(&dest->netlabel, src->netlabel); + if (r < 0) + return r; + *ret = TAKE_PTR(dest); return 0; } @@ -485,6 +492,8 @@ static int address_update(Address *address) { if (r < 0) return log_link_warning_errno(link, r, "Could not enable IP masquerading: %m"); + address_add_netlabel(address); + if (address_is_ready(address) && address->callback) { r = address->callback(address); if (r < 0) @@ -511,6 +520,8 @@ static int address_drop(Address *address) { if (r < 0) log_link_warning_errno(link, r, "Failed to disable IP masquerading, ignoring: %m"); + address_del_netlabel(address); + if (address->state == 0) address_free(address); @@ -1941,6 +1952,47 @@ int config_parse_duplicate_address_detection( return 0; } +int config_parse_address_netlabel( + const char *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_free_or_set_invalidp) Address *n = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + assert(network); + + r = address_new_static(network, filename, section_line, &n); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate new address, ignoring assignment: %m"); + return 0; + } + + r = config_parse_string(unit, filename, line, section, section_line, + lvalue, CONFIG_PARSE_STRING_SAFE, rvalue, &n->netlabel, network); + if (r < 0) + return r; + + TAKE_PTR(n); + return 0; +} + static int address_section_verify(Address *address) { if (section_is_invalid(address->section)) return -EINVAL; diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h index f97342504c..d1eff9090a 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -38,7 +38,7 @@ struct Address { unsigned char scope; uint32_t flags; uint32_t route_metric; /* route metric for prefix route */ - char *label; + char *label, *netlabel; int set_broadcast; struct in_addr broadcast; @@ -130,3 +130,4 @@ CONFIG_PARSER_PROTOTYPE(config_parse_address_flags); CONFIG_PARSER_PROTOTYPE(config_parse_address_scope); CONFIG_PARSER_PROTOTYPE(config_parse_address_route_metric); CONFIG_PARSER_PROTOTYPE(config_parse_duplicate_address_detection); +CONFIG_PARSER_PROTOTYPE(config_parse_address_netlabel); diff --git a/src/network/networkd-dhcp-prefix-delegation.c b/src/network/networkd-dhcp-prefix-delegation.c index 62f0192bcb..5c883ba37d 100644 --- a/src/network/networkd-dhcp-prefix-delegation.c +++ b/src/network/networkd-dhcp-prefix-delegation.c @@ -412,6 +412,10 @@ static int dhcp_pd_request_address( log_dhcp_pd_address(link, address); + r = free_and_strdup_warn(&address->netlabel, link->network->dhcp_pd_netlabel); + if (r < 0) + return r; + if (address_get(link, address, &existing) < 0) link->dhcp_pd_configured = false; else diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 4df2848c54..02deac0033 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -877,6 +877,10 @@ static int dhcp4_request_address(Link *link, bool announce) { if (r < 0) return r; + r = free_and_strdup_warn(&addr->netlabel, link->network->dhcp_netlabel); + if (r < 0) + return r; + if (address_get(link, addr, &existing) < 0) /* The address is new. */ link->dhcp4_configured = false; else diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 4293eb6242..501e210d5f 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -224,6 +224,10 @@ static int dhcp6_request_address( if (verify_dhcp6_address(link, addr) < 0) return 0; + r = free_and_strdup_warn(&addr->netlabel, link->network->dhcp6_netlabel); + if (r < 0) + return r; + if (address_get(link, addr, &existing) < 0) link->dhcp6_configured = false; else diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 579282136f..12168a20fb 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -278,6 +278,10 @@ static int ndisc_request_address(Address *in, Link *link, sd_ndisc_router *rt) { address->source = NETWORK_CONFIG_SOURCE_NDISC; address->provider.in6 = router; + r = free_and_strdup_warn(&address->netlabel, link->network->ndisc_netlabel); + if (r < 0) + return r; + if (address_get(link, address, &existing) < 0) link->ndisc_configured = false; else diff --git a/src/network/networkd-netlabel.c b/src/network/networkd-netlabel.c new file mode 100644 index 0000000000..94bf8f5d26 --- /dev/null +++ b/src/network/networkd-netlabel.c @@ -0,0 +1,128 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "escape.h" +#include "netlink-util.h" +#include "networkd-address.h" +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-netlabel.h" +#include "networkd-network.h" + +static int netlabel_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { + int r; + + assert_se(rtnl); + assert_se(m); + assert_se(link); + + r = sd_netlink_message_get_errno(m); + if (r < 0) { + log_link_message_warning_errno(link, m, r, "NetLabel operation failed, ignoring"); + return 1; + } + + log_link_debug(link, "NetLabel operation successful"); + + return 1; +} + +static int netlabel_command(uint16_t command, const char *label, const Address *address) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(command != NLBL_UNLABEL_C_UNSPEC && command < __NLBL_UNLABEL_C_MAX); + assert(address); + assert(address->link); + assert(address->link->ifname); + assert(address->link->manager); + assert(address->link->manager->genl); + assert(IN_SET(address->family, AF_INET, AF_INET6)); + + r = sd_genl_message_new(address->link->manager->genl, NETLBL_NLTYPE_UNLABELED_NAME, command, &m); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, NLBL_UNLABEL_A_IFACE, address->link->ifname); + if (r < 0) + return r; + + if (command == NLBL_UNLABEL_C_STATICADD) { + assert(label); + r = sd_netlink_message_append_string(m, NLBL_UNLABEL_A_SECCTX, label); + if (r < 0) + return r; + } + + union in_addr_union netmask, masked_addr; + r = in_addr_prefixlen_to_netmask(address->family, &netmask, address->prefixlen); + if (r < 0) + return r; + + /* + * When adding rules, kernel adds the address to its hash table _applying also the netmask_, but on + * removal, an exact match is required _without netmask applied_, so apply the mask on both + * operations. + */ + masked_addr = address->in_addr; + r = in_addr_mask(address->family, &masked_addr, address->prefixlen); + if (r < 0) + return r; + + if (address->family == AF_INET) { + r = sd_netlink_message_append_in_addr(m, NLBL_UNLABEL_A_IPV4ADDR, &masked_addr.in); + if (r < 0) + return r; + + r = sd_netlink_message_append_in_addr(m, NLBL_UNLABEL_A_IPV4MASK, &netmask.in); + } else if (address->family == AF_INET6) { + r = sd_netlink_message_append_in6_addr(m, NLBL_UNLABEL_A_IPV6ADDR, &masked_addr.in6); + if (r < 0) + return r; + + r = sd_netlink_message_append_in6_addr(m, NLBL_UNLABEL_A_IPV6MASK, &netmask.in6); + } + if (r < 0) + return r; + + r = netlink_call_async(address->link->manager->genl, NULL, m, netlabel_handler, link_netlink_destroy_callback, + address->link); + if (r < 0) + return r; + + link_ref(address->link); + return 0; +} + +void address_add_netlabel(const Address *address) { + int r; + + assert(address); + + if (!address->netlabel) + return; + + r = netlabel_command(NLBL_UNLABEL_C_STATICADD, address->netlabel, address); + if (r < 0) + log_link_warning_errno(address->link, r, "Adding NetLabel %s for IP address %s failed, ignoring", address->netlabel, + IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen)); + else + log_link_debug(address->link, "Adding NetLabel %s for IP address %s", address->netlabel, + IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen)); +} + +void address_del_netlabel(const Address *address) { + int r; + + assert(address); + + if (!address->netlabel) + return; + + r = netlabel_command(NLBL_UNLABEL_C_STATICREMOVE, address->netlabel, address); + if (r < 0) + log_link_warning_errno(address->link, r, "Deleting NetLabel %s for IP address %s failed, ignoring", address->netlabel, + IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen)); + else + log_link_debug(address->link, "Deleting NetLabel %s for IP address %s", address->netlabel, + IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen)); +} diff --git a/src/network/networkd-netlabel.h b/src/network/networkd-netlabel.h new file mode 100644 index 0000000000..2f30b8feda --- /dev/null +++ b/src/network/networkd-netlabel.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +void address_add_netlabel(const Address *address); +void address_del_netlabel(const Address *address); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index dce7339d2f..aec5452849 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -25,6 +25,7 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") #include "networkd-ipv6ll.h" #include "networkd-lldp-tx.h" #include "networkd-ndisc.h" +#include "networkd-netlabel.h" #include "networkd-network.h" #include "networkd-neighbor.h" #include "networkd-nexthop.h" @@ -158,6 +159,7 @@ Address.AutoJoin, config_parse_address_flags, Address.DuplicateAddressDetection, config_parse_duplicate_address_detection, 0, 0 Address.Scope, config_parse_address_scope, 0, 0 Address.RouteMetric, config_parse_address_route_metric, 0, 0 +Address.NetLabel, config_parse_address_netlabel, 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 @@ -246,6 +248,7 @@ DHCPv4.SendVendorOption, config_parse_dhcp_send_option, DHCPv4.RouteMTUBytes, config_parse_mtu, AF_INET, offsetof(Network, dhcp_route_mtu) DHCPv4.FallbackLeaseLifetimeSec, config_parse_dhcp_fallback_lease_lifetime, 0, 0 DHCPv4.Use6RD, config_parse_bool, 0, offsetof(Network, dhcp_use_6rd) +DHCPv4.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_netlabel) DHCPv6.UseAddress, config_parse_bool, 0, offsetof(Network, dhcp6_use_address) DHCPv6.UseDelegatedPrefix, config_parse_bool, 0, offsetof(Network, dhcp6_use_pd_prefix) DHCPv6.UseDNS, config_parse_dhcp_use_dns, AF_INET6, 0 @@ -264,6 +267,7 @@ DHCPv6.IAID, config_parse_iaid, DHCPv6.DUIDType, config_parse_duid_type, 0, offsetof(Network, dhcp6_duid) DHCPv6.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, dhcp6_duid) DHCPv6.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp6_use_rapid_commit) +DHCPv6.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp6_netlabel) IPv6AcceptRA.UseGateway, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_gateway) IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_route_prefix) IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_autonomous_prefix) @@ -281,6 +285,7 @@ IPv6AcceptRA.PrefixDenyList, config_parse_in_addr_prefixes, IPv6AcceptRA.RouteAllowList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_allow_listed_route_prefix) IPv6AcceptRA.RouteDenyList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_deny_listed_route_prefix) IPv6AcceptRA.Token, config_parse_address_generation_type, 0, offsetof(Network, ndisc_tokens) +IPv6AcceptRA.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, ndisc_netlabel) DHCPServer.ServerAddress, config_parse_dhcp_server_address, 0, 0 DHCPServer.UplinkInterface, config_parse_uplink, 0, 0 DHCPServer.RelayTarget, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_server_relay_target) @@ -347,6 +352,7 @@ DHCPPrefixDelegation.Assign, config_parse_bool, DHCPPrefixDelegation.ManageTemporaryAddress, config_parse_bool, 0, offsetof(Network, dhcp_pd_manage_temporary_address) DHCPPrefixDelegation.Token, config_parse_address_generation_type, 0, offsetof(Network, dhcp_pd_tokens) DHCPPrefixDelegation.RouteMetric, config_parse_uint32, 0, offsetof(Network, dhcp_pd_route_metric) +DHCPPrefixDelegation.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_pd_netlabel) IPv6SendRA.RouterLifetimeSec, config_parse_router_lifetime, 0, offsetof(Network, router_lifetime_usec) IPv6SendRA.Managed, config_parse_bool, 0, offsetof(Network, router_managed) IPv6SendRA.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 61b900ada2..00bbaf6eab 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -712,6 +712,7 @@ static Network *network_free(Network *network) { set_free(network->dhcp_request_options); ordered_hashmap_free(network->dhcp_client_send_options); ordered_hashmap_free(network->dhcp_client_send_vendor_options); + free(network->dhcp_netlabel); /* DHCPv6 client */ free(network->dhcp6_mudurl); @@ -720,10 +721,12 @@ static Network *network_free(Network *network) { set_free(network->dhcp6_request_options); ordered_hashmap_free(network->dhcp6_client_send_options); ordered_hashmap_free(network->dhcp6_client_send_vendor_options); + free(network->dhcp6_netlabel); /* DHCP PD */ free(network->dhcp_pd_uplink_name); set_free(network->dhcp_pd_tokens); + free(network->dhcp_pd_netlabel); /* Router advertisement */ ordered_set_free(network->router_search_domains); @@ -738,6 +741,7 @@ static Network *network_free(Network *network) { set_free(network->ndisc_deny_listed_route_prefix); set_free(network->ndisc_allow_listed_route_prefix); set_free(network->ndisc_tokens); + free(network->ndisc_netlabel); /* LLDP */ free(network->lldp_mudurl); diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 3df7eee555..a2743f9743 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -155,6 +155,7 @@ struct Network { Set *dhcp_request_options; OrderedHashmap *dhcp_client_send_options; OrderedHashmap *dhcp_client_send_vendor_options; + char *dhcp_netlabel; /* DHCPv6 Client support */ bool dhcp6_use_address; @@ -180,6 +181,7 @@ struct Network { OrderedHashmap *dhcp6_client_send_options; OrderedHashmap *dhcp6_client_send_vendor_options; Set *dhcp6_request_options; + char *dhcp6_netlabel; /* DHCP Server Support */ bool dhcp_server; @@ -237,6 +239,7 @@ struct Network { Set *dhcp_pd_tokens; int dhcp_pd_uplink_index; char *dhcp_pd_uplink_name; + char *dhcp_pd_netlabel; /* Bridge Support */ int use_bpdu; @@ -321,6 +324,7 @@ struct Network { Set *ndisc_deny_listed_route_prefix; Set *ndisc_allow_listed_route_prefix; Set *ndisc_tokens; + char *ndisc_netlabel; /* LLDP support */ LLDPMode lldp_mode; /* LLDP reception */ diff --git a/test/fuzz/fuzz-network-parser/directives b/test/fuzz/fuzz-network-parser/directives index 6f76f7ee18..171322ef20 100644 --- a/test/fuzz/fuzz-network-parser/directives +++ b/test/fuzz/fuzz-network-parser/directives @@ -132,6 +132,7 @@ MUDURL= RouteMTUBytes= FallbackLeaseLifetimeSec= Use6RD= +NetLabel= [DHCPv6] UseAddress= UseDelegatedPrefix= @@ -153,6 +154,7 @@ RouteMetric= IAID= DUIDType= DUIDRawData= +NetLabel= [DHCPv6PrefixDelegation] SubnetId= Announce= @@ -160,6 +162,7 @@ Assign= ManageTemporaryAddress= Token= RouteMetric= +NetLabel= [DHCPPrefixDelegation] UplinkInterface= SubnetId= @@ -168,6 +171,7 @@ Assign= ManageTemporaryAddress= Token= RouteMetric= +NetLabel= [Route] Destination= Protocol= @@ -346,6 +350,7 @@ EmitDomains= Managed= OtherInformation= UplinkInterface= +NetLabel= [IPv6PrefixDelegation] RouterPreference= DNSLifetimeSec= From a4640bed744015f8788355cbf0a0c03980ff77cb Mon Sep 17 00:00:00 2001 From: Topi Miettinen Date: Sat, 20 Aug 2022 20:57:06 +0300 Subject: [PATCH 4/4] test: testing for networkd NetLabel feature --- .../conf/25-address-static.network | 5 +++++ .../conf/25-dhcp-client-ipv4-only.network | 2 ++ .../25-dhcp-pd-downstream-dummy98.network | 2 ++ .../conf/25-ipv6-prefix-veth.network | 2 ++ test/test-network/systemd-networkd-tests.py | 21 +++++++++++++++++++ 5 files changed, 32 insertions(+) diff --git a/test/test-network/conf/25-address-static.network b/test/test-network/conf/25-address-static.network index 2a4c1a6039..6d2baa3972 100644 --- a/test/test-network/conf/25-address-static.network +++ b/test/test-network/conf/25-address-static.network @@ -361,3 +361,8 @@ Address=10.3.3.251/16 Address=10.3.3.252/16 Address=10.3.3.253/16 Address=10.3.3.254/16 + +[Address] +Address=10.4.3.2/24 +# just a random label which should exist +NetLabel=system_u:object_r:root_t:s0 diff --git a/test/test-network/conf/25-dhcp-client-ipv4-only.network b/test/test-network/conf/25-dhcp-client-ipv4-only.network index 23c6f047ce..bd8843b177 100644 --- a/test/test-network/conf/25-dhcp-client-ipv4-only.network +++ b/test/test-network/conf/25-dhcp-client-ipv4-only.network @@ -26,6 +26,8 @@ SendDecline=yes # DenyList= will be ignored AllowList=192.168.5.0/24 192.168.6.0/24 DenyList=192.168.5.0/24 +# just a random label which should exist +NetLabel=system_u:object_r:root_t:s0 [Route] Destination=192.168.5.0/24 diff --git a/test/test-network/conf/25-dhcp-pd-downstream-dummy98.network b/test/test-network/conf/25-dhcp-pd-downstream-dummy98.network index f7e3cbfee4..2babc43203 100644 --- a/test/test-network/conf/25-dhcp-pd-downstream-dummy98.network +++ b/test/test-network/conf/25-dhcp-pd-downstream-dummy98.network @@ -14,3 +14,5 @@ SubnetId=0 Announce=no Token=eui64 Token=::1a:2b:3c:4d +# just a random label which should exist +NetLabel=system_u:object_r:root_t:s0 diff --git a/test/test-network/conf/25-ipv6-prefix-veth.network b/test/test-network/conf/25-ipv6-prefix-veth.network index b6724eaf5e..905e2fd2c8 100644 --- a/test/test-network/conf/25-ipv6-prefix-veth.network +++ b/test/test-network/conf/25-ipv6-prefix-veth.network @@ -7,3 +7,5 @@ IPv6AcceptRA=true [IPv6AcceptRA] UseDomains=yes +# just a random label which should exist +NetLabel=system_u:object_r:root_t:s0 diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index 6e8c8e58ea..2f638d158a 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -889,6 +889,18 @@ class Utilities(): self.assertNotRegex(output, address_regex) + def check_netlabel(self, interface, address, label='system_u:object_r:root_t:s0'): + if not shutil.which('selinuxenabled'): + print(f'## Checking NetLabel skipped: selinuxenabled command not found.') + elif call_quiet('selinuxenabled') != 0: + print(f'## Checking NetLabel skipped: SELinux disabled.') + elif not shutil.which('netlabelctl'): # not packaged by all distros + print(f'## Checking NetLabel skipped: netlabelctl command not found.') + else: + output = check_output('netlabelctl unlbl list') + print(output) + self.assertRegex(output, f'interface:{interface},address:{address},label:"{label}"') + class NetworkctlTests(unittest.TestCase, Utilities): def setUp(self): @@ -2209,6 +2221,8 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities): self.assertIn('inet6 2001:db8:1:f101::1/64 scope global deprecated', output) self.assertRegex(output, r'inet6 fd[0-9a-f:]*1/64 scope global') + self.check_netlabel('dummy98', '10\.4\.3\.0/24') + # Tests for #20891. # 1. set preferred lifetime forever to drop the deprecated flag for testing #20891. check_output('ip address change 10.7.8.9/16 dev dummy98 preferred_lft forever') @@ -4267,6 +4281,9 @@ class NetworkdRATests(unittest.TestCase, Utilities): print(output) self.assertRegex(output, '2002:da8:1:0') + self.check_netlabel('veth99', '2002:da8:1::/64') + self.check_netlabel('veth99', '2002:da8:2::/64') + def test_ipv6_token_static(self): copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-static.network') start_networkd() @@ -4560,6 +4577,8 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities): self.assertIn('client provides name: test-hostname', output) self.assertIn('26:mtu', output) + self.check_netlabel('veth99', '192\.168\.5\.0/24') + def test_dhcp_client_ipv4_use_routes_gateway(self): first = True for (routes, gateway, dns_and_ntp_routes, classless) in itertools.product([True, False], repeat=4): @@ -5117,6 +5136,8 @@ class NetworkdDHCPPDTests(unittest.TestCase, Utilities): print(output) self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]02::/64 proto dhcp metric [0-9]* expires') + self.check_netlabel('dummy98', '3ffe:501:ffff:[2-9a-f]00::/64') + def verify_dhcp4_6rd(self, tunnel_name): print('### ip -4 address show dev veth-peer scope global') output = check_output('ip -4 address show dev veth-peer scope global')