[PATCH 12/13] drivers: macsec_linux: add driver for macsec on linux kernels
Sabrina Dubroca
sd at queasysnail.net
Sun Nov 27 11:08:55 PST 2016
This uses libnl3 to communicate with the macsec module available on
Linux.
Signed-off-by: Sabrina Dubroca <sd at queasysnail.net>
---
src/drivers/driver.h | 3 +
src/drivers/driver_macsec_linux.c | 1160 +++++++++++++++++++++++++++++++++++++
src/drivers/drivers.c | 3 +
src/drivers/drivers.mak | 7 +
src/pae/ieee802_1x_kay.c | 11 +
src/pae/ieee802_1x_kay.h | 2 +
6 files changed, 1186 insertions(+)
create mode 100644 src/drivers/driver_macsec_linux.c
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index 0cb68ba9752f..3b79192bf32c 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -5011,6 +5011,9 @@ extern const struct wpa_driver_ops wpa_driver_wired_ops; /* driver_wired.c */
/* driver_macsec_qca.c */
extern const struct wpa_driver_ops wpa_driver_macsec_qca_ops;
#endif /* CONFIG_DRIVER_MACSEC_QCA */
+#ifdef CONFIG_DRIVER_MACSEC_LINUX
+extern const struct wpa_driver_ops wpa_driver_macsec_linux_ops; /* driver_macsec_linux.c */
+#endif /* CONFIG_DRIVER_MACSEC_LINUX */
#ifdef CONFIG_DRIVER_ROBOSWITCH
/* driver_roboswitch.c */
extern const struct wpa_driver_ops wpa_driver_roboswitch_ops;
diff --git a/src/drivers/driver_macsec_linux.c b/src/drivers/driver_macsec_linux.c
new file mode 100644
index 000000000000..5807bfa6122e
--- /dev/null
+++ b/src/drivers/driver_macsec_linux.c
@@ -0,0 +1,1160 @@
+/*
+ * Driver interaction with Linux MACsec kernel module
+ * Copyright (c) 2016, Sabrina Dubroca <sd at queasysnail.net> and Red Hat, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "includes.h"
+
+#include "common.h"
+#include "eloop.h"
+#include "driver.h"
+
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <netpacket/packet.h>
+#include <net/if_arp.h>
+#include <net/if.h>
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/route/link.h>
+#include <netlink/route/link/macsec.h>
+#include <linux/if_macsec.h>
+
+#include <inttypes.h>
+
+#include "pae/ieee802_1x_kay.h"
+#include "pae/ieee802_1x_kay_i.h"
+
+#include "driver_wired_common.h"
+
+#define DRV_PREFIX "macsec_linux: "
+
+#define UNUSED_SCI 0xffffffffffffffff
+
+struct cb_arg {
+ struct macsec_drv_data *drv;
+ u32 *pn;
+ int ifindex;
+ u8 txsa;
+ u8 rxsa;
+ u64 rxsci;
+};
+
+struct macsec_genl_ctx {
+ struct nl_sock *sk;
+ int macsec_genl_id;
+ struct cb_arg cb_arg;
+};
+
+struct macsec_drv_data {
+ struct driver_wired_common_data common;
+ struct rtnl_link *link;
+ struct nl_cache *link_cache;
+ struct nl_sock *sk;
+ struct macsec_genl_ctx ctx;
+
+ struct netlink_data *netlink;
+ struct nl_handle *nl;
+ char ifname[IFNAMSIZ + 1];
+ int ifi;
+ int parent_ifi;
+
+ Boolean created_link;
+
+ Boolean controlled_port_enabled;
+ Boolean controlled_port_enabled_set;
+
+ Boolean protect_frames;
+ Boolean protect_frames_set;
+
+ Boolean encrypt;
+ Boolean encrypt_set;
+
+ Boolean replay_protect;
+ Boolean replay_protect_set;
+
+ u32 replay_window;
+
+ u8 encoding_sa;
+ Boolean encoding_sa_set;
+};
+
+
+static struct nl_msg *msg_prepare(enum macsec_nl_commands cmd, const struct macsec_genl_ctx *ctx, unsigned int ifindex)
+{
+ struct nl_msg *msg;
+
+ msg = nlmsg_alloc();
+ if (!msg) {
+ wpa_printf(MSG_ERROR, DRV_PREFIX "failed to alloc message\n");
+ return NULL;
+ }
+
+ if (!genlmsg_put(msg, 0, 0, ctx->macsec_genl_id, 0, 0, cmd, 0)) {
+ wpa_printf(MSG_ERROR, DRV_PREFIX "failed to put header\n");
+ goto nla_put_failure;
+ }
+
+ NLA_PUT_U32(msg, MACSEC_ATTR_IFINDEX, ifindex);
+
+ return msg;
+
+nla_put_failure:
+ nlmsg_free(msg);
+ return NULL;
+}
+
+static int nla_put_rxsc_config(struct nl_msg *msg, u64 sci)
+{
+ struct nlattr *nest = nla_nest_start(msg, MACSEC_ATTR_RXSC_CONFIG);
+ if (!nest)
+ return -1;
+
+ NLA_PUT_U64(msg, MACSEC_RXSC_ATTR_SCI, sci);
+
+ nla_nest_end(msg, nest);
+
+ return 0;
+
+nla_put_failure:
+ return -1;
+}
+
+
+static int dump_callback(struct nl_msg *msg, void *argp);
+static int init_genl_ctx(struct macsec_drv_data *drv)
+{
+ struct macsec_genl_ctx *ctx = &drv->ctx;
+
+ ctx->sk = nl_socket_alloc();
+ if (!ctx->sk) {
+ wpa_printf(MSG_ERROR, DRV_PREFIX "failed to alloc genl socket\n");
+ return -1;
+ }
+
+ if (genl_connect(ctx->sk) < 0) {
+ wpa_printf(MSG_ERROR, DRV_PREFIX "connection to genl socket failed\n");
+ goto out_free;
+ }
+
+ ctx->macsec_genl_id = genl_ctrl_resolve(ctx->sk, "macsec");
+ if (ctx->macsec_genl_id < 0) {
+ wpa_printf(MSG_ERROR, DRV_PREFIX "genl resolve failed\n");
+ goto out_free;
+ }
+
+ memset(&ctx->cb_arg, 0, sizeof(ctx->cb_arg));
+ ctx->cb_arg.drv = drv;
+
+ nl_socket_modify_cb(ctx->sk, NL_CB_VALID, NL_CB_CUSTOM, dump_callback, &ctx->cb_arg);
+
+ return 0;
+
+out_free:
+ nl_socket_free(ctx->sk);
+ ctx->sk = NULL;
+ return -1;
+}
+
+
+static int try_commit(struct macsec_drv_data *drv)
+{
+ int err;
+
+ if (!drv->link)
+ return 0;
+
+ if (drv->controlled_port_enabled_set) {
+ struct rtnl_link *change = rtnl_link_alloc();
+ rtnl_link_set_name(change, drv->ifname);
+
+ if (drv->controlled_port_enabled)
+ rtnl_link_set_flags(change, IFF_UP);
+ else
+ rtnl_link_unset_flags(change, IFF_UP);
+
+ err = rtnl_link_change(drv->sk, change, change, 0);
+ if (err < 0)
+ return err;
+
+ rtnl_link_put(change);
+
+ drv->controlled_port_enabled_set = FALSE;
+ }
+
+ if (drv->protect_frames_set)
+ rtnl_link_macsec_set_protect(drv->link, drv->protect_frames);
+
+ if (drv->encrypt_set)
+ rtnl_link_macsec_set_encrypt(drv->link, drv->encrypt);
+
+ if (drv->replay_protect_set) {
+ rtnl_link_macsec_set_replay_protect(drv->link, drv->replay_protect);
+ if (drv->replay_protect)
+ rtnl_link_macsec_set_window(drv->link, drv->replay_window);
+ }
+
+ if (drv->encoding_sa_set)
+ rtnl_link_macsec_set_encoding_sa(drv->link, drv->encoding_sa);
+
+ err = rtnl_link_add(drv->sk, drv->link, 0);
+ if (err < 0)
+ return err;
+
+ drv->protect_frames_set = FALSE;
+ drv->encrypt_set = FALSE;
+ drv->replay_protect_set = FALSE;
+
+ return 0;
+}
+
+
+static void macsec_drv_wpa_deinit(void *priv)
+{
+ struct macsec_drv_data *drv = priv;
+
+ driver_wired_deinit_common(&drv->common);
+ os_free(drv);
+}
+
+static void * macsec_drv_wpa_init(void *ctx, const char *ifname)
+{
+ struct macsec_drv_data *drv;
+
+ drv = os_zalloc(sizeof(*drv));
+ if (drv == NULL)
+ return NULL;
+
+ if (!driver_wired_init_common(&drv->common, ifname, ctx)) {
+ os_free(drv);
+ return NULL;
+ }
+
+ return drv;
+}
+
+static int macsec_drv_macsec_init(void *priv, struct macsec_init_params *params)
+{
+ struct macsec_drv_data *drv = priv;
+ int err;
+
+ wpa_printf(MSG_DEBUG, "%s\n", __func__);
+
+ drv->sk = nl_socket_alloc();
+ if (!drv->sk)
+ return -1;
+
+ err = nl_connect(drv->sk, NETLINK_ROUTE);
+ if (err < 0) {
+ wpa_printf(MSG_ERROR, DRV_PREFIX "Unable to connect NETLINK_ROUTE socket: %s", strerror(errno));
+ goto sock;
+ }
+
+ err = rtnl_link_alloc_cache(drv->sk, AF_UNSPEC, &drv->link_cache);
+ if (err < 0) {
+ wpa_printf(MSG_ERROR, DRV_PREFIX "Unable to get link cache: %s", strerror(errno));
+ goto sock;
+ }
+
+ drv->parent_ifi = rtnl_link_name2i(drv->link_cache, drv->common.ifname);
+ if (drv->parent_ifi == 0) {
+ wpa_printf(MSG_ERROR, DRV_PREFIX "couldn't find ifindex for interface %s\n", drv->common.ifname);
+ goto cache;
+ }
+
+ err = init_genl_ctx(drv);
+ if (err < 0)
+ goto cache;
+
+ return 0;
+
+cache:
+ nl_cache_free(drv->link_cache);
+ drv->link_cache = NULL;
+sock:
+ nl_socket_free(drv->sk);
+ drv->sk = NULL;
+ return -1;
+}
+
+static int macsec_drv_macsec_deinit(void *priv)
+{
+ struct macsec_drv_data *drv = priv;
+
+ wpa_printf(MSG_DEBUG, "%s\n", __func__);
+
+ if (drv->sk)
+ nl_socket_free(drv->sk);
+ drv->sk = NULL;
+
+ if (drv->link_cache)
+ nl_cache_free(drv->link_cache);
+ drv->link_cache = NULL;
+
+ if (drv->ctx.sk)
+ nl_socket_free(drv->ctx.sk);
+
+ return 0;
+}
+
+static int macsec_drv_get_capability(void *priv, enum macsec_cap *cap)
+{
+ wpa_printf(MSG_DEBUG, "%s\n", __func__);
+
+ *cap = MACSEC_CAP_INTEG_AND_CONF;
+
+ return 0;
+}
+
+/**
+ * macsec_drv_enable_protect_frames - Set protect frames status
+ * @priv: Private driver interface data
+ * @enabled: TRUE = protect frames enabled
+ * FALSE = protect frames disabled
+ * Returns: 0 on success, -1 on failure (or if not supported)
+ */
+static int macsec_drv_enable_protect_frames(void *priv, Boolean enabled)
+{
+ struct macsec_drv_data *drv = priv;
+
+ wpa_printf(MSG_DEBUG, "%s -> %s\n", __func__, enabled ? "TRUE" : "FALSE");
+
+ drv->protect_frames_set = TRUE;
+ drv->protect_frames = enabled;
+
+ return try_commit(drv);
+}
+
+/**
+ * macsec_drv_enable_encrypt - Set protect frames status
+ * @priv: Private driver interface data
+ * @enabled: TRUE = protect frames enabled
+ * FALSE = protect frames disabled
+ * Returns: 0 on success, -1 on failure (or if not supported)
+ */
+static int macsec_drv_enable_encrypt(void *priv, Boolean enabled)
+{
+ struct macsec_drv_data *drv = priv;
+
+ wpa_printf(MSG_DEBUG, "%s -> %s\n", __func__, enabled ? "TRUE" : "FALSE");
+
+ drv->encrypt_set = TRUE;
+ drv->encrypt = enabled;
+
+ return try_commit(drv);
+}
+
+/**
+ * macsec_drv_set_replay_protect - Set replay protect status and window size
+ * @priv: Private driver interface data
+ * @enabled: TRUE = replay protect enabled
+ * FALSE = replay protect disabled
+ * @window: replay window size, valid only when replay protect enabled
+ * Returns: 0 on success, -1 on failure (or if not supported)
+ */
+static int macsec_drv_set_replay_protect(void *priv, Boolean enabled, u32 window)
+{
+ struct macsec_drv_data *drv = priv;
+ char buf[20];
+
+ if (enabled)
+ sprintf(buf, "TRUE, %d", window);
+ else
+ sprintf(buf, "FALSE");
+
+ wpa_printf(MSG_DEBUG, "%s -> %s\n", __func__, buf);
+
+ drv->replay_protect_set = TRUE;
+ drv->replay_protect = enabled;
+ if (enabled)
+ drv->replay_window = window;
+
+ return try_commit(drv);
+}
+
+/**
+ * macsec_drv_set_current_cipher_suite - Set current cipher suite
+ * @priv: Private driver interface data
+ * @cs: EUI64 identifier
+ * Returns: 0 on success, -1 on failure (or if not supported)
+ */
+static int macsec_drv_set_current_cipher_suite(void *priv, u64 cs)
+{
+ wpa_printf(MSG_DEBUG, "%s -> %016" PRIx64 "\n", __func__, cs);
+ return 0;
+}
+
+/**
+ * macsec_drv_enable_controlled_port - Set controlled port status
+ * @priv: Private driver interface data
+ * @enabled: TRUE = controlled port enabled
+ * FALSE = controlled port disabled
+ * Returns: 0 on success, -1 on failure (or if not supported)
+ */
+static int macsec_drv_enable_controlled_port(void *priv, Boolean enabled)
+{
+ struct macsec_drv_data *drv = priv;
+
+ wpa_printf(MSG_DEBUG, "%s -> %s\n", __func__, enabled ? "TRUE" : "FALSE");
+
+ drv->controlled_port_enabled = enabled;
+ drv->controlled_port_enabled_set = TRUE;
+
+ return try_commit(drv);
+}
+
+static struct nla_policy sa_policy[MACSEC_SA_ATTR_MAX + 1] = {
+ [MACSEC_SA_ATTR_AN] = { .type = NLA_U8 },
+ [MACSEC_SA_ATTR_ACTIVE] = { .type = NLA_U8 },
+ [MACSEC_SA_ATTR_PN] = { .type = NLA_U32 },
+ [MACSEC_SA_ATTR_KEYID] = { .type = NLA_BINARY },
+};
+
+static struct nla_policy sc_policy[MACSEC_RXSC_ATTR_MAX + 1] = {
+ [MACSEC_RXSC_ATTR_SCI] = { .type = NLA_U64 },
+ [MACSEC_RXSC_ATTR_ACTIVE] = { .type = NLA_U8 },
+ [MACSEC_RXSC_ATTR_SA_LIST] = { .type = NLA_NESTED },
+};
+
+static struct nla_policy main_policy[MACSEC_ATTR_MAX + 1] = {
+ [MACSEC_ATTR_IFINDEX] = { .type = NLA_U32 },
+ [MACSEC_ATTR_SECY] = { .type = NLA_NESTED },
+ [MACSEC_ATTR_TXSA_LIST] = { .type = NLA_NESTED },
+ [MACSEC_ATTR_RXSC_LIST] = { .type = NLA_NESTED },
+};
+
+static int dump_callback(struct nl_msg *msg, void *argp)
+{
+ struct nlmsghdr *ret_hdr = nlmsg_hdr(msg);
+ struct nlattr *tb_msg[MACSEC_ATTR_MAX + 1];
+ struct cb_arg *arg = (struct cb_arg *) argp;
+ struct genlmsghdr *gnlh = (struct genlmsghdr*) nlmsg_data(ret_hdr);
+ int err;
+
+ if (ret_hdr->nlmsg_type != arg->drv->ctx.macsec_genl_id)
+ return 0;
+
+ err = nla_parse(tb_msg, MACSEC_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
+ genlmsg_attrlen(gnlh, 0), main_policy);
+ if (err < 0)
+ return 0;
+
+ if (!tb_msg[MACSEC_ATTR_IFINDEX])
+ return 0;
+
+ if (nla_get_u32(tb_msg[MACSEC_ATTR_IFINDEX]) != arg->ifindex)
+ return 0;
+
+ if (arg->txsa < 4 && !tb_msg[MACSEC_ATTR_TXSA_LIST]) {
+ return 0;
+ } else if (arg->txsa < 4) {
+ struct nlattr *nla;
+ int rem;
+
+ nla_for_each_nested(nla, tb_msg[MACSEC_ATTR_TXSA_LIST], rem) {
+ struct nlattr *tb[MACSEC_SA_ATTR_MAX + 1];
+ err = nla_parse_nested(tb, MACSEC_SA_ATTR_MAX, nla, sa_policy);
+ if (err < 0)
+ continue;
+ if (!tb[MACSEC_SA_ATTR_AN])
+ continue;
+ if (nla_get_u8(tb[MACSEC_SA_ATTR_AN]) != arg->txsa)
+ continue;
+ if (!tb[MACSEC_SA_ATTR_PN])
+ return 0;
+ *arg->pn = nla_get_u32(tb[MACSEC_SA_ATTR_PN]);
+ return 0;
+ }
+
+ return 0;
+ }
+
+ if (arg->rxsci == UNUSED_SCI)
+ return 0;
+
+ if (tb_msg[MACSEC_ATTR_RXSC_LIST]) {
+ struct nlattr *nla;
+ int rem;
+
+ nla_for_each_nested(nla, tb_msg[MACSEC_ATTR_RXSC_LIST], rem) {
+ struct nlattr *tb[MACSEC_RXSC_ATTR_MAX + 1];
+ err = nla_parse_nested(tb, MACSEC_RXSC_ATTR_MAX, nla, sc_policy);
+ if (err < 0)
+ return 0;
+ if (!tb[MACSEC_RXSC_ATTR_SCI])
+ continue;
+ if (nla_get_u64(tb[MACSEC_RXSC_ATTR_SCI]) != arg->rxsci)
+ continue;
+ if (!tb[MACSEC_RXSC_ATTR_SA_LIST])
+ return 0;
+
+ nla_for_each_nested(nla, tb[MACSEC_RXSC_ATTR_SA_LIST], rem) {
+ struct nlattr *tb_sa[MACSEC_SA_ATTR_MAX + 1];
+ err = nla_parse_nested(tb_sa, MACSEC_SA_ATTR_MAX, nla, sa_policy);
+ if (err < 0)
+ continue;
+ if (!tb_sa[MACSEC_SA_ATTR_AN])
+ continue;
+ if (nla_get_u8(tb_sa[MACSEC_SA_ATTR_AN]) != arg->rxsa)
+ continue;
+ if (!tb_sa[MACSEC_SA_ATTR_PN])
+ return 0;
+ *arg->pn = nla_get_u32(tb_sa[MACSEC_SA_ATTR_PN]);
+
+ return 0;
+ }
+
+ return 0;
+ }
+
+ return 0;
+ }
+
+ return 0;
+}
+
+static int nl_send_recv(struct nl_sock *sk, struct nl_msg *msg)
+{
+ int ret;
+
+ ret = nl_send_auto_complete(sk, msg);
+ if (ret < 0) {
+ wpa_printf(MSG_ERROR, DRV_PREFIX "%s: failed to send: %d (%s)\n", __func__, ret, nl_geterror(-ret));
+ return ret;
+ }
+
+ ret = nl_recvmsgs_default(sk);
+ if (ret < 0) {
+ wpa_printf(MSG_ERROR, DRV_PREFIX "%s: failed to recv: %d (%s)\n", __func__, ret, nl_geterror(-ret));
+ }
+
+ return ret;
+}
+
+static int do_dump(struct macsec_drv_data *drv, u8 txsa, u64 rxsci, u8 rxsa, u32 *pn)
+{
+ struct macsec_genl_ctx *ctx = &drv->ctx;
+ struct nl_msg *msg;
+ int ret = 1;
+
+ ctx->cb_arg.ifindex = drv->ifi;
+ ctx->cb_arg.rxsci = rxsci;
+ ctx->cb_arg.rxsa = rxsa;
+ ctx->cb_arg.txsa = txsa;
+ ctx->cb_arg.pn = pn;
+
+ msg = nlmsg_alloc();
+ if (!msg) {
+ wpa_printf(MSG_ERROR, DRV_PREFIX "%s: failed to alloc message\n", __func__);
+ return 1;
+ }
+
+ if (!genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, ctx->macsec_genl_id, 0, NLM_F_DUMP, MACSEC_CMD_GET_TXSC, 0)) {
+ wpa_printf(MSG_ERROR, DRV_PREFIX "%s: failed to put header\n", __func__);
+ goto out_free_msg;
+ }
+
+ ret = nl_send_recv(ctx->sk, msg);
+ if (ret < 0)
+ wpa_printf(MSG_ERROR, DRV_PREFIX "failed to communicate: %d (%s)\n", ret, nl_geterror(-ret));
+
+ ctx->cb_arg.pn = 0;
+
+out_free_msg:
+ nlmsg_free(msg);
+ return ret;
+}
+
+/**
+ * macsec_drv_get_receive_lowest_pn - Get receive lowest pn
+ * @priv: Private driver interface data
+ * @sa: secure association
+ * Returns: 0 on success, -1 on failure (or if not supported)
+ */
+static int macsec_drv_get_receive_lowest_pn(void *priv, struct receive_sa *sa)
+{
+ struct macsec_drv_data *drv = priv;
+
+ wpa_printf(MSG_DEBUG, DRV_PREFIX "%s\n", __func__);
+
+ int err = do_dump(drv, 0xff, mka_sci_u64(&sa->sc->sci), sa->an, &sa->lowest_pn);
+ wpa_printf(MSG_DEBUG, DRV_PREFIX "%s: result %d\n", __func__, sa->lowest_pn);
+
+ return err;
+}
+
+/**
+ * macsec_drv_get_transmit_next_pn - Get transmit next pn
+ * @priv: Private driver interface data
+ * @sa: secure association
+ * Returns: 0 on success, -1 on failure (or if not supported)
+ */
+static int macsec_drv_get_transmit_next_pn(void *priv, struct transmit_sa *sa)
+{
+ struct macsec_drv_data *drv = priv;
+
+ wpa_printf(MSG_DEBUG, "%s\n", __func__);
+
+ int err = do_dump(drv, sa->an, UNUSED_SCI, 0xff, &sa->next_pn);
+ wpa_printf(MSG_DEBUG, DRV_PREFIX "%s: result %d\n", __func__, sa->next_pn);
+ return err;
+}
+
+/**
+ * macsec_drv_set_transmit_next_pn - Set transmit next pn
+ * @priv: Private driver interface data
+ * @sa: secure association
+ * Returns: 0 on success, -1 on failure (or if not supported)
+ */
+static int macsec_drv_set_transmit_next_pn(void *priv, struct transmit_sa *sa)
+{
+ struct macsec_drv_data *drv = priv;
+ struct macsec_genl_ctx *ctx = &drv->ctx;
+ struct nl_msg *msg;
+ struct nlattr *nest;
+ int ret = -1;
+
+ wpa_printf(MSG_DEBUG, "%s -> %d: %d\n", __func__, sa->an, sa->next_pn);
+
+ msg = msg_prepare(MACSEC_CMD_UPD_TXSA, ctx, drv->ifi);
+ if (!msg)
+ return ret;
+
+ nest = nla_nest_start(msg, MACSEC_ATTR_SA_CONFIG);
+ if (!nest)
+ goto nla_put_failure;
+
+ NLA_PUT_U8(msg, MACSEC_SA_ATTR_AN, sa->an);
+ NLA_PUT_U32(msg, MACSEC_SA_ATTR_PN, sa->next_pn);
+
+ nla_nest_end(msg, nest);
+
+ ret = nl_send_recv(ctx->sk, msg);
+ if (ret < 0)
+ wpa_printf(MSG_ERROR, DRV_PREFIX "failed to communicate: %d (%s)\n", ret, nl_geterror(-ret));
+
+nla_put_failure:
+ nlmsg_free(msg);
+ return ret;
+}
+
+#define SCISTR MACSTR "::%hx"
+#define SCI2STR(addr, port) MAC2STR(addr), htons(port)
+
+/**
+ * macsec_drv_create_receive_sc - create secure channel for receiving
+ * @priv: Private driver interface data
+ * @sc: secure channel
+ * @sci_addr: secure channel identifier - address
+ * @sci_port: secure channel identifier - port
+ * @conf_offset: confidentiality offset (0, 30, or 50)
+ * @validation: frame validation policy (0 = Disabled, 1 = Checked,
+ * 2 = Strict)
+ * Returns: 0 on success, -1 on failure (or if not supported)
+ */
+static int macsec_drv_create_receive_sc(void *priv, struct receive_sc *sc,
+ unsigned int conf_offset,
+ int validation)
+{
+ struct macsec_drv_data *drv = priv;
+ struct macsec_genl_ctx *ctx = &drv->ctx;
+ struct nl_msg *msg;
+ int ret = -1;
+
+ wpa_printf(MSG_DEBUG, "%s -> " SCISTR "\n", __func__, SCI2STR(sc->sci.addr, sc->sci.port));
+
+ msg = msg_prepare(MACSEC_CMD_ADD_RXSC, ctx, drv->ifi);
+ if (!msg)
+ return ret;
+
+ if (nla_put_rxsc_config(msg, mka_sci_u64(&sc->sci)))
+ goto nla_put_failure;
+
+ ret = nl_send_recv(ctx->sk, msg);
+ if (ret < 0)
+ wpa_printf(MSG_ERROR, DRV_PREFIX "%s: failed to communicate: %d (%s)\n", __func__, ret, nl_geterror(-ret));
+
+nla_put_failure:
+ nlmsg_free(msg);
+ return ret;
+}
+
+/**
+ * macsec_drv_delete_receive_sc - delete secure connection for receiving
+ * @priv: private driver interface data from init()
+ * @sc: secure channel
+ * Returns: 0 on success, -1 on failure
+ */
+static int macsec_drv_delete_receive_sc(void *priv, struct receive_sc *sc)
+{
+ struct macsec_drv_data *drv = priv;
+ struct macsec_genl_ctx *ctx = &drv->ctx;
+ struct nl_msg *msg;
+ int ret = -1;
+
+ wpa_printf(MSG_DEBUG, "%s -> " SCISTR "\n", __func__, SCI2STR(sc->sci.addr, sc->sci.port));
+
+ msg = msg_prepare(MACSEC_CMD_DEL_RXSC, ctx, drv->ifi);
+ if (!msg)
+ return ret;
+
+ if (nla_put_rxsc_config(msg, mka_sci_u64(&sc->sci)))
+ goto nla_put_failure;
+
+ ret = nl_send_recv(ctx->sk, msg);
+ if (ret < 0)
+ wpa_printf(MSG_ERROR, DRV_PREFIX "%s: failed to communicate: %d (%s)\n", __func__, ret, nl_geterror(-ret));
+
+nla_put_failure:
+ nlmsg_free(msg);
+ return ret;
+}
+
+/**
+ * macsec_drv_create_receive_sa - create secure association for receive
+ * @priv: private driver interface data from init()
+ * @sa: secure association
+ * Returns: 0 on success, -1 on failure
+ */
+static int macsec_drv_create_receive_sa(void *priv, struct receive_sa *sa)
+{
+ struct macsec_drv_data *drv = priv;
+ struct macsec_genl_ctx *ctx = &drv->ctx;
+ struct nl_msg *msg;
+ struct nlattr *nest;
+ int ret = -1;
+
+ wpa_printf(MSG_DEBUG, "%s -> %d on " SCISTR "\n", __func__, sa->an, SCI2STR(sa->sc->sci.addr, sa->sc->sci.port));
+
+ msg = msg_prepare(MACSEC_CMD_ADD_RXSA, ctx, drv->ifi);
+ if (!msg)
+ return ret;
+
+ if (nla_put_rxsc_config(msg, mka_sci_u64(&sa->sc->sci)))
+ goto nla_put_failure;
+
+ nest = nla_nest_start(msg, MACSEC_ATTR_SA_CONFIG);
+ if (!nest)
+ goto nla_put_failure;
+
+ NLA_PUT_U8(msg, MACSEC_SA_ATTR_AN, sa->an);
+ NLA_PUT_U8(msg, MACSEC_SA_ATTR_ACTIVE, sa->enable_receive);
+ NLA_PUT_U32(msg, MACSEC_SA_ATTR_PN, sa->next_pn);
+ NLA_PUT(msg, MACSEC_SA_ATTR_KEYID, sizeof(sa->pkey->key_identifier), &sa->pkey->key_identifier);
+ NLA_PUT(msg, MACSEC_SA_ATTR_KEY, sa->pkey->key_len, sa->pkey->key);
+
+ nla_nest_end(msg, nest);
+
+ ret = nl_send_recv(ctx->sk, msg);
+ if (ret < 0)
+ wpa_printf(MSG_ERROR, DRV_PREFIX "%s: failed to communicate: %d (%s)\n", __func__, ret, nl_geterror(-ret));
+
+nla_put_failure:
+ nlmsg_free(msg);
+ return ret;
+}
+
+/**
+ * macsec_drv_delete_receive_sa - delete secure association for receive
+ * @priv: private driver interface data from init()
+ * @sa: secure association
+ * Returns: 0 on success, -1 on failure
+ */
+static int macsec_drv_delete_receive_sa(void *priv, struct receive_sa *sa)
+{
+ struct macsec_drv_data *drv = priv;
+ struct macsec_genl_ctx *ctx = &drv->ctx;
+ struct nl_msg *msg;
+ struct nlattr *nest;
+ int ret = -1;
+
+ wpa_printf(MSG_DEBUG, "%s -> %d on " SCISTR "\n", __func__, sa->an, SCI2STR(sa->sc->sci.addr, sa->sc->sci.port));
+
+ msg = msg_prepare(MACSEC_CMD_DEL_RXSA, ctx, drv->ifi);
+ if (!msg)
+ return ret;
+
+ if (nla_put_rxsc_config(msg, mka_sci_u64(&sa->sc->sci)))
+ goto nla_put_failure;
+
+ nest = nla_nest_start(msg, MACSEC_ATTR_SA_CONFIG);
+ if (!nest)
+ goto nla_put_failure;
+
+ NLA_PUT_U8(msg, MACSEC_SA_ATTR_AN, sa->an);
+
+ nla_nest_end(msg, nest);
+
+ ret = nl_send_recv(ctx->sk, msg);
+ if (ret < 0)
+ wpa_printf(MSG_ERROR, DRV_PREFIX "%s: failed to communicate: %d (%s)\n", __func__, ret, nl_geterror(-ret));
+
+nla_put_failure:
+ nlmsg_free(msg);
+ return ret;
+}
+
+static int set_active_rx_sa(const struct macsec_genl_ctx *ctx, int ifindex, u64 sci, unsigned char an, Boolean state)
+{
+ struct nl_msg *msg;
+ struct nlattr *nest;
+ int ret = -1;
+
+ msg = msg_prepare(MACSEC_CMD_UPD_RXSA, ctx, ifindex);
+ if (!msg)
+ return ret;
+
+ if (nla_put_rxsc_config(msg, sci))
+ goto nla_put_failure;
+
+ nest = nla_nest_start(msg, MACSEC_ATTR_SA_CONFIG);
+ if (!nest)
+ goto nla_put_failure;
+
+ NLA_PUT_U8(msg, MACSEC_SA_ATTR_AN, an);
+ NLA_PUT_U8(msg, MACSEC_SA_ATTR_ACTIVE, !!state);
+
+ nla_nest_end(msg, nest);
+
+ ret = nl_send_recv(ctx->sk, msg);
+ if (ret < 0)
+ wpa_printf(MSG_ERROR, DRV_PREFIX "%s: failed to communicate: %d (%s)\n", __func__, ret, nl_geterror(-ret));
+
+nla_put_failure:
+ nlmsg_free(msg);
+ return ret;
+}
+
+/**
+ * macsec_drv_enable_receive_sa - enable the SA for receive
+ * @priv: private driver interface data from init()
+ * @sa: secure association
+ * Returns: 0 on success, -1 on failure
+ */
+static int macsec_drv_enable_receive_sa(void *priv, struct receive_sa *sa)
+{
+ struct macsec_drv_data *drv = priv;
+ struct macsec_genl_ctx *ctx = &drv->ctx;
+
+ wpa_printf(MSG_DEBUG, "%s -> %d on " SCISTR "\n", __func__, sa->an, SCI2STR(sa->sc->sci.addr, sa->sc->sci.port));
+
+ return set_active_rx_sa(ctx, drv->ifi, mka_sci_u64(&sa->sc->sci), sa->an, TRUE);
+}
+
+/**
+ * macsec_drv_disable_receive_sa - disable SA for receive
+ * @priv: private driver interface data from init()
+ * @sa: secure association
+ * Returns: 0 on success, -1 on failure
+ */
+static int macsec_drv_disable_receive_sa(void *priv, struct receive_sa *sa)
+{
+ struct macsec_drv_data *drv = priv;
+ struct macsec_genl_ctx *ctx = &drv->ctx;
+
+ wpa_printf(MSG_DEBUG, "%s -> %d on " SCISTR "\n", __func__, sa->an, SCI2STR(sa->sc->sci.addr, sa->sc->sci.port));
+
+ return set_active_rx_sa(ctx, drv->ifi, mka_sci_u64(&sa->sc->sci), sa->an, FALSE);
+}
+
+static struct rtnl_link *lookup_sc(struct nl_cache *cache, int parent, u64 sci)
+{
+ struct rtnl_link *needle;
+ void *match;
+
+ needle = rtnl_link_macsec_alloc();
+ if (!needle)
+ return NULL;
+
+ rtnl_link_set_link(needle, parent);
+ rtnl_link_macsec_set_sci(needle, sci);
+
+ match = nl_cache_find(cache, (struct nl_object *)needle);
+ rtnl_link_put(needle);
+
+ return (struct rtnl_link *) match;
+}
+
+/**
+ * macsec_drv_create_transmit_sc - create secure connection for transmit
+ * @priv: private driver interface data from init()
+ * @sc: secure channel
+ * @conf_offset: confidentiality offset
+ * Returns: 0 on success, -1 on failure
+ */
+static int macsec_drv_create_transmit_sc(void *priv, struct transmit_sc *sc,
+ enum confidentiality_offset conf_offset)
+{
+ struct macsec_drv_data *drv = priv;
+ struct rtnl_link *link;
+ char *ifname;
+ u64 sci;
+ int err;
+
+ wpa_printf(MSG_DEBUG, "%s\n", __func__);
+
+ link = rtnl_link_macsec_alloc();
+ if (!link) {
+ wpa_printf(MSG_ERROR, DRV_PREFIX "couldn't allocate link\n");
+ return -1;
+ }
+
+ rtnl_link_set_link(link, drv->parent_ifi);
+
+ sci = mka_sci_u64(&sc->sci);
+ rtnl_link_macsec_set_sci(link, sci);
+
+ drv->created_link = TRUE;
+
+ err = rtnl_link_add(drv->sk, link, NLM_F_CREATE);
+ if (err == -NLE_BUSY) {
+ wpa_printf(MSG_INFO, DRV_PREFIX "link already exists, using it\n");
+ drv->created_link = FALSE;
+ } else if (err < 0) {
+ rtnl_link_put(link);
+ wpa_printf(MSG_ERROR, DRV_PREFIX "couldn't create link: err %d\n", err);
+ return err;
+ }
+
+ rtnl_link_put(link);
+
+ nl_cache_refill(drv->sk, drv->link_cache);
+ link = lookup_sc(drv->link_cache, drv->parent_ifi, sci);
+ if (!link) {
+ wpa_printf(MSG_ERROR, DRV_PREFIX "couldn't find link\n");
+ return -1;
+ }
+
+ drv->ifi = rtnl_link_get_ifindex(link);
+ ifname = rtnl_link_get_name(link);
+ strncpy(drv->ifname, ifname, IFNAMSIZ + 1);
+ rtnl_link_put(link);
+
+ drv->link = rtnl_link_macsec_alloc();
+ if (!drv->link) {
+ wpa_printf(MSG_ERROR, DRV_PREFIX "couldn't allocate link\n");
+ return -1;
+ }
+
+ rtnl_link_set_name(drv->link, drv->ifname);
+
+ /* in case some settings have already been done but we
+ * couldn't apply them */
+ return try_commit(drv);
+}
+
+/**
+ * macsec_drv_delete_transmit_sc - delete secure connection for transmit
+ * @priv: private driver interface data from init()
+ * @sc: secure channel
+ * Returns: 0 on success, -1 on failure
+ */
+static int macsec_drv_delete_transmit_sc(void *priv, struct transmit_sc *sc)
+{
+ struct macsec_drv_data *drv = priv;
+ int err;
+
+ wpa_printf(MSG_DEBUG, "%s\n", __func__);
+
+ if (!drv->created_link) {
+ rtnl_link_put(drv->link);
+ drv->link = NULL;
+ wpa_printf(MSG_DEBUG, "we didn't create the link, leave it alone\n");
+ return 0;
+ }
+
+ err = rtnl_link_delete(drv->sk, drv->link);
+ if (err < 0)
+ wpa_printf(MSG_ERROR, DRV_PREFIX "couldn't delete link\n");
+ rtnl_link_put(drv->link);
+ drv->link = NULL;
+
+ return err;
+}
+
+/**
+ * macsec_drv_create_transmit_sa - create secure association for transmit
+ * @priv: private driver interface data from init()
+ * @sa: secure association
+ * Returns: 0 on success, -1 on failure
+ */
+static int macsec_drv_create_transmit_sa(void *priv, struct transmit_sa *sa)
+{
+ struct macsec_drv_data *drv = priv;
+ struct macsec_genl_ctx *ctx = &drv->ctx;
+ struct nl_msg *msg;
+ struct nlattr *nest;
+ int ret = -1;
+
+ wpa_printf(MSG_DEBUG, "%s -> %d\n", __func__, sa->an);
+
+ msg = msg_prepare(MACSEC_CMD_ADD_TXSA, ctx, drv->ifi);
+ if (!msg)
+ return ret;
+
+ nest = nla_nest_start(msg, MACSEC_ATTR_SA_CONFIG);
+ if (!nest)
+ goto nla_put_failure;
+
+ NLA_PUT_U8(msg, MACSEC_SA_ATTR_AN, sa->an);
+ NLA_PUT_U32(msg, MACSEC_SA_ATTR_PN, sa->next_pn);
+ NLA_PUT(msg, MACSEC_SA_ATTR_KEYID, sizeof(sa->pkey->key_identifier), &sa->pkey->key_identifier);
+ NLA_PUT(msg, MACSEC_SA_ATTR_KEY, sa->pkey->key_len, sa->pkey->key);
+ NLA_PUT_U8(msg, MACSEC_SA_ATTR_ACTIVE, sa->enable_transmit);
+
+ nla_nest_end(msg, nest);
+
+ ret = nl_send_recv(ctx->sk, msg);
+ if (ret < 0)
+ wpa_printf(MSG_ERROR, DRV_PREFIX "%s: failed to communicate: %d (%s)\n", __func__, ret, nl_geterror(-ret));
+
+nla_put_failure:
+ nlmsg_free(msg);
+ return ret;
+}
+
+/**
+ * macsec_drv_delete_transmit_sa - delete secure association for transmit
+ * @priv: private driver interface data from init()
+ * @sa: secure association
+ * Returns: 0 on success, -1 on failure
+ */
+static int macsec_drv_delete_transmit_sa(void *priv, struct transmit_sa *sa)
+{
+ struct macsec_drv_data *drv = priv;
+ struct macsec_genl_ctx *ctx = &drv->ctx;
+ struct nl_msg *msg;
+ struct nlattr *nest;
+ int ret = -1;
+
+ wpa_printf(MSG_DEBUG, "%s -> %d\n", __func__, sa->an);
+
+ msg = msg_prepare(MACSEC_CMD_DEL_TXSA, ctx, drv->ifi);
+ if (!msg)
+ return ret;
+
+ nest = nla_nest_start(msg, MACSEC_ATTR_SA_CONFIG);
+ if (!nest)
+ goto nla_put_failure;
+
+ NLA_PUT_U8(msg, MACSEC_SA_ATTR_AN, sa->an);
+
+ nla_nest_end(msg, nest);
+
+ ret = nl_send_recv(ctx->sk, msg);
+ if (ret < 0)
+ wpa_printf(MSG_ERROR, DRV_PREFIX "%s: failed to communicate: %d (%s)\n", __func__, ret, nl_geterror(-ret));
+
+nla_put_failure:
+ nlmsg_free(msg);
+ return ret;
+}
+
+static int set_active_tx_sa(const struct macsec_genl_ctx *ctx, int ifindex, unsigned char an, Boolean state)
+{
+ struct nl_msg *msg;
+ struct nlattr *nest;
+ int ret = -1;
+
+ msg = msg_prepare(MACSEC_CMD_UPD_TXSA, ctx, ifindex);
+ if (!msg)
+ return ret;
+
+ nest = nla_nest_start(msg, MACSEC_ATTR_SA_CONFIG);
+ if (!nest)
+ goto nla_put_failure;
+
+ NLA_PUT_U8(msg, MACSEC_SA_ATTR_AN, an);
+ NLA_PUT_U8(msg, MACSEC_SA_ATTR_ACTIVE, !!state);
+
+ nla_nest_end(msg, nest);
+
+ ret = nl_send_recv(ctx->sk, msg);
+ if (ret < 0)
+ wpa_printf(MSG_ERROR, DRV_PREFIX "%s: failed to communicate: %d (%s)\n", __func__, ret, nl_geterror(-ret));
+
+nla_put_failure:
+ nlmsg_free(msg);
+ return ret;
+}
+
+/**
+ * macsec_drv_enable_transmit_sa - enable SA for transmit
+ * @priv: private driver interface data from init()
+ * @sa: secure association
+ * Returns: 0 on success, -1 on failure
+ */
+static int macsec_drv_enable_transmit_sa(void *priv, struct transmit_sa *sa)
+{
+ struct macsec_drv_data *drv = priv;
+ struct macsec_genl_ctx *ctx = &drv->ctx;
+ int ret;
+
+ wpa_printf(MSG_DEBUG, "%s -> %d\n", __func__, sa->an);
+
+ ret = set_active_tx_sa(ctx, drv->ifi, sa->an, TRUE);
+ if (ret < 0) {
+ wpa_printf(MSG_ERROR, DRV_PREFIX "failed to enable txsa\n");
+ return ret;
+ }
+
+ drv->encoding_sa_set = TRUE;
+ drv->encoding_sa = sa->an;
+
+ return try_commit(drv);
+}
+
+/**
+ * macsec_drv_disable_transmit_sa - disable SA for transmit
+ * @priv: private driver interface data from init()
+ * @sa: secure association
+ * Returns: 0 on success, -1 on failure
+ */
+static int macsec_drv_disable_transmit_sa(void *priv, struct transmit_sa *sa)
+{
+ struct macsec_drv_data *drv = priv;
+ struct macsec_genl_ctx *ctx = &drv->ctx;
+
+ wpa_printf(MSG_DEBUG, "%s -> %d\n", __func__, sa->an);
+
+ return set_active_tx_sa(ctx, drv->ifi, sa->an, FALSE);
+}
+
+
+const struct wpa_driver_ops wpa_driver_macsec_linux_ops = {
+ .name = "macsec_linux",
+ .desc = "MACsec Ethernet driver for Linux",
+ .get_ssid = driver_wired_get_ssid,
+ .get_bssid = driver_wired_get_bssid,
+ .get_capa = driver_wired_get_capa,
+ .init = macsec_drv_wpa_init,
+ .deinit = macsec_drv_wpa_deinit,
+
+ .macsec_init = macsec_drv_macsec_init,
+ .macsec_deinit = macsec_drv_macsec_deinit,
+ .macsec_get_capability = macsec_drv_get_capability,
+ .enable_protect_frames = macsec_drv_enable_protect_frames,
+ .enable_encrypt = macsec_drv_enable_encrypt,
+ .set_replay_protect = macsec_drv_set_replay_protect,
+ .set_current_cipher_suite = macsec_drv_set_current_cipher_suite,
+ .enable_controlled_port = macsec_drv_enable_controlled_port,
+ .get_receive_lowest_pn = macsec_drv_get_receive_lowest_pn,
+ .get_transmit_next_pn = macsec_drv_get_transmit_next_pn,
+ .set_transmit_next_pn = macsec_drv_set_transmit_next_pn,
+ .create_receive_sc = macsec_drv_create_receive_sc,
+ .delete_receive_sc = macsec_drv_delete_receive_sc,
+ .create_receive_sa = macsec_drv_create_receive_sa,
+ .delete_receive_sa = macsec_drv_delete_receive_sa,
+ .enable_receive_sa = macsec_drv_enable_receive_sa,
+ .disable_receive_sa = macsec_drv_disable_receive_sa,
+ .create_transmit_sc = macsec_drv_create_transmit_sc,
+ .delete_transmit_sc = macsec_drv_delete_transmit_sc,
+ .create_transmit_sa = macsec_drv_create_transmit_sa,
+ .delete_transmit_sa = macsec_drv_delete_transmit_sa,
+ .enable_transmit_sa = macsec_drv_enable_transmit_sa,
+ .disable_transmit_sa = macsec_drv_disable_transmit_sa,
+};
diff --git a/src/drivers/drivers.c b/src/drivers/drivers.c
index 00773a7113f6..e95df6ddb20a 100644
--- a/src/drivers/drivers.c
+++ b/src/drivers/drivers.c
@@ -34,6 +34,9 @@ const struct wpa_driver_ops *const wpa_drivers[] =
#ifdef CONFIG_DRIVER_WIRED
&wpa_driver_wired_ops,
#endif /* CONFIG_DRIVER_WIRED */
+#ifdef CONFIG_DRIVER_MACSEC_LINUX
+ &wpa_driver_macsec_linux_ops,
+#endif /* CONFIG_DRIVER_MACSEC_LINUX */
#ifdef CONFIG_DRIVER_MACSEC_QCA
&wpa_driver_macsec_qca_ops,
#endif /* CONFIG_DRIVER_MACSEC_QCA */
diff --git a/src/drivers/drivers.mak b/src/drivers/drivers.mak
index 282da5035398..1496b47d0ad5 100644
--- a/src/drivers/drivers.mak
+++ b/src/drivers/drivers.mak
@@ -18,6 +18,13 @@ DRV_OBJS += ../src/drivers/driver_wired.o
NEED_DRV_WIRED_COMMON=1
endif
+ifdef CONFIG_DRIVER_MACSEC_LINUX
+DRV_CFLAGS += -DCONFIG_DRIVER_MACSEC_LINUX
+DRV_OBJS += ../src/drivers/driver_macsec_linux.o
+NEED_DRV_WIRED_COMMON=1
+CONFIG_LIBNL3_ROUTE=y
+endif
+
ifdef CONFIG_DRIVER_MACSEC_QCA
DRV_CFLAGS += -DCONFIG_DRIVER_MACSEC_QCA
DRV_OBJS += ../src/drivers/driver_macsec_qca.o
diff --git a/src/pae/ieee802_1x_kay.c b/src/pae/ieee802_1x_kay.c
index 3a495ca7a8a4..8dfaf5fe954c 100644
--- a/src/pae/ieee802_1x_kay.c
+++ b/src/pae/ieee802_1x_kay.c
@@ -379,6 +379,17 @@ ieee802_1x_kay_get_cipher_suite(struct ieee802_1x_mka_participant *participant,
}
+u64 mka_sci_u64(struct ieee802_1x_mka_sci *sci)
+{
+ struct ieee802_1x_mka_sci tmp;
+
+ memcpy(tmp.addr, sci->addr, ETH_ALEN);
+ tmp.port = sci->port;
+
+ return *((u64 *)&tmp);
+}
+
+
static Boolean sci_equal(const struct ieee802_1x_mka_sci *a,
const struct ieee802_1x_mka_sci *b)
{
diff --git a/src/pae/ieee802_1x_kay.h b/src/pae/ieee802_1x_kay.h
index ea5a0ddcff5d..9a92d1c029c1 100644
--- a/src/pae/ieee802_1x_kay.h
+++ b/src/pae/ieee802_1x_kay.h
@@ -231,6 +231,8 @@ struct ieee802_1x_kay {
};
+u64 mka_sci_u64(struct ieee802_1x_mka_sci *sci);
+
struct ieee802_1x_kay *
ieee802_1x_kay_init(struct ieee802_1x_kay_ctx *ctx, enum macsec_policy policy,
u16 port, const char *ifname, const u8 *addr);
--
2.10.2
More information about the Hostap
mailing list