162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * xfrm_device.c - IPsec device offloading code. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2015 secunet Security Networks AG 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author: 862306a36Sopenharmony_ci * Steffen Klassert <steffen.klassert@secunet.com> 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/errno.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/netdevice.h> 1462306a36Sopenharmony_ci#include <linux/skbuff.h> 1562306a36Sopenharmony_ci#include <linux/slab.h> 1662306a36Sopenharmony_ci#include <linux/spinlock.h> 1762306a36Sopenharmony_ci#include <net/dst.h> 1862306a36Sopenharmony_ci#include <net/gso.h> 1962306a36Sopenharmony_ci#include <net/xfrm.h> 2062306a36Sopenharmony_ci#include <linux/notifier.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#ifdef CONFIG_XFRM_OFFLOAD 2362306a36Sopenharmony_cistatic void __xfrm_transport_prep(struct xfrm_state *x, struct sk_buff *skb, 2462306a36Sopenharmony_ci unsigned int hsize) 2562306a36Sopenharmony_ci{ 2662306a36Sopenharmony_ci struct xfrm_offload *xo = xfrm_offload(skb); 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci skb_reset_mac_len(skb); 2962306a36Sopenharmony_ci if (xo->flags & XFRM_GSO_SEGMENT) 3062306a36Sopenharmony_ci skb->transport_header -= x->props.header_len; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci pskb_pull(skb, skb_transport_offset(skb) + x->props.header_len); 3362306a36Sopenharmony_ci} 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistatic void __xfrm_mode_tunnel_prep(struct xfrm_state *x, struct sk_buff *skb, 3662306a36Sopenharmony_ci unsigned int hsize) 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci{ 3962306a36Sopenharmony_ci struct xfrm_offload *xo = xfrm_offload(skb); 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci if (xo->flags & XFRM_GSO_SEGMENT) 4262306a36Sopenharmony_ci skb->transport_header = skb->network_header + hsize; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci skb_reset_mac_len(skb); 4562306a36Sopenharmony_ci pskb_pull(skb, skb->mac_len + x->props.header_len); 4662306a36Sopenharmony_ci} 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic void __xfrm_mode_beet_prep(struct xfrm_state *x, struct sk_buff *skb, 4962306a36Sopenharmony_ci unsigned int hsize) 5062306a36Sopenharmony_ci{ 5162306a36Sopenharmony_ci struct xfrm_offload *xo = xfrm_offload(skb); 5262306a36Sopenharmony_ci int phlen = 0; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci if (xo->flags & XFRM_GSO_SEGMENT) 5562306a36Sopenharmony_ci skb->transport_header = skb->network_header + hsize; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci skb_reset_mac_len(skb); 5862306a36Sopenharmony_ci if (x->sel.family != AF_INET6) { 5962306a36Sopenharmony_ci phlen = IPV4_BEET_PHMAXLEN; 6062306a36Sopenharmony_ci if (x->outer_mode.family == AF_INET6) 6162306a36Sopenharmony_ci phlen += sizeof(struct ipv6hdr) - sizeof(struct iphdr); 6262306a36Sopenharmony_ci } 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci pskb_pull(skb, skb->mac_len + hsize + (x->props.header_len - phlen)); 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci/* Adjust pointers into the packet when IPsec is done at layer2 */ 6862306a36Sopenharmony_cistatic void xfrm_outer_mode_prep(struct xfrm_state *x, struct sk_buff *skb) 6962306a36Sopenharmony_ci{ 7062306a36Sopenharmony_ci switch (x->outer_mode.encap) { 7162306a36Sopenharmony_ci case XFRM_MODE_TUNNEL: 7262306a36Sopenharmony_ci if (x->outer_mode.family == AF_INET) 7362306a36Sopenharmony_ci return __xfrm_mode_tunnel_prep(x, skb, 7462306a36Sopenharmony_ci sizeof(struct iphdr)); 7562306a36Sopenharmony_ci if (x->outer_mode.family == AF_INET6) 7662306a36Sopenharmony_ci return __xfrm_mode_tunnel_prep(x, skb, 7762306a36Sopenharmony_ci sizeof(struct ipv6hdr)); 7862306a36Sopenharmony_ci break; 7962306a36Sopenharmony_ci case XFRM_MODE_TRANSPORT: 8062306a36Sopenharmony_ci if (x->outer_mode.family == AF_INET) 8162306a36Sopenharmony_ci return __xfrm_transport_prep(x, skb, 8262306a36Sopenharmony_ci sizeof(struct iphdr)); 8362306a36Sopenharmony_ci if (x->outer_mode.family == AF_INET6) 8462306a36Sopenharmony_ci return __xfrm_transport_prep(x, skb, 8562306a36Sopenharmony_ci sizeof(struct ipv6hdr)); 8662306a36Sopenharmony_ci break; 8762306a36Sopenharmony_ci case XFRM_MODE_BEET: 8862306a36Sopenharmony_ci if (x->outer_mode.family == AF_INET) 8962306a36Sopenharmony_ci return __xfrm_mode_beet_prep(x, skb, 9062306a36Sopenharmony_ci sizeof(struct iphdr)); 9162306a36Sopenharmony_ci if (x->outer_mode.family == AF_INET6) 9262306a36Sopenharmony_ci return __xfrm_mode_beet_prep(x, skb, 9362306a36Sopenharmony_ci sizeof(struct ipv6hdr)); 9462306a36Sopenharmony_ci break; 9562306a36Sopenharmony_ci case XFRM_MODE_ROUTEOPTIMIZATION: 9662306a36Sopenharmony_ci case XFRM_MODE_IN_TRIGGER: 9762306a36Sopenharmony_ci break; 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci} 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_cistatic inline bool xmit_xfrm_check_overflow(struct sk_buff *skb) 10262306a36Sopenharmony_ci{ 10362306a36Sopenharmony_ci struct xfrm_offload *xo = xfrm_offload(skb); 10462306a36Sopenharmony_ci __u32 seq = xo->seq.low; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci seq += skb_shinfo(skb)->gso_segs; 10762306a36Sopenharmony_ci if (unlikely(seq < xo->seq.low)) 10862306a36Sopenharmony_ci return true; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci return false; 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_cistruct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t features, bool *again) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci int err; 11662306a36Sopenharmony_ci unsigned long flags; 11762306a36Sopenharmony_ci struct xfrm_state *x; 11862306a36Sopenharmony_ci struct softnet_data *sd; 11962306a36Sopenharmony_ci struct sk_buff *skb2, *nskb, *pskb = NULL; 12062306a36Sopenharmony_ci netdev_features_t esp_features = features; 12162306a36Sopenharmony_ci struct xfrm_offload *xo = xfrm_offload(skb); 12262306a36Sopenharmony_ci struct net_device *dev = skb->dev; 12362306a36Sopenharmony_ci struct sec_path *sp; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci if (!xo || (xo->flags & XFRM_XMIT)) 12662306a36Sopenharmony_ci return skb; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci if (!(features & NETIF_F_HW_ESP)) 12962306a36Sopenharmony_ci esp_features = features & ~(NETIF_F_SG | NETIF_F_CSUM_MASK); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci sp = skb_sec_path(skb); 13262306a36Sopenharmony_ci x = sp->xvec[sp->len - 1]; 13362306a36Sopenharmony_ci if (xo->flags & XFRM_GRO || x->xso.dir == XFRM_DEV_OFFLOAD_IN) 13462306a36Sopenharmony_ci return skb; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci /* The packet was sent to HW IPsec packet offload engine, 13762306a36Sopenharmony_ci * but to wrong device. Drop the packet, so it won't skip 13862306a36Sopenharmony_ci * XFRM stack. 13962306a36Sopenharmony_ci */ 14062306a36Sopenharmony_ci if (x->xso.type == XFRM_DEV_OFFLOAD_PACKET && x->xso.dev != dev) { 14162306a36Sopenharmony_ci kfree_skb(skb); 14262306a36Sopenharmony_ci dev_core_stats_tx_dropped_inc(dev); 14362306a36Sopenharmony_ci return NULL; 14462306a36Sopenharmony_ci } 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci /* This skb was already validated on the upper/virtual dev */ 14762306a36Sopenharmony_ci if ((x->xso.dev != dev) && (x->xso.real_dev == dev)) 14862306a36Sopenharmony_ci return skb; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci local_irq_save(flags); 15162306a36Sopenharmony_ci sd = this_cpu_ptr(&softnet_data); 15262306a36Sopenharmony_ci err = !skb_queue_empty(&sd->xfrm_backlog); 15362306a36Sopenharmony_ci local_irq_restore(flags); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci if (err) { 15662306a36Sopenharmony_ci *again = true; 15762306a36Sopenharmony_ci return skb; 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci if (skb_is_gso(skb) && (unlikely(x->xso.dev != dev) || 16162306a36Sopenharmony_ci unlikely(xmit_xfrm_check_overflow(skb)))) { 16262306a36Sopenharmony_ci struct sk_buff *segs; 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci /* Packet got rerouted, fixup features and segment it. */ 16562306a36Sopenharmony_ci esp_features = esp_features & ~(NETIF_F_HW_ESP | NETIF_F_GSO_ESP); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci segs = skb_gso_segment(skb, esp_features); 16862306a36Sopenharmony_ci if (IS_ERR(segs)) { 16962306a36Sopenharmony_ci kfree_skb(skb); 17062306a36Sopenharmony_ci dev_core_stats_tx_dropped_inc(dev); 17162306a36Sopenharmony_ci return NULL; 17262306a36Sopenharmony_ci } else { 17362306a36Sopenharmony_ci consume_skb(skb); 17462306a36Sopenharmony_ci skb = segs; 17562306a36Sopenharmony_ci } 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci if (!skb->next) { 17962306a36Sopenharmony_ci esp_features |= skb->dev->gso_partial_features; 18062306a36Sopenharmony_ci xfrm_outer_mode_prep(x, skb); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci xo->flags |= XFRM_DEV_RESUME; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci err = x->type_offload->xmit(x, skb, esp_features); 18562306a36Sopenharmony_ci if (err) { 18662306a36Sopenharmony_ci if (err == -EINPROGRESS) 18762306a36Sopenharmony_ci return NULL; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci XFRM_INC_STATS(xs_net(x), LINUX_MIB_XFRMOUTSTATEPROTOERROR); 19062306a36Sopenharmony_ci kfree_skb(skb); 19162306a36Sopenharmony_ci return NULL; 19262306a36Sopenharmony_ci } 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci skb_push(skb, skb->data - skb_mac_header(skb)); 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci return skb; 19762306a36Sopenharmony_ci } 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci skb_list_walk_safe(skb, skb2, nskb) { 20062306a36Sopenharmony_ci esp_features |= skb->dev->gso_partial_features; 20162306a36Sopenharmony_ci skb_mark_not_on_list(skb2); 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci xo = xfrm_offload(skb2); 20462306a36Sopenharmony_ci xo->flags |= XFRM_DEV_RESUME; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci xfrm_outer_mode_prep(x, skb2); 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci err = x->type_offload->xmit(x, skb2, esp_features); 20962306a36Sopenharmony_ci if (!err) { 21062306a36Sopenharmony_ci skb2->next = nskb; 21162306a36Sopenharmony_ci } else if (err != -EINPROGRESS) { 21262306a36Sopenharmony_ci XFRM_INC_STATS(xs_net(x), LINUX_MIB_XFRMOUTSTATEPROTOERROR); 21362306a36Sopenharmony_ci skb2->next = nskb; 21462306a36Sopenharmony_ci kfree_skb_list(skb2); 21562306a36Sopenharmony_ci return NULL; 21662306a36Sopenharmony_ci } else { 21762306a36Sopenharmony_ci if (skb == skb2) 21862306a36Sopenharmony_ci skb = nskb; 21962306a36Sopenharmony_ci else 22062306a36Sopenharmony_ci pskb->next = nskb; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci continue; 22362306a36Sopenharmony_ci } 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci skb_push(skb2, skb2->data - skb_mac_header(skb2)); 22662306a36Sopenharmony_ci pskb = skb2; 22762306a36Sopenharmony_ci } 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci return skb; 23062306a36Sopenharmony_ci} 23162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(validate_xmit_xfrm); 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ciint xfrm_dev_state_add(struct net *net, struct xfrm_state *x, 23462306a36Sopenharmony_ci struct xfrm_user_offload *xuo, 23562306a36Sopenharmony_ci struct netlink_ext_ack *extack) 23662306a36Sopenharmony_ci{ 23762306a36Sopenharmony_ci int err; 23862306a36Sopenharmony_ci struct dst_entry *dst; 23962306a36Sopenharmony_ci struct net_device *dev; 24062306a36Sopenharmony_ci struct xfrm_dev_offload *xso = &x->xso; 24162306a36Sopenharmony_ci xfrm_address_t *saddr; 24262306a36Sopenharmony_ci xfrm_address_t *daddr; 24362306a36Sopenharmony_ci bool is_packet_offload; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci if (!x->type_offload) { 24662306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Type doesn't support offload"); 24762306a36Sopenharmony_ci return -EINVAL; 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci if (xuo->flags & 25162306a36Sopenharmony_ci ~(XFRM_OFFLOAD_IPV6 | XFRM_OFFLOAD_INBOUND | XFRM_OFFLOAD_PACKET)) { 25262306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Unrecognized flags in offload request"); 25362306a36Sopenharmony_ci return -EINVAL; 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci is_packet_offload = xuo->flags & XFRM_OFFLOAD_PACKET; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci /* We don't yet support UDP encapsulation and TFC padding. */ 25962306a36Sopenharmony_ci if ((!is_packet_offload && x->encap) || x->tfcpad) { 26062306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Encapsulation and TFC padding can't be offloaded"); 26162306a36Sopenharmony_ci return -EINVAL; 26262306a36Sopenharmony_ci } 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci dev = dev_get_by_index(net, xuo->ifindex); 26562306a36Sopenharmony_ci if (!dev) { 26662306a36Sopenharmony_ci if (!(xuo->flags & XFRM_OFFLOAD_INBOUND)) { 26762306a36Sopenharmony_ci saddr = &x->props.saddr; 26862306a36Sopenharmony_ci daddr = &x->id.daddr; 26962306a36Sopenharmony_ci } else { 27062306a36Sopenharmony_ci saddr = &x->id.daddr; 27162306a36Sopenharmony_ci daddr = &x->props.saddr; 27262306a36Sopenharmony_ci } 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci dst = __xfrm_dst_lookup(net, 0, 0, saddr, daddr, 27562306a36Sopenharmony_ci x->props.family, 27662306a36Sopenharmony_ci xfrm_smark_get(0, x)); 27762306a36Sopenharmony_ci if (IS_ERR(dst)) 27862306a36Sopenharmony_ci return (is_packet_offload) ? -EINVAL : 0; 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci dev = dst->dev; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci dev_hold(dev); 28362306a36Sopenharmony_ci dst_release(dst); 28462306a36Sopenharmony_ci } 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci if (!dev->xfrmdev_ops || !dev->xfrmdev_ops->xdo_dev_state_add) { 28762306a36Sopenharmony_ci xso->dev = NULL; 28862306a36Sopenharmony_ci dev_put(dev); 28962306a36Sopenharmony_ci return (is_packet_offload) ? -EINVAL : 0; 29062306a36Sopenharmony_ci } 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci if (!is_packet_offload && x->props.flags & XFRM_STATE_ESN && 29362306a36Sopenharmony_ci !dev->xfrmdev_ops->xdo_dev_state_advance_esn) { 29462306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Device doesn't support offload with ESN"); 29562306a36Sopenharmony_ci xso->dev = NULL; 29662306a36Sopenharmony_ci dev_put(dev); 29762306a36Sopenharmony_ci return -EINVAL; 29862306a36Sopenharmony_ci } 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci xso->dev = dev; 30162306a36Sopenharmony_ci netdev_tracker_alloc(dev, &xso->dev_tracker, GFP_ATOMIC); 30262306a36Sopenharmony_ci xso->real_dev = dev; 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci if (xuo->flags & XFRM_OFFLOAD_INBOUND) 30562306a36Sopenharmony_ci xso->dir = XFRM_DEV_OFFLOAD_IN; 30662306a36Sopenharmony_ci else 30762306a36Sopenharmony_ci xso->dir = XFRM_DEV_OFFLOAD_OUT; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci if (is_packet_offload) 31062306a36Sopenharmony_ci xso->type = XFRM_DEV_OFFLOAD_PACKET; 31162306a36Sopenharmony_ci else 31262306a36Sopenharmony_ci xso->type = XFRM_DEV_OFFLOAD_CRYPTO; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci err = dev->xfrmdev_ops->xdo_dev_state_add(x, extack); 31562306a36Sopenharmony_ci if (err) { 31662306a36Sopenharmony_ci xso->dev = NULL; 31762306a36Sopenharmony_ci xso->dir = 0; 31862306a36Sopenharmony_ci xso->real_dev = NULL; 31962306a36Sopenharmony_ci netdev_put(dev, &xso->dev_tracker); 32062306a36Sopenharmony_ci xso->type = XFRM_DEV_OFFLOAD_UNSPECIFIED; 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci /* User explicitly requested packet offload mode and configured 32362306a36Sopenharmony_ci * policy in addition to the XFRM state. So be civil to users, 32462306a36Sopenharmony_ci * and return an error instead of taking fallback path. 32562306a36Sopenharmony_ci * 32662306a36Sopenharmony_ci * This WARN_ON() can be seen as a documentation for driver 32762306a36Sopenharmony_ci * authors to do not return -EOPNOTSUPP in packet offload mode. 32862306a36Sopenharmony_ci */ 32962306a36Sopenharmony_ci WARN_ON(err == -EOPNOTSUPP && is_packet_offload); 33062306a36Sopenharmony_ci if (err != -EOPNOTSUPP || is_packet_offload) { 33162306a36Sopenharmony_ci NL_SET_ERR_MSG_WEAK(extack, "Device failed to offload this state"); 33262306a36Sopenharmony_ci return err; 33362306a36Sopenharmony_ci } 33462306a36Sopenharmony_ci } 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci return 0; 33762306a36Sopenharmony_ci} 33862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(xfrm_dev_state_add); 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ciint xfrm_dev_policy_add(struct net *net, struct xfrm_policy *xp, 34162306a36Sopenharmony_ci struct xfrm_user_offload *xuo, u8 dir, 34262306a36Sopenharmony_ci struct netlink_ext_ack *extack) 34362306a36Sopenharmony_ci{ 34462306a36Sopenharmony_ci struct xfrm_dev_offload *xdo = &xp->xdo; 34562306a36Sopenharmony_ci struct net_device *dev; 34662306a36Sopenharmony_ci int err; 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci if (!xuo->flags || xuo->flags & ~XFRM_OFFLOAD_PACKET) { 34962306a36Sopenharmony_ci /* We support only packet offload mode and it means 35062306a36Sopenharmony_ci * that user must set XFRM_OFFLOAD_PACKET bit. 35162306a36Sopenharmony_ci */ 35262306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Unrecognized flags in offload request"); 35362306a36Sopenharmony_ci return -EINVAL; 35462306a36Sopenharmony_ci } 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_ci dev = dev_get_by_index(net, xuo->ifindex); 35762306a36Sopenharmony_ci if (!dev) 35862306a36Sopenharmony_ci return -EINVAL; 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci if (!dev->xfrmdev_ops || !dev->xfrmdev_ops->xdo_dev_policy_add) { 36162306a36Sopenharmony_ci xdo->dev = NULL; 36262306a36Sopenharmony_ci dev_put(dev); 36362306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Policy offload is not supported"); 36462306a36Sopenharmony_ci return -EINVAL; 36562306a36Sopenharmony_ci } 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci xdo->dev = dev; 36862306a36Sopenharmony_ci netdev_tracker_alloc(dev, &xdo->dev_tracker, GFP_ATOMIC); 36962306a36Sopenharmony_ci xdo->real_dev = dev; 37062306a36Sopenharmony_ci xdo->type = XFRM_DEV_OFFLOAD_PACKET; 37162306a36Sopenharmony_ci switch (dir) { 37262306a36Sopenharmony_ci case XFRM_POLICY_IN: 37362306a36Sopenharmony_ci xdo->dir = XFRM_DEV_OFFLOAD_IN; 37462306a36Sopenharmony_ci break; 37562306a36Sopenharmony_ci case XFRM_POLICY_OUT: 37662306a36Sopenharmony_ci xdo->dir = XFRM_DEV_OFFLOAD_OUT; 37762306a36Sopenharmony_ci break; 37862306a36Sopenharmony_ci case XFRM_POLICY_FWD: 37962306a36Sopenharmony_ci xdo->dir = XFRM_DEV_OFFLOAD_FWD; 38062306a36Sopenharmony_ci break; 38162306a36Sopenharmony_ci default: 38262306a36Sopenharmony_ci xdo->dev = NULL; 38362306a36Sopenharmony_ci netdev_put(dev, &xdo->dev_tracker); 38462306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Unrecognized offload direction"); 38562306a36Sopenharmony_ci return -EINVAL; 38662306a36Sopenharmony_ci } 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci err = dev->xfrmdev_ops->xdo_dev_policy_add(xp, extack); 38962306a36Sopenharmony_ci if (err) { 39062306a36Sopenharmony_ci xdo->dev = NULL; 39162306a36Sopenharmony_ci xdo->real_dev = NULL; 39262306a36Sopenharmony_ci xdo->type = XFRM_DEV_OFFLOAD_UNSPECIFIED; 39362306a36Sopenharmony_ci xdo->dir = 0; 39462306a36Sopenharmony_ci netdev_put(dev, &xdo->dev_tracker); 39562306a36Sopenharmony_ci NL_SET_ERR_MSG_WEAK(extack, "Device failed to offload this policy"); 39662306a36Sopenharmony_ci return err; 39762306a36Sopenharmony_ci } 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci return 0; 40062306a36Sopenharmony_ci} 40162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(xfrm_dev_policy_add); 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_cibool xfrm_dev_offload_ok(struct sk_buff *skb, struct xfrm_state *x) 40462306a36Sopenharmony_ci{ 40562306a36Sopenharmony_ci int mtu; 40662306a36Sopenharmony_ci struct dst_entry *dst = skb_dst(skb); 40762306a36Sopenharmony_ci struct xfrm_dst *xdst = (struct xfrm_dst *)dst; 40862306a36Sopenharmony_ci struct net_device *dev = x->xso.dev; 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci if (!x->type_offload || 41162306a36Sopenharmony_ci (x->xso.type == XFRM_DEV_OFFLOAD_UNSPECIFIED && x->encap)) 41262306a36Sopenharmony_ci return false; 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci if (x->xso.type == XFRM_DEV_OFFLOAD_PACKET || 41562306a36Sopenharmony_ci ((!dev || (dev == xfrm_dst_path(dst)->dev)) && 41662306a36Sopenharmony_ci !xdst->child->xfrm)) { 41762306a36Sopenharmony_ci mtu = xfrm_state_mtu(x, xdst->child_mtu_cached); 41862306a36Sopenharmony_ci if (skb->len <= mtu) 41962306a36Sopenharmony_ci goto ok; 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci if (skb_is_gso(skb) && skb_gso_validate_network_len(skb, mtu)) 42262306a36Sopenharmony_ci goto ok; 42362306a36Sopenharmony_ci } 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci return false; 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ciok: 42862306a36Sopenharmony_ci if (dev && dev->xfrmdev_ops && dev->xfrmdev_ops->xdo_dev_offload_ok) 42962306a36Sopenharmony_ci return x->xso.dev->xfrmdev_ops->xdo_dev_offload_ok(skb, x); 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci return true; 43262306a36Sopenharmony_ci} 43362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(xfrm_dev_offload_ok); 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_civoid xfrm_dev_resume(struct sk_buff *skb) 43662306a36Sopenharmony_ci{ 43762306a36Sopenharmony_ci struct net_device *dev = skb->dev; 43862306a36Sopenharmony_ci int ret = NETDEV_TX_BUSY; 43962306a36Sopenharmony_ci struct netdev_queue *txq; 44062306a36Sopenharmony_ci struct softnet_data *sd; 44162306a36Sopenharmony_ci unsigned long flags; 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci rcu_read_lock(); 44462306a36Sopenharmony_ci txq = netdev_core_pick_tx(dev, skb, NULL); 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci HARD_TX_LOCK(dev, txq, smp_processor_id()); 44762306a36Sopenharmony_ci if (!netif_xmit_frozen_or_stopped(txq)) 44862306a36Sopenharmony_ci skb = dev_hard_start_xmit(skb, dev, txq, &ret); 44962306a36Sopenharmony_ci HARD_TX_UNLOCK(dev, txq); 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci if (!dev_xmit_complete(ret)) { 45262306a36Sopenharmony_ci local_irq_save(flags); 45362306a36Sopenharmony_ci sd = this_cpu_ptr(&softnet_data); 45462306a36Sopenharmony_ci skb_queue_tail(&sd->xfrm_backlog, skb); 45562306a36Sopenharmony_ci raise_softirq_irqoff(NET_TX_SOFTIRQ); 45662306a36Sopenharmony_ci local_irq_restore(flags); 45762306a36Sopenharmony_ci } 45862306a36Sopenharmony_ci rcu_read_unlock(); 45962306a36Sopenharmony_ci} 46062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(xfrm_dev_resume); 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_civoid xfrm_dev_backlog(struct softnet_data *sd) 46362306a36Sopenharmony_ci{ 46462306a36Sopenharmony_ci struct sk_buff_head *xfrm_backlog = &sd->xfrm_backlog; 46562306a36Sopenharmony_ci struct sk_buff_head list; 46662306a36Sopenharmony_ci struct sk_buff *skb; 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_ci if (skb_queue_empty(xfrm_backlog)) 46962306a36Sopenharmony_ci return; 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci __skb_queue_head_init(&list); 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci spin_lock(&xfrm_backlog->lock); 47462306a36Sopenharmony_ci skb_queue_splice_init(xfrm_backlog, &list); 47562306a36Sopenharmony_ci spin_unlock(&xfrm_backlog->lock); 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci while (!skb_queue_empty(&list)) { 47862306a36Sopenharmony_ci skb = __skb_dequeue(&list); 47962306a36Sopenharmony_ci xfrm_dev_resume(skb); 48062306a36Sopenharmony_ci } 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci} 48362306a36Sopenharmony_ci#endif 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_cistatic int xfrm_api_check(struct net_device *dev) 48662306a36Sopenharmony_ci{ 48762306a36Sopenharmony_ci#ifdef CONFIG_XFRM_OFFLOAD 48862306a36Sopenharmony_ci if ((dev->features & NETIF_F_HW_ESP_TX_CSUM) && 48962306a36Sopenharmony_ci !(dev->features & NETIF_F_HW_ESP)) 49062306a36Sopenharmony_ci return NOTIFY_BAD; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci if ((dev->features & NETIF_F_HW_ESP) && 49362306a36Sopenharmony_ci (!(dev->xfrmdev_ops && 49462306a36Sopenharmony_ci dev->xfrmdev_ops->xdo_dev_state_add && 49562306a36Sopenharmony_ci dev->xfrmdev_ops->xdo_dev_state_delete))) 49662306a36Sopenharmony_ci return NOTIFY_BAD; 49762306a36Sopenharmony_ci#else 49862306a36Sopenharmony_ci if (dev->features & (NETIF_F_HW_ESP | NETIF_F_HW_ESP_TX_CSUM)) 49962306a36Sopenharmony_ci return NOTIFY_BAD; 50062306a36Sopenharmony_ci#endif 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci return NOTIFY_DONE; 50362306a36Sopenharmony_ci} 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_cistatic int xfrm_dev_down(struct net_device *dev) 50662306a36Sopenharmony_ci{ 50762306a36Sopenharmony_ci if (dev->features & NETIF_F_HW_ESP) { 50862306a36Sopenharmony_ci xfrm_dev_state_flush(dev_net(dev), dev, true); 50962306a36Sopenharmony_ci xfrm_dev_policy_flush(dev_net(dev), dev, true); 51062306a36Sopenharmony_ci } 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci return NOTIFY_DONE; 51362306a36Sopenharmony_ci} 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_cistatic int xfrm_dev_event(struct notifier_block *this, unsigned long event, void *ptr) 51662306a36Sopenharmony_ci{ 51762306a36Sopenharmony_ci struct net_device *dev = netdev_notifier_info_to_dev(ptr); 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci switch (event) { 52062306a36Sopenharmony_ci case NETDEV_REGISTER: 52162306a36Sopenharmony_ci return xfrm_api_check(dev); 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci case NETDEV_FEAT_CHANGE: 52462306a36Sopenharmony_ci return xfrm_api_check(dev); 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci case NETDEV_DOWN: 52762306a36Sopenharmony_ci case NETDEV_UNREGISTER: 52862306a36Sopenharmony_ci return xfrm_dev_down(dev); 52962306a36Sopenharmony_ci } 53062306a36Sopenharmony_ci return NOTIFY_DONE; 53162306a36Sopenharmony_ci} 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_cistatic struct notifier_block xfrm_dev_notifier = { 53462306a36Sopenharmony_ci .notifier_call = xfrm_dev_event, 53562306a36Sopenharmony_ci}; 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_civoid __init xfrm_dev_init(void) 53862306a36Sopenharmony_ci{ 53962306a36Sopenharmony_ci register_netdevice_notifier(&xfrm_dev_notifier); 54062306a36Sopenharmony_ci} 541