]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network: Introduce MACsec
authorSusant Sahani <ssahani@redhat.com>
Wed, 3 Apr 2019 11:27:36 +0000 (16:57 +0530)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 12 Apr 2019 01:12:41 +0000 (10:12 +0900)
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, including denial of service, intrusion,
man-in-the-middle, masquerading, passive wiretapping, and playback attacks.

Closes #5754

17 files changed:
man/systemd.netdev.xml
man/systemd.network.xml
src/libsystemd/sd-netlink/generic-netlink.c
src/libsystemd/sd-netlink/netlink-message.c
src/libsystemd/sd-netlink/netlink-types.c
src/libsystemd/sd-netlink/netlink-types.h
src/network/meson.build
src/network/netdev/macsec.c [new file with mode: 0644]
src/network/netdev/macsec.h [new file with mode: 0644]
src/network/netdev/netdev-gperf.gperf
src/network/netdev/netdev.c
src/network/netdev/netdev.h
src/network/networkd-network-gperf.gperf
src/network/networkd-network.c
src/systemd/sd-netlink.h
test/fuzz/fuzz-netdev-parser/directives.netdev
test/fuzz/fuzz-network-parser/directives.network

index 1836b5fe00e3a91e9af449675f5cc2844dc5f491..9b131a16b67068564c4e1266a6f37ea6277be5c5 100644 (file)
           <row><entry><varname>l2tp</varname></entry>
           <entry>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</entry></row>
 
+          <row><entry><varname>macsec</varname></entry>
+          <entry>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.</entry></row>
+
           <row><entry><varname>vrf</varname></entry>
           <entry>A Virtual Routing and Forwarding (<ulink url="https://www.kernel.org/doc/Documentation/networking/vrf.txt">VRF</ulink>) interface to create separate routing and forwarding domains.</entry></row>
 
       </varlistentry>
     </variablelist>
   </refsect1>
+  <refsect1>
+    <title>[MACsec] Section Options</title>
+    <para>The <literal>[MACsec]</literal> section only applies for network devices of kind
+    <literal>macsec</literal>, and accepts the following keys:</para>
+
+    <variablelist class='network-directives'>
+      <varlistentry>
+        <term><varname>Port=</varname></term>
+        <listitem>
+          <para>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.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><varname>Encrypt=</varname></term>
+        <listitem>
+          <para>Takes a boolean. When true, enable encryption. Defaults to unset.</para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+  <refsect1>
+    <title>[MACsecReceiveChannel] Section Options</title>
+    <para>The <literal>[MACsecReceiveChannel]</literal> section only applies for network devices of
+    kind <literal>macsec</literal>, and accepts the following keys:</para>
+
+    <variablelist class='network-directives'>
+      <varlistentry>
+        <term><varname>Port=</varname></term>
+        <listitem>
+          <para>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.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><varname>MACAddress=</varname></term>
+        <listitem>
+          <para>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.</para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+  <refsect1>
+    <title>[MACsecTransmitAssociation] Section Options</title>
+    <para>The <literal>[MACsecTransmitAssociation]</literal> section only applies for network devices
+    of kind <literal>macsec</literal>, and accepts the following keys:</para>
+
+    <variablelist class='network-directives'>
+      <varlistentry>
+        <term><varname>PacketNumber=</varname></term>
+        <listitem>
+          <para>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.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><varname>KeyId=</varname></term>
+        <listitem>
+          <para>Specifies the identification for the key. Takes a number between 0-255. This option
+          is compulsory, and is not set by default.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><varname>Key=</varname></term>
+        <listitem>
+          <para>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
+          <literal>dffafc8d7b9a43d5b9a3dfbbf6a30c16</literal>.</para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+  <refsect1>
+    <title>[MACsecReceiveAssociation] Section Options</title>
+    <para>The <literal>[MACsecReceiveAssociation]</literal> section only applies for
+    network devices of kind <literal>macsec</literal>, and accepts the
+    following keys:</para>
+
+    <variablelist class='network-directives'>
+      <varlistentry>
+        <term><varname>Port=</varname></term>
+        <listitem>
+          <para>Accepts the same key in <literal>[MACsecReceiveChannel]</literal> section.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><varname>MACAddress=</varname></term>
+        <listitem>
+          <para>Accepts the same key in <literal>[MACsecReceiveChannel]</literal> section.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><varname>PacketNumber=</varname></term>
+        <listitem>
+          <para>Accepts the same key in <literal>[MACsecTransmitAssociation]</literal> section.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><varname>KeyId=</varname></term>
+        <listitem>
+          <para>Accepts the same key in <literal>[MACsecTransmitAssociation]</literal> section.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><varname>Key=</varname></term>
+        <listitem>
+          <para>Accepts the same key in <literal>[MACsecTransmitAssociation]</literal> section.</para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
   <refsect1>
     <title>[Tunnel] Section Options</title>
 
