162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* Copyright(c) 2018 Oracle and/or its affiliates. All rights reserved. */ 362306a36Sopenharmony_ci 462306a36Sopenharmony_ci#include <crypto/aead.h> 562306a36Sopenharmony_ci#include <linux/debugfs.h> 662306a36Sopenharmony_ci#include <net/xfrm.h> 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include "netdevsim.h" 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#define NSIM_IPSEC_AUTH_BITS 128 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_cistatic ssize_t nsim_dbg_netdev_ops_read(struct file *filp, 1362306a36Sopenharmony_ci char __user *buffer, 1462306a36Sopenharmony_ci size_t count, loff_t *ppos) 1562306a36Sopenharmony_ci{ 1662306a36Sopenharmony_ci struct netdevsim *ns = filp->private_data; 1762306a36Sopenharmony_ci struct nsim_ipsec *ipsec = &ns->ipsec; 1862306a36Sopenharmony_ci size_t bufsize; 1962306a36Sopenharmony_ci char *buf, *p; 2062306a36Sopenharmony_ci int len; 2162306a36Sopenharmony_ci int i; 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci /* the buffer needed is 2462306a36Sopenharmony_ci * (num SAs * 3 lines each * ~60 bytes per line) + one more line 2562306a36Sopenharmony_ci */ 2662306a36Sopenharmony_ci bufsize = (ipsec->count * 4 * 60) + 60; 2762306a36Sopenharmony_ci buf = kzalloc(bufsize, GFP_KERNEL); 2862306a36Sopenharmony_ci if (!buf) 2962306a36Sopenharmony_ci return -ENOMEM; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci p = buf; 3262306a36Sopenharmony_ci p += scnprintf(p, bufsize - (p - buf), 3362306a36Sopenharmony_ci "SA count=%u tx=%u\n", 3462306a36Sopenharmony_ci ipsec->count, ipsec->tx); 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci for (i = 0; i < NSIM_IPSEC_MAX_SA_COUNT; i++) { 3762306a36Sopenharmony_ci struct nsim_sa *sap = &ipsec->sa[i]; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci if (!sap->used) 4062306a36Sopenharmony_ci continue; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci p += scnprintf(p, bufsize - (p - buf), 4362306a36Sopenharmony_ci "sa[%i] %cx ipaddr=0x%08x %08x %08x %08x\n", 4462306a36Sopenharmony_ci i, (sap->rx ? 'r' : 't'), sap->ipaddr[0], 4562306a36Sopenharmony_ci sap->ipaddr[1], sap->ipaddr[2], sap->ipaddr[3]); 4662306a36Sopenharmony_ci p += scnprintf(p, bufsize - (p - buf), 4762306a36Sopenharmony_ci "sa[%i] spi=0x%08x proto=0x%x salt=0x%08x crypt=%d\n", 4862306a36Sopenharmony_ci i, be32_to_cpu(sap->xs->id.spi), 4962306a36Sopenharmony_ci sap->xs->id.proto, sap->salt, sap->crypt); 5062306a36Sopenharmony_ci p += scnprintf(p, bufsize - (p - buf), 5162306a36Sopenharmony_ci "sa[%i] key=0x%08x %08x %08x %08x\n", 5262306a36Sopenharmony_ci i, sap->key[0], sap->key[1], 5362306a36Sopenharmony_ci sap->key[2], sap->key[3]); 5462306a36Sopenharmony_ci } 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci len = simple_read_from_buffer(buffer, count, ppos, buf, p - buf); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci kfree(buf); 5962306a36Sopenharmony_ci return len; 6062306a36Sopenharmony_ci} 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistatic const struct file_operations ipsec_dbg_fops = { 6362306a36Sopenharmony_ci .owner = THIS_MODULE, 6462306a36Sopenharmony_ci .open = simple_open, 6562306a36Sopenharmony_ci .read = nsim_dbg_netdev_ops_read, 6662306a36Sopenharmony_ci}; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic int nsim_ipsec_find_empty_idx(struct nsim_ipsec *ipsec) 6962306a36Sopenharmony_ci{ 7062306a36Sopenharmony_ci u32 i; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci if (ipsec->count == NSIM_IPSEC_MAX_SA_COUNT) 7362306a36Sopenharmony_ci return -ENOSPC; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci /* search sa table */ 7662306a36Sopenharmony_ci for (i = 0; i < NSIM_IPSEC_MAX_SA_COUNT; i++) { 7762306a36Sopenharmony_ci if (!ipsec->sa[i].used) 7862306a36Sopenharmony_ci return i; 7962306a36Sopenharmony_ci } 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci return -ENOSPC; 8262306a36Sopenharmony_ci} 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic int nsim_ipsec_parse_proto_keys(struct xfrm_state *xs, 8562306a36Sopenharmony_ci u32 *mykey, u32 *mysalt) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci const char aes_gcm_name[] = "rfc4106(gcm(aes))"; 8862306a36Sopenharmony_ci struct net_device *dev = xs->xso.real_dev; 8962306a36Sopenharmony_ci unsigned char *key_data; 9062306a36Sopenharmony_ci char *alg_name = NULL; 9162306a36Sopenharmony_ci int key_len; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci if (!xs->aead) { 9462306a36Sopenharmony_ci netdev_err(dev, "Unsupported IPsec algorithm\n"); 9562306a36Sopenharmony_ci return -EINVAL; 9662306a36Sopenharmony_ci } 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci if (xs->aead->alg_icv_len != NSIM_IPSEC_AUTH_BITS) { 9962306a36Sopenharmony_ci netdev_err(dev, "IPsec offload requires %d bit authentication\n", 10062306a36Sopenharmony_ci NSIM_IPSEC_AUTH_BITS); 10162306a36Sopenharmony_ci return -EINVAL; 10262306a36Sopenharmony_ci } 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci key_data = &xs->aead->alg_key[0]; 10562306a36Sopenharmony_ci key_len = xs->aead->alg_key_len; 10662306a36Sopenharmony_ci alg_name = xs->aead->alg_name; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci if (strcmp(alg_name, aes_gcm_name)) { 10962306a36Sopenharmony_ci netdev_err(dev, "Unsupported IPsec algorithm - please use %s\n", 11062306a36Sopenharmony_ci aes_gcm_name); 11162306a36Sopenharmony_ci return -EINVAL; 11262306a36Sopenharmony_ci } 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci /* 160 accounts for 16 byte key and 4 byte salt */ 11562306a36Sopenharmony_ci if (key_len > NSIM_IPSEC_AUTH_BITS) { 11662306a36Sopenharmony_ci *mysalt = ((u32 *)key_data)[4]; 11762306a36Sopenharmony_ci } else if (key_len == NSIM_IPSEC_AUTH_BITS) { 11862306a36Sopenharmony_ci *mysalt = 0; 11962306a36Sopenharmony_ci } else { 12062306a36Sopenharmony_ci netdev_err(dev, "IPsec hw offload only supports 128 bit keys with optional 32 bit salt\n"); 12162306a36Sopenharmony_ci return -EINVAL; 12262306a36Sopenharmony_ci } 12362306a36Sopenharmony_ci memcpy(mykey, key_data, 16); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci return 0; 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cistatic int nsim_ipsec_add_sa(struct xfrm_state *xs, 12962306a36Sopenharmony_ci struct netlink_ext_ack *extack) 13062306a36Sopenharmony_ci{ 13162306a36Sopenharmony_ci struct nsim_ipsec *ipsec; 13262306a36Sopenharmony_ci struct net_device *dev; 13362306a36Sopenharmony_ci struct netdevsim *ns; 13462306a36Sopenharmony_ci struct nsim_sa sa; 13562306a36Sopenharmony_ci u16 sa_idx; 13662306a36Sopenharmony_ci int ret; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci dev = xs->xso.real_dev; 13962306a36Sopenharmony_ci ns = netdev_priv(dev); 14062306a36Sopenharmony_ci ipsec = &ns->ipsec; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci if (xs->id.proto != IPPROTO_ESP && xs->id.proto != IPPROTO_AH) { 14362306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Unsupported protocol for ipsec offload"); 14462306a36Sopenharmony_ci return -EINVAL; 14562306a36Sopenharmony_ci } 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci if (xs->calg) { 14862306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Compression offload not supported"); 14962306a36Sopenharmony_ci return -EINVAL; 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci if (xs->xso.type != XFRM_DEV_OFFLOAD_CRYPTO) { 15362306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Unsupported ipsec offload type"); 15462306a36Sopenharmony_ci return -EINVAL; 15562306a36Sopenharmony_ci } 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci /* find the first unused index */ 15862306a36Sopenharmony_ci ret = nsim_ipsec_find_empty_idx(ipsec); 15962306a36Sopenharmony_ci if (ret < 0) { 16062306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "No space for SA in Rx table!"); 16162306a36Sopenharmony_ci return ret; 16262306a36Sopenharmony_ci } 16362306a36Sopenharmony_ci sa_idx = (u16)ret; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci memset(&sa, 0, sizeof(sa)); 16662306a36Sopenharmony_ci sa.used = true; 16762306a36Sopenharmony_ci sa.xs = xs; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci if (sa.xs->id.proto & IPPROTO_ESP) 17062306a36Sopenharmony_ci sa.crypt = xs->ealg || xs->aead; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci /* get the key and salt */ 17362306a36Sopenharmony_ci ret = nsim_ipsec_parse_proto_keys(xs, sa.key, &sa.salt); 17462306a36Sopenharmony_ci if (ret) { 17562306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Failed to get key data for SA table"); 17662306a36Sopenharmony_ci return ret; 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci if (xs->xso.dir == XFRM_DEV_OFFLOAD_IN) { 18062306a36Sopenharmony_ci sa.rx = true; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci if (xs->props.family == AF_INET6) 18362306a36Sopenharmony_ci memcpy(sa.ipaddr, &xs->id.daddr.a6, 16); 18462306a36Sopenharmony_ci else 18562306a36Sopenharmony_ci memcpy(&sa.ipaddr[3], &xs->id.daddr.a4, 4); 18662306a36Sopenharmony_ci } 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci /* the preparations worked, so save the info */ 18962306a36Sopenharmony_ci memcpy(&ipsec->sa[sa_idx], &sa, sizeof(sa)); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci /* the XFRM stack doesn't like offload_handle == 0, 19262306a36Sopenharmony_ci * so add a bitflag in case our array index is 0 19362306a36Sopenharmony_ci */ 19462306a36Sopenharmony_ci xs->xso.offload_handle = sa_idx | NSIM_IPSEC_VALID; 19562306a36Sopenharmony_ci ipsec->count++; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci return 0; 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cistatic void nsim_ipsec_del_sa(struct xfrm_state *xs) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci struct netdevsim *ns = netdev_priv(xs->xso.real_dev); 20362306a36Sopenharmony_ci struct nsim_ipsec *ipsec = &ns->ipsec; 20462306a36Sopenharmony_ci u16 sa_idx; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci sa_idx = xs->xso.offload_handle & ~NSIM_IPSEC_VALID; 20762306a36Sopenharmony_ci if (!ipsec->sa[sa_idx].used) { 20862306a36Sopenharmony_ci netdev_err(ns->netdev, "Invalid SA for delete sa_idx=%d\n", 20962306a36Sopenharmony_ci sa_idx); 21062306a36Sopenharmony_ci return; 21162306a36Sopenharmony_ci } 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci memset(&ipsec->sa[sa_idx], 0, sizeof(struct nsim_sa)); 21462306a36Sopenharmony_ci ipsec->count--; 21562306a36Sopenharmony_ci} 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_cistatic bool nsim_ipsec_offload_ok(struct sk_buff *skb, struct xfrm_state *xs) 21862306a36Sopenharmony_ci{ 21962306a36Sopenharmony_ci struct netdevsim *ns = netdev_priv(xs->xso.real_dev); 22062306a36Sopenharmony_ci struct nsim_ipsec *ipsec = &ns->ipsec; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci ipsec->ok++; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci return true; 22562306a36Sopenharmony_ci} 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_cistatic const struct xfrmdev_ops nsim_xfrmdev_ops = { 22862306a36Sopenharmony_ci .xdo_dev_state_add = nsim_ipsec_add_sa, 22962306a36Sopenharmony_ci .xdo_dev_state_delete = nsim_ipsec_del_sa, 23062306a36Sopenharmony_ci .xdo_dev_offload_ok = nsim_ipsec_offload_ok, 23162306a36Sopenharmony_ci}; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_cibool nsim_ipsec_tx(struct netdevsim *ns, struct sk_buff *skb) 23462306a36Sopenharmony_ci{ 23562306a36Sopenharmony_ci struct sec_path *sp = skb_sec_path(skb); 23662306a36Sopenharmony_ci struct nsim_ipsec *ipsec = &ns->ipsec; 23762306a36Sopenharmony_ci struct xfrm_state *xs; 23862306a36Sopenharmony_ci struct nsim_sa *tsa; 23962306a36Sopenharmony_ci u32 sa_idx; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci /* do we even need to check this packet? */ 24262306a36Sopenharmony_ci if (!sp) 24362306a36Sopenharmony_ci return true; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci if (unlikely(!sp->len)) { 24662306a36Sopenharmony_ci netdev_err(ns->netdev, "no xfrm state len = %d\n", 24762306a36Sopenharmony_ci sp->len); 24862306a36Sopenharmony_ci return false; 24962306a36Sopenharmony_ci } 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci xs = xfrm_input_state(skb); 25262306a36Sopenharmony_ci if (unlikely(!xs)) { 25362306a36Sopenharmony_ci netdev_err(ns->netdev, "no xfrm_input_state() xs = %p\n", xs); 25462306a36Sopenharmony_ci return false; 25562306a36Sopenharmony_ci } 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci sa_idx = xs->xso.offload_handle & ~NSIM_IPSEC_VALID; 25862306a36Sopenharmony_ci if (unlikely(sa_idx >= NSIM_IPSEC_MAX_SA_COUNT)) { 25962306a36Sopenharmony_ci netdev_err(ns->netdev, "bad sa_idx=%d max=%d\n", 26062306a36Sopenharmony_ci sa_idx, NSIM_IPSEC_MAX_SA_COUNT); 26162306a36Sopenharmony_ci return false; 26262306a36Sopenharmony_ci } 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci tsa = &ipsec->sa[sa_idx]; 26562306a36Sopenharmony_ci if (unlikely(!tsa->used)) { 26662306a36Sopenharmony_ci netdev_err(ns->netdev, "unused sa_idx=%d\n", sa_idx); 26762306a36Sopenharmony_ci return false; 26862306a36Sopenharmony_ci } 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci if (xs->id.proto != IPPROTO_ESP && xs->id.proto != IPPROTO_AH) { 27162306a36Sopenharmony_ci netdev_err(ns->netdev, "unexpected proto=%d\n", xs->id.proto); 27262306a36Sopenharmony_ci return false; 27362306a36Sopenharmony_ci } 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci ipsec->tx++; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci return true; 27862306a36Sopenharmony_ci} 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_civoid nsim_ipsec_init(struct netdevsim *ns) 28162306a36Sopenharmony_ci{ 28262306a36Sopenharmony_ci ns->netdev->xfrmdev_ops = &nsim_xfrmdev_ops; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci#define NSIM_ESP_FEATURES (NETIF_F_HW_ESP | \ 28562306a36Sopenharmony_ci NETIF_F_HW_ESP_TX_CSUM | \ 28662306a36Sopenharmony_ci NETIF_F_GSO_ESP) 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci ns->netdev->features |= NSIM_ESP_FEATURES; 28962306a36Sopenharmony_ci ns->netdev->hw_enc_features |= NSIM_ESP_FEATURES; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci ns->ipsec.pfile = debugfs_create_file("ipsec", 0400, 29262306a36Sopenharmony_ci ns->nsim_dev_port->ddir, ns, 29362306a36Sopenharmony_ci &ipsec_dbg_fops); 29462306a36Sopenharmony_ci} 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_civoid nsim_ipsec_teardown(struct netdevsim *ns) 29762306a36Sopenharmony_ci{ 29862306a36Sopenharmony_ci struct nsim_ipsec *ipsec = &ns->ipsec; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci if (ipsec->count) 30162306a36Sopenharmony_ci netdev_err(ns->netdev, "tearing down IPsec offload with %d SAs left\n", 30262306a36Sopenharmony_ci ipsec->count); 30362306a36Sopenharmony_ci debugfs_remove_recursive(ipsec->pfile); 30462306a36Sopenharmony_ci} 305