diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:49:52 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:49:52 +0000 |
commit | 55944e5e40b1be2afc4855d8d2baf4b73d1876b5 (patch) | |
tree | 33f869f55a1b149e9b7c2b7e201867ca5dd52992 /src/network/netdev/macsec.c | |
parent | Initial commit. (diff) | |
download | systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.tar.xz systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.zip |
Adding upstream version 255.4.upstream/255.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/network/netdev/macsec.c')
-rw-r--r-- | src/network/netdev/macsec.c | 1204 |
1 files changed, 1204 insertions, 0 deletions
diff --git a/src/network/netdev/macsec.c b/src/network/netdev/macsec.c new file mode 100644 index 0000000..17d6ace --- /dev/null +++ b/src/network/netdev/macsec.c @@ -0,0 +1,1204 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include <netinet/in.h> +#include <linux/if_arp.h> +#include <linux/if_ether.h> +#include <linux/if_macsec.h> +#include <linux/genetlink.h> + +#include "conf-parser.h" +#include "fileio.h" +#include "hashmap.h" +#include "hexdecoct.h" +#include "macsec.h" +#include "memory-util.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "parse-helpers.h" +#include "socket-util.h" +#include "string-table.h" +#include "string-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 ReceiveAssociation* macsec_receive_association_free(ReceiveAssociation *c) { + if (!c) + return NULL; + + if (c->macsec && c->section) + ordered_hashmap_remove(c->macsec->receive_associations_by_section, c->section); + + config_section_free(c->section); + security_association_clear(&c->sa); + + return mfree(c); +} + +DEFINE_SECTION_CLEANUP_FUNCTIONS(ReceiveAssociation, macsec_receive_association_free); + +static int macsec_receive_association_new_static(MACsec *s, const char *filename, unsigned section_line, ReceiveAssociation **ret) { + _cleanup_(config_section_freep) ConfigSection *n = NULL; + _cleanup_(macsec_receive_association_freep) ReceiveAssociation *c = NULL; + int r; + + assert(s); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = 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_put(&s->receive_associations_by_section, &config_section_hash_ops, c->section, c); + if (r < 0) + return r; + + *ret = TAKE_PTR(c); + + return 0; +} + +static ReceiveChannel* macsec_receive_channel_free(ReceiveChannel *c) { + if (!c) + return NULL; + + if (c->macsec) { + if (c->sci.as_uint64 > 0) + ordered_hashmap_remove_value(c->macsec->receive_channels, &c->sci.as_uint64, c); + + if (c->section) + ordered_hashmap_remove(c->macsec->receive_channels_by_section, c->section); + } + + config_section_free(c->section); + + return mfree(c); +} + +DEFINE_SECTION_CLEANUP_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_(config_section_freep) ConfigSection *n = NULL; + _cleanup_(macsec_receive_channel_freep) ReceiveChannel *c = NULL; + int r; + + assert(s); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = 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_put(&s->receive_channels_by_section, &config_section_hash_ops, c->section, c); + if (r < 0) + return r; + + *ret = TAKE_PTR(c); + + return 0; +} + +static TransmitAssociation* macsec_transmit_association_free(TransmitAssociation *a) { + if (!a) + return NULL; + + if (a->macsec && a->section) + ordered_hashmap_remove(a->macsec->transmit_associations_by_section, a->section); + + config_section_free(a->section); + security_association_clear(&a->sa); + + return mfree(a); +} + +DEFINE_SECTION_CLEANUP_FUNCTIONS(TransmitAssociation, macsec_transmit_association_free); + +static int macsec_transmit_association_new_static(MACsec *s, const char *filename, unsigned section_line, TransmitAssociation **ret) { + _cleanup_(config_section_freep) ConfigSection *n = NULL; + _cleanup_(macsec_transmit_association_freep) TransmitAssociation *a = NULL; + int r; + + assert(s); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = 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_put(&s->transmit_associations_by_section, &config_section_hash_ops, a->section, a); + if (r < 0) + return r; + + *ret = TAKE_PTR(a); + + return 0; +} + +static int netdev_macsec_create_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, MACSEC_GENL_NAME, command, &m); + if (r < 0) + return r; + + r = sd_netlink_message_append_u32(m, MACSEC_ATTR_IFINDEX, netdev->ifindex); + if (r < 0) + return r; + + *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 r; + + r = sd_netlink_message_append_u64(m, MACSEC_RXSC_ATTR_SCI, sci->as_uint64); + if (r < 0) + return r; + + r = sd_netlink_message_close_container(m); + if (r < 0) + return r; + + 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 r; + + r = sd_netlink_message_append_u8(m, MACSEC_SA_ATTR_AN, a->association_number); + if (r < 0) + return r; + + if (a->packet_number > 0) { + r = sd_netlink_message_append_u32(m, MACSEC_SA_ATTR_PN, a->packet_number); + if (r < 0) + return r; + } + + 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 r; + + r = sd_netlink_message_append_data(m, MACSEC_SA_ATTR_KEY, a->key, a->key_len); + if (r < 0) + return r; + } + + if (a->activate >= 0) { + r = sd_netlink_message_append_u8(m, MACSEC_SA_ATTR_ACTIVE, a->activate); + if (r < 0) + return r; + } + + r = sd_netlink_message_close_container(m); + if (r < 0) + return r; + + 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 it without changing parameters"); + else if (r < 0) { + log_netdev_warning_errno(netdev, r, + "Failed to add receive secure association: %m"); + netdev_enter_failed(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_create_message(netdev, MACSEC_CMD_ADD_RXSA, &m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to create netlink message: %m"); + + r = netdev_macsec_fill_message_sa(netdev, &a->sa, m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to fill netlink message: %m"); + + r = netdev_macsec_fill_message_sci(netdev, &a->sci, m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to fill netlink message: %m"); + + 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) { + assert(c); + assert(c->macsec); + + NetDev *netdev = ASSERT_PTR(NETDEV(c->macsec)); + int r; + + 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 it without changing parameters"); + else if (r < 0) { + log_netdev_warning_errno(netdev, r, + "Failed to add receive secure channel: %m"); + netdev_enter_failed(netdev); + + return 1; + } + + log_netdev_debug(netdev, "Receive channel is configured"); + + for (unsigned 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_enter_failed(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_create_message(netdev, MACSEC_CMD_ADD_RXSC, &m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to create netlink message: %m"); + + r = netdev_macsec_fill_message_sci(netdev, &c->sci, m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to fill netlink message: %m"); + + 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 it without changing parameters"); + else if (r < 0) { + log_netdev_warning_errno(netdev, r, + "Failed to add transmit secure association: %m"); + netdev_enter_failed(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_create_message(netdev, MACSEC_CMD_ADD_TXSA, &m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to create netlink message: %m"); + + r = netdev_macsec_fill_message_sa(netdev, &a->sa, m); + if (r < 0) + return log_netdev_error_errno(netdev, r, "Failed to fill netlink message: %m"); + + 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) { + MACsec *s = MACSEC(netdev); + TransmitAssociation *a; + ReceiveChannel *c; + int r; + + ORDERED_HASHMAP_FOREACH(a, s->transmit_associations_by_section) { + r = netdev_macsec_configure_transmit_association(netdev, a); + if (r < 0) + return r; + } + + ORDERED_HASHMAP_FOREACH(c, s->receive_channels) { + 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) { + assert(m); + + MACsec *v = MACSEC(netdev); + int r; + + if (v->port > 0) { + r = sd_netlink_message_append_u16(m, IFLA_MACSEC_PORT, v->port); + if (r < 0) + return r; + } + + if (v->encrypt >= 0) { + r = sd_netlink_message_append_u8(m, IFLA_MACSEC_ENCRYPT, v->encrypt); + if (r < 0) + return r; + } + + r = sd_netlink_message_append_u8(m, IFLA_MACSEC_ENCODING_SA, v->encoding_an); + if (r < 0) + return r; + + return 0; +} + +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) { + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + MACsec *s = ASSERT_PTR(userdata); + _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL; + _cleanup_(macsec_receive_channel_free_or_set_invalidp) ReceiveChannel *c = NULL; + uint16_t port; + void *dest; + int r; + + /* 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 log_oom(); + + dest = &c->sci.port; + } else { + assert(streq(section, "MACsecReceiveAssociation")); + + r = macsec_receive_association_new_static(s, filename, section_line, &b); + if (r < 0) + return log_oom(); + + dest = &b->sci.port; + } + + r = parse_ip_port(rvalue, &port); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse port '%s' for secure channel identifier. Ignoring assignment: %m", + rvalue); + return 0; + } + + unaligned_write_be16(dest, 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) { + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + MACsec *s = ASSERT_PTR(userdata); + _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL; + _cleanup_(macsec_receive_channel_free_or_set_invalidp) ReceiveChannel *c = NULL; + int r; + + 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 log_oom(); + + r = parse_ether_addr(rvalue, b ? &b->sci.mac : &c->sci.mac); + if (r < 0) { + log_syntax(unit, LOG_WARNING, 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) { + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + MACsec *s = ASSERT_PTR(userdata); + _cleanup_(macsec_transmit_association_free_or_set_invalidp) TransmitAssociation *a = NULL; + _cleanup_(macsec_receive_association_free_or_set_invalidp) ReceiveAssociation *b = NULL; + uint32_t val, *dest; + int r; + + 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 log_oom(); + + dest = a ? &a->sa.packet_number : &b->sa.packet_number; + + r = safe_atou32(rvalue, &val); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse packet number. Ignoring assignment: %s", rvalue); + return 0; + } + if (streq(section, "MACsecTransmitAssociation") && val == 0) { + log_syntax(unit, LOG_WARNING, 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_(erase_and_freep) void *p = NULL; + 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 log_oom(); + + dest = a ? &a->sa : &b->sa; + + r = unhexmem_full(rvalue, strlen(rvalue), true, &p, &l); + if (r < 0) { + log_syntax(unit, LOG_WARNING, 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_WARNING, filename, line, 0, "Invalid key length (%zu). Ignoring assignment", l); + return 0; + } + + explicit_bzero_safe(dest->key, dest->key_len); + 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 log_oom(); + + 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 = NULL; + 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 log_oom(); + + r = unhexmem(rvalue, strlen(rvalue), &p, &l); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse KeyId=%s, ignoring assignment: %m", rvalue); + return 0; + } + if (l > MACSEC_KEYID_LEN) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Specified KeyId= is larger then the allowed maximum (%zu > %i), ignoring: %s", + l, 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, 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 log_oom(); + + dest = a ? &a->sa.activate : &b->sa.activate; + + r = parse_tristate(rvalue, dest); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse activation mode of %s security association. " + "Ignoring assignment: %s", + streq(section, "MACsecTransmitAssociation") ? "transmit" : "receive", + rvalue); + return 0; + } + + 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 log_oom(); + + if (isempty(rvalue)) { + a->sa.use_for_encoding = -1; + TAKE_PTR(a); + return 0; + } + + r = parse_tristate(rvalue, &a->sa.use_for_encoding); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s= setting. Ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + 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_(erase_and_freep) 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( + AT_FDCWD, sa->key_file, UINT64_MAX, MACSEC_KEYID_LEN, + READ_FULL_FILE_SECURE | + READ_FULL_FILE_UNHEX | + READ_FULL_FILE_WARN_WORLD_READABLE | + READ_FULL_FILE_CONNECT_SOCKET | + READ_FULL_FILE_FAIL_WHEN_LARGER, + NULL, (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 != MACSEC_KEYID_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_put(&c->macsec->receive_channels, &uint64_hash_ops, &c->sci.as_uint64, c); + if (r == -ENOMEM) + return log_oom(); + 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_put(&a->macsec->receive_channels, &uint64_hash_ops, &new_channel->sci.as_uint64, new_channel); + if (r == -ENOMEM) + return log_oom(); + 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) { + assert(filename); + + MACsec *v = MACSEC(netdev); + TransmitAssociation *a; + ReceiveAssociation *n; + ReceiveChannel *c; + uint8_t an, encoding_an; + bool use_for_encoding; + int r; + + ORDERED_HASHMAP_FOREACH(c, v->receive_channels_by_section) { + 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) { + 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) { + r = macsec_receive_association_verify(n); + if (r < 0) + macsec_receive_association_free(n); + } + + return 0; +} + +static void macsec_init(NetDev *netdev) { + MACsec *v = MACSEC(netdev); + + v->encrypt = -1; +} + +static void macsec_done(NetDev *netdev) { + MACsec *v = MACSEC(netdev); + + ordered_hashmap_free_with_destructor(v->receive_channels, macsec_receive_channel_free); + ordered_hashmap_free_with_destructor(v->receive_channels_by_section, macsec_receive_channel_free); + ordered_hashmap_free_with_destructor(v->transmit_associations_by_section, macsec_transmit_association_free); + ordered_hashmap_free_with_destructor(v->receive_associations_by_section, macsec_receive_association_free); +} + +const NetDevVTable macsec_vtable = { + .object_size = sizeof(MACsec), + .init = macsec_init, + .sections = NETDEV_COMMON_SECTIONS "MACsec\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, + .iftype = ARPHRD_ETHER, + .generate_mac = true, +}; |