index 41270847031e78c67e430617d8ae8f167c381369..2d8eeee88fb37c1c0a83a16890e88932eb95c8fe 100644 (file)
             This option may be specified more than once.</para>
           </listitem>
         </varlistentry>
+        <varlistentry>
+          <term><varname>MACsec=</varname></term>
+          <listitem>
+            <para>The name of a MACsec device to create on the link. See
+            <citerefentry><refentrytitle>systemd.netdev</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+            This option may be specified more than once.</para>
+          </listitem>
+        </varlistentry>
       <varlistentry>
         <term><varname>ActiveSlave=</varname></term>
         <listitem>
index 384072e881451183b5db9bcdb03f344743341a0c..473d8670a93b72dd3c28d759fc2981c3986aa34c 100644 (file)
@@ -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) {
index 0dcc53be55a153e5d90ce1966c81cf83feebc2cc..68b232b7d42242929285ad631bfa8d0aae42fca4 100644 (file)
@@ -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;
 
index 0c67d1c68f55c378311ec8a0aa53c12beef70bf3..118f319a2082799ac17feb08c6609eb2a3825ed4 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/if_addrlabel.h>
 #include <linux/if_bridge.h>
 #include <linux/if_link.h>
+#include <linux/if_macsec.h>
 #include <linux/if_tunnel.h>
 #include <linux/l2tp.h>
 #include <linux/veth.h>
@@ -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 = {
index b84fa4762b074ef8e62ec7b1b02249b75483a369..a2b3087d1596158dd748e56b55fe629724ff1c8a 100644 (file)
@@ -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;
index c95e7503069a91e4cd763c90d0474eb62e56a59a..2acbe858bb5a52b44937a4ad87af405c5c77893a 100644 (file)
@@ -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 (file)
index 0000000..1581008
--- /dev/null
@@ -0,0 +1,903 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <arpa/inet.h>
+#include <linux/if_ether.h>
+#include <linux/if_macsec.h>
+#include <linux/genetlink.h>
+
+#include "conf-parser.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 "sd-netlink.h"
+#include "socket-util.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "util.h"
+
+static void macsec_receive_association_free(ReceiveAssociation *c) {
+        if (!c)
+                return;
+
+        if (c->macsec && c->section)
+                ordered_hashmap_remove(c->macsec->receive_associations_by_section, c->section);
+
+        network_config_section_free(c->section);
+        free(c->sa.key);
+
+        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),
+        };
+
+        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 && 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_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;
+        }
+
+        c = new(ReceiveChannel, 1);
+        if (!c)
+                return -ENOMEM;
+
+        *c = (ReceiveChannel) {
+                .macsec = s,
+                .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);
+        free(a->sa.key);
+
+        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),
+        };
+
+        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");
+        }
+
+        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, NetDev *netdev) {
+        int r;
+
+        assert(netdev);
+        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");
+
+        return 1;
+}
+
+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,
+                               netdev_destroy_callback, netdev);
+        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) {
+        ReceiveAssociation *n;
+        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_by_section, i) {
+                r = netdev_macsec_configure_receive_channel(netdev, c);
+                if (r < 0)
+                        return r;
+        }
+
+        ORDERED_HASHMAP_FOREACH(n, s->receive_associations_by_section, i) {
+                r = netdev_macsec_configure_receive_association(netdev, n);
+                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");
+        }
+
+        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);
+
+        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(rvalue, strlen(rvalue), &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 */
+                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_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;
+}
+
+static int macsec_receive_channel_verify(ReceiveChannel *c) {
+        NetDev *netdev;
+
+        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);
+
+        return 0;
+}
+
+static int macsec_transmit_association_verify(TransmitAssociation *t) {
+        NetDev *netdev;
+
+        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);
+
+        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) {
+        NetDev *netdev;
+
+        assert(a);
+        assert(a->macsec);
+
+        netdev = NETDEV(a->macsec);
+
+        if (section_is_invalid(a->section))
+                return -EINVAL;
+
+        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);
+
+        return 0;
+}
+
+static int netdev_macsec_verify(NetDev *netdev, const char *filename) {
+        MACsec *v = MACSEC(netdev);
+        TransmitAssociation *a;
+        ReceiveAssociation *n;
+        ReceiveChannel *c;
+        Iterator i;
+        int r;
+
+        assert(netdev);
+        assert(v);
+        assert(filename);
+
+        ORDERED_HASHMAP_FOREACH(c, v->receive_channels_by_section, i) {
+                r = macsec_receive_channel_verify(c);
+                if (r < 0)
+                        macsec_receive_channel_free(c);
+        }
+
+        ORDERED_HASHMAP_FOREACH(a, v->transmit_associations_by_section, i) {
+                r = macsec_transmit_association_verify(a);
+                if (r < 0)
+                        macsec_transmit_association_free(a);
+        }
+
+        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_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 (file)
index 0000000..780f4fc
--- /dev/null
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <linux/if_macsec.h>
+
+#include "in-addr-util.h"
+#include "netdev.h"
+#include "networkd-util.h"
+#include "sparse-endian.h"
+
+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;
+} 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;
+} ReceiveChannel;
+
+struct MACsec {
+        NetDev meta;
+
+        uint16_t port;
+        int encrypt;
+
+        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);
index 1a3d6caeb9f77ce66be89f8a24a1a133f37aa557..6dd8e10d597404a39b2079abab44a69ec62e7d4f 100644 (file)
@@ -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"
@@ -132,6 +133,18 @@ GENEVE.UDP6ZeroCheckSumTx,         config_parse_bool,                    0,
 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
