diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml
index 1836b5fe00..a58de37b3c 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,161 @@
+
+ [MACsec] Section Options
+ The [MACsec] section only applies for network devices of kind
+ macsec, and accepts the following keys:
+
+
+
+ Port=
+
+ Specifies the port to be used for the MACsec transmit channel. The port is used to make
+ secure channel identifier (SCI). Takes a value between 1 and 65535. Defaults to unset.
+
+
+
+
+ Encrypt=
+
+ Takes a boolean. When true, enable encryption. Defaults to unset.
+
+
+
+
+
+ [MACsecReceiveChannel] Section Options
+ The [MACsecReceiveChannel] section only applies for network devices of
+ kind macsec, and accepts the following keys:
+
+
+
+ Port=
+
+ Specifies the port to be used for the MACsec receive channel. The port is used to make
+ secure channel identifier (SCI). Takes a value between 1 and 65535. This option is
+ compulsory, and is not set by default.
+
+
+
+ MACAddress=
+
+ Specifies the MAC address to be used for the MACsec receive channel. The MAC address
+ used to make secure channel identifier (SCI). This option is compulsory, and is not set by
+ default.
+
+
+
+
+
+ [MACsecTransmitAssociation] Section Options
+ The [MACsecTransmitAssociation] section only applies for network devices
+ 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]). Takes a value
+ between 1-4,294,967,295. Defaults to unset.
+
+
+
+
+ KeyId=
+
+ Specifies the identification for the key. Takes a number between 0-255. This option
+ is compulsory, and is not set by default.
+
+
+
+ Key=
+
+ Specifies the encryption key used in the transmission channel. The same key must be
+ configured on the peer’s matching receive channel. This option is compulsory, and is not set
+ by default. Takes a 128-bit key encoded in a hexadecimal string, for example
+ dffafc8d7b9a43d5b9a3dfbbf6a30c16.
+
+
+
+ KeyFile=
+
+ Takes a absolute path to a file which contains a 128-bit key encoded in a hexadecimal
+ string, which will be used in the transmission channel. When this option is specified,
+ Key= is ignored. Note that the file must be readable by the user
+ systemd-network, so it should be, e.g., owned by
+ root:systemd-network with a 0640 file mode.
+
+
+
+ Activate=
+
+ Takes a boolean. If enabled, then the security association is activated. Defaults to
+ unset.
+
+
+
+ UseForEncoding=
+
+ Takes a boolean. If enabled, then the security association is used for encoding. Only
+ one [MACsecTransmitAssociation] section can enable this option. When enabled,
+ Activate=yes is implied. Defaults to unset.
+
+
+
+
+
+ [MACsecReceiveAssociation] Section Options
+ The [MACsecReceiveAssociation] section only applies for
+ network devices of kind macsec, and accepts the
+ following keys:
+
+
+
+ Port=
+
+ Accepts the same key in [MACsecReceiveChannel] section.
+
+
+
+ MACAddress=
+
+ Accepts the same key in [MACsecReceiveChannel] section.
+
+
+
+ PacketNumber=
+
+ Accepts the same key in [MACsecTransmitAssociation] section.
+
+
+
+ KeyId=
+
+ Accepts the same key in [MACsecTransmitAssociation] section.
+
+
+
+ Key=
+
+ Accepts the same key in [MACsecTransmitAssociation] section.
+
+
+
+ KeyFile=
+
+ Accepts the same key in [MACsecTransmitAssociation] section.
+
+
+
+ Activate=
+
+ Accepts the same key in [MACsecTransmitAssociation] section.
+
+
+
+
[Tunnel] Section Options
diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index 4127084703..2d8eeee88f 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -768,6 +768,14 @@
This option may be specified more than once.
+
+ MACsec=
+
+ The name of a MACsec device to create on the link. See
+ systemd.netdev5.
+ This option may be specified more than once.
+
+
ActiveSlave=
diff --git a/src/basic/fileio.c b/src/basic/fileio.c
index 85a49b1f9e..2cec054610 100644
--- a/src/basic/fileio.c
+++ b/src/basic/fileio.c
@@ -317,7 +317,8 @@ int read_full_stream_full(
assert(f);
assert(ret_contents);
- assert(!(flags & READ_FULL_FILE_UNBASE64) || ret_size);
+ assert(!FLAGS_SET(flags, READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX));
+ assert(!(flags & (READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)) || ret_size);
n_next = LINE_MAX; /* Start size */
@@ -394,9 +395,12 @@ int read_full_stream_full(
n_next = MIN(n * 2, READ_FULL_BYTES_MAX);
}
- if (flags & READ_FULL_FILE_UNBASE64) {
+ if (flags & (READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_UNHEX)) {
buf[l++] = 0;
- r = unbase64mem_full(buf, l, flags & READ_FULL_FILE_SECURE, (void **) ret_contents, ret_size);
+ if (flags & READ_FULL_FILE_UNBASE64)
+ r = unbase64mem_full(buf, l, flags & READ_FULL_FILE_SECURE, (void **) ret_contents, ret_size);
+ else
+ r = unhexmem_full(buf, l, flags & READ_FULL_FILE_SECURE, (void **) ret_contents, ret_size);
goto finalize;
}
diff --git a/src/basic/fileio.h b/src/basic/fileio.h
index ffe900b486..eb551c7ac1 100644
--- a/src/basic/fileio.h
+++ b/src/basic/fileio.h
@@ -31,6 +31,7 @@ typedef enum {
typedef enum {
READ_FULL_FILE_SECURE = 1 << 0,
READ_FULL_FILE_UNBASE64 = 1 << 1,
+ READ_FULL_FILE_UNHEX = 1 << 2,
} ReadFullFileFlags;
int fopen_unlocked(const char *path, const char *options, FILE **ret);
diff --git a/src/basic/hexdecoct.c b/src/basic/hexdecoct.c
index 132439fd1c..5e425b0231 100644
--- a/src/basic/hexdecoct.c
+++ b/src/basic/hexdecoct.c
@@ -108,10 +108,12 @@ static int unhex_next(const char **p, size_t *l) {
return r;
}
-int unhexmem(const char *p, size_t l, void **ret, size_t *ret_len) {
+int unhexmem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_len) {
_cleanup_free_ uint8_t *buf = NULL;
+ size_t buf_size;
const char *x;
uint8_t *z;
+ int r;
assert(ret);
assert(ret_len);
@@ -121,7 +123,8 @@ int unhexmem(const char *p, size_t l, void **ret, size_t *ret_len) {
l = strlen(p);
/* Note that the calculation of memory size is an upper boundary, as we ignore whitespace while decoding */
- buf = malloc((l + 1) / 2 + 1);
+ buf_size = (l + 1) / 2 + 1;
+ buf = malloc(buf_size);
if (!buf)
return -ENOMEM;
@@ -131,12 +134,16 @@ int unhexmem(const char *p, size_t l, void **ret, size_t *ret_len) {
a = unhex_next(&x, &l);
if (a == -EPIPE) /* End of string */
break;
- if (a < 0)
- return a;
+ if (a < 0) {
+ r = a;
+ goto on_failure;
+ }
b = unhex_next(&x, &l);
- if (b < 0)
- return b;
+ if (b < 0) {
+ r = b;
+ goto on_failure;
+ }
*(z++) = (uint8_t) a << 4 | (uint8_t) b;
}
@@ -147,6 +154,12 @@ int unhexmem(const char *p, size_t l, void **ret, size_t *ret_len) {
*ret = TAKE_PTR(buf);
return 0;
+
+on_failure:
+ if (secure)
+ explicit_bzero_safe(buf, buf_size);
+
+ return r;
}
/* https://tools.ietf.org/html/rfc4648#section-6
diff --git a/src/basic/hexdecoct.h b/src/basic/hexdecoct.h
index fa6013ee75..dfdff1e9bb 100644
--- a/src/basic/hexdecoct.h
+++ b/src/basic/hexdecoct.h
@@ -18,7 +18,10 @@ char hexchar(int x) _const_;
int unhexchar(char c) _const_;
char *hexmem(const void *p, size_t l);
-int unhexmem(const char *p, size_t l, void **mem, size_t *len);
+int unhexmem_full(const char *p, size_t l, bool secure, void **mem, size_t *len);
+static inline int unhexmem(const char *p, size_t l, void **mem, size_t *len) {
+ return unhexmem_full(p, l, false, mem, len);
+}
char base32hexchar(int x) _const_;
int unbase32hexchar(char c) _const_;
diff --git a/src/basic/linux/if_macsec.h b/src/basic/linux/if_macsec.h
new file mode 100644
index 0000000000..98e4d5d7c4
--- /dev/null
+++ b/src/basic/linux/if_macsec.h
@@ -0,0 +1,177 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/*
+ * include/uapi/linux/if_macsec.h - MACsec device
+ *
+ * Copyright (c) 2015 Sabrina Dubroca
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef _UAPI_MACSEC_H
+#define _UAPI_MACSEC_H
+
+#include
+
+#define MACSEC_GENL_NAME "macsec"
+#define MACSEC_GENL_VERSION 1
+
+#define MACSEC_MAX_KEY_LEN 128
+
+#define MACSEC_KEYID_LEN 16
+
+/* cipher IDs as per IEEE802.1AEbn-2011 */
+#define MACSEC_CIPHER_ID_GCM_AES_128 0x0080C20001000001ULL
+#define MACSEC_CIPHER_ID_GCM_AES_256 0x0080C20001000002ULL
+
+/* deprecated cipher ID for GCM-AES-128 */
+#define MACSEC_DEFAULT_CIPHER_ID 0x0080020001000001ULL
+#define MACSEC_DEFAULT_CIPHER_ALT MACSEC_CIPHER_ID_GCM_AES_128
+
+#define MACSEC_MIN_ICV_LEN 8
+#define MACSEC_MAX_ICV_LEN 32
+/* upper limit for ICV length as recommended by IEEE802.1AE-2006 */
+#define MACSEC_STD_ICV_LEN 16
+
+enum macsec_attrs {
+ MACSEC_ATTR_UNSPEC,
+ MACSEC_ATTR_IFINDEX, /* u32, ifindex of the MACsec netdevice */
+ MACSEC_ATTR_RXSC_CONFIG, /* config, nested macsec_rxsc_attrs */
+ MACSEC_ATTR_SA_CONFIG, /* config, nested macsec_sa_attrs */
+ MACSEC_ATTR_SECY, /* dump, nested macsec_secy_attrs */
+ MACSEC_ATTR_TXSA_LIST, /* dump, nested, macsec_sa_attrs for each TXSA */
+ MACSEC_ATTR_RXSC_LIST, /* dump, nested, macsec_rxsc_attrs for each RXSC */
+ MACSEC_ATTR_TXSC_STATS, /* dump, nested, macsec_txsc_stats_attr */
+ MACSEC_ATTR_SECY_STATS, /* dump, nested, macsec_secy_stats_attr */
+ __MACSEC_ATTR_END,
+ NUM_MACSEC_ATTR = __MACSEC_ATTR_END,
+ MACSEC_ATTR_MAX = __MACSEC_ATTR_END - 1,
+};
+
+enum macsec_secy_attrs {
+ MACSEC_SECY_ATTR_UNSPEC,
+ MACSEC_SECY_ATTR_SCI,
+ MACSEC_SECY_ATTR_ENCODING_SA,
+ MACSEC_SECY_ATTR_WINDOW,
+ MACSEC_SECY_ATTR_CIPHER_SUITE,
+ MACSEC_SECY_ATTR_ICV_LEN,
+ MACSEC_SECY_ATTR_PROTECT,
+ MACSEC_SECY_ATTR_REPLAY,
+ MACSEC_SECY_ATTR_OPER,
+ MACSEC_SECY_ATTR_VALIDATE,
+ MACSEC_SECY_ATTR_ENCRYPT,
+ MACSEC_SECY_ATTR_INC_SCI,
+ MACSEC_SECY_ATTR_ES,
+ MACSEC_SECY_ATTR_SCB,
+ MACSEC_SECY_ATTR_PAD,
+ __MACSEC_SECY_ATTR_END,
+ NUM_MACSEC_SECY_ATTR = __MACSEC_SECY_ATTR_END,
+ MACSEC_SECY_ATTR_MAX = __MACSEC_SECY_ATTR_END - 1,
+};
+
+enum macsec_rxsc_attrs {
+ MACSEC_RXSC_ATTR_UNSPEC,
+ MACSEC_RXSC_ATTR_SCI, /* config/dump, u64 */
+ MACSEC_RXSC_ATTR_ACTIVE, /* config/dump, u8 0..1 */
+ MACSEC_RXSC_ATTR_SA_LIST, /* dump, nested */
+ MACSEC_RXSC_ATTR_STATS, /* dump, nested, macsec_rxsc_stats_attr */
+ MACSEC_RXSC_ATTR_PAD,
+ __MACSEC_RXSC_ATTR_END,
+ NUM_MACSEC_RXSC_ATTR = __MACSEC_RXSC_ATTR_END,
+ MACSEC_RXSC_ATTR_MAX = __MACSEC_RXSC_ATTR_END - 1,
+};
+
+enum macsec_sa_attrs {
+ MACSEC_SA_ATTR_UNSPEC,
+ MACSEC_SA_ATTR_AN, /* config/dump, u8 0..3 */
+ MACSEC_SA_ATTR_ACTIVE, /* config/dump, u8 0..1 */
+ MACSEC_SA_ATTR_PN, /* config/dump, u32 */
+ MACSEC_SA_ATTR_KEY, /* config, data */
+ MACSEC_SA_ATTR_KEYID, /* config/dump, 128-bit */
+ MACSEC_SA_ATTR_STATS, /* dump, nested, macsec_sa_stats_attr */
+ MACSEC_SA_ATTR_PAD,
+ __MACSEC_SA_ATTR_END,
+ NUM_MACSEC_SA_ATTR = __MACSEC_SA_ATTR_END,
+ MACSEC_SA_ATTR_MAX = __MACSEC_SA_ATTR_END - 1,
+};
+
+enum macsec_nl_commands {
+ MACSEC_CMD_GET_TXSC,
+ MACSEC_CMD_ADD_RXSC,
+ MACSEC_CMD_DEL_RXSC,
+ MACSEC_CMD_UPD_RXSC,
+ MACSEC_CMD_ADD_TXSA,
+ MACSEC_CMD_DEL_TXSA,
+ MACSEC_CMD_UPD_TXSA,
+ MACSEC_CMD_ADD_RXSA,
+ MACSEC_CMD_DEL_RXSA,
+ MACSEC_CMD_UPD_RXSA,
+};
+
+/* u64 per-RXSC stats */
+enum macsec_rxsc_stats_attr {
+ MACSEC_RXSC_STATS_ATTR_UNSPEC,
+ MACSEC_RXSC_STATS_ATTR_IN_OCTETS_VALIDATED,
+ MACSEC_RXSC_STATS_ATTR_IN_OCTETS_DECRYPTED,
+ MACSEC_RXSC_STATS_ATTR_IN_PKTS_UNCHECKED,
+ MACSEC_RXSC_STATS_ATTR_IN_PKTS_DELAYED,
+ MACSEC_RXSC_STATS_ATTR_IN_PKTS_OK,
+ MACSEC_RXSC_STATS_ATTR_IN_PKTS_INVALID,
+ MACSEC_RXSC_STATS_ATTR_IN_PKTS_LATE,
+ MACSEC_RXSC_STATS_ATTR_IN_PKTS_NOT_VALID,
+ MACSEC_RXSC_STATS_ATTR_IN_PKTS_NOT_USING_SA,
+ MACSEC_RXSC_STATS_ATTR_IN_PKTS_UNUSED_SA,
+ MACSEC_RXSC_STATS_ATTR_PAD,
+ __MACSEC_RXSC_STATS_ATTR_END,
+ NUM_MACSEC_RXSC_STATS_ATTR = __MACSEC_RXSC_STATS_ATTR_END,
+ MACSEC_RXSC_STATS_ATTR_MAX = __MACSEC_RXSC_STATS_ATTR_END - 1,
+};
+
+/* u32 per-{RX,TX}SA stats */
+enum macsec_sa_stats_attr {
+ MACSEC_SA_STATS_ATTR_UNSPEC,
+ MACSEC_SA_STATS_ATTR_IN_PKTS_OK,
+ MACSEC_SA_STATS_ATTR_IN_PKTS_INVALID,
+ MACSEC_SA_STATS_ATTR_IN_PKTS_NOT_VALID,
+ MACSEC_SA_STATS_ATTR_IN_PKTS_NOT_USING_SA,
+ MACSEC_SA_STATS_ATTR_IN_PKTS_UNUSED_SA,
+ MACSEC_SA_STATS_ATTR_OUT_PKTS_PROTECTED,
+ MACSEC_SA_STATS_ATTR_OUT_PKTS_ENCRYPTED,
+ __MACSEC_SA_STATS_ATTR_END,
+ NUM_MACSEC_SA_STATS_ATTR = __MACSEC_SA_STATS_ATTR_END,
+ MACSEC_SA_STATS_ATTR_MAX = __MACSEC_SA_STATS_ATTR_END - 1,
+};
+
+/* u64 per-TXSC stats */
+enum macsec_txsc_stats_attr {
+ MACSEC_TXSC_STATS_ATTR_UNSPEC,
+ MACSEC_TXSC_STATS_ATTR_OUT_PKTS_PROTECTED,
+ MACSEC_TXSC_STATS_ATTR_OUT_PKTS_ENCRYPTED,
+ MACSEC_TXSC_STATS_ATTR_OUT_OCTETS_PROTECTED,
+ MACSEC_TXSC_STATS_ATTR_OUT_OCTETS_ENCRYPTED,
+ MACSEC_TXSC_STATS_ATTR_PAD,
+ __MACSEC_TXSC_STATS_ATTR_END,
+ NUM_MACSEC_TXSC_STATS_ATTR = __MACSEC_TXSC_STATS_ATTR_END,
+ MACSEC_TXSC_STATS_ATTR_MAX = __MACSEC_TXSC_STATS_ATTR_END - 1,
+};
+
+/* u64 per-SecY stats */
+enum macsec_secy_stats_attr {
+ MACSEC_SECY_STATS_ATTR_UNSPEC,
+ MACSEC_SECY_STATS_ATTR_OUT_PKTS_UNTAGGED,
+ MACSEC_SECY_STATS_ATTR_IN_PKTS_UNTAGGED,
+ MACSEC_SECY_STATS_ATTR_OUT_PKTS_TOO_LONG,
+ MACSEC_SECY_STATS_ATTR_IN_PKTS_NO_TAG,
+ MACSEC_SECY_STATS_ATTR_IN_PKTS_BAD_TAG,
+ MACSEC_SECY_STATS_ATTR_IN_PKTS_UNKNOWN_SCI,
+ MACSEC_SECY_STATS_ATTR_IN_PKTS_NO_SCI,
+ MACSEC_SECY_STATS_ATTR_IN_PKTS_OVERRUN,
+ MACSEC_SECY_STATS_ATTR_PAD,
+ __MACSEC_SECY_STATS_ATTR_END,
+ NUM_MACSEC_SECY_STATS_ATTR = __MACSEC_SECY_STATS_ATTR_END,
+ MACSEC_SECY_STATS_ATTR_MAX = __MACSEC_SECY_STATS_ATTR_END - 1,
+};
+
+#endif /* _UAPI_MACSEC_H */
diff --git a/src/basic/meson.build b/src/basic/meson.build
index de1e42013d..524f3785dc 100644
--- a/src/basic/meson.build
+++ b/src/basic/meson.build
@@ -95,6 +95,7 @@ basic_sources = files('''
linux/if_bonding.h
linux/if_bridge.h
linux/if_link.h
+ linux/if_macsec.h
linux/if_tun.h
linux/if_tunnel.h
linux/libc-compat.h
diff --git a/src/libsystemd/sd-netlink/generic-netlink.c b/src/libsystemd/sd-netlink/generic-netlink.c
index 384072e881..473d8670a9 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 0dcc53be55..68b232b7d4 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 0c67d1c68f..118f319a20 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
#include
#include
@@ -306,6 +307,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",
@@ -334,6 +351,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);
@@ -383,6 +401,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 = {
@@ -843,11 +863,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 b84fa4762b..a2b3087d15 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 c95e750306..2acbe858bb 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 0000000000..15b5378f30
--- /dev/null
+++ b/src/network/netdev/macsec.c
@@ -0,0 +1,1249 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include
+#include
+#include
+#include
+
+#include "conf-parser.h"
+#include "fileio.h"
+#include "hashmap.h"
+#include "hexdecoct.h"
+#include "macsec.h"
+#include "memory-util.h"
+#include "missing.h"
+#include "netlink-util.h"
+#include "network-internal.h"
+#include "networkd-address.h"
+#include "networkd-manager.h"
+#include "path-util.h"
+#include "sd-netlink.h"
+#include "socket-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "util.h"
+
+static void security_association_clear(SecurityAssociation *sa) {
+ if (!sa)
+ return;
+
+ explicit_bzero_safe(sa->key, sa->key_len);
+ free(sa->key);
+ free(sa->key_file);
+}
+
+static void security_association_init(SecurityAssociation *sa) {
+ assert(sa);
+
+ sa->activate = -1;
+ sa->use_for_encoding = -1;
+}
+
+static void macsec_receive_association_free(ReceiveAssociation *c) {
+ if (!c)
+ return;
+
+ if (c->macsec && c->section)
+ ordered_hashmap_remove(c->macsec->receive_associations_by_section, c->section);
+
+ network_config_section_free(c->section);
+ security_association_clear(&c->sa);
+
+ 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_associations_by_section, n);
+ if (c) {
+ *ret = TAKE_PTR(c);
+ return 0;
+ }
+
+ c = new(ReceiveAssociation, 1);
+ if (!c)
+ return -ENOMEM;
+
+ *c = (ReceiveAssociation) {
+ .macsec = s,
+ .section = TAKE_PTR(n),
+ };
+
+ security_association_init(&c->sa);
+
+ r = ordered_hashmap_ensure_allocated(&s->receive_associations_by_section, &network_config_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = ordered_hashmap_put(s->receive_associations_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->macsec) {
+ if (c->sci.as_uint64 > 0)
+ ordered_hashmap_remove(c->macsec->receive_channels, &c->sci.as_uint64);
+
+ if (c->section)
+ ordered_hashmap_remove(c->macsec->receive_channels_by_section, c->section);
+ }
+
+ network_config_section_free(c->section);
+
+ free(c);
+}
+
+DEFINE_NETWORK_SECTION_FUNCTIONS(ReceiveChannel, macsec_receive_channel_free);
+
+static int macsec_receive_channel_new(MACsec *s, uint64_t sci, ReceiveChannel **ret) {
+ ReceiveChannel *c;
+
+ assert(s);
+
+ c = new(ReceiveChannel, 1);
+ if (!c)
+ return -ENOMEM;
+
+ *c = (ReceiveChannel) {
+ .macsec = s,
+ .sci.as_uint64 = sci,
+ };
+
+ *ret = c;
+ return 0;
+}
+
+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->receive_channels_by_section, n);
+ if (c) {
+ *ret = TAKE_PTR(c);
+ return 0;
+ }
+
+ r = macsec_receive_channel_new(s, 0, &c);
+ if (r < 0)
+ return r;
+
+ c->section = TAKE_PTR(n);
+
+ r = ordered_hashmap_ensure_allocated(&s->receive_channels_by_section, &network_config_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = ordered_hashmap_put(s->receive_channels_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->macsec && a->section)
+ ordered_hashmap_remove(a->macsec->transmit_associations_by_section, a->section);
+
+ network_config_section_free(a->section);
+ security_association_clear(&a->sa);
+
+ free(a);
+}
+
+DEFINE_NETWORK_SECTION_FUNCTIONS(TransmitAssociation, macsec_transmit_association_free);
+
+static int macsec_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_associations_by_section, n);
+ if (a) {
+ *ret = TAKE_PTR(a);
+ return 0;
+ }
+
+ a = new(TransmitAssociation, 1);
+ if (!a)
+ return -ENOMEM;
+
+ *a = (TransmitAssociation) {
+ .macsec = s,
+ .section = TAKE_PTR(n),
+ };
+
+ security_association_init(&a->sa);
+
+ r = ordered_hashmap_ensure_allocated(&s->transmit_associations_by_section, &network_config_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = ordered_hashmap_put(s->transmit_associations_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;
+ int r;
+
+ assert(netdev);
+ assert(netdev->ifindex > 0);
+
+ 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, MACsecSCI *sci, sd_netlink_message *m) {
+ int r;
+
+ assert(netdev);
+ assert(m);
+ assert(sci);
+
+ 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");
+
+ r = sd_netlink_message_append_u64(m, MACSEC_RXSC_ATTR_SCI, sci->as_uint64);
+ 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, SecurityAssociation *a, sd_netlink_message *m) {
+ int r;
+
+ assert(netdev);
+ assert(a);
+ assert(m);
+
+ 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->association_number);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append MACSEC_SA_ATTR_AN attribute: %m");
+
+ if (a->packet_number > 0) {
+ r = sd_netlink_message_append_u32(m, MACSEC_SA_ATTR_PN, a->packet_number);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append MACSEC_SA_ATTR_PN attribute: %m");
+ }
+
+ if (a->key_len > 0) {
+ 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");
+ }
+
+ if (a->activate >= 0) {
+ r = sd_netlink_message_append_u8(m, MACSEC_SA_ATTR_ACTIVE, a->activate);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append MACSEC_SA_ATTR_ACTIVE 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) {
+ int r;
+
+ assert(netdev);
+ assert(netdev->state != _NETDEV_STATE_INVALID);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r == -EEXIST)
+ log_netdev_info(netdev,
+ "MACsec receive secure association exists, "
+ "using existing without changing its parameters");
+ else if (r < 0) {
+ log_netdev_warning_errno(netdev, r,
+ "Failed to add receive secure association: %m");
+ netdev_drop(netdev);
+
+ return 1;
+ }
+
+ log_netdev_debug(netdev, "Receive secure association is configured");
+
+ return 1;
+}
+
+static int netdev_macsec_configure_receive_association(NetDev *netdev, ReceiveAssociation *a) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+ int r;
+
+ assert(netdev);
+ assert(a);
+
+ 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->sci, 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 secure association: %m");
+
+ netdev_ref(netdev);
+
+ return 0;
+}
+
+static int macsec_receive_channel_handler(sd_netlink *rtnl, sd_netlink_message *m, ReceiveChannel *c) {
+ NetDev *netdev;
+ unsigned i;
+ int r;
+
+ assert(c);
+ assert(c->macsec);
+
+ netdev = NETDEV(c->macsec);
+
+ assert(netdev->state != _NETDEV_STATE_INVALID);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r == -EEXIST)
+ log_netdev_debug(netdev,
+ "MACsec receive channel exists, "
+ "using existing without changing its parameters");
+ else if (r < 0) {
+ log_netdev_warning_errno(netdev, r,
+ "Failed to add receive secure channel: %m");
+ netdev_drop(netdev);
+
+ return 1;
+ }
+
+ log_netdev_debug(netdev, "Receive channel is configured");
+
+ for (i = 0; i < c->n_rxsa; i++) {
+ r = netdev_macsec_configure_receive_association(netdev, c->rxsa[i]);
+ if (r < 0) {
+ log_netdev_warning_errno(netdev, r,
+ "Failed to configure receive security association: %m");
+ netdev_drop(netdev);
+ return 1;
+ }
+ }
+
+ return 1;
+}
+
+static void receive_channel_destroy_callback(ReceiveChannel *c) {
+ assert(c);
+ assert(c->macsec);
+
+ netdev_unref(NETDEV(c->macsec));
+}
+
+static int netdev_macsec_configure_receive_channel(NetDev *netdev, ReceiveChannel *c) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+ int r;
+
+ assert(netdev);
+ assert(c);
+
+ r = netdev_macsec_fill_message(netdev, MACSEC_CMD_ADD_RXSC, &m);
+ if (r < 0)
+ return r;
+
+ r = netdev_macsec_fill_message_sci(netdev, &c->sci, m);
+ if (r < 0)
+ return r;
+
+ r = netlink_call_async(netdev->manager->genl, NULL, m, macsec_receive_channel_handler,
+ receive_channel_destroy_callback, c);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Failed to configure receive channel: %m");
+
+ netdev_ref(netdev);
+
+ return 0;
+}
+
+static int macsec_transmit_association_handler(sd_netlink *rtnl, sd_netlink_message *m, NetDev *netdev) {
+ int r;
+
+ assert(netdev);
+ assert(netdev->state != _NETDEV_STATE_INVALID);
+
+ r = sd_netlink_message_get_errno(m);
+ if (r == -EEXIST)
+ log_netdev_info(netdev,
+ "MACsec transmit secure association exists, "
+ "using existing without changing its parameters");
+ else if (r < 0) {
+ log_netdev_warning_errno(netdev, r,
+ "Failed to add transmit secure association: %m");
+ netdev_drop(netdev);
+
+ return 1;
+ }
+
+ log_netdev_debug(netdev, "Transmit secure association is configured");
+
+ return 1;
+}
+
+static int netdev_macsec_configure_transmit_association(NetDev *netdev, TransmitAssociation *a) {
+ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
+ int r;
+
+ assert(netdev);
+ assert(a);
+
+ r = netdev_macsec_fill_message(netdev, MACSEC_CMD_ADD_TXSA, &m);
+ if (r < 0)
+ return r;
+
+ r = netdev_macsec_fill_message_sa(netdev, &a->sa, m);
+ if (r < 0)
+ return r;
+
+ r = netlink_call_async(netdev->manager->genl, NULL, m, macsec_transmit_association_handler,
+ netdev_destroy_callback, netdev);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Failed to configure transmit secure association: %m");
+
+ netdev_ref(netdev);
+
+ return 0;
+}
+
+static int netdev_macsec_configure(NetDev *netdev, Link *link, sd_netlink_message *m) {
+ TransmitAssociation *a;
+ ReceiveChannel *c;
+ Iterator i;
+ MACsec *s;
+ int r;
+
+ assert(netdev);
+ s = MACSEC(netdev);
+ assert(s);
+
+ ORDERED_HASHMAP_FOREACH(a, s->transmit_associations_by_section, i) {
+ r = netdev_macsec_configure_transmit_association(netdev, a);
+ if (r < 0)
+ return r;
+ }
+
+ ORDERED_HASHMAP_FOREACH(c, s->receive_channels, i) {
+ r = netdev_macsec_configure_receive_channel(netdev, c);
+ if (r < 0)
+ return r;
+ }
+
+ 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);
+
+ if (v->port > 0) {
+ 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 >= 0) {
+ 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");
+ }
+
+ r = sd_netlink_message_append_u8(m, IFLA_MACSEC_ENCODING_SA, v->encoding_an);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r, "Could not append IFLA_MACSEC_ENCODING_SA 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;
+ uint16_t port;
+ be16_t *dest;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ /* This parses port used to make Secure Channel Identifier (SCI) */
+
+ if (streq(section, "MACsec"))
+ dest = &s->port;
+ else if (streq(section, "MACsecReceiveChannel")) {
+ r = macsec_receive_channel_new_static(s, filename, section_line, &c);
+ if (r < 0)
+ return r;
+
+ dest = &c->sci.port;
+ } else {
+ assert(streq(section, "MACsecReceiveAssociation"));
+
+ r = macsec_receive_association_new_static(s, filename, section_line, &b);
+ if (r < 0)
+ return r;
+
+ dest = &b->sci.port;
+ }
+
+ r = parse_ip_port(rvalue, &port);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to parse port '%s' for secure channel identifier. Ignoring assignment: %m",
+ rvalue);
+ return 0;
+ }
+
+ *dest = htobe16(port);
+
+ TAKE_PTR(b);
+ TAKE_PTR(c);
+
+ 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;
+
+ r = ether_addr_from_string(rvalue, b ? &b->sci.mac : &c->sci.mac);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to parse MAC address for secure channel identifier. "
+ "Ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ TAKE_PTR(b);
+ TAKE_PTR(c);
+
+ return 0;
+}
+
+int config_parse_macsec_packet_number(
+ 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;
+ uint32_t val, *dest;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (streq(section, "MACsecTransmitAssociation"))
+ r = macsec_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;
+
+ dest = a ? &a->sa.packet_number : &b->sa.packet_number;
+
+ r = safe_atou32(rvalue, &val);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to parse packet number. Ignoring assignment: %s", rvalue);
+ return 0;
+ }
+ if (streq(section, "MACsecTransmitAssociation") && val == 0) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "Invalid packet number. Ignoring assignment: %s", rvalue);
+ return 0;
+ }
+
+ *dest = val;
+ TAKE_PTR(a);
+ TAKE_PTR(b);
+
+ 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_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL;
+ _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL;
+ _cleanup_free_ void *p;
+ MACsec *s = userdata;
+ SecurityAssociation *dest;
+ size_t l;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ (void) warn_file_is_world_accessible(filename, NULL, unit, line);
+
+ if (streq(section, "MACsecTransmitAssociation"))
+ r = macsec_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;
+
+ dest = a ? &a->sa : &b->sa;
+
+ r = unhexmem_full(rvalue, strlen(rvalue), true, &p, &l);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to parse key. Ignoring assignment: %m");
+ return 0;
+ }
+ if (l != 16) {
+ /* See DEFAULT_SAK_LEN in drivers/net/macsec.c */
+ explicit_bzero_safe(p, l);
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "Invalid key length (%zu). Ignoring assignment", l);
+ return 0;
+ }
+
+ free_and_replace(dest->key, p);
+ dest->key_len = l;
+
+ TAKE_PTR(a);
+ TAKE_PTR(b);
+
+ return 0;
+}
+
+int config_parse_macsec_key_file(
+ 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;
+ _cleanup_free_ char *path = NULL;
+ MACsec *s = userdata;
+ char **dest;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (streq(section, "MACsecTransmitAssociation"))
+ r = macsec_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;
+
+ dest = a ? &a->sa.key_file : &b->sa.key_file;
+
+ if (isempty(rvalue)) {
+ *dest = mfree(*dest);
+ return 0;
+ }
+
+ path = strdup(rvalue);
+ if (!path)
+ return log_oom();
+
+ if (path_simplify_and_warn(path, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue) < 0)
+ return 0;
+
+ free_and_replace(*dest, path);
+ TAKE_PTR(a);
+ TAKE_PTR(b);
+
+ 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;
+ _cleanup_free_ void *p;
+ MACsec *s = userdata;
+ uint8_t *dest;
+ size_t l;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (streq(section, "MACsecTransmitAssociation"))
+ r = macsec_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;
+
+ r = unhexmem(rvalue, strlen(rvalue), &p, &l);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse key id. Ignoring assignment: %s", rvalue);
+ return 0;
+ }
+ if (l > MACSEC_KEYID_LEN) {
+ log_syntax(unit, LOG_ERR, filename, line, 0,
+ "The size of key id is too large (%zu), maximum of %zu permitted. "
+ "Ignoring assignment: %s", l, (size_t) MACSEC_KEYID_LEN, rvalue);
+ return 0;
+ }
+
+ dest = a ? a->sa.key_id : b->sa.key_id;
+ memcpy_safe(dest, p, l);
+ memzero(dest + l, MACSEC_KEYID_LEN - l);
+
+ TAKE_PTR(a);
+ TAKE_PTR(b);
+
+ return 0;
+}
+
+int config_parse_macsec_sa_activate(
+ 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 *dest;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ if (streq(section, "MACsecTransmitAssociation"))
+ r = macsec_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;
+
+ dest = a ? &a->sa.activate : &b->sa.activate;
+
+ if (isempty(rvalue))
+ r = -1;
+ else {
+ r = parse_boolean(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to parse activation mode of %s security association. "
+ "Ignoring assignment: %s",
+ streq(section, "MACsecTransmitAssociation") ? "transmit" : "receive",
+ rvalue);
+ return 0;
+ }
+ }
+
+ *dest = r;
+ TAKE_PTR(a);
+ TAKE_PTR(b);
+
+ return 0;
+}
+
+int config_parse_macsec_use_for_encoding(
+ 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;
+ MACsec *s = userdata;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = macsec_transmit_association_new_static(s, filename, section_line, &a);
+ if (r < 0)
+ return r;
+
+ if (isempty(rvalue))
+ r = -1;
+ else {
+ r = parse_boolean(rvalue);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r,
+ "Failed to parse %s= setting. Ignoring assignment: %s",
+ lvalue, rvalue);
+ return 0;
+ }
+ }
+
+ a->sa.use_for_encoding = r;
+ if (a->sa.use_for_encoding > 0)
+ a->sa.activate = true;
+
+ TAKE_PTR(a);
+
+ return 0;
+}
+
+static int macsec_read_key_file(NetDev *netdev, SecurityAssociation *sa) {
+ _cleanup_free_ uint8_t *key = NULL;
+ size_t key_len;
+ int r;
+
+ assert(netdev);
+ assert(sa);
+
+ if (!sa->key_file)
+ return 0;
+
+ r = read_full_file_full(sa->key_file, READ_FULL_FILE_SECURE | READ_FULL_FILE_UNHEX, (char **) &key, &key_len);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r,
+ "Failed to read key from '%s', ignoring: %m",
+ sa->key_file);
+ if (key_len != 16) {
+ explicit_bzero_safe(key, key_len);
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "Invalid key length (%zu bytes), ignoring: %m",
+ key_len);
+ }
+
+ explicit_bzero_safe(sa->key, sa->key_len);
+ free_and_replace(sa->key, key);
+ sa->key_len = key_len;
+
+ return 0;
+}
+
+static int macsec_receive_channel_verify(ReceiveChannel *c) {
+ NetDev *netdev;
+ int r;
+
+ assert(c);
+ assert(c->macsec);
+
+ netdev = NETDEV(c->macsec);
+
+ if (section_is_invalid(c->section))
+ return -EINVAL;
+
+ if (ether_addr_is_null(&c->sci.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->sci.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);
+
+ r = ordered_hashmap_ensure_allocated(&c->macsec->receive_channels, &uint64_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ r = ordered_hashmap_put(c->macsec->receive_channels, &c->sci.as_uint64, c);
+ if (r == -EEXIST)
+ return log_netdev_error_errno(netdev, r,
+ "%s: Multiple [MACsecReceiveChannel] sections have same SCI, "
+ "Ignoring [MACsecReceiveChannel] section from line %u",
+ c->section->filename, c->section->line);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r,
+ "%s: Failed to store [MACsecReceiveChannel] section at hashmap, "
+ "Ignoring [MACsecReceiveChannel] section from line %u",
+ c->section->filename, c->section->line);
+ return 0;
+}
+
+static int macsec_transmit_association_verify(TransmitAssociation *t) {
+ NetDev *netdev;
+ int r;
+
+ assert(t);
+ assert(t->macsec);
+
+ netdev = NETDEV(t->macsec);
+
+ if (section_is_invalid(t->section))
+ return -EINVAL;
+
+ if (t->sa.packet_number == 0)
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "%s: MACsec transmit secure association without PacketNumber= configured. "
+ "Ignoring [MACsecTransmitAssociation] section from line %u",
+ t->section->filename, t->section->line);
+
+ r = macsec_read_key_file(netdev, &t->sa);
+ if (r < 0)
+ return r;
+
+ if (t->sa.key_len <= 0)
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "%s: MACsec transmit secure association without key configured. "
+ "Ignoring [MACsecTransmitAssociation] section from line %u",
+ t->section->filename, t->section->line);
+
+ return 0;
+}
+
+static int macsec_receive_association_verify(ReceiveAssociation *a) {
+ ReceiveChannel *c;
+ NetDev *netdev;
+ int r;
+
+ assert(a);
+ assert(a->macsec);
+
+ netdev = NETDEV(a->macsec);
+
+ if (section_is_invalid(a->section))
+ return -EINVAL;
+
+ r = macsec_read_key_file(netdev, &a->sa);
+ if (r < 0)
+ return r;
+
+ if (a->sa.key_len <= 0)
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "%s: MACsec receive secure association without key configured. "
+ "Ignoring [MACsecReceiveAssociation] section from line %u",
+ a->section->filename, a->section->line);
+
+ if (ether_addr_is_null(&a->sci.mac))
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "%s: MACsec receive secure association without MAC address configured. "
+ "Ignoring [MACsecReceiveAssociation] section from line %u",
+ a->section->filename, a->section->line);
+
+ if (a->sci.port == 0)
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+ "%s: MACsec receive secure association without port configured. "
+ "Ignoring [MACsecReceiveAssociation] section from line %u",
+ a->section->filename, a->section->line);
+
+ c = ordered_hashmap_get(a->macsec->receive_channels, &a->sci.as_uint64);
+ if (!c) {
+ _cleanup_(macsec_receive_channel_freep) ReceiveChannel *new_channel = NULL;
+
+ r = macsec_receive_channel_new(a->macsec, a->sci.as_uint64, &new_channel);
+ if (r < 0)
+ return log_oom();
+
+ r = ordered_hashmap_ensure_allocated(&a->macsec->receive_channels, &uint64_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ r = ordered_hashmap_put(a->macsec->receive_channels, &new_channel->sci.as_uint64, new_channel);
+ if (r < 0)
+ return log_netdev_error_errno(netdev, r,
+ "%s: Failed to store receive channel at hashmap, "
+ "Ignoring [MACsecReceiveAssociation] section from line %u",
+ a->section->filename, a->section->line);
+ c = TAKE_PTR(new_channel);
+ }
+ if (c->n_rxsa >= MACSEC_MAX_ASSOCIATION_NUMBER)
+ return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(ERANGE),
+ "%s: Too many [MACsecReceiveAssociation] sections for the same receive channel, "
+ "Ignoring [MACsecReceiveAssociation] section from line %u",
+ a->section->filename, a->section->line);
+
+ a->sa.association_number = c->n_rxsa;
+ c->rxsa[c->n_rxsa++] = a;
+
+ return 0;
+}
+
+static int netdev_macsec_verify(NetDev *netdev, const char *filename) {
+ MACsec *v = MACSEC(netdev);
+ TransmitAssociation *a;
+ ReceiveAssociation *n;
+ ReceiveChannel *c;
+ Iterator i;
+ uint8_t an, encoding_an;
+ bool use_for_encoding;
+ int r;
+
+ assert(netdev);
+ assert(v);
+ assert(filename);
+
+ ORDERED_HASHMAP_FOREACH(c, v->receive_channels_by_section, i) {
+ r = macsec_receive_channel_verify(c);
+ if (r < 0)
+ macsec_receive_channel_free(c);
+ }
+
+ an = 0;
+ use_for_encoding = false;
+ encoding_an = 0;
+ ORDERED_HASHMAP_FOREACH(a, v->transmit_associations_by_section, i) {
+ r = macsec_transmit_association_verify(a);
+ if (r < 0) {
+ macsec_transmit_association_free(a);
+ continue;
+ }
+
+ if (an >= MACSEC_MAX_ASSOCIATION_NUMBER) {
+ log_netdev_error(netdev,
+ "%s: Too many [MACsecTransmitAssociation] sections configured. "
+ "Ignoring [MACsecTransmitAssociation] section from line %u",
+ a->section->filename, a->section->line);
+ macsec_transmit_association_free(a);
+ continue;
+ }
+
+ a->sa.association_number = an++;
+
+ if (a->sa.use_for_encoding > 0) {
+ if (use_for_encoding) {
+ log_netdev_warning(netdev,
+ "%s: Multiple security associations are set to be used for transmit channel."
+ "Disabling UseForEncoding= in [MACsecTransmitAssociation] section from line %u",
+ a->section->filename, a->section->line);
+ a->sa.use_for_encoding = false;
+ } else {
+ encoding_an = a->sa.association_number;
+ use_for_encoding = true;
+ }
+ }
+ }
+
+ assert(encoding_an < MACSEC_MAX_ASSOCIATION_NUMBER);
+ v->encoding_an = encoding_an;
+
+ ORDERED_HASHMAP_FOREACH(n, v->receive_associations_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->receive_channels, macsec_receive_channel_free);
+ ordered_hashmap_free_with_destructor(t->receive_channels_by_section, macsec_receive_channel_free);
+ ordered_hashmap_free_with_destructor(t->transmit_associations_by_section, macsec_transmit_association_free);
+ ordered_hashmap_free_with_destructor(t->receive_associations_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 0000000000..2bd08ac500
--- /dev/null
+++ b/src/network/netdev/macsec.h
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include
+
+#include "in-addr-util.h"
+#include "netdev.h"
+#include "networkd-util.h"
+#include "sparse-endian.h"
+
+/* See the definition of MACSEC_NUM_AN in kernel's drivers/net/macsec.c */
+#define MACSEC_MAX_ASSOCIATION_NUMBER 4
+
+typedef struct MACsec MACsec;
+
+typedef union MACsecSCI {
+ uint64_t as_uint64;
+
+ struct {
+ struct ether_addr mac;
+ be16_t port;
+ } _packed_;
+} MACsecSCI;
+
+assert_cc(sizeof(MACsecSCI) == sizeof(uint64_t));
+
+typedef struct SecurityAssociation {
+ uint8_t association_number;
+ uint32_t packet_number;
+ uint8_t key_id[MACSEC_KEYID_LEN];
+ uint8_t *key;
+ uint32_t key_len;
+ char *key_file;
+ int activate;
+ int use_for_encoding;
+} SecurityAssociation;
+
+typedef struct TransmitAssociation {
+ MACsec *macsec;
+ NetworkConfigSection *section;
+
+ SecurityAssociation sa;
+} TransmitAssociation;
+
+typedef struct ReceiveAssociation {
+ MACsec *macsec;
+ NetworkConfigSection *section;
+
+ MACsecSCI sci;
+ SecurityAssociation sa;
+} ReceiveAssociation;
+
+typedef struct ReceiveChannel {
+ MACsec *macsec;
+ NetworkConfigSection *section;
+
+ MACsecSCI sci;
+ ReceiveAssociation *rxsa[MACSEC_MAX_ASSOCIATION_NUMBER];
+ unsigned n_rxsa;
+} ReceiveChannel;
+
+struct MACsec {
+ NetDev meta;
+
+ uint16_t port;
+ int encrypt;
+ uint8_t encoding_an;
+
+ OrderedHashmap *receive_channels;
+ OrderedHashmap *receive_channels_by_section;
+ OrderedHashmap *transmit_associations_by_section;
+ OrderedHashmap *receive_associations_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_packet_number);
+CONFIG_PARSER_PROTOTYPE(config_parse_macsec_key_id);
+CONFIG_PARSER_PROTOTYPE(config_parse_macsec_key);
+CONFIG_PARSER_PROTOTYPE(config_parse_macsec_key_file);
+CONFIG_PARSER_PROTOTYPE(config_parse_macsec_sa_activate);
+CONFIG_PARSER_PROTOTYPE(config_parse_macsec_use_for_encoding);
diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf
index 1a3d6caeb9..107827567e 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,158 +35,175 @@ 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.PresharedKeyFile, config_parse_wireguard_preshared_key_file, 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_macsec_port, 0, 0
+MACsec.Encrypt, config_parse_tristate, 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_packet_number, 0, 0
+MACsecTransmitAssociation.KeyId, config_parse_macsec_key_id, 0, 0
+MACsecTransmitAssociation.Key, config_parse_macsec_key, 0, 0
+MACsecTransmitAssociation.KeyFile, config_parse_macsec_key_file, 0, 0
+MACsecTransmitAssociation.Activate, config_parse_macsec_sa_activate, 0, 0
+MACsecTransmitAssociation.UseForEncoding, config_parse_macsec_use_for_encoding, 0, 0
+MACsecReceiveAssociation.Port, config_parse_macsec_port, 0, 0
+MACsecReceiveAssociation.MACAddress, config_parse_macsec_hw_address, 0, 0
+MACsecReceiveAssociation.PacketNumber, config_parse_macsec_packet_number, 0, 0
+MACsecReceiveAssociation.KeyId, config_parse_macsec_key_id, 0, 0
+MACsecReceiveAssociation.Key, config_parse_macsec_key, 0, 0
+MACsecReceiveAssociation.KeyFile, config_parse_macsec_key_file, 0, 0
+MACsecReceiveAssociation.Activate, config_parse_macsec_sa_activate, 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.PresharedKeyFile, config_parse_wireguard_preshared_key_file, 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 c1bcfc66e6..e138393514 100644
--- a/src/network/netdev/netdev.c
+++ b/src/network/netdev/netdev.c
@@ -14,6 +14,7 @@
#include "netdev/geneve.h"
#include "netdev/ipvlan.h"
#include "netdev/l2tp-tunnel.h"
+#include "netdev/macsec.h"
#include "netdev/macvlan.h"
#include "netdev/netdev.h"
#include "netdev/netdevsim.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 ad4dd2e2b0..29ecead029 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 47a9f7d808..0db59473ff 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -20,191 +20,192 @@ struct ConfigPerfItem;
%struct-type
%includes
%%
-Match.MACAddress, config_parse_hwaddrs, 0, offsetof(Network, match_mac)
-Match.Path, config_parse_strv, 0, offsetof(Network, match_path)
-Match.Driver, config_parse_strv, 0, offsetof(Network, match_driver)
-Match.Type, config_parse_strv, 0, offsetof(Network, match_type)
-Match.Name, config_parse_ifnames, 0, offsetof(Network, match_name)
-Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(Network, conditions)
-Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(Network, conditions)
-Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(Network, conditions)
-Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(Network, conditions)
-Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(Network, conditions)
-Link.MACAddress, config_parse_hwaddr, 0, offsetof(Network, mac)
-Link.MTUBytes, config_parse_mtu, AF_UNSPEC, offsetof(Network, mtu)
-Link.ARP, config_parse_tristate, 0, offsetof(Network, arp)
-Link.Multicast, config_parse_tristate, 0, offsetof(Network, multicast)
-Link.AllMulticast, config_parse_tristate, 0, offsetof(Network, allmulticast)
-Link.Unmanaged, config_parse_bool, 0, offsetof(Network, unmanaged)
-Link.RequiredForOnline, config_parse_required_for_online, 0, 0
-Network.Description, config_parse_string, 0, offsetof(Network, description)
-Network.Bridge, config_parse_ifname, 0, offsetof(Network, bridge_name)
-Network.Bond, config_parse_ifname, 0, offsetof(Network, bond_name)
-Network.VLAN, config_parse_stacked_netdev, NETDEV_KIND_VLAN, offsetof(Network, stacked_netdev_names)
-Network.MACVLAN, config_parse_stacked_netdev, NETDEV_KIND_MACVLAN, offsetof(Network, stacked_netdev_names)
-Network.MACVTAP, config_parse_stacked_netdev, NETDEV_KIND_MACVTAP, offsetof(Network, stacked_netdev_names)
-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.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)
-Network.DHCPServer, config_parse_bool, 0, offsetof(Network, dhcp_server)
-Network.LinkLocalAddressing, config_parse_address_family_boolean, 0, offsetof(Network, link_local)
-Network.IPv4LLRoute, config_parse_bool, 0, offsetof(Network, ipv4ll_route)
-Network.IPv6Token, config_parse_ipv6token, 0, offsetof(Network, ipv6_token)
-Network.LLDP, config_parse_lldp_mode, 0, offsetof(Network, lldp_mode)
-Network.EmitLLDP, config_parse_lldp_emit, 0, offsetof(Network, lldp_emit)
-Network.Address, config_parse_address, 0, 0
-Network.Gateway, config_parse_gateway, 0, 0
-Network.Domains, config_parse_domains, 0, 0
-Network.DNS, config_parse_dns, 0, 0
-Network.DNSDefaultRoute, config_parse_tristate, 0, offsetof(Network, dns_default_route)
-Network.LLMNR, config_parse_resolve_support, 0, offsetof(Network, llmnr)
-Network.MulticastDNS, config_parse_resolve_support, 0, offsetof(Network, mdns)
-Network.DNSOverTLS, config_parse_dns_over_tls_mode, 0, offsetof(Network, dns_over_tls_mode)
-Network.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Network, dnssec_mode)
-Network.DNSSECNegativeTrustAnchors, config_parse_dnssec_negative_trust_anchors, 0, 0
-Network.NTP, config_parse_ntp, 0, offsetof(Network, ntp)
-Network.IPForward, config_parse_address_family_boolean_with_kernel,0, offsetof(Network, ip_forward)
-Network.IPMasquerade, config_parse_bool, 0, offsetof(Network, ip_masquerade)
-Network.IPv6PrivacyExtensions, config_parse_ipv6_privacy_extensions, 0, offsetof(Network, ipv6_privacy_extensions)
-Network.IPv6AcceptRA, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra)
-Network.IPv6AcceptRouterAdvertisements, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra)
-Network.IPv6DuplicateAddressDetection, config_parse_int, 0, offsetof(Network, ipv6_dad_transmits)
-Network.IPv6HopLimit, config_parse_int, 0, offsetof(Network, ipv6_hop_limit)
-Network.IPv6ProxyNDP, config_parse_tristate, 0, offsetof(Network, ipv6_proxy_ndp)
-Network.IPv6MTUBytes, config_parse_mtu, AF_INET6, offsetof(Network, ipv6_mtu)
-Network.ActiveSlave, config_parse_bool, 0, offsetof(Network, active_slave)
-Network.PrimarySlave, config_parse_bool, 0, offsetof(Network, primary_slave)
-Network.IPv4ProxyARP, config_parse_tristate, 0, offsetof(Network, proxy_arp)
-Network.ProxyARP, config_parse_tristate, 0, offsetof(Network, proxy_arp)
-Network.IPv6ProxyNDPAddress, config_parse_ipv6_proxy_ndp_address, 0, 0
-Network.BindCarrier, config_parse_strv, 0, offsetof(Network, bind_carrier)
-Network.ConfigureWithoutCarrier, config_parse_bool, 0, offsetof(Network, configure_without_carrier)
-Network.IgnoreCarrierLoss, config_parse_bool, 0, offsetof(Network, ignore_carrier_loss)
-Address.Address, config_parse_address, 0, 0
-Address.Peer, config_parse_address, 0, 0
-Address.Broadcast, config_parse_broadcast, 0, 0
-Address.Label, config_parse_label, 0, 0
-Address.PreferredLifetime, config_parse_lifetime, 0, 0
-Address.HomeAddress, config_parse_address_flags, 0, 0
-Address.DuplicateAddressDetection, config_parse_address_flags, 0, 0
-Address.ManageTemporaryAddress, config_parse_address_flags, 0, 0
-Address.PrefixRoute, config_parse_address_flags, 0, 0
-Address.AutoJoin, config_parse_address_flags, 0, 0
-Address.Scope, config_parse_address_scope, 0, 0
-IPv6AddressLabel.Prefix, config_parse_address_label_prefix, 0, 0
-IPv6AddressLabel.Label, config_parse_address_label, 0, 0
-Neighbor.Address, config_parse_neighbor_address, 0, 0
-Neighbor.MACAddress, config_parse_neighbor_hwaddr, 0, 0
-RoutingPolicyRule.TypeOfService, config_parse_routing_policy_rule_tos, 0, 0
-RoutingPolicyRule.Priority, config_parse_routing_policy_rule_priority, 0, 0
-RoutingPolicyRule.Table, config_parse_routing_policy_rule_table, 0, 0
-RoutingPolicyRule.FirewallMark, config_parse_routing_policy_rule_fwmark_mask, 0, 0
-RoutingPolicyRule.From, config_parse_routing_policy_rule_prefix, 0, 0
-RoutingPolicyRule.To, config_parse_routing_policy_rule_prefix, 0, 0
-RoutingPolicyRule.IncomingInterface, config_parse_routing_policy_rule_device, 0, 0
-RoutingPolicyRule.OutgoingInterface, config_parse_routing_policy_rule_device, 0, 0
-RoutingPolicyRule.IPProtocol, config_parse_routing_policy_rule_ip_protocol, 0, 0
-RoutingPolicyRule.SourcePort, config_parse_routing_policy_rule_port_range, 0, 0
-RoutingPolicyRule.DestinationPort, config_parse_routing_policy_rule_port_range, 0, 0
-RoutingPolicyRule.InvertRule, config_parse_routing_policy_rule_invert, 0, 0
-Route.Gateway, config_parse_gateway, 0, 0
-Route.Destination, config_parse_destination, 0, 0
-Route.Source, config_parse_destination, 0, 0
-Route.Metric, config_parse_route_priority, 0, 0
-Route.Scope, config_parse_route_scope, 0, 0
-Route.PreferredSource, config_parse_preferred_src, 0, 0
-Route.Table, config_parse_route_table, 0, 0
-Route.MTUBytes, config_parse_route_mtu, AF_UNSPEC, 0
-Route.GatewayOnLink, config_parse_gateway_onlink, 0, 0
-Route.GatewayOnlink, config_parse_gateway_onlink, 0, 0
-Route.IPv6Preference, config_parse_ipv6_route_preference, 0, 0
-Route.Protocol, config_parse_route_protocol, 0, 0
-Route.Type, config_parse_route_type, 0, 0
-Route.InitialCongestionWindow, config_parse_tcp_window, 0, 0
-Route.InitialAdvertisedReceiveWindow, config_parse_tcp_window, 0, 0
-Route.QuickAck, config_parse_quickack, 0, 0
-DHCP.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier)
-DHCP.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns)
-DHCP.UseNTP, config_parse_bool, 0, offsetof(Network, dhcp_use_ntp)
-DHCP.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu)
-DHCP.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname)
-DHCP.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains)
-DHCP.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_use_routes)
-DHCP.Anonymize, config_parse_bool, 0, offsetof(Network, dhcp_anonymize)
-DHCP.SendHostname, config_parse_bool, 0, offsetof(Network, dhcp_send_hostname)
-DHCP.Hostname, config_parse_hostname, 0, offsetof(Network, dhcp_hostname)
-DHCP.RequestBroadcast, config_parse_bool, 0, offsetof(Network, dhcp_broadcast)
-DHCP.CriticalConnection, config_parse_bool, 0, offsetof(Network, dhcp_critical)
-DHCP.VendorClassIdentifier, config_parse_string, 0, offsetof(Network, dhcp_vendor_class_identifier)
-DHCP.UserClass, config_parse_dhcp_user_class, 0, offsetof(Network, dhcp_user_class)
-DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Network, duid)
-DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, duid)
-DHCP.RouteMetric, config_parse_unsigned, 0, offsetof(Network, dhcp_route_metric)
-DHCP.RouteTable, config_parse_section_route_table, 0, 0
-DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone)
-DHCP.IAID, config_parse_iaid, 0, 0
-DHCP.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port)
-DHCP.RapidCommit, config_parse_bool, 0, offsetof(Network, rapid_commit)
-DHCP.ForceDHCPv6PDOtherInformation, config_parse_bool, 0, offsetof(Network, dhcp6_force_pd_other_information)
-IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_autonomous_prefix)
-IPv6AcceptRA.UseOnLinkPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_onlink_prefix)
-IPv6AcceptRA.UseDNS, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_dns)
-IPv6AcceptRA.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, ipv6_accept_ra_use_domains)
-IPv6AcceptRA.RouteTable, config_parse_section_route_table, 0, 0
-DHCPServer.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec)
-DHCPServer.DefaultLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_default_lease_time_usec)
-DHCPServer.EmitDNS, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_dns)
-DHCPServer.DNS, config_parse_dhcp_server_dns, 0, 0
-DHCPServer.EmitNTP, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_ntp)
-DHCPServer.NTP, config_parse_dhcp_server_ntp, 0, 0
-DHCPServer.EmitRouter, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_router)
-DHCPServer.EmitTimezone, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_timezone)
-DHCPServer.Timezone, config_parse_timezone, 0, offsetof(Network, dhcp_server_timezone)
-DHCPServer.PoolOffset, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_offset)
-DHCPServer.PoolSize, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_size)
-Bridge.Cost, config_parse_uint32, 0, offsetof(Network, cost)
-Bridge.UseBPDU, config_parse_tristate, 0, offsetof(Network, use_bpdu)
-Bridge.HairPin, config_parse_tristate, 0, offsetof(Network, hairpin)
-Bridge.FastLeave, config_parse_tristate, 0, offsetof(Network, fast_leave)
-Bridge.AllowPortToBeRoot, config_parse_tristate, 0, offsetof(Network, allow_port_to_be_root)
-Bridge.UnicastFlood, config_parse_tristate, 0, offsetof(Network, unicast_flood)
-Bridge.MulticastFlood, config_parse_tristate, 0, offsetof(Network, multicast_flood)
-Bridge.MulticastToUnicast, config_parse_tristate, 0, offsetof(Network, multicast_to_unicast)
-Bridge.NeighborSuppression, config_parse_tristate, 0, offsetof(Network, neighbor_suppression)
-Bridge.Learning, config_parse_tristate, 0, offsetof(Network, learning)
-Bridge.Priority, config_parse_bridge_port_priority, 0, offsetof(Network, priority)
-BridgeFDB.MACAddress, config_parse_fdb_hwaddr, 0, 0
-BridgeFDB.VLANId, config_parse_fdb_vlan_id, 0, 0
-BridgeVLAN.PVID, config_parse_brvlan_pvid, 0, 0
-BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0
-BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0
-Network.IPv6PrefixDelegation, config_parse_router_prefix_delegation, 0, 0
-IPv6PrefixDelegation.RouterLifetimeSec, config_parse_sec, 0, offsetof(Network, router_lifetime_usec)
-IPv6PrefixDelegation.Managed, config_parse_bool, 0, offsetof(Network, router_managed)
-IPv6PrefixDelegation.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information)
-IPv6PrefixDelegation.RouterPreference, config_parse_router_preference, 0, 0
-IPv6PrefixDelegation.EmitDNS, config_parse_bool, 0, offsetof(Network, router_emit_dns)
-IPv6PrefixDelegation.DNS, config_parse_radv_dns, 0, 0
-IPv6PrefixDelegation.EmitDomains, config_parse_bool, 0, offsetof(Network, router_emit_domains)
-IPv6PrefixDelegation.Domains, config_parse_radv_search_domains, 0, 0
-IPv6PrefixDelegation.DNSLifetimeSec, config_parse_sec, 0, offsetof(Network, router_dns_lifetime_usec)
-IPv6Prefix.Prefix, config_parse_prefix, 0, 0
-IPv6Prefix.OnLink, config_parse_prefix_flags, 0, 0
-IPv6Prefix.AddressAutoconfiguration, config_parse_prefix_flags, 0, 0
-IPv6Prefix.ValidLifetimeSec, config_parse_prefix_lifetime, 0, 0
-IPv6Prefix.PreferredLifetimeSec, config_parse_prefix_lifetime, 0, 0
-CAN.BitRate, config_parse_si_size, 0, offsetof(Network, can_bitrate)
-CAN.SamplePoint, config_parse_permille, 0, offsetof(Network, can_sample_point)
-CAN.RestartSec, config_parse_sec, 0, offsetof(Network, can_restart_us)
-CAN.TripleSampling, config_parse_tristate, 0, offsetof(Network, can_triple_sampling)
+Match.MACAddress, config_parse_hwaddrs, 0, offsetof(Network, match_mac)
+Match.Path, config_parse_strv, 0, offsetof(Network, match_path)
+Match.Driver, config_parse_strv, 0, offsetof(Network, match_driver)
+Match.Type, config_parse_strv, 0, offsetof(Network, match_type)
+Match.Name, config_parse_ifnames, 0, offsetof(Network, match_name)
+Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(Network, conditions)
+Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(Network, conditions)
+Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(Network, conditions)
+Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(Network, conditions)
+Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(Network, conditions)
+Link.MACAddress, config_parse_hwaddr, 0, offsetof(Network, mac)
+Link.MTUBytes, config_parse_mtu, AF_UNSPEC, offsetof(Network, mtu)
+Link.ARP, config_parse_tristate, 0, offsetof(Network, arp)
+Link.Multicast, config_parse_tristate, 0, offsetof(Network, multicast)
+Link.AllMulticast, config_parse_tristate, 0, offsetof(Network, allmulticast)
+Link.Unmanaged, config_parse_bool, 0, offsetof(Network, unmanaged)
+Link.RequiredForOnline, config_parse_required_for_online, 0, 0
+Network.Description, config_parse_string, 0, offsetof(Network, description)
+Network.Bridge, config_parse_ifname, 0, offsetof(Network, bridge_name)
+Network.Bond, config_parse_ifname, 0, offsetof(Network, bond_name)
+Network.VLAN, config_parse_stacked_netdev, NETDEV_KIND_VLAN, offsetof(Network, stacked_netdev_names)
+Network.MACVLAN, config_parse_stacked_netdev, NETDEV_KIND_MACVLAN, offsetof(Network, stacked_netdev_names)
+Network.MACVTAP, config_parse_stacked_netdev, NETDEV_KIND_MACVTAP, offsetof(Network, stacked_netdev_names)
+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)
+Network.DHCPServer, config_parse_bool, 0, offsetof(Network, dhcp_server)
+Network.LinkLocalAddressing, config_parse_address_family_boolean, 0, offsetof(Network, link_local)
+Network.IPv4LLRoute, config_parse_bool, 0, offsetof(Network, ipv4ll_route)
+Network.IPv6Token, config_parse_ipv6token, 0, offsetof(Network, ipv6_token)
+Network.LLDP, config_parse_lldp_mode, 0, offsetof(Network, lldp_mode)
+Network.EmitLLDP, config_parse_lldp_emit, 0, offsetof(Network, lldp_emit)
+Network.Address, config_parse_address, 0, 0
+Network.Gateway, config_parse_gateway, 0, 0
+Network.Domains, config_parse_domains, 0, 0
+Network.DNS, config_parse_dns, 0, 0
+Network.DNSDefaultRoute, config_parse_tristate, 0, offsetof(Network, dns_default_route)
+Network.LLMNR, config_parse_resolve_support, 0, offsetof(Network, llmnr)
+Network.MulticastDNS, config_parse_resolve_support, 0, offsetof(Network, mdns)
+Network.DNSOverTLS, config_parse_dns_over_tls_mode, 0, offsetof(Network, dns_over_tls_mode)
+Network.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Network, dnssec_mode)
+Network.DNSSECNegativeTrustAnchors, config_parse_dnssec_negative_trust_anchors, 0, 0
+Network.NTP, config_parse_ntp, 0, offsetof(Network, ntp)
+Network.IPForward, config_parse_address_family_boolean_with_kernel, 0, offsetof(Network, ip_forward)
+Network.IPMasquerade, config_parse_bool, 0, offsetof(Network, ip_masquerade)
+Network.IPv6PrivacyExtensions, config_parse_ipv6_privacy_extensions, 0, offsetof(Network, ipv6_privacy_extensions)
+Network.IPv6AcceptRA, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra)
+Network.IPv6AcceptRouterAdvertisements, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra)
+Network.IPv6DuplicateAddressDetection, config_parse_int, 0, offsetof(Network, ipv6_dad_transmits)
+Network.IPv6HopLimit, config_parse_int, 0, offsetof(Network, ipv6_hop_limit)
+Network.IPv6ProxyNDP, config_parse_tristate, 0, offsetof(Network, ipv6_proxy_ndp)
+Network.IPv6MTUBytes, config_parse_mtu, AF_INET6, offsetof(Network, ipv6_mtu)
+Network.ActiveSlave, config_parse_bool, 0, offsetof(Network, active_slave)
+Network.PrimarySlave, config_parse_bool, 0, offsetof(Network, primary_slave)
+Network.IPv4ProxyARP, config_parse_tristate, 0, offsetof(Network, proxy_arp)
+Network.ProxyARP, config_parse_tristate, 0, offsetof(Network, proxy_arp)
+Network.IPv6ProxyNDPAddress, config_parse_ipv6_proxy_ndp_address, 0, 0
+Network.BindCarrier, config_parse_strv, 0, offsetof(Network, bind_carrier)
+Network.ConfigureWithoutCarrier, config_parse_bool, 0, offsetof(Network, configure_without_carrier)
+Network.IgnoreCarrierLoss, config_parse_bool, 0, offsetof(Network, ignore_carrier_loss)
+Address.Address, config_parse_address, 0, 0
+Address.Peer, config_parse_address, 0, 0
+Address.Broadcast, config_parse_broadcast, 0, 0
+Address.Label, config_parse_label, 0, 0
+Address.PreferredLifetime, config_parse_lifetime, 0, 0
+Address.HomeAddress, config_parse_address_flags, 0, 0
+Address.DuplicateAddressDetection, config_parse_address_flags, 0, 0
+Address.ManageTemporaryAddress, config_parse_address_flags, 0, 0
+Address.PrefixRoute, config_parse_address_flags, 0, 0
+Address.AutoJoin, config_parse_address_flags, 0, 0
+Address.Scope, config_parse_address_scope, 0, 0
+IPv6AddressLabel.Prefix, config_parse_address_label_prefix, 0, 0
+IPv6AddressLabel.Label, config_parse_address_label, 0, 0
+Neighbor.Address, config_parse_neighbor_address, 0, 0
+Neighbor.MACAddress, config_parse_neighbor_hwaddr, 0, 0
+RoutingPolicyRule.TypeOfService, config_parse_routing_policy_rule_tos, 0, 0
+RoutingPolicyRule.Priority, config_parse_routing_policy_rule_priority, 0, 0
+RoutingPolicyRule.Table, config_parse_routing_policy_rule_table, 0, 0
+RoutingPolicyRule.FirewallMark, config_parse_routing_policy_rule_fwmark_mask, 0, 0
+RoutingPolicyRule.From, config_parse_routing_policy_rule_prefix, 0, 0
+RoutingPolicyRule.To, config_parse_routing_policy_rule_prefix, 0, 0
+RoutingPolicyRule.IncomingInterface, config_parse_routing_policy_rule_device, 0, 0
+RoutingPolicyRule.OutgoingInterface, config_parse_routing_policy_rule_device, 0, 0
+RoutingPolicyRule.IPProtocol, config_parse_routing_policy_rule_ip_protocol, 0, 0
+RoutingPolicyRule.SourcePort, config_parse_routing_policy_rule_port_range, 0, 0
+RoutingPolicyRule.DestinationPort, config_parse_routing_policy_rule_port_range, 0, 0
+RoutingPolicyRule.InvertRule, config_parse_routing_policy_rule_invert, 0, 0
+Route.Gateway, config_parse_gateway, 0, 0
+Route.Destination, config_parse_destination, 0, 0
+Route.Source, config_parse_destination, 0, 0
+Route.Metric, config_parse_route_priority, 0, 0
+Route.Scope, config_parse_route_scope, 0, 0
+Route.PreferredSource, config_parse_preferred_src, 0, 0
+Route.Table, config_parse_route_table, 0, 0
+Route.MTUBytes, config_parse_route_mtu, AF_UNSPEC, 0
+Route.GatewayOnLink, config_parse_gateway_onlink, 0, 0
+Route.GatewayOnlink, config_parse_gateway_onlink, 0, 0
+Route.IPv6Preference, config_parse_ipv6_route_preference, 0, 0
+Route.Protocol, config_parse_route_protocol, 0, 0
+Route.Type, config_parse_route_type, 0, 0
+Route.InitialCongestionWindow, config_parse_tcp_window, 0, 0
+Route.InitialAdvertisedReceiveWindow, config_parse_tcp_window, 0, 0
+Route.QuickAck, config_parse_quickack, 0, 0
+DHCP.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier)
+DHCP.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns)
+DHCP.UseNTP, config_parse_bool, 0, offsetof(Network, dhcp_use_ntp)
+DHCP.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu)
+DHCP.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname)
+DHCP.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains)
+DHCP.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_use_routes)
+DHCP.Anonymize, config_parse_bool, 0, offsetof(Network, dhcp_anonymize)
+DHCP.SendHostname, config_parse_bool, 0, offsetof(Network, dhcp_send_hostname)
+DHCP.Hostname, config_parse_hostname, 0, offsetof(Network, dhcp_hostname)
+DHCP.RequestBroadcast, config_parse_bool, 0, offsetof(Network, dhcp_broadcast)
+DHCP.CriticalConnection, config_parse_bool, 0, offsetof(Network, dhcp_critical)
+DHCP.VendorClassIdentifier, config_parse_string, 0, offsetof(Network, dhcp_vendor_class_identifier)
+DHCP.UserClass, config_parse_dhcp_user_class, 0, offsetof(Network, dhcp_user_class)
+DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Network, duid)
+DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Network, duid)
+DHCP.RouteMetric, config_parse_unsigned, 0, offsetof(Network, dhcp_route_metric)
+DHCP.RouteTable, config_parse_section_route_table, 0, 0
+DHCP.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone)
+DHCP.IAID, config_parse_iaid, 0, 0
+DHCP.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port)
+DHCP.RapidCommit, config_parse_bool, 0, offsetof(Network, rapid_commit)
+DHCP.ForceDHCPv6PDOtherInformation, config_parse_bool, 0, offsetof(Network, dhcp6_force_pd_other_information)
+IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_autonomous_prefix)
+IPv6AcceptRA.UseOnLinkPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_onlink_prefix)
+IPv6AcceptRA.UseDNS, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_dns)
+IPv6AcceptRA.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, ipv6_accept_ra_use_domains)
+IPv6AcceptRA.RouteTable, config_parse_section_route_table, 0, 0
+DHCPServer.MaxLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_max_lease_time_usec)
+DHCPServer.DefaultLeaseTimeSec, config_parse_sec, 0, offsetof(Network, dhcp_server_default_lease_time_usec)
+DHCPServer.EmitDNS, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_dns)
+DHCPServer.DNS, config_parse_dhcp_server_dns, 0, 0
+DHCPServer.EmitNTP, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_ntp)
+DHCPServer.NTP, config_parse_dhcp_server_ntp, 0, 0
+DHCPServer.EmitRouter, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_router)
+DHCPServer.EmitTimezone, config_parse_bool, 0, offsetof(Network, dhcp_server_emit_timezone)
+DHCPServer.Timezone, config_parse_timezone, 0, offsetof(Network, dhcp_server_timezone)
+DHCPServer.PoolOffset, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_offset)
+DHCPServer.PoolSize, config_parse_uint32, 0, offsetof(Network, dhcp_server_pool_size)
+Bridge.Cost, config_parse_uint32, 0, offsetof(Network, cost)
+Bridge.UseBPDU, config_parse_tristate, 0, offsetof(Network, use_bpdu)
+Bridge.HairPin, config_parse_tristate, 0, offsetof(Network, hairpin)
+Bridge.FastLeave, config_parse_tristate, 0, offsetof(Network, fast_leave)
+Bridge.AllowPortToBeRoot, config_parse_tristate, 0, offsetof(Network, allow_port_to_be_root)
+Bridge.UnicastFlood, config_parse_tristate, 0, offsetof(Network, unicast_flood)
+Bridge.MulticastFlood, config_parse_tristate, 0, offsetof(Network, multicast_flood)
+Bridge.MulticastToUnicast, config_parse_tristate, 0, offsetof(Network, multicast_to_unicast)
+Bridge.NeighborSuppression, config_parse_tristate, 0, offsetof(Network, neighbor_suppression)
+Bridge.Learning, config_parse_tristate, 0, offsetof(Network, learning)
+Bridge.Priority, config_parse_bridge_port_priority, 0, offsetof(Network, priority)
+BridgeFDB.MACAddress, config_parse_fdb_hwaddr, 0, 0
+BridgeFDB.VLANId, config_parse_fdb_vlan_id, 0, 0
+BridgeVLAN.PVID, config_parse_brvlan_pvid, 0, 0
+BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0
+BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0
+Network.IPv6PrefixDelegation, config_parse_router_prefix_delegation, 0, 0
+IPv6PrefixDelegation.RouterLifetimeSec, config_parse_sec, 0, offsetof(Network, router_lifetime_usec)
+IPv6PrefixDelegation.Managed, config_parse_bool, 0, offsetof(Network, router_managed)
+IPv6PrefixDelegation.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information)
+IPv6PrefixDelegation.RouterPreference, config_parse_router_preference, 0, 0
+IPv6PrefixDelegation.EmitDNS, config_parse_bool, 0, offsetof(Network, router_emit_dns)
+IPv6PrefixDelegation.DNS, config_parse_radv_dns, 0, 0
+IPv6PrefixDelegation.EmitDomains, config_parse_bool, 0, offsetof(Network, router_emit_domains)
+IPv6PrefixDelegation.Domains, config_parse_radv_search_domains, 0, 0
+IPv6PrefixDelegation.DNSLifetimeSec, config_parse_sec, 0, offsetof(Network, router_dns_lifetime_usec)
+IPv6Prefix.Prefix, config_parse_prefix, 0, 0
+IPv6Prefix.OnLink, config_parse_prefix_flags, 0, 0
+IPv6Prefix.AddressAutoconfiguration, config_parse_prefix_flags, 0, 0
+IPv6Prefix.ValidLifetimeSec, config_parse_prefix_lifetime, 0, 0
+IPv6Prefix.PreferredLifetimeSec, config_parse_prefix_lifetime, 0, 0
+CAN.BitRate, config_parse_si_size, 0, offsetof(Network, can_bitrate)
+CAN.SamplePoint, config_parse_permille, 0, offsetof(Network, can_sample_point)
+CAN.RestartSec, config_parse_sec, 0, offsetof(Network, can_restart_us)
+CAN.TripleSampling, config_parse_tristate, 0, offsetof(Network, can_triple_sampling)
/* backwards compatibility: do not add new entries to this section */
-Network.IPv4LL, config_parse_ipv4ll, 0, offsetof(Network, link_local)
-DHCPv4.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns)
-DHCPv4.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu)
-DHCPv4.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname)
-DHCP.UseDomainName, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains)
-DHCPv4.UseDomainName, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains)
-DHCPv4.CriticalConnection, config_parse_bool, 0, offsetof(Network, dhcp_critical)
+Network.IPv4LL, config_parse_ipv4ll, 0, offsetof(Network, link_local)
+DHCPv4.UseDNS, config_parse_bool, 0, offsetof(Network, dhcp_use_dns)
+DHCPv4.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu)
+DHCPv4.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname)
+DHCP.UseDomainName, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains)
+DHCPv4.UseDomainName, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains)
+DHCPv4.CriticalConnection, config_parse_bool, 0, offsetof(Network, dhcp_critical)
diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c
index 2c8896530a..52cfc4bec1 100644
--- a/src/network/networkd-network.c
+++ b/src/network/networkd-network.c
@@ -687,7 +687,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 804fe9f03e..d327b27308 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 7da3955af6..128f8b6341 100644
--- a/test/fuzz/fuzz-netdev-parser/directives.netdev
+++ b/test/fuzz/fuzz-netdev-parser/directives.netdev
@@ -174,3 +174,24 @@ SessionId=
PeerSessionId=
Layer2SpecificHeader=
Name=
+[MACSEC]
+Port=
+Encrypt=
+[MACsecReceiveAssociation]
+Port=
+MACAddress=
+PacketNumber=
+KeyId=
+Key=
+KeyFile=
+Activate=
+UseForEncoding=
+[MACsecReceiveChannel]
+Port=
+MACAddress=
+[MACsecTransmitAssociation]
+PacketNumber=
+KeyId=
+Key=
+KeyFile=
+Activate=
diff --git a/test/fuzz/fuzz-network-parser/directives.network b/test/fuzz/fuzz-network-parser/directives.network
index ddc60a9cbb..cd2031150f 100644
--- a/test/fuzz/fuzz-network-parser/directives.network
+++ b/test/fuzz/fuzz-network-parser/directives.network
@@ -111,6 +111,7 @@ IPv6Token=
Description=
VXLAN=
L2TP=
+MACsec=
LinkLocalAddressing=
ConfigureWithoutCarrier=
NTP=
diff --git a/test/test-network/conf/25-macsec.key b/test/test-network/conf/25-macsec.key
new file mode 100644
index 0000000000..56c51fa9f7
--- /dev/null
+++ b/test/test-network/conf/25-macsec.key
@@ -0,0 +1 @@
+85858585858585858585858585858585
diff --git a/test/test-network/conf/25-macsec.netdev b/test/test-network/conf/25-macsec.netdev
new file mode 100644
index 0000000000..9b603ca039
--- /dev/null
+++ b/test/test-network/conf/25-macsec.netdev
@@ -0,0 +1,68 @@
+[NetDev]
+Name=macsec99
+Kind=macsec
+
+[MACsec]
+Port=11
+Encrypt=yes
+
+[MACsecTransmitAssociation]
+PacketNumber=1024
+KeyId=01
+Key=81818181818181818181818181818181
+Activate=yes
+
+[MACsecTransmitAssociation]
+PacketNumber=512
+KeyId=0203
+Key=82828282828282828282828282828282
+UseForEncoding=yes
+
+[MACsecReceiveChannel]
+Port=2
+MACAddress=8c:16:45:6c:83:a9
+
+[MACsecReceiveAssociation]
+Port=2
+MACAddress=8c:16:45:6c:83:a9
+PacketNumber=16
+KeyId=020304
+Key=83838383838383838383838383838383
+
+[MACsecReceiveAssociation]
+Port=256
+MACAddress=c6:19:52:8f:e6:a0
+PacketNumber=32
+KeyId=02030405
+Key=84848484848484848484848484848484
+Activate=yes
+
+[MACsecReceiveAssociation]
+Port=256
+MACAddress=c6:19:52:8f:e6:a0
+PacketNumber=128
+KeyId=0203040506
+KeyFile=/run/systemd/network/25-macsec.key
+Activate=yes
+
+[MACsecReceiveAssociation]
+Port=256
+MACAddress=c6:19:52:8f:e6:a0
+KeyId=020304050607
+Key=86868686868686868686868686868686
+Activate=no
+
+[MACsecReceiveAssociation]
+Port=256
+MACAddress=c6:19:52:8f:e6:a0
+KeyId=02030405060708
+Key=87878787878787878787878787878787
+Activate=no
+
+[MACsecReceiveAssociation]
+# This section should be dropped.
+Port=256
+MACAddress=c6:19:52:8f:e6:a0
+KeyId=0203040506070809
+Key=88888888888888888888888888888888
+Activate=no
diff --git a/test/test-network/conf/25-macsec.network b/test/test-network/conf/25-macsec.network
new file mode 100644
index 0000000000..7037048a45
--- /dev/null
+++ b/test/test-network/conf/25-macsec.network
@@ -0,0 +1,6 @@
+[Match]
+Name=macsec99
+
+[Network]
+IPv6AcceptRA=no
+Address=10.1.2.3/16
diff --git a/test/test-network/conf/macsec.network b/test/test-network/conf/macsec.network
new file mode 100644
index 0000000000..d1360a59eb
--- /dev/null
+++ b/test/test-network/conf/macsec.network
@@ -0,0 +1,9 @@
+[Match]
+Name=dummy98
+
+[Link]
+MACAddress=00:50:56:c0:00:19
+
+[Network]
+IPv6AcceptRA=no
+MACsec=macsec99
diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py
index b44cfe8d45..d9db9c1657 100755
--- a/test/test-network/systemd-networkd-tests.py
+++ b/test/test-network/systemd-networkd-tests.py
@@ -290,6 +290,9 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
'25-ipip-tunnel.netdev',
'25-ipvlan.netdev',
'25-isatap-tunnel.netdev',
+ '25-macsec.key',
+ '25-macsec.netdev',
+ '25-macsec.network',
'25-sit-tunnel-local-any.netdev',
'25-sit-tunnel-remote-any.netdev',
'25-sit-tunnel.netdev',
@@ -322,6 +325,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
'ipip.network',
'ipvlan.network',
'isatap.network',
+ 'macsec.network',
'macvlan.network',
'macvtap.network',
'sit.network',
@@ -875,6 +879,35 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
self.assertRegex(output, 'remcsumrx')
self.assertRegex(output, 'gbp')
+ def test_macsec(self):
+ self.copy_unit_to_networkd_unit_path('25-macsec.netdev', '25-macsec.network', '25-macsec.key',
+ 'macsec.network', '12-dummy.netdev')
+ self.start_networkd(0)
+
+ self.wait_online(['dummy98:degraded', 'macsec99:routable'])
+
+ output = subprocess.check_output(['ip', '-d', 'link', 'show', 'macsec99']).rstrip().decode('utf-8')
+ print(output)
+ self.assertRegex(output, 'macsec99@dummy98')
+ self.assertRegex(output, 'macsec sci [0-9a-f]*000b')
+ self.assertRegex(output, 'encrypt on')
+
+ output = subprocess.check_output(['ip', 'macsec', 'show', 'macsec99']).rstrip().decode('utf-8')
+ print(output)
+ self.assertRegex(output, 'encrypt on')
+ self.assertRegex(output, 'TXSC: [0-9a-f]*000b on SA 1')
+ self.assertRegex(output, '0: PN [0-9]*, state on, key 01000000000000000000000000000000')
+ self.assertRegex(output, '1: PN [0-9]*, state on, key 02030000000000000000000000000000')
+ self.assertRegex(output, 'RXSC: c619528fe6a00100, state on')
+ self.assertRegex(output, '0: PN [0-9]*, state on, key 02030405000000000000000000000000')
+ self.assertRegex(output, '1: PN [0-9]*, state on, key 02030405060000000000000000000000')
+ self.assertRegex(output, '2: PN [0-9]*, state off, key 02030405060700000000000000000000')
+ self.assertRegex(output, '3: PN [0-9]*, state off, key 02030405060708000000000000000000')
+ self.assertNotRegex(output, 'key 02030405067080900000000000000000')
+ self.assertRegex(output, 'RXSC: 8c16456c83a90002, state on')
+ self.assertRegex(output, '0: PN [0-9]*, state off, key 02030400000000000000000000000000')
+
+
class NetworkdL2TPTests(unittest.TestCase, Utilities):
links =[