xref: /openbmc/linux/drivers/net/netdevsim/ipsec.c (revision fd5e9fccbd504c5179ab57ff695c610bca8809d6)
17699353dSShannon Nelson // SPDX-License-Identifier: GPL-2.0
27699353dSShannon Nelson /* Copyright(c) 2018 Oracle and/or its affiliates. All rights reserved. */
37699353dSShannon Nelson 
47699353dSShannon Nelson #include <crypto/aead.h>
57699353dSShannon Nelson #include <linux/debugfs.h>
67699353dSShannon Nelson #include <net/xfrm.h>
77699353dSShannon Nelson 
87699353dSShannon Nelson #include "netdevsim.h"
97699353dSShannon Nelson 
107699353dSShannon Nelson #define NSIM_IPSEC_AUTH_BITS	128
117699353dSShannon Nelson 
nsim_dbg_netdev_ops_read(struct file * filp,char __user * buffer,size_t count,loff_t * ppos)127699353dSShannon Nelson static ssize_t nsim_dbg_netdev_ops_read(struct file *filp,
137699353dSShannon Nelson 					char __user *buffer,
147699353dSShannon Nelson 					size_t count, loff_t *ppos)
157699353dSShannon Nelson {
167699353dSShannon Nelson 	struct netdevsim *ns = filp->private_data;
177699353dSShannon Nelson 	struct nsim_ipsec *ipsec = &ns->ipsec;
187699353dSShannon Nelson 	size_t bufsize;
197699353dSShannon Nelson 	char *buf, *p;
207699353dSShannon Nelson 	int len;
217699353dSShannon Nelson 	int i;
227699353dSShannon Nelson 
237699353dSShannon Nelson 	/* the buffer needed is
247699353dSShannon Nelson 	 * (num SAs * 3 lines each * ~60 bytes per line) + one more line
257699353dSShannon Nelson 	 */
267699353dSShannon Nelson 	bufsize = (ipsec->count * 4 * 60) + 60;
277699353dSShannon Nelson 	buf = kzalloc(bufsize, GFP_KERNEL);
287699353dSShannon Nelson 	if (!buf)
297699353dSShannon Nelson 		return -ENOMEM;
307699353dSShannon Nelson 
317699353dSShannon Nelson 	p = buf;
322da222f6STakashi Iwai 	p += scnprintf(p, bufsize - (p - buf),
337699353dSShannon Nelson 		       "SA count=%u tx=%u\n",
347699353dSShannon Nelson 		       ipsec->count, ipsec->tx);
357699353dSShannon Nelson 
367699353dSShannon Nelson 	for (i = 0; i < NSIM_IPSEC_MAX_SA_COUNT; i++) {
377699353dSShannon Nelson 		struct nsim_sa *sap = &ipsec->sa[i];
387699353dSShannon Nelson 
397699353dSShannon Nelson 		if (!sap->used)
407699353dSShannon Nelson 			continue;
417699353dSShannon Nelson 
42*30472935SHangbin Liu 		if (sap->xs->props.family == AF_INET6)
432da222f6STakashi Iwai 			p += scnprintf(p, bufsize - (p - buf),
44*30472935SHangbin Liu 				       "sa[%i] %cx ipaddr=%pI6c\n",
45*30472935SHangbin Liu 				       i, (sap->rx ? 'r' : 't'), &sap->ipaddr);
46*30472935SHangbin Liu 		else
47*30472935SHangbin Liu 			p += scnprintf(p, bufsize - (p - buf),
48*30472935SHangbin Liu 				       "sa[%i] %cx ipaddr=%pI4\n",
49*30472935SHangbin Liu 				       i, (sap->rx ? 'r' : 't'), &sap->ipaddr[3]);
502da222f6STakashi Iwai 		p += scnprintf(p, bufsize - (p - buf),
517699353dSShannon Nelson 			       "sa[%i]    spi=0x%08x proto=0x%x salt=0x%08x crypt=%d\n",
527699353dSShannon Nelson 			       i, be32_to_cpu(sap->xs->id.spi),
537699353dSShannon Nelson 			       sap->xs->id.proto, sap->salt, sap->crypt);
542da222f6STakashi Iwai 		p += scnprintf(p, bufsize - (p - buf),
557699353dSShannon Nelson 			       "sa[%i]    key=0x%08x %08x %08x %08x\n",
567699353dSShannon Nelson 			       i, sap->key[0], sap->key[1],
577699353dSShannon Nelson 			       sap->key[2], sap->key[3]);
587699353dSShannon Nelson 	}
597699353dSShannon Nelson 
607699353dSShannon Nelson 	len = simple_read_from_buffer(buffer, count, ppos, buf, p - buf);
617699353dSShannon Nelson 
627699353dSShannon Nelson 	kfree(buf);
637699353dSShannon Nelson 	return len;
647699353dSShannon Nelson }
657699353dSShannon Nelson 
667699353dSShannon Nelson static const struct file_operations ipsec_dbg_fops = {
677699353dSShannon Nelson 	.owner = THIS_MODULE,
687699353dSShannon Nelson 	.open = simple_open,
697699353dSShannon Nelson 	.read = nsim_dbg_netdev_ops_read,
707699353dSShannon Nelson };
717699353dSShannon Nelson 
nsim_ipsec_find_empty_idx(struct nsim_ipsec * ipsec)727699353dSShannon Nelson static int nsim_ipsec_find_empty_idx(struct nsim_ipsec *ipsec)
737699353dSShannon Nelson {
747699353dSShannon Nelson 	u32 i;
757699353dSShannon Nelson 
767699353dSShannon Nelson 	if (ipsec->count == NSIM_IPSEC_MAX_SA_COUNT)
777699353dSShannon Nelson 		return -ENOSPC;
787699353dSShannon Nelson 
797699353dSShannon Nelson 	/* search sa table */
807699353dSShannon Nelson 	for (i = 0; i < NSIM_IPSEC_MAX_SA_COUNT; i++) {
817699353dSShannon Nelson 		if (!ipsec->sa[i].used)
827699353dSShannon Nelson 			return i;
837699353dSShannon Nelson 	}
847699353dSShannon Nelson 
857699353dSShannon Nelson 	return -ENOSPC;
867699353dSShannon Nelson }
877699353dSShannon Nelson 
nsim_ipsec_parse_proto_keys(struct xfrm_state * xs,u32 * mykey,u32 * mysalt)887699353dSShannon Nelson static int nsim_ipsec_parse_proto_keys(struct xfrm_state *xs,
897699353dSShannon Nelson 				       u32 *mykey, u32 *mysalt)
907699353dSShannon Nelson {
917699353dSShannon Nelson 	const char aes_gcm_name[] = "rfc4106(gcm(aes))";
9209adf756STaehee Yoo 	struct net_device *dev = xs->xso.real_dev;
937699353dSShannon Nelson 	unsigned char *key_data;
947699353dSShannon Nelson 	char *alg_name = NULL;
957699353dSShannon Nelson 	int key_len;
967699353dSShannon Nelson 
977699353dSShannon Nelson 	if (!xs->aead) {
987699353dSShannon Nelson 		netdev_err(dev, "Unsupported IPsec algorithm\n");
997699353dSShannon Nelson 		return -EINVAL;
1007699353dSShannon Nelson 	}
1017699353dSShannon Nelson 
1027699353dSShannon Nelson 	if (xs->aead->alg_icv_len != NSIM_IPSEC_AUTH_BITS) {
1037699353dSShannon Nelson 		netdev_err(dev, "IPsec offload requires %d bit authentication\n",
1047699353dSShannon Nelson 			   NSIM_IPSEC_AUTH_BITS);
1057699353dSShannon Nelson 		return -EINVAL;
1067699353dSShannon Nelson 	}
1077699353dSShannon Nelson 
1087699353dSShannon Nelson 	key_data = &xs->aead->alg_key[0];
1097699353dSShannon Nelson 	key_len = xs->aead->alg_key_len;
1107699353dSShannon Nelson 	alg_name = xs->aead->alg_name;
1117699353dSShannon Nelson 
1127699353dSShannon Nelson 	if (strcmp(alg_name, aes_gcm_name)) {
1137699353dSShannon Nelson 		netdev_err(dev, "Unsupported IPsec algorithm - please use %s\n",
1147699353dSShannon Nelson 			   aes_gcm_name);
1157699353dSShannon Nelson 		return -EINVAL;
1167699353dSShannon Nelson 	}
1177699353dSShannon Nelson 
1187699353dSShannon Nelson 	/* 160 accounts for 16 byte key and 4 byte salt */
1197699353dSShannon Nelson 	if (key_len > NSIM_IPSEC_AUTH_BITS) {
1207699353dSShannon Nelson 		*mysalt = ((u32 *)key_data)[4];
1217699353dSShannon Nelson 	} else if (key_len == NSIM_IPSEC_AUTH_BITS) {
1227699353dSShannon Nelson 		*mysalt = 0;
1237699353dSShannon Nelson 	} else {
1247699353dSShannon Nelson 		netdev_err(dev, "IPsec hw offload only supports 128 bit keys with optional 32 bit salt\n");
1257699353dSShannon Nelson 		return -EINVAL;
1267699353dSShannon Nelson 	}
1277699353dSShannon Nelson 	memcpy(mykey, key_data, 16);
1287699353dSShannon Nelson 
1297699353dSShannon Nelson 	return 0;
1307699353dSShannon Nelson }
1317699353dSShannon Nelson 
nsim_ipsec_add_sa(struct xfrm_state * xs,struct netlink_ext_ack * extack)1327681a4f5SLeon Romanovsky static int nsim_ipsec_add_sa(struct xfrm_state *xs,
1337681a4f5SLeon Romanovsky 			     struct netlink_ext_ack *extack)
1347699353dSShannon Nelson {
1357699353dSShannon Nelson 	struct nsim_ipsec *ipsec;
1367699353dSShannon Nelson 	struct net_device *dev;
1377699353dSShannon Nelson 	struct netdevsim *ns;
1387699353dSShannon Nelson 	struct nsim_sa sa;
1397699353dSShannon Nelson 	u16 sa_idx;
1407699353dSShannon Nelson 	int ret;
1417699353dSShannon Nelson 
14209adf756STaehee Yoo 	dev = xs->xso.real_dev;
1437699353dSShannon Nelson 	ns = netdev_priv(dev);
1447699353dSShannon Nelson 	ipsec = &ns->ipsec;
1457699353dSShannon Nelson 
1467699353dSShannon Nelson 	if (xs->id.proto != IPPROTO_ESP && xs->id.proto != IPPROTO_AH) {
1476c486979SLeon Romanovsky 		NL_SET_ERR_MSG_MOD(extack, "Unsupported protocol for ipsec offload");
1487699353dSShannon Nelson 		return -EINVAL;
1497699353dSShannon Nelson 	}
1507699353dSShannon Nelson 
1517699353dSShannon Nelson 	if (xs->calg) {
1526c486979SLeon Romanovsky 		NL_SET_ERR_MSG_MOD(extack, "Compression offload not supported");
1537699353dSShannon Nelson 		return -EINVAL;
1547699353dSShannon Nelson 	}
1557699353dSShannon Nelson 
15662f6eca5SLeon Romanovsky 	if (xs->xso.type != XFRM_DEV_OFFLOAD_CRYPTO) {
1576c486979SLeon Romanovsky 		NL_SET_ERR_MSG_MOD(extack, "Unsupported ipsec offload type");
15862f6eca5SLeon Romanovsky 		return -EINVAL;
15962f6eca5SLeon Romanovsky 	}
16062f6eca5SLeon Romanovsky 
1617699353dSShannon Nelson 	/* find the first unused index */
1627699353dSShannon Nelson 	ret = nsim_ipsec_find_empty_idx(ipsec);
1637699353dSShannon Nelson 	if (ret < 0) {
1646c486979SLeon Romanovsky 		NL_SET_ERR_MSG_MOD(extack, "No space for SA in Rx table!");
1657699353dSShannon Nelson 		return ret;
1667699353dSShannon Nelson 	}
1677699353dSShannon Nelson 	sa_idx = (u16)ret;
1687699353dSShannon Nelson 
1697699353dSShannon Nelson 	memset(&sa, 0, sizeof(sa));
1707699353dSShannon Nelson 	sa.used = true;
1717699353dSShannon Nelson 	sa.xs = xs;
1727699353dSShannon Nelson 
1737699353dSShannon Nelson 	if (sa.xs->id.proto & IPPROTO_ESP)
1747699353dSShannon Nelson 		sa.crypt = xs->ealg || xs->aead;
1757699353dSShannon Nelson 
1767699353dSShannon Nelson 	/* get the key and salt */
1777699353dSShannon Nelson 	ret = nsim_ipsec_parse_proto_keys(xs, sa.key, &sa.salt);
1787699353dSShannon Nelson 	if (ret) {
1796c486979SLeon Romanovsky 		NL_SET_ERR_MSG_MOD(extack, "Failed to get key data for SA table");
1807699353dSShannon Nelson 		return ret;
1817699353dSShannon Nelson 	}
1827699353dSShannon Nelson 
18380afbcbdSHangbin Liu 	if (xs->xso.dir == XFRM_DEV_OFFLOAD_IN)
1847699353dSShannon Nelson 		sa.rx = true;
1857699353dSShannon Nelson 
1867699353dSShannon Nelson 	if (xs->props.family == AF_INET6)
1877699353dSShannon Nelson 		memcpy(sa.ipaddr, &xs->id.daddr.a6, 16);
1887699353dSShannon Nelson 	else
1897699353dSShannon Nelson 		memcpy(&sa.ipaddr[3], &xs->id.daddr.a4, 4);
1907699353dSShannon Nelson 
1917699353dSShannon Nelson 	/* the preparations worked, so save the info */
1927699353dSShannon Nelson 	memcpy(&ipsec->sa[sa_idx], &sa, sizeof(sa));
1937699353dSShannon Nelson 
1947699353dSShannon Nelson 	/* the XFRM stack doesn't like offload_handle == 0,
1957699353dSShannon Nelson 	 * so add a bitflag in case our array index is 0
1967699353dSShannon Nelson 	 */
1977699353dSShannon Nelson 	xs->xso.offload_handle = sa_idx | NSIM_IPSEC_VALID;
1987699353dSShannon Nelson 	ipsec->count++;
1997699353dSShannon Nelson 
2007699353dSShannon Nelson 	return 0;
2017699353dSShannon Nelson }
2027699353dSShannon Nelson 
nsim_ipsec_del_sa(struct xfrm_state * xs)2037699353dSShannon Nelson static void nsim_ipsec_del_sa(struct xfrm_state *xs)
2047699353dSShannon Nelson {
20509adf756STaehee Yoo 	struct netdevsim *ns = netdev_priv(xs->xso.real_dev);
2067699353dSShannon Nelson 	struct nsim_ipsec *ipsec = &ns->ipsec;
2077699353dSShannon Nelson 	u16 sa_idx;
2087699353dSShannon Nelson 
2097699353dSShannon Nelson 	sa_idx = xs->xso.offload_handle & ~NSIM_IPSEC_VALID;
2107699353dSShannon Nelson 	if (!ipsec->sa[sa_idx].used) {
2117699353dSShannon Nelson 		netdev_err(ns->netdev, "Invalid SA for delete sa_idx=%d\n",
2127699353dSShannon Nelson 			   sa_idx);
2137699353dSShannon Nelson 		return;
2147699353dSShannon Nelson 	}
2157699353dSShannon Nelson 
2167699353dSShannon Nelson 	memset(&ipsec->sa[sa_idx], 0, sizeof(struct nsim_sa));
2177699353dSShannon Nelson 	ipsec->count--;
2187699353dSShannon Nelson }
2197699353dSShannon Nelson 
nsim_ipsec_offload_ok(struct sk_buff * skb,struct xfrm_state * xs)2207699353dSShannon Nelson static bool nsim_ipsec_offload_ok(struct sk_buff *skb, struct xfrm_state *xs)
2217699353dSShannon Nelson {
22209adf756STaehee Yoo 	struct netdevsim *ns = netdev_priv(xs->xso.real_dev);
2237699353dSShannon Nelson 	struct nsim_ipsec *ipsec = &ns->ipsec;
2247699353dSShannon Nelson 
2257699353dSShannon Nelson 	ipsec->ok++;
2267699353dSShannon Nelson 
2277699353dSShannon Nelson 	return true;
2287699353dSShannon Nelson }
2297699353dSShannon Nelson 
2307699353dSShannon Nelson static const struct xfrmdev_ops nsim_xfrmdev_ops = {
2317699353dSShannon Nelson 	.xdo_dev_state_add	= nsim_ipsec_add_sa,
2327699353dSShannon Nelson 	.xdo_dev_state_delete	= nsim_ipsec_del_sa,
2337699353dSShannon Nelson 	.xdo_dev_offload_ok	= nsim_ipsec_offload_ok,
2347699353dSShannon Nelson };
2357699353dSShannon Nelson 
nsim_ipsec_tx(struct netdevsim * ns,struct sk_buff * skb)2367699353dSShannon Nelson bool nsim_ipsec_tx(struct netdevsim *ns, struct sk_buff *skb)
2377699353dSShannon Nelson {
23856d1ac32SFlorian Westphal 	struct sec_path *sp = skb_sec_path(skb);
2397699353dSShannon Nelson 	struct nsim_ipsec *ipsec = &ns->ipsec;
2407699353dSShannon Nelson 	struct xfrm_state *xs;
2417699353dSShannon Nelson 	struct nsim_sa *tsa;
2427699353dSShannon Nelson 	u32 sa_idx;
2437699353dSShannon Nelson 
2447699353dSShannon Nelson 	/* do we even need to check this packet? */
24556d1ac32SFlorian Westphal 	if (!sp)
2467699353dSShannon Nelson 		return true;
2477699353dSShannon Nelson 
24856d1ac32SFlorian Westphal 	if (unlikely(!sp->len)) {
2497699353dSShannon Nelson 		netdev_err(ns->netdev, "no xfrm state len = %d\n",
25056d1ac32SFlorian Westphal 			   sp->len);
2517699353dSShannon Nelson 		return false;
2527699353dSShannon Nelson 	}
2537699353dSShannon Nelson 
2547699353dSShannon Nelson 	xs = xfrm_input_state(skb);
2557699353dSShannon Nelson 	if (unlikely(!xs)) {
2567699353dSShannon Nelson 		netdev_err(ns->netdev, "no xfrm_input_state() xs = %p\n", xs);
2577699353dSShannon Nelson 		return false;
2587699353dSShannon Nelson 	}
2597699353dSShannon Nelson 
2607699353dSShannon Nelson 	sa_idx = xs->xso.offload_handle & ~NSIM_IPSEC_VALID;
261c02462d8SColin Ian King 	if (unlikely(sa_idx >= NSIM_IPSEC_MAX_SA_COUNT)) {
2627699353dSShannon Nelson 		netdev_err(ns->netdev, "bad sa_idx=%d max=%d\n",
2637699353dSShannon Nelson 			   sa_idx, NSIM_IPSEC_MAX_SA_COUNT);
2647699353dSShannon Nelson 		return false;
2657699353dSShannon Nelson 	}
2667699353dSShannon Nelson 
2677699353dSShannon Nelson 	tsa = &ipsec->sa[sa_idx];
2687699353dSShannon Nelson 	if (unlikely(!tsa->used)) {
2697699353dSShannon Nelson 		netdev_err(ns->netdev, "unused sa_idx=%d\n", sa_idx);
2707699353dSShannon Nelson 		return false;
2717699353dSShannon Nelson 	}
2727699353dSShannon Nelson 
2737699353dSShannon Nelson 	if (xs->id.proto != IPPROTO_ESP && xs->id.proto != IPPROTO_AH) {
2747699353dSShannon Nelson 		netdev_err(ns->netdev, "unexpected proto=%d\n", xs->id.proto);
2757699353dSShannon Nelson 		return false;
2767699353dSShannon Nelson 	}
2777699353dSShannon Nelson 
2787699353dSShannon Nelson 	ipsec->tx++;
2797699353dSShannon Nelson 
2807699353dSShannon Nelson 	return true;
2817699353dSShannon Nelson }
2827699353dSShannon Nelson 
nsim_ipsec_init(struct netdevsim * ns)2837699353dSShannon Nelson void nsim_ipsec_init(struct netdevsim *ns)
2847699353dSShannon Nelson {
2857699353dSShannon Nelson 	ns->netdev->xfrmdev_ops = &nsim_xfrmdev_ops;
2867699353dSShannon Nelson 
2877699353dSShannon Nelson #define NSIM_ESP_FEATURES	(NETIF_F_HW_ESP | \
2887699353dSShannon Nelson 				 NETIF_F_HW_ESP_TX_CSUM | \
2897699353dSShannon Nelson 				 NETIF_F_GSO_ESP)
2907699353dSShannon Nelson 
2917699353dSShannon Nelson 	ns->netdev->features |= NSIM_ESP_FEATURES;
2927699353dSShannon Nelson 	ns->netdev->hw_enc_features |= NSIM_ESP_FEATURES;
2937699353dSShannon Nelson 
294e05b2d14SJiri Pirko 	ns->ipsec.pfile = debugfs_create_file("ipsec", 0400,
295e05b2d14SJiri Pirko 					      ns->nsim_dev_port->ddir, ns,
2967699353dSShannon Nelson 					      &ipsec_dbg_fops);
2977699353dSShannon Nelson }
2987699353dSShannon Nelson 
nsim_ipsec_teardown(struct netdevsim * ns)2997699353dSShannon Nelson void nsim_ipsec_teardown(struct netdevsim *ns)
3007699353dSShannon Nelson {
3017699353dSShannon Nelson 	struct nsim_ipsec *ipsec = &ns->ipsec;
3027699353dSShannon Nelson 
3037699353dSShannon Nelson 	if (ipsec->count)
3047699353dSShannon Nelson 		netdev_err(ns->netdev, "tearing down IPsec offload with %d SAs left\n",
3057699353dSShannon Nelson 			   ipsec->count);
3067699353dSShannon Nelson 	debugfs_remove_recursive(ipsec->pfile);
3077699353dSShannon Nelson }
308