+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
 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)
index c1bcfc66e66181b9f5c9f84177d27f2136430d37..e138393514ba4831d595d16678693b8e5afb7eaa 100644 (file)
@@ -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);
index ad4dd2e2b0d507d51067d0d48a4e9cfbe9502c5c..29ecead029b8bca71e509a8857869a01a6dbefab 100644 (file)
@@ -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
index 47a9f7d808a6c353c3c790e33a411dc6416a25b4..6777d1aba84100fffea77d102076635f3a619546 100644 (file)
@@ -46,6 +46,7 @@ Network.MACVTAP,                        config_parse_stacked_netdev,
 Network.IPVLAN,                         config_parse_stacked_netdev,                    NETDEV_KIND_IPVLAN,            offsetof(Network, stacked_netdev_names)
 Network.VXLAN,                          config_parse_stacked_netdev,                    NETDEV_KIND_VXLAN,             offsetof(Network, stacked_netdev_names)
 Network.L2TP,                           config_parse_stacked_netdev,                    NETDEV_KIND_L2TP,              offsetof(Network, stacked_netdev_names)
+Network.MACsec,                         config_parse_stacked_netdev,                    NETDEV_KIND_MACSEC,            offsetof(Network, stacked_netdev_names)
 Network.Tunnel,                         config_parse_stacked_netdev,                    _NETDEV_KIND_TUNNEL,           offsetof(Network, stacked_netdev_names)
 Network.VRF,                            config_parse_ifname,                            0,                             offsetof(Network, vrf_name)
 Network.DHCP,                           config_parse_dhcp,                              0,                             offsetof(Network, dhcp)
index 2c8896530a094e9b08bc4e5810d6e362ebe51166..52cfc4bec15dec3ea8a6485c37b8f9e54e7f668b 100644 (file)
@@ -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,
index 804fe9f03ecd809dbb164dc6c32bacf8420bb058..d327b27308b68d56e139cf619ce698f7ccf9f4df 100644 (file)
@@ -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);
index 7da3955af6b275f1e2044075b312c939d927cadf..9ea34753030e71dcf8e38de160b815e88aab2296 100644 (file)
@@ -174,3 +174,19 @@ SessionId=
 PeerSessionId=
 Layer2SpecificHeader=
 Name=
+[MACSEC]
+Port=
+Encrypt=
+[MACsecReceiveAssociation]
+Port=
+MACAddress=
+PacketNumber=
+KeyId=
+Key=
+[MACsecReceiveChannel]
+Port=
+MACAddress=
+[MACsecTransmitAssociation]
+PacketNumber=
+KeyId=
+Key=
index ddc60a9cbbdc53d7ba98240a667a7adff1653aa1..cd2031150f3a23cc5777df3fab838d99d94a2fde 100644 (file)
@@ -111,6 +111,7 @@ IPv6Token=
 Description=
 VXLAN=
 L2TP=
+MACsec=
 LinkLocalAddressing=
 ConfigureWithoutCarrier=
 NTP=