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/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/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);
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/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);
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=
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')