diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml
index a44018cad6d6c..ebd3bfcdeb8ff 100644
--- a/man/systemd.netdev.xml
+++ b/man/systemd.netdev.xml
@@ -151,6 +151,9 @@
l2tp
A Layer 2 Tunneling Protocol (L2TP) is a tunneling protocol used to support virtual private networks (VPNs) or as part of the delivery of services by ISPs. It does not provide any encryption or confidentiality by itself
+ macsec
+ Media Access Control Security (MACsec) is an 802.1AE IEEE industry-standard security technology that provides secure communication for all traffic on Ethernet links. MACsec provides point-to-point security on Ethernet links between directly connected nodes and is capable of identifying and preventing most security threats.
+
vrf
A Virtual Routing and Forwarding (VRF) interface to create separate routing and forwarding domains.
@@ -851,6 +854,120 @@
+
+ [MACSEC] Section Options
+ The [MACSEC] section only applies for
+ netdevs of kind macsec, and accepts the
+ following keys:
+
+
+
+ Port=
+
+ Specifies the to be used for the MACsec. Takes either value between 1 and 65535. This option is compulsory.
+
+
+
+ Encrypt=
+
+ Takes a boolean. When true, enable encryption. Defaults to unset.
+
+
+
+
+
+ [MACsecReceiveChannel] Section Options
+ The [MACsecReceiveChannel] section only applies for
+ netdevs of kind macsec, and accepts the
+ following keys:
+
+
+
+ Port=
+
+ Specifies the port to be used for the MACsec Receive Channel. The port used to make Secure Channel Identifier (SCI).
+ Takes either value between 1 and 65535. This option is compulsory.
+
+
+
+ MACAddress=
+
+ Specifies the MAC address to be used for the MACsec Receive Channel. The port used to make Secure Channel Identifier (SCI).
+ This option is compulsory.
+
+
+
+
+
+ [MACsecTransmitAssociation] Section Options
+ The [MACsecTransmitAssociation] section only applies for
+ netdevs of kind macsec, and accepts the
+ following keys:
+
+
+
+ PacketNumber=
+
+ Specifies the packet number to be used for replay protection and the construction of
+ the initialization vector (along with the secure channel identifier [SCI]). Ranges a number between 1 and 4,294,967,295.
+
+
+
+
+ KeyId=
+
+ Specifies the identification for they key. Ranges a number between 0 to 255 This option is compulsory.
+
+
+
+ Key=
+
+ Specifies the identification for the key used in the transmit channel. That same key must be configured on the peer’s matching receive channel.
+ This option is compulsory. Takes a 128-bits key for example "dffafc8d7b9a43d5b9a3dfbbf6a30c16".
+
+
+
+
+
+ [MACsecReceiveAssociation] Section Options
+ The [MACsecReceiveAssociation] section only applies for
+ netdevs of kind macsec, and accepts the
+ following keys:
+
+
+
+ Port=
+
+ See [MACsecTransmitAssociation] Section.
+
+
+
+ MACAddress=
+
+ See [MACsecTransmitAssociation] Section.
+
+
+
+ PacketNumber=
+
+ See [MACsecTransmitAssociation] Section.
+
+
+
+
+ KeyId=
+
+ See [MACsecTransmitAssociation] Section.
+
+
+
+ Key=
+
+ See [MACsecTransmitAssociation] Section.
+
+
+
+
[Tunnel] Section Options
diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
index 87724af693068..5c939298c195b 100644
--- a/src/basic/parse-util.c
+++ b/src/basic/parse-util.c
@@ -566,6 +566,38 @@ int safe_atod(const char *s, double *ret_d) {
return 0;
}
+int safe_hexstring_aton(const char *s, uint8_t *b, unsigned int buf_len, unsigned int *len) {
+ unsigned int c = 0;
+ char *p;
+
+ assert(s);
+ assert(b);
+ assert(len);
+
+ if (strlen(s) % 2)
+ return -EINVAL;
+
+ for(c = 0; c < buf_len && strlen(s) > 1; s += 2) {
+ unsigned int a;
+ char t[3];
+
+ strncpy(t, s, 2);
+ t[2] = '\0';
+
+ errno = 0;
+
+ a = strtoul(t, &p, 16);
+ if (errno != 0 || a > 0xFF || *p != '\0')
+ return -EINVAL;
+
+ b[c++] = a;
+ }
+
+ *len = c;
+
+ return 0;
+}
+
int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) {
size_t i;
unsigned val = 0;
diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h
index e47641b429544..41d07998d71a7 100644
--- a/src/basic/parse-util.h
+++ b/src/basic/parse-util.h
@@ -104,6 +104,8 @@ static inline int safe_atozu(const char *s, size_t *ret_u) {
int safe_atod(const char *s, double *ret_d);
+int safe_hexstring_aton(const char *s, uint8_t *b, unsigned int buf_len, unsigned int *len);
+
int parse_fractional_part_u(const char **s, size_t digits, unsigned *res);
int parse_percent_unbounded(const char *p);
diff --git a/src/libsystemd/sd-netlink/generic-netlink.c b/src/libsystemd/sd-netlink/generic-netlink.c
index 384072e881451..473d8670a93b7 100644
--- a/src/libsystemd/sd-netlink/generic-netlink.c
+++ b/src/libsystemd/sd-netlink/generic-netlink.c
@@ -14,6 +14,7 @@ static const genl_family genl_families[] = {
[SD_GENL_WIREGUARD] = { .name = "wireguard", .version = 1 },
[SD_GENL_FOU] = { .name = "fou", .version = 1 },
[SD_GENL_L2TP] = { .name = "l2tp", .version = 1},
+ [SD_GENL_MACSEC] = { .name = "macsec", .version = 1},
};
int sd_genl_socket_open(sd_netlink **ret) {
diff --git a/src/libsystemd/sd-netlink/netlink-message.c b/src/libsystemd/sd-netlink/netlink-message.c
index 0dcc53be55a15..68b232b7d4224 100644
--- a/src/libsystemd/sd-netlink/netlink-message.c
+++ b/src/libsystemd/sd-netlink/netlink-message.c
@@ -318,6 +318,23 @@ int sd_netlink_message_append_u32(sd_netlink_message *m, unsigned short type, ui
return 0;
}
+int sd_netlink_message_append_u64(sd_netlink_message *m, unsigned short type, uint64_t data) {
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(!m->sealed, -EPERM);
+
+ r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U64);
+ if (r < 0)
+ return r;
+
+ r = add_rtattr(m, type, &data, sizeof(uint64_t));
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
int sd_netlink_message_append_data(sd_netlink_message *m, unsigned short type, const void *data, size_t len) {
int r;
diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c
index 8248ac0f5a502..8ffe0239cf1ec 100644
--- a/src/libsystemd/sd-netlink/netlink-types.c
+++ b/src/libsystemd/sd-netlink/netlink-types.c
@@ -16,6 +16,7 @@
#include
#include
#include
+#include
#include
#if HAVE_LINUX_FOU_H
@@ -312,6 +313,22 @@ static const NLType rtnl_link_info_data_can_types[] = {
[IFLA_CAN_CTRLMODE] = { .size = sizeof(struct can_ctrlmode) },
};
+static const NLType rtnl_link_info_data_macsec_types[] = {
+ [IFLA_MACSEC_SCI] = { .type = NETLINK_TYPE_U64 },
+ [IFLA_MACSEC_PORT] = { .type = NETLINK_TYPE_U16 },
+ [IFLA_MACSEC_ICV_LEN] = { .type = NETLINK_TYPE_U8 },
+ [IFLA_MACSEC_CIPHER_SUITE] = { .type = NETLINK_TYPE_U64 },
+ [IFLA_MACSEC_WINDOW] = { .type = NETLINK_TYPE_U32 },
+ [IFLA_MACSEC_ENCODING_SA] = { .type = NETLINK_TYPE_U8 },
+ [IFLA_MACSEC_ENCRYPT] = { .type = NETLINK_TYPE_U8 },
+ [IFLA_MACSEC_PROTECT] = { .type = NETLINK_TYPE_U8 },
+ [IFLA_MACSEC_INC_SCI] = { .type = NETLINK_TYPE_U8 },
+ [IFLA_MACSEC_ES] = { .type = NETLINK_TYPE_U8 },
+ [IFLA_MACSEC_SCB] = { .type = NETLINK_TYPE_U8 },
+ [IFLA_MACSEC_REPLAY_PROTECT] = { .type = NETLINK_TYPE_U8 },
+ [IFLA_MACSEC_VALIDATION] = { .type = NETLINK_TYPE_U8 },
+};
+
/* these strings must match the .kind entries in the kernel */
static const char* const nl_union_link_info_data_table[] = {
[NL_UNION_LINK_INFO_DATA_BOND] = "bond",
@@ -340,6 +357,7 @@ static const char* const nl_union_link_info_data_table[] = {
[NL_UNION_LINK_INFO_DATA_WIREGUARD] = "wireguard",
[NL_UNION_LINK_INFO_DATA_NETDEVSIM] = "netdevsim",
[NL_UNION_LINK_INFO_DATA_CAN] = "can",
+ [NL_UNION_LINK_INFO_DATA_MACSEC] = "macsec",
};
DEFINE_STRING_TABLE_LOOKUP(nl_union_link_info_data, NLUnionLinkInfoData);
@@ -389,6 +407,8 @@ static const NLTypeSystem rtnl_link_info_data_type_systems[] = {
.types = rtnl_link_info_data_vxcan_types },
[NL_UNION_LINK_INFO_DATA_CAN] = { .count = ELEMENTSOF(rtnl_link_info_data_can_types),
.types = rtnl_link_info_data_can_types },
+ [NL_UNION_LINK_INFO_DATA_MACSEC] = { .count = ELEMENTSOF(rtnl_link_info_data_macsec_types),
+ .types = rtnl_link_info_data_macsec_types },
};
static const NLTypeSystemUnion rtnl_link_info_data_type_system_union = {
@@ -849,11 +869,76 @@ static const NLTypeSystem genl_l2tp_tunnel_session_type_system = {
.types = genl_l2tp,
};
+static const NLType genl_rxsc_types[] = {
+ [MACSEC_RXSC_ATTR_SCI] = { .type = NETLINK_TYPE_U64 },
+};
+
+static const NLTypeSystem genl_rxsc_config_type_system = {
+ .count = ELEMENTSOF(genl_rxsc_types),
+ .types = genl_rxsc_types,
+};
+
+static const NLType genl_macsec_rxsc_types[] = {
+ [MACSEC_ATTR_IFINDEX] = { .type = NETLINK_TYPE_U32 },
+ [MACSEC_ATTR_RXSC_CONFIG] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_rxsc_config_type_system },
+};
+
+static const NLTypeSystem genl_macsec_rxsc_type_system = {
+ .count = ELEMENTSOF(genl_macsec_rxsc_types),
+ .types = genl_macsec_rxsc_types,
+};
+
+static const NLType genl_macsec_sa_config_types[] = {
+ [MACSEC_SA_ATTR_AN] = { .type = NETLINK_TYPE_U8 },
+ [MACSEC_SA_ATTR_ACTIVE] = { .type = NETLINK_TYPE_U8 },
+ [MACSEC_SA_ATTR_PN] = { .type = NETLINK_TYPE_U32 },
+ [MACSEC_SA_ATTR_KEYID] = { .size = MACSEC_KEYID_LEN },
+ [MACSEC_SA_ATTR_KEY] = { .size = MACSEC_MAX_KEY_LEN },
+};
+
+static const NLTypeSystem genl_macsec_sa_config_type_system = {
+ .count = ELEMENTSOF(genl_macsec_sa_config_types),
+ .types = genl_macsec_sa_config_types,
+};
+
+static const NLType genl_macsec_rxsa_types[] = {
+ [MACSEC_ATTR_IFINDEX] = { .type = NETLINK_TYPE_U32 },
+ [MACSEC_ATTR_SA_CONFIG] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_macsec_sa_config_type_system },
+};
+
+static const NLTypeSystem genl_macsec_rxsa_type_system = {
+ .count = ELEMENTSOF(genl_macsec_rxsa_types),
+ .types = genl_macsec_rxsa_types,
+};
+
+static const NLType genl_macsec_sa_types[] = {
+ [MACSEC_ATTR_IFINDEX] = { .type = NETLINK_TYPE_U32 },
+ [MACSEC_ATTR_RXSC_CONFIG] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_rxsc_config_type_system },
+ [MACSEC_ATTR_SA_CONFIG] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_macsec_sa_config_type_system },
+};
+
+static const NLTypeSystem genl_macsec_sa_type_system = {
+ .count = ELEMENTSOF(genl_macsec_sa_types),
+ .types = genl_macsec_sa_types,
+};
+
+static const NLType genl_macsec[] = {
+ [MACSEC_CMD_ADD_RXSC] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_macsec_rxsc_type_system },
+ [MACSEC_CMD_ADD_TXSA] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_macsec_rxsa_type_system},
+ [MACSEC_CMD_ADD_RXSA] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_macsec_sa_type_system },
+};
+
+static const NLTypeSystem genl_macsec_device_type_system = {
+ .count = ELEMENTSOF(genl_macsec),
+ .types = genl_macsec,
+};
+
static const NLType genl_families[] = {
[SD_GENL_ID_CTRL] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_ctrl_id_ctrl_type_system },
[SD_GENL_WIREGUARD] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_wireguard_type_system },
[SD_GENL_FOU] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_fou_cmds_type_system},
[SD_GENL_L2TP] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_l2tp_tunnel_session_type_system },
+ [SD_GENL_MACSEC] = { .type = NETLINK_TYPE_NESTED, .type_system = &genl_macsec_device_type_system },
};
const NLTypeSystem genl_family_type_system_root = {
diff --git a/src/libsystemd/sd-netlink/netlink-types.h b/src/libsystemd/sd-netlink/netlink-types.h
index b84fa4762b074..a2b3087d15961 100644
--- a/src/libsystemd/sd-netlink/netlink-types.h
+++ b/src/libsystemd/sd-netlink/netlink-types.h
@@ -80,6 +80,7 @@ typedef enum NLUnionLinkInfoData {
NL_UNION_LINK_INFO_DATA_WIREGUARD,
NL_UNION_LINK_INFO_DATA_NETDEVSIM,
NL_UNION_LINK_INFO_DATA_CAN,
+ NL_UNION_LINK_INFO_DATA_MACSEC,
_NL_UNION_LINK_INFO_DATA_MAX,
_NL_UNION_LINK_INFO_DATA_INVALID = -1
} NLUnionLinkInfoData;
diff --git a/src/network/meson.build b/src/network/meson.build
index c95e7503069a9..2acbe858bb5a5 100644
--- a/src/network/meson.build
+++ b/src/network/meson.build
@@ -39,6 +39,8 @@ sources = files('''
netdev/fou-tunnel.h
netdev/l2tp-tunnel.c
netdev/l2tp-tunnel.h
+ netdev/macsec.c
+ netdev/macsec.h
networkd-address-label.c
networkd-address-label.h
networkd-address-pool.c
diff --git a/src/network/netdev/macsec.c b/src/network/netdev/macsec.c
new file mode 100644
index 0000000000000..3214c912bcc3f
--- /dev/null
+++ b/src/network/netdev/macsec.c
@@ -0,0 +1,915 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include
+#include
+#include
+#include
+
+#include "conf-parser.h"
+#include "hashmap.h"
+#include "hexdecoct.h"
+#include "macsec.h"
+#include "missing.h"
+#include "netlink-util.h"
+#include "network-internal.h"
+#include "networkd-address.h"
+#include "networkd-manager.h"
+#include "parse-util.h"
+#include "sd-netlink.h"
+#include "socket-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "util.h"
+
+static void macsec_receive_association_free(ReceiveAssociation *c) {
+ if (!c)
+ return;
+
+ if (c->mac_sec && c->section)
+ ordered_hashmap_remove(c->mac_sec->receive_association_by_section, c);
+
+ network_config_section_free(c->section);
+
+ free(c);
+}
+
+DEFINE_NETWORK_SECTION_FUNCTIONS(ReceiveAssociation, macsec_receive_association_free);
+
+static int macsec_receive_association_new_static(MACsec *s, const char *filename, unsigned section_line, ReceiveAssociation **ret) {
+ _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
+ _cleanup_(macsec_receive_association_freep) ReceiveAssociation *c = NULL;
+ int r;
+
+ assert(s);
+ assert(ret);
+ assert(filename);
+ assert(section_line > 0);
+
+ r = network_config_section_new(filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ c = ordered_hashmap_get(s->receive_association_by_section, n);
+ if (c) {
+ *ret = TAKE_PTR(c);
+ return 0;
+ }
+
+ c = new(ReceiveAssociation, 1);
+ if (!c)
+ return -ENOMEM;
+
+ *c = (ReceiveAssociation) {
+ .mac_sec = s,
+ .section = TAKE_PTR(n),
+ };
+
+ r = ordered_hashmap_ensure_allocated(&s->receive_association_by_section, &network_config_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = ordered_hashmap_put(s->receive_association_by_section, c->section, c);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(c);
+
+ return 0;
+}
+
+static void macsec_receive_channel_free(ReceiveChannel *c) {
+ if (!c)
+ return;
+
+ if (c->mac_sec && c->section)
+ ordered_hashmap_remove(c->mac_sec->rx_channel_by_section, c);
+
+ network_config_section_free(c->section);
+
+ free(c);
+}
+
+DEFINE_NETWORK_SECTION_FUNCTIONS(ReceiveChannel, macsec_receive_channel_free);
+
+static int macsec_receive_channel_new_static(MACsec *s, const char *filename, unsigned section_line, ReceiveChannel **ret) {
+ _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
+ _cleanup_(macsec_receive_channel_freep) ReceiveChannel *c = NULL;
+ int r;
+
+ assert(s);
+ assert(ret);
+ assert(filename);
+ assert(section_line > 0);
+
+ r = network_config_section_new(filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ c = ordered_hashmap_get(s->rx_channel_by_section, n);
+ if (c) {
+ *ret = TAKE_PTR(c);
+ return 0;
+ }
+
+ c = new(ReceiveChannel, 1);
+ if (!c)
+ return -ENOMEM;
+
+ *c = (ReceiveChannel) {
+ .mac_sec = s,
+ .section = TAKE_PTR(n),
+ };
+
+ r = ordered_hashmap_ensure_allocated(&s->rx_channel_by_section, &network_config_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = ordered_hashmap_put(s->rx_channel_by_section, c->section, c);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(c);
+
+ return 0;
+}
+
+static void macsec_transmit_association_free(TransmitAssociation *a) {
+ if (!a)
+ return;
+
+ if (a->mac_sec && a->section)
+ ordered_hashmap_remove(a->mac_sec->transmit_association_by_section, a);
+
+ network_config_section_free(a->section);
+
+ free(a);
+}
+
+DEFINE_NETWORK_SECTION_FUNCTIONS(TransmitAssociation, macsec_transmit_association_free);
+
+static int macsec_transmit_transmit_association_new_static(MACsec *s, const char *filename, unsigned section_line, TransmitAssociation **ret) {
+ _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
+ _cleanup_(macsec_transmit_association_freep) TransmitAssociation *a = NULL;
+ int r;
+
+ assert(s);
+ assert(ret);
+ assert(filename);
+ assert(section_line > 0);
+
+ r = network_config_section_new(filename, section_line, &n);
+ if (r < 0)
+ return r;
+
+ a = ordered_hashmap_get(s->transmit_association_by_section, n);
+ if (a) {
+ *ret = TAKE_PTR(a);
+ return 0;
+ }
+
+ a = new(TransmitAssociation, 1);
+ if (!a)
+ return -ENOMEM;
+
+ *a = (TransmitAssociation) {
+ .mac_sec = s,
+ .section = TAKE_PTR(n),
+ };
+
+ r = ordered_hashmap_ensure_allocated(&s->transmit_association_by_section, &network_config_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = ordered_hashmap_put(s->transmit_association_by_section, a->section, a);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(a);
+
+ return 0;
+}
+
+static int netdev_macsec_fill_message(NetDev *netdev, int command, sd_netlink_message **ret) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+ MACsec *t;
+ int r;
+
+ assert(netdev);
+ assert(netdev->ifindex > 0);
+
+ t = MACSEC(netdev);
+
+ assert(t);
+
+ r = sd_genl_message_new(netdev->manager->genl, SD_GENL_MACSEC, command, &m);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Failed to create generic netlink message: %m");
+
+ r = sd_netlink_message_append_u32(m, MACSEC_ATTR_IFINDEX, netdev->ifindex);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append MACSEC_ATTR_IFINDEX attribute: %m");
+
+ *ret = TAKE_PTR(m);
+
+ return 0;
+}
+
+static int netdev_macsec_fill_message_sci(NetDev *netdev, ReceiveChannel *c, sd_netlink_message *m) {
+ uint64_t sci;
+ MACsec *t;
+ int r;
+
+ assert(netdev);
+ assert(m);
+
+ t = MACSEC(netdev);
+
+ assert(t);
+ assert(m);
+
+ assert(c);
+ assert(c->mac);
+ assert(c->port > 0);
+
+ r = sd_netlink_message_open_container(m, MACSEC_ATTR_RXSC_CONFIG);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append MACSEC_ATTR_RXSC_CONFIG attribute: %m");
+
+ memcpy(&sci, c->mac, ETH_ALEN);
+ memcpy(((char *) &sci) + ETH_ALEN, &c->port, sizeof(c->port));
+
+ r = sd_netlink_message_append_u64(m, MACSEC_RXSC_ATTR_SCI, sci);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append MACSEC_RXSC_ATTR_SCI attribute: %m");
+
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append MACSEC_ATTR_RXSC_CONFIG attribute: %m");
+
+ return 0;
+}
+
+static int netdev_macsec_fill_message_sa(NetDev *netdev, TransmitAssociation *a, sd_netlink_message *m) {
+ MACsec *t;
+ int r;
+
+ assert(netdev);
+ assert(a);
+ assert(m);
+
+ t = MACSEC(netdev);
+
+ assert(t);
+
+ r = sd_netlink_message_open_container(m, MACSEC_ATTR_SA_CONFIG);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append MACSEC_ATTR_SA_CONFIG attribute: %m");
+
+ r = sd_netlink_message_append_u8(m, MACSEC_SA_ATTR_AN, a->an);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append MACSEC_SA_ATTR_AN attribute: %m");
+
+ if (a->pn) {
+ r = sd_netlink_message_append_u32(m, MACSEC_SA_ATTR_PN, a->pn);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append MACSEC_SA_ATTR_PN attribute: %m");
+ }
+
+ if (a->key_len) {
+ r = sd_netlink_message_append_data(m, MACSEC_SA_ATTR_KEYID, a->key_id, MACSEC_KEYID_LEN);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append MACSEC_SA_ATTR_KEYID attribute: %m");
+
+ r = sd_netlink_message_append_data(m, MACSEC_SA_ATTR_KEY, &a->key, a->key_len);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append MACSEC_SA_ATTR_KEY attribute: %m");
+ }
+
+ r = sd_netlink_message_close_container(m);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append MACSEC_ATTR_SA_CONFIG attribute: %m");
+
+ return 0;
+}
+
+static int macsec_receive_association_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) {
+ MACsec *t;
+ int r;
+
+ assert(netdev);
+ assert(netdev->state != _NETDEV_STATE_INVALID);
+
+ t = MACSEC(netdev);
+
+ assert(t);
+
+ 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 add receive association configurations: %m");
+ netdev_drop(netdev);
+
+ return 1;
+ }
+
+ log_netdev_debug(netdev, "MACsec add receive association configuration success");
+
+ return 1;
+}
+
+static int netdev_macsec_receive_association_fill_message(NetDev *netdev, ReceiveAssociation *a, sd_netlink_message **ret) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+ MACsec *t;
+ int r;
+
+ assert(netdev);
+ assert(a);
+
+ t = MACSEC(netdev);
+
+ assert(t);
+
+ r = netdev_macsec_fill_message(netdev, MACSEC_CMD_ADD_RXSA, &m);
+ if (r < 0)
+ return r;
+
+ r = netdev_macsec_fill_message_sa(netdev, &a->sa, m);
+ if (r < 0)
+ return r;
+
+ r = netdev_macsec_fill_message_sci(netdev, &a->rx, m);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(m);
+
+ return 0;
+}
+
+static int macsec_add_tx_sa_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) {
+ MACsec *t;
+ int r;
+
+ assert(netdev);
+ assert(netdev->state != _NETDEV_STATE_INVALID);
+
+ t = MACSEC(netdev);
+
+ assert(t);
+
+ 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 add rxsc configurations: %m");
+ netdev_drop(netdev);
+
+ return 1;
+ }
+
+ log_netdev_debug(netdev, "MACsec add rxsc configuration success");
+
+ return 1;
+}
+
+static int netdev_macsec_add_tx_sa_fill_message(NetDev *netdev, TransmitAssociation *a, sd_netlink_message **ret) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+ MACsec *t;
+ int r;
+
+ assert(netdev);
+ assert(a);
+
+ t = MACSEC(netdev);
+
+ assert(t);
+
+ r = netdev_macsec_fill_message(netdev, MACSEC_CMD_ADD_TXSA, &m);
+ if (r < 0)
+ return r;
+
+ r = netdev_macsec_fill_message_sa(netdev, a, m);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(m);
+
+ return 0;
+}
+
+static int macsec_add_rxsc_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) {
+ MACsec *t;
+ int r;
+
+ assert(netdev);
+ assert(netdev->state != _NETDEV_STATE_INVALID);
+
+ t = MACSEC(netdev);
+
+ assert(t);
+
+ 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 add rxsc configurations: %m");
+ netdev_drop(netdev);
+
+ return 1;
+ }
+
+ log_netdev_debug(netdev, "MACsec add rxsc configuration success");
+
+ return 1;
+}
+
+static int netdev_macsec_add_rxsc_fill_message(NetDev *netdev, ReceiveChannel *c, sd_netlink_message **ret) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+ MACsec *t;
+ int r;
+
+ assert(netdev);
+ assert(c);
+
+ t = MACSEC(netdev);
+
+ assert(t);
+
+ r = netdev_macsec_fill_message(netdev, MACSEC_CMD_ADD_RXSC, &m);
+ if (r < 0)
+ return r;
+
+ r = netdev_macsec_fill_message_sci(netdev, c, m);
+ if (r < 0)
+ return r;
+
+ *ret = TAKE_PTR(m);
+
+ return 0;
+}
+
+static int netdev_macsec_configure(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ ReceiveAssociation *n;
+ TransmitAssociation *a;
+ ReceiveChannel *c;
+ Iterator i;
+ MACsec *s;
+ int r;
+
+ assert(netdev);
+
+ s = MACSEC(netdev);
+
+ assert(s);
+
+ ORDERED_HASHMAP_FOREACH(c, s->rx_channel_by_section, i) {
+ r = netdev_macsec_add_rxsc_fill_message(netdev, c, &m);
+ if (r < 0)
+ return r;
+
+ r = netlink_call_async(netdev->manager->genl, NULL, m, macsec_add_rxsc_handler,
+ netdev_destroy_callback, netdev);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Failed to configure receive channel: %m");
+
+ netdev_ref(netdev);
+ }
+
+ ORDERED_HASHMAP_FOREACH(a, s->transmit_association_by_section, i) {
+ r = netdev_macsec_add_tx_sa_fill_message(netdev, a, &m);
+ if (r < 0)
+ return r;
+
+ r = netlink_call_async(netdev->manager->genl, NULL, m, macsec_add_tx_sa_handler,
+ netdev_destroy_callback, netdev);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Failed to configure secure association: %m");
+
+ netdev_ref(netdev);
+ }
+
+ ORDERED_HASHMAP_FOREACH(n, s->receive_association_by_section, i) {
+ r = netdev_macsec_receive_association_fill_message(netdev, n, &m);
+ if (r < 0)
+ return r;
+
+ r = netlink_call_async(netdev->manager->genl, NULL, m, macsec_receive_association_handler,
+ netdev_destroy_callback, netdev);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Failed to configure receive association: %m");
+
+ netdev_ref(netdev);
+ }
+
+ return 0;
+}
+
+static int netdev_macsec_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ MACsec *v;
+ int r;
+
+ assert(netdev);
+ assert(m);
+
+ v = MACSEC(netdev);
+
+ r = sd_netlink_message_append_u16(m, IFLA_MACSEC_PORT, v->port);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_MACSEC_PORT attribute: %m");
+
+ if (v->encrypt != -1) {
+ r = sd_netlink_message_append_u8(m, IFLA_MACSEC_ENCRYPT, v->encrypt);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_MACSEC_ENCRYPT attribute: %m");
+ }
+
+ return r;
+}
+
+int config_parse_macsec_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) {
+
+ _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL;
+ _cleanup_(macsec_receive_channel_free_or_set_invalidp) ReceiveChannel *c = NULL;
+ MACsec *s = userdata;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (streq(section, "MACsecReceiveChannel"))
+ r = macsec_receive_channel_new_static(s, filename, section_line, &c);
+ else
+ r = macsec_receive_association_new_static(s, filename, section_line, &b);
+
+ if (r < 0)
+ return r;
+
+ if (b)
+ r = parse_ip_port(rvalue, &b->rx.port);
+ else
+ r = parse_ip_port(rvalue, &c->port);
+
+ if (r < 0)
+ return 0;
+
+ b = NULL;
+ c = NULL;
+
+ return 0;
+}
+
+int config_parse_macsec_hw_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) {
+
+ _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL;
+ _cleanup_(macsec_receive_channel_free_or_set_invalidp) ReceiveChannel *c = NULL;
+ MACsec *s = userdata;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (streq(section, "MACsecReceiveChannel"))
+ r = macsec_receive_channel_new_static(s, filename, section_line, &c);
+ else
+ r = macsec_receive_association_new_static(s, filename, section_line, &b);
+
+ if (r < 0)
+ return r;
+
+ if (b)
+ r = config_parse_hwaddr(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &b->rx.mac, userdata);
+ else
+ r = config_parse_hwaddr(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &c->mac, userdata);
+
+ if (r < 0)
+ return 0;
+
+ c = NULL;
+ b = NULL;
+
+ return 0;
+}
+
+int config_parse_macsec_pn(
+ 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_(macsec_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL;
+ _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL;
+ MACsec *s = userdata;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (streq(section, "MACsecTransmitAssociation"))
+ r = macsec_transmit_transmit_association_new_static(s, filename, section_line, &a);
+ else
+ r = macsec_receive_association_new_static(s, filename, section_line, &b);
+
+ if (r < 0)
+ return r;
+
+ if (a)
+ r = safe_atou32(rvalue, &a->pn);
+ else
+ r = safe_atou32(rvalue, &b->sa.pn);
+
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "Failed to parse packet number. Ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ a = NULL;
+ b = NULL;
+
+ return 0;
+}
+
+int config_parse_macsec_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_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL;
+ _cleanup_(macsec_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL;
+ MACsec *s = userdata;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (streq(section, "MACsecTransmitAssociation"))
+ r = macsec_transmit_transmit_association_new_static(s, filename, section_line, &a);
+ else
+ r = macsec_receive_association_new_static(s, filename, section_line, &b);
+
+ if (r < 0)
+ return r;
+
+ if (a)
+ r = safe_hexstring_aton(rvalue, a->key, MACSEC_MAX_KEY_LEN, &a->key_len);
+ else
+ r = safe_hexstring_aton(rvalue, b->sa.key, MACSEC_MAX_KEY_LEN, &b->sa.key_len);
+
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse key. Ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ a = NULL;
+ b = NULL;
+
+ return 0;
+}
+
+int config_parse_macsec_key_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) {
+
+ _cleanup_(macsec_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL;
+ _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL;
+ MACsec *s = userdata;
+ unsigned int len;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (streq(section, "MACsecTransmitAssociation"))
+ r = macsec_transmit_transmit_association_new_static(s, filename, section_line, &a);
+ else
+ r = macsec_receive_association_new_static(s, filename, section_line, &b);
+
+ if (r < 0)
+ return r;
+
+ if (a)
+ r = safe_hexstring_aton(rvalue, a->key_id, MACSEC_KEYID_LEN, &len);
+ else
+ r = safe_hexstring_aton(rvalue, b->sa.key_id, MACSEC_KEYID_LEN, &len);
+
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse key id. Ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ a = NULL;
+ b = NULL;
+
+ return 0;
+}
+
+static int macsec_receive_channel_verify(ReceiveChannel *c) {
+ NetDev *netdev;
+
+ assert(c);
+ assert(c->mac_sec);
+
+ netdev = NETDEV(c->mac_sec);
+
+ if (section_is_invalid(c->section))
+ return -EINVAL;
+
+ if (!c->mac)
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "%s: MACsec receive channel without MAC address configured. "
+ "Ignoring [MACsecReceiveChannel] section from line %u",
+ c->section->filename, c->section->line);
+
+ if (c->port == 0)
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "%s: MACsec receive channel without port configured. "
+ "Ignoring [MACsecReceiveChannel] section from line %u",
+ c->section->filename, c->section->line);
+
+ return 0;
+}
+
+static int macsec_transmit_association_verify(TransmitAssociation *c) {
+ NetDev *netdev;
+
+ assert(c);
+ assert(c->mac_sec);
+
+ netdev = NETDEV(c->mac_sec);
+
+ if (section_is_invalid(c->section))
+ return -EINVAL;
+
+ if (c->key_len <= 0)
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "%s: MACsec secure association without key configured. "
+ "Ignoring [MACsecTransmitAssociation] section from line %u",
+ c->section->filename, c->section->line);
+
+ return 0;
+}
+
+static int macsec_receive_association_verify(ReceiveAssociation *c) {
+ NetDev *netdev;
+
+ assert(c);
+ assert(c->mac_sec);
+
+ netdev = NETDEV(c->mac_sec);
+
+ if (section_is_invalid(c->section))
+ return -EINVAL;
+
+ if (c->sa.key_len <= 0)
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "%s: MACsec receive association without key configured. "
+ "Ignoring [MACsecReceiveAssociation] section from line %u",
+ c->section->filename, c->section->line);
+
+ if (!c->rx.mac)
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "%s: MACsec receive association without MAC address configured. "
+ "Ignoring [MACsecReceiveAssociation] section from line %u",
+ c->section->filename, c->section->line);
+
+ if (c->rx.port == 0)
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "%s: MACsec receive association without port configured. "
+ "Ignoring [MACsecReceiveAssociation] section from line %u",
+ c->section->filename, c->section->line);
+
+ return 0;
+}
+
+static int netdev_macsec_verify(NetDev *netdev, const char *filename) {
+ MACsec *v = MACSEC(netdev);
+ TransmitAssociation *a;
+ ReceiveAssociation *n;
+ ReceiveChannel *c;
+ Iterator i;
+ int r;
+
+ assert(netdev);
+ assert(v);
+ assert(filename);
+
+ if (v->port == 0)
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "Invalid MACsec port value '0' configured in '%s'. "
+ "Ignoring [MACSEC] section from line %u",
+ c->section->filename, c->section->line);
+
+ ORDERED_HASHMAP_FOREACH(c, v->rx_channel_by_section, i) {
+ r = macsec_receive_channel_verify(c);
+ if (r < 0)
+ macsec_receive_channel_free(c);
+ }
+
+ ORDERED_HASHMAP_FOREACH(a, v->transmit_association_by_section, i) {
+ r = macsec_transmit_association_verify(a);
+ if (r < 0)
+ macsec_transmit_association_free(a);
+ }
+
+ ORDERED_HASHMAP_FOREACH(n, v->receive_association_by_section, i) {
+ r = macsec_receive_association_verify(n);
+ if (r < 0)
+ macsec_receive_association_free(n);
+ }
+
+ return 0;
+}
+
+static void macsec_init(NetDev *netdev) {
+ MACsec *v;
+
+ assert(netdev);
+
+ v = MACSEC(netdev);
+
+ assert(v);
+
+ v->encrypt = -1;
+}
+
+static void macsec_done(NetDev *netdev) {
+ MACsec *t;
+
+ assert(netdev);
+
+ t = MACSEC(netdev);
+
+ assert(t);
+
+ ordered_hashmap_free_with_destructor(t->rx_channel_by_section, macsec_receive_channel_free);
+ ordered_hashmap_free_with_destructor(t->transmit_association_by_section, macsec_transmit_association_free);
+ ordered_hashmap_free_with_destructor(t->receive_association_by_section, macsec_receive_association_free);
+}
+
+const NetDevVTable macsec_vtable = {
+ .object_size = sizeof(MACsec),
+ .init = macsec_init,
+ .sections = "Match\0NetDev\0MACSEC\0MACsecReceiveChannel\0MACsecTransmitAssociation\0MACsecReceiveAssociation\0",
+ .fill_message_create = netdev_macsec_fill_message_create,
+ .post_create = netdev_macsec_configure,
+ .done = macsec_done,
+ .create_type = NETDEV_CREATE_STACKED,
+ .config_verify = netdev_macsec_verify,
+};
diff --git a/src/network/netdev/macsec.h b/src/network/netdev/macsec.h
new file mode 100644
index 0000000000000..8e98708861cf8
--- /dev/null
+++ b/src/network/netdev/macsec.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include
+
+#include "in-addr-util.h"
+#include "netdev.h"
+#include "networkd-util.h"
+
+typedef struct MACsec MACsec;
+
+typedef struct ReceiveChannel {
+ MACsec *mac_sec;
+ NetworkConfigSection *section;
+
+ uint16_t port;
+ struct ether_addr *mac;
+} ReceiveChannel;
+
+typedef struct TransmitAssociation {
+ MACsec *mac_sec;
+ NetworkConfigSection *section;
+
+ uint8_t an;
+ uint8_t active;
+
+ uint32_t pn;
+ uint32_t key_len;
+
+ uint8_t key_id[MACSEC_KEYID_LEN];
+ uint8_t key[MACSEC_MAX_KEY_LEN];
+
+} TransmitAssociation;
+
+typedef struct ReceiveAssociation {
+ MACsec *mac_sec;
+ NetworkConfigSection *section;
+
+ TransmitAssociation sa;
+ ReceiveChannel rx;
+
+} ReceiveAssociation;
+
+struct MACsec {
+ NetDev meta;
+
+ uint16_t port;
+ int encrypt;
+
+ OrderedHashmap *rx_channel_by_section;
+ OrderedHashmap *transmit_association_by_section;
+ OrderedHashmap *receive_association_by_section;
+};
+
+DEFINE_NETDEV_CAST(MACSEC, MACsec);
+extern const NetDevVTable macsec_vtable;
+
+CONFIG_PARSER_PROTOTYPE(config_parse_macsec_port);
+CONFIG_PARSER_PROTOTYPE(config_parse_macsec_hw_address);
+CONFIG_PARSER_PROTOTYPE(config_parse_macsec_pn);
+CONFIG_PARSER_PROTOTYPE(config_parse_macsec_key_id);
+CONFIG_PARSER_PROTOTYPE(config_parse_macsec_key);
diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf
index fcd2ec2097bab..936f51bd5fb6c 100644
--- a/src/network/netdev/netdev-gperf.gperf
+++ b/src/network/netdev/netdev-gperf.gperf
@@ -9,6 +9,7 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"")
#include "netdev/bridge.h"
#include "netdev/geneve.h"
#include "netdev/ipvlan.h"
+#include "netdev/macsec.h"
#include "netdev/macvlan.h"
#include "netdev/tunnel.h"
#include "netdev/tuntap.h"
@@ -34,157 +35,169 @@ struct ConfigPerfItem;
%struct-type
%includes
%%
-Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(NetDev, conditions)
-Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(NetDev, conditions)
-Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(NetDev, conditions)
-Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(NetDev, conditions)
-Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(NetDev, conditions)
-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, gre_erspan_sequence)
-Tunnel.ISATAP, config_parse_tristate, 0, offsetof(Tunnel, isatap)
-FooOverUDP.Protocol, config_parse_ip_protocol, 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)
-L2TP.TunnelId, config_parse_l2tp_tunnel_id, 0, offsetof(L2tpTunnel, tunnel_id)
-L2TP.PeerTunnelId, config_parse_l2tp_tunnel_id, 0, offsetof(L2tpTunnel, peer_tunnel_id)
-L2TP.UDPSourcePort, config_parse_ip_port, 0, offsetof(L2tpTunnel, l2tp_udp_sport)
-L2TP.UDPDestinationPort, config_parse_ip_port, 0, offsetof(L2tpTunnel, l2tp_udp_dport)
-L2TP.Local, config_parse_l2tp_tunnel_address, 0, offsetof(L2tpTunnel, local)
-L2TP.Remote, config_parse_l2tp_tunnel_address, 0, offsetof(L2tpTunnel, remote)
-L2TP.EncapsulationType, config_parse_l2tp_encap_type, 0, offsetof(L2tpTunnel, l2tp_encap_type)
-L2TP.UDPCheckSum, config_parse_bool, 0, offsetof(L2tpTunnel, udp_csum)
-L2TP.UDP6CheckSumRx, config_parse_bool, 0, offsetof(L2tpTunnel, udp6_csum_rx)
-L2TP.UDP6CheckSumTx, config_parse_bool, 0, offsetof(L2tpTunnel, udp6_csum_tx)
-L2TPSession.SessionId, config_parse_l2tp_session_id, 0, 0
-L2TPSession.PeerSessionId, config_parse_l2tp_session_id, 0, 0
-L2TPSession.Layer2SpecificHeader, config_parse_l2tp_session_l2spec, 0, 0
-L2TPSession.Name, config_parse_l2tp_session_name, 0, 0
-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
-WireGuard.PrivateKeyFile, config_parse_wireguard_private_key_file, 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
+Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(NetDev, conditions)
+Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(NetDev, conditions)
+Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(NetDev, conditions)
+Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(NetDev, conditions)
+Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(NetDev, conditions)
+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, gre_erspan_sequence)
+Tunnel.ISATAP, config_parse_tristate, 0, offsetof(Tunnel, isatap)
+FooOverUDP.Protocol, config_parse_ip_protocol, 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)
+L2TP.TunnelId, config_parse_l2tp_tunnel_id, 0, offsetof(L2tpTunnel, tunnel_id)
+L2TP.PeerTunnelId, config_parse_l2tp_tunnel_id, 0, offsetof(L2tpTunnel, peer_tunnel_id)
+L2TP.UDPSourcePort, config_parse_ip_port, 0, offsetof(L2tpTunnel, l2tp_udp_sport)
+L2TP.UDPDestinationPort, config_parse_ip_port, 0, offsetof(L2tpTunnel, l2tp_udp_dport)
+L2TP.Local, config_parse_l2tp_tunnel_address, 0, offsetof(L2tpTunnel, local)
+L2TP.Remote, config_parse_l2tp_tunnel_address, 0, offsetof(L2tpTunnel, remote)
+L2TP.EncapsulationType, config_parse_l2tp_encap_type, 0, offsetof(L2tpTunnel, l2tp_encap_type)
+L2TP.UDPCheckSum, config_parse_bool, 0, offsetof(L2tpTunnel, udp_csum)
+L2TP.UDP6CheckSumRx, config_parse_bool, 0, offsetof(L2tpTunnel, udp6_csum_rx)
+L2TP.UDP6CheckSumTx, config_parse_bool, 0, offsetof(L2tpTunnel, udp6_csum_tx)
+L2TPSession.SessionId, config_parse_l2tp_session_id, 0, 0
+L2TPSession.PeerSessionId, config_parse_l2tp_session_id, 0, 0
+L2TPSession.Layer2SpecificHeader, config_parse_l2tp_session_l2spec, 0, 0
+L2TPSession.Name, config_parse_l2tp_session_name, 0, 0
+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
+MACSEC.Port, config_parse_ip_port, 0, offsetof(MACsec, port)
+MACSEC.Encrypt, config_parse_bool, 0, offsetof(MACsec, encrypt)
+MACsecReceiveChannel.Port, config_parse_macsec_port, 0, 0
+MACsecReceiveChannel.MACAddress, config_parse_macsec_hw_address, 0, 0
+MACsecTransmitAssociation.PacketNumber, config_parse_macsec_pn, 0, 0
+MACsecTransmitAssociation.KeyId, config_parse_macsec_key_id, 0, 0
+MACsecTransmitAssociation.Key, config_parse_macsec_key, 0, 0
+MACsecReceiveAssociation.Port, config_parse_macsec_port, 0, 0
+MACsecReceiveAssociation.MACAddress, config_parse_macsec_hw_address, 0, 0
+MACsecReceiveAssociation.PacketNumber, config_parse_macsec_pn, 0, 0
+MACsecReceiveAssociation.KeyId, config_parse_macsec_key_id, 0, 0
+MACsecReceiveAssociation.Key, config_parse_macsec_key, 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
+WireGuard.PrivateKeyFile, config_parse_wireguard_private_key_file, 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
index c1bcfc66e6618..494ac788f89e5 100644
--- a/src/network/netdev/netdev.c
+++ b/src/network/netdev/netdev.c
@@ -15,6 +15,7 @@
#include "netdev/ipvlan.h"
#include "netdev/l2tp-tunnel.h"
#include "netdev/macvlan.h"
+#include "netdev/macsec.h"
#include "netdev/netdev.h"
#include "netdev/netdevsim.h"
#include "netdev/tunnel.h"
@@ -66,6 +67,7 @@ const NetDevVTable * const netdev_vtable[_NETDEV_KIND_MAX] = {
[NETDEV_KIND_FOU] = &foutnl_vtable,
[NETDEV_KIND_ERSPAN] = &erspan_vtable,
[NETDEV_KIND_L2TP] = &l2tptnl_vtable,
+ [NETDEV_KIND_MACSEC] = &macsec_vtable,
};
static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = {
@@ -98,6 +100,7 @@ static const char* const netdev_kind_table[_NETDEV_KIND_MAX] = {
[NETDEV_KIND_FOU] = "fou",
[NETDEV_KIND_ERSPAN] = "erspan",
[NETDEV_KIND_L2TP] = "l2tp",
+ [NETDEV_KIND_MACSEC] = "macsec",
};
DEFINE_STRING_TABLE_LOOKUP(netdev_kind, NetDevKind);
diff --git a/src/network/netdev/netdev.h b/src/network/netdev/netdev.h
index ad4dd2e2b0d50..29ecead029b8b 100644
--- a/src/network/netdev/netdev.h
+++ b/src/network/netdev/netdev.h
@@ -47,6 +47,7 @@ typedef enum NetDevKind {
NETDEV_KIND_FOU,
NETDEV_KIND_ERSPAN,
NETDEV_KIND_L2TP,
+ NETDEV_KIND_MACSEC,
_NETDEV_KIND_MAX,
_NETDEV_KIND_TUNNEL, /* Used by config_parse_stacked_netdev() */
_NETDEV_KIND_INVALID = -1
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 47a9f7d808a6c..d559ccf9e06bd 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -46,6 +46,7 @@ Network.MACVTAP, config_parse_stacked_netdev,
Network.IPVLAN, config_parse_stacked_netdev, NETDEV_KIND_IPVLAN, offsetof(Network, stacked_netdev_names)
Network.VXLAN, config_parse_stacked_netdev, NETDEV_KIND_VXLAN, offsetof(Network, stacked_netdev_names)
Network.L2TP, config_parse_stacked_netdev, NETDEV_KIND_L2TP, offsetof(Network, stacked_netdev_names)
+Network.MACSEC, config_parse_stacked_netdev, NETDEV_KIND_MACSEC, offsetof(Network, stacked_netdev_names)
Network.Tunnel, config_parse_stacked_netdev, _NETDEV_KIND_TUNNEL, offsetof(Network, stacked_netdev_names)
Network.VRF, config_parse_ifname, 0, offsetof(Network, vrf_name)
Network.DHCP, config_parse_dhcp, 0, offsetof(Network, dhcp)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 836776ae8496c..045b12e2d92cd 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -689,7 +689,7 @@ int config_parse_stacked_netdev(const char *unit,
assert(IN_SET(kind,
NETDEV_KIND_VLAN, NETDEV_KIND_MACVLAN, NETDEV_KIND_MACVTAP,
NETDEV_KIND_IPVLAN, NETDEV_KIND_VXLAN, NETDEV_KIND_L2TP,
- _NETDEV_KIND_TUNNEL));
+ NETDEV_KIND_MACSEC, _NETDEV_KIND_TUNNEL));
if (!ifname_valid(rvalue)) {
log_syntax(unit, LOG_ERR, filename, line, 0,
diff --git a/src/systemd/sd-netlink.h b/src/systemd/sd-netlink.h
index 804fe9f03ecd8..d327b27308b68 100644
--- a/src/systemd/sd-netlink.h
+++ b/src/systemd/sd-netlink.h
@@ -40,6 +40,7 @@ typedef enum sd_gen_family {
SD_GENL_WIREGUARD,
SD_GENL_FOU,
SD_GENL_L2TP,
+ SD_GENL_MACSEC,
} sd_genl_family;
/* callback */
@@ -81,6 +82,7 @@ int sd_netlink_message_append_flag(sd_netlink_message *m, unsigned short type);
int sd_netlink_message_append_u8(sd_netlink_message *m, unsigned short type, uint8_t data);
int sd_netlink_message_append_u16(sd_netlink_message *m, unsigned short type, uint16_t data);
int sd_netlink_message_append_u32(sd_netlink_message *m, unsigned short type, uint32_t data);
+int sd_netlink_message_append_u64(sd_netlink_message *m, unsigned short type, uint64_t data);
int sd_netlink_message_append_data(sd_netlink_message *m, unsigned short type, const void *data, size_t len);
int sd_netlink_message_append_in_addr(sd_netlink_message *m, unsigned short type, const struct in_addr *data);
int sd_netlink_message_append_in6_addr(sd_netlink_message *m, unsigned short type, const struct in6_addr *data);
diff --git a/test/fuzz/fuzz-netdev-parser/directives.netdev b/test/fuzz/fuzz-netdev-parser/directives.netdev
index e0756dc755485..aed0610b5da52 100644
--- a/test/fuzz/fuzz-netdev-parser/directives.netdev
+++ b/test/fuzz/fuzz-netdev-parser/directives.netdev
@@ -173,3 +173,18 @@ SessionId=
PeerSessionId=
Layer2SpecificHeader=
Name=
+[MACSEC]
+Port=11
+[MACsecReceiveAssociation]
+Port=
+MACAddress=
+PacketNumber=
+KeyId=
+Key=
+[MACsecReceiveChannel]
+Port=
+MACAddress=
+[MACsecTransmitAssociation]
+PacketNumber=
+KeyId=
+Key=