Merge branch 'Extend XFRM core to allow packet offload configuration'
Leon Romanovsky says: ============ The following series extends XFRM core code to handle a new type of IPsec offload - packet offload. In this mode, the HW is going to be responsible for the whole data path, so both policy and state should be offloaded. IPsec packet offload is an improved version of IPsec crypto mode, In packet mode, HW is responsible to trim/add headers in addition to decrypt/encrypt. In this mode, the packet arrives to the stack as already decrypted and vice versa for TX (exits to HW as not-encrypted). Devices that implement IPsec packet offload mode offload policies too. In the RX path, it causes the situation that HW can't effectively handle mixed SW and HW priorities unless users make sure that HW offloaded policies have higher priorities. It means that we don't need to perform any search of inexact policies and/or priority checks if HW policy was discovered. In such situation, the HW will catch the packets anyway and HW can still implement inexact lookups. In case specific policy is not found, we will continue with packet lookup and check for existence of HW policies in inexact list. HW policies are added to the head of SPD to ensure fast lookup, as XFRM iterates over all policies in the loop. This simple solution allows us to achieve same benefits of separate HW/SW policies databases without over-engineering the code to iterate and manage two databases at the same path. To not over-engineer the code, HW policies are treated as SW ones and don't take into account netdev to allow reuse of the same priorities for policies databases without over-engineering the code to iterate and manage two databases at the same path. To not over-engineer the code, HW policies are treated as SW ones and don't take into account netdev to allow reuse of the same priorities for different devices. * No software fallback * Fragments are dropped, both in RX and TX * No sockets policies * Only IPsec transport mode is implemented ================================================================================ Rekeying: In order to support rekeying, as XFRM core is skipped, the HW/driver should do the following: * Count the handled packets * Raise event that limits are reached * Drop packets once hard limit is occurred. The XFRM core calls to newly introduced xfrm_dev_state_update_curlft() function in order to perform sync between device statistics and internal structures. On HW limit event, driver calls to xfrm_state_check_expire() to allow XFRM core take relevant decisions. This separation between control logic (in XFRM) and data plane allows us to packet reuse SW stack. ================================================================================ Configuration: iproute2: https://lore.kernel.org/netdev/cover.1652179360.git.leonro@nvidia.com/ Packet offload mode: ip xfrm state offload packet dev <if-name> dir <in|out> ip xfrm policy .... offload packet dev <if-name> Crypto offload mode: ip xfrm state offload crypto dev <if-name> dir <in|out> or (backward compatibility) ip xfrm state offload dev <if-name> dir <in|out> ================================================================================ Performance results: TCP multi-stream, using iperf3 instance per-CPU. +----------------------+--------+--------+--------+--------+---------+---------+ | | 1 CPU | 2 CPUs | 4 CPUs | 8 CPUs | 16 CPUs | 32 CPUs | | +--------+--------+--------+--------+---------+---------+ | | BW (Gbps) | +----------------------+--------+--------+-------+---------+---------+---------+ | Baseline | 27.9 | 59 | 93.1 | 92.8 | 93.7 | 94.4 | +----------------------+--------+--------+-------+---------+---------+---------+ | Software IPsec | 6 | 11.9 | 23.3 | 45.9 | 83.8 | 91.8 | +----------------------+--------+--------+-------+---------+---------+---------+ | IPsec crypto offload | 15 | 29.7 | 58.5 | 89.6 | 90.4 | 90.8 | +----------------------+--------+--------+-------+---------+---------+---------+ | IPsec packet offload | 28 | 57 | 90.7 | 91 | 91.3 | 91.9 | +----------------------+--------+--------+-------+---------+---------+---------+ IPsec packet offload mode behaves as baseline and reaches linerate with same amount of CPUs. Setups details (similar for both sides): * NIC: ConnectX6-DX dual port, 100 Gbps each. Single port used in the tests. * CPU: Intel(R) Xeon(R) Platinum 8380 CPU @ 2.30GHz ================================================================================ Series together with mlx5 part: https://git.kernel.org/pub/scm/linux/kernel/git/leon/linux-rdma.git/log/?h=xfrm-next ================================================================================ Changelog: v10: * Added forgotten xdo_dev_state_del. Patch #4. * Moved changelog in cover letter to the end. * Added "if (xs->xso.type != XFRM_DEV_OFFLOAD_CRYPTO) {" line to newly added netronome IPsec support. Patch #2. v9: https://lore.kernel.org/all/cover.1669547603.git.leonro@nvidia.com * Added acquire support v8: https://lore.kernel.org/all/cover.1668753030.git.leonro@nvidia.com * Removed not-related blank line * Fixed typos in documentation v7: https://lore.kernel.org/all/cover.1667997522.git.leonro@nvidia.com As was discussed in IPsec workshop: * Renamed "full offload" to be "packet offload". * Added check that offloaded SA and policy have same device while sending packet * Added to SAD same optimization as was done for SPD to speed-up lookups. v6: https://lore.kernel.org/all/cover.1666692948.git.leonro@nvidia.com * Fixed misplaced "!" in sixth patch. v5: https://lore.kernel.org/all/cover.1666525321.git.leonro@nvidia.com * Rebased to latest ipsec-next. * Replaced HW priority patch with solution which mimics separated SPDs for SW and HW. See more description in this cover letter. * Dropped RFC tag, usecase, API and implementation are clear. v4: https://lore.kernel.org/all/cover.1662295929.git.leonro@nvidia.com * Changed title from "PATCH" to "PATCH RFC" per-request. * Added two new patches: one to update hard/soft limits and another initial take on documentation. * Added more info about lifetime/rekeying flow to cover letter, see relevant section. * perf traces for crypto mode will come later. v3: https://lore.kernel.org/all/cover.1661260787.git.leonro@nvidia.com * I didn't hear any suggestion what term to use instead of "packet offload", so left it as is. It is used in commit messages and documentation only and easy to rename. * Added performance data and background info to cover letter * Reused xfrm_output_resume() function to support multiple XFRM transformations * Add PMTU check in addition to driver .xdo_dev_offload_ok validation * Documentation is in progress, but not part of this series yet. v2: https://lore.kernel.org/all/cover.1660639789.git.leonro@nvidia.com * Rebased to latest 6.0-rc1 * Add an extra check in TX datapath patch to validate packets before forwarding to HW. * Added policy cleanup logic in case of netdev down event v1: https://lore.kernel.org/all/cover.1652851393.git.leonro@nvidia.com * Moved comment to be before if (...) in third patch. v0: https://lore.kernel.org/all/cover.1652176932.git.leonro@nvidia.com ----------------------------------------------------------------------- ============ Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
This commit is contained in:
commit
89ae65734a
Documentation/networking
drivers/net
ethernet
chelsio/inline_crypto/ch_ipsec
intel
mellanox/mlx5/core/en_accel
netronome/nfp/crypto
netdevsim
include
net/xfrm
@ -5,6 +5,7 @@ XFRM device - offloading the IPsec computations
|
||||
===============================================
|
||||
|
||||
Shannon Nelson <shannon.nelson@oracle.com>
|
||||
Leon Romanovsky <leonro@nvidia.com>
|
||||
|
||||
|
||||
Overview
|
||||
@ -18,10 +19,21 @@ can radically increase throughput and decrease CPU utilization. The XFRM
|
||||
Device interface allows NIC drivers to offer to the stack access to the
|
||||
hardware offload.
|
||||
|
||||
Right now, there are two types of hardware offload that kernel supports.
|
||||
* IPsec crypto offload:
|
||||
* NIC performs encrypt/decrypt
|
||||
* Kernel does everything else
|
||||
* IPsec packet offload:
|
||||
* NIC performs encrypt/decrypt
|
||||
* NIC does encapsulation
|
||||
* Kernel and NIC have SA and policy in-sync
|
||||
* NIC handles the SA and policies states
|
||||
* The Kernel talks to the keymanager
|
||||
|
||||
Userland access to the offload is typically through a system such as
|
||||
libreswan or KAME/raccoon, but the iproute2 'ip xfrm' command set can
|
||||
be handy when experimenting. An example command might look something
|
||||
like this::
|
||||
like this for crypto offload:
|
||||
|
||||
ip x s add proto esp dst 14.0.0.70 src 14.0.0.52 spi 0x07 mode transport \
|
||||
reqid 0x07 replay-window 32 \
|
||||
@ -29,6 +41,17 @@ like this::
|
||||
sel src 14.0.0.52/24 dst 14.0.0.70/24 proto tcp \
|
||||
offload dev eth4 dir in
|
||||
|
||||
and for packet offload
|
||||
|
||||
ip x s add proto esp dst 14.0.0.70 src 14.0.0.52 spi 0x07 mode transport \
|
||||
reqid 0x07 replay-window 32 \
|
||||
aead 'rfc4106(gcm(aes))' 0x44434241343332312423222114131211f4f3f2f1 128 \
|
||||
sel src 14.0.0.52/24 dst 14.0.0.70/24 proto tcp \
|
||||
offload packet dev eth4 dir in
|
||||
|
||||
ip x p add src 14.0.0.70 dst 14.0.0.52 offload packet dev eth4 dir in
|
||||
tmpl src 14.0.0.70 dst 14.0.0.52 proto esp reqid 10000 mode transport
|
||||
|
||||
Yes, that's ugly, but that's what shell scripts and/or libreswan are for.
|
||||
|
||||
|
||||
@ -40,17 +63,24 @@ Callbacks to implement
|
||||
|
||||
/* from include/linux/netdevice.h */
|
||||
struct xfrmdev_ops {
|
||||
/* Crypto and Packet offload callbacks */
|
||||
int (*xdo_dev_state_add) (struct xfrm_state *x);
|
||||
void (*xdo_dev_state_delete) (struct xfrm_state *x);
|
||||
void (*xdo_dev_state_free) (struct xfrm_state *x);
|
||||
bool (*xdo_dev_offload_ok) (struct sk_buff *skb,
|
||||
struct xfrm_state *x);
|
||||
void (*xdo_dev_state_advance_esn) (struct xfrm_state *x);
|
||||
|
||||
/* Solely packet offload callbacks */
|
||||
void (*xdo_dev_state_update_curlft) (struct xfrm_state *x);
|
||||
int (*xdo_dev_policy_add) (struct xfrm_policy *x);
|
||||
void (*xdo_dev_policy_delete) (struct xfrm_policy *x);
|
||||
void (*xdo_dev_policy_free) (struct xfrm_policy *x);
|
||||
};
|
||||
|
||||
The NIC driver offering ipsec offload will need to implement these
|
||||
callbacks to make the offload available to the network stack's
|
||||
XFRM subsystem. Additionally, the feature bits NETIF_F_HW_ESP and
|
||||
The NIC driver offering ipsec offload will need to implement callbacks
|
||||
relevant to supported offload to make the offload available to the network
|
||||
stack's XFRM subsystem. Additionally, the feature bits NETIF_F_HW_ESP and
|
||||
NETIF_F_HW_ESP_TX_CSUM will signal the availability of the offload.
|
||||
|
||||
|
||||
@ -79,7 +109,8 @@ and an indication of whether it is for Rx or Tx. The driver should
|
||||
|
||||
=========== ===================================
|
||||
0 success
|
||||
-EOPNETSUPP offload not supported, try SW IPsec
|
||||
-EOPNETSUPP offload not supported, try SW IPsec,
|
||||
not applicable for packet offload mode
|
||||
other fail the request
|
||||
=========== ===================================
|
||||
|
||||
@ -96,6 +127,7 @@ will serviceable. This can check the packet information to be sure the
|
||||
offload can be supported (e.g. IPv4 or IPv6, no IPv4 options, etc) and
|
||||
return true of false to signify its support.
|
||||
|
||||
Crypto offload mode:
|
||||
When ready to send, the driver needs to inspect the Tx packet for the
|
||||
offload information, including the opaque context, and set up the packet
|
||||
send accordingly::
|
||||
@ -139,13 +171,25 @@ the stack in xfrm_input().
|
||||
In ESN mode, xdo_dev_state_advance_esn() is called from xfrm_replay_advance_esn().
|
||||
Driver will check packet seq number and update HW ESN state machine if needed.
|
||||
|
||||
Packet offload mode:
|
||||
HW adds and deletes XFRM headers. So in RX path, XFRM stack is bypassed if HW
|
||||
reported success. In TX path, the packet lefts kernel without extra header
|
||||
and not encrypted, the HW is responsible to perform it.
|
||||
|
||||
When the SA is removed by the user, the driver's xdo_dev_state_delete()
|
||||
is asked to disable the offload. Later, xdo_dev_state_free() is called
|
||||
from a garbage collection routine after all reference counts to the state
|
||||
and xdo_dev_policy_delete() are asked to disable the offload. Later,
|
||||
xdo_dev_state_free() and xdo_dev_policy_free() are called from a garbage
|
||||
collection routine after all reference counts to the state and policy
|
||||
have been removed and any remaining resources can be cleared for the
|
||||
offload state. How these are used by the driver will depend on specific
|
||||
hardware needs.
|
||||
|
||||
As a netdev is set to DOWN the XFRM stack's netdev listener will call
|
||||
xdo_dev_state_delete() and xdo_dev_state_free() on any remaining offloaded
|
||||
states.
|
||||
xdo_dev_state_delete(), xdo_dev_policy_delete(), xdo_dev_state_free() and
|
||||
xdo_dev_policy_free() on any remaining offloaded states.
|
||||
|
||||
Outcome of HW handling packets, the XFRM core can't count hard, soft limits.
|
||||
The HW/driver are responsible to perform it and provide accurate data when
|
||||
xdo_dev_state_update_curlft() is called. In case of one of these limits
|
||||
occuried, the driver needs to call to xfrm_state_check_expire() to make sure
|
||||
that XFRM performs rekeying sequence.
|
||||
|
@ -283,6 +283,10 @@ static int ch_ipsec_xfrm_add_state(struct xfrm_state *x)
|
||||
pr_debug("Cannot offload xfrm states with geniv other than seqiv\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (x->xso.type != XFRM_DEV_OFFLOAD_CRYPTO) {
|
||||
pr_debug("Unsupported xfrm offload\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
sa_entry = kzalloc(sizeof(*sa_entry), GFP_KERNEL);
|
||||
if (!sa_entry) {
|
||||
|
@ -585,6 +585,11 @@ static int ixgbe_ipsec_add_sa(struct xfrm_state *xs)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (xs->xso.type != XFRM_DEV_OFFLOAD_CRYPTO) {
|
||||
netdev_err(dev, "Unsupported ipsec offload type\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (xs->xso.dir == XFRM_DEV_OFFLOAD_IN) {
|
||||
struct rx_sa rsa;
|
||||
|
||||
|
@ -280,6 +280,11 @@ static int ixgbevf_ipsec_add_sa(struct xfrm_state *xs)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (xs->xso.type != XFRM_DEV_OFFLOAD_CRYPTO) {
|
||||
netdev_err(dev, "Unsupported ipsec offload type\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (xs->xso.dir == XFRM_DEV_OFFLOAD_IN) {
|
||||
struct rx_sa rsa;
|
||||
|
||||
|
@ -253,6 +253,10 @@ static inline int mlx5e_xfrm_validate_state(struct xfrm_state *x)
|
||||
netdev_info(netdev, "Cannot offload xfrm states with geniv other than seqiv\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (x->xso.type != XFRM_DEV_OFFLOAD_CRYPTO) {
|
||||
netdev_info(netdev, "Unsupported xfrm offload type\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -302,6 +302,11 @@ static int nfp_net_xfrm_add_state(struct xfrm_state *x)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (x->xso.type != XFRM_DEV_OFFLOAD_CRYPTO) {
|
||||
nn_err(nn, "Unsupported xfrm offload tyoe\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
cfg->spi = ntohl(x->id.spi);
|
||||
|
||||
/* Hash/Authentication */
|
||||
|
@ -149,6 +149,11 @@ static int nsim_ipsec_add_sa(struct xfrm_state *xs)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (xs->xso.type != XFRM_DEV_OFFLOAD_CRYPTO) {
|
||||
netdev_err(dev, "Unsupported ipsec offload type\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* find the first unused index */
|
||||
ret = nsim_ipsec_find_empty_idx(ipsec);
|
||||
if (ret < 0) {
|
||||
|
@ -1040,6 +1040,10 @@ struct xfrmdev_ops {
|
||||
bool (*xdo_dev_offload_ok) (struct sk_buff *skb,
|
||||
struct xfrm_state *x);
|
||||
void (*xdo_dev_state_advance_esn) (struct xfrm_state *x);
|
||||
void (*xdo_dev_state_update_curlft) (struct xfrm_state *x);
|
||||
int (*xdo_dev_policy_add) (struct xfrm_policy *x);
|
||||
void (*xdo_dev_policy_delete) (struct xfrm_policy *x);
|
||||
void (*xdo_dev_policy_free) (struct xfrm_policy *x);
|
||||
};
|
||||
#endif
|
||||
|
||||
|
@ -129,6 +129,13 @@ struct xfrm_state_walk {
|
||||
enum {
|
||||
XFRM_DEV_OFFLOAD_IN = 1,
|
||||
XFRM_DEV_OFFLOAD_OUT,
|
||||
XFRM_DEV_OFFLOAD_FWD,
|
||||
};
|
||||
|
||||
enum {
|
||||
XFRM_DEV_OFFLOAD_UNSPECIFIED,
|
||||
XFRM_DEV_OFFLOAD_CRYPTO,
|
||||
XFRM_DEV_OFFLOAD_PACKET,
|
||||
};
|
||||
|
||||
struct xfrm_dev_offload {
|
||||
@ -137,6 +144,7 @@ struct xfrm_dev_offload {
|
||||
struct net_device *real_dev;
|
||||
unsigned long offload_handle;
|
||||
u8 dir : 2;
|
||||
u8 type : 2;
|
||||
};
|
||||
|
||||
struct xfrm_mode {
|
||||
@ -534,6 +542,8 @@ struct xfrm_policy {
|
||||
struct xfrm_tmpl xfrm_vec[XFRM_MAX_DEPTH];
|
||||
struct hlist_node bydst_inexact_list;
|
||||
struct rcu_head rcu;
|
||||
|
||||
struct xfrm_dev_offload xdo;
|
||||
};
|
||||
|
||||
static inline struct net *xp_net(const struct xfrm_policy *xp)
|
||||
@ -1092,6 +1102,29 @@ xfrm_state_addr_cmp(const struct xfrm_tmpl *tmpl, const struct xfrm_state *x, un
|
||||
return !0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_XFRM
|
||||
static inline struct xfrm_state *xfrm_input_state(struct sk_buff *skb)
|
||||
{
|
||||
struct sec_path *sp = skb_sec_path(skb);
|
||||
|
||||
return sp->xvec[sp->len - 1];
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline struct xfrm_offload *xfrm_offload(struct sk_buff *skb)
|
||||
{
|
||||
#ifdef CONFIG_XFRM
|
||||
struct sec_path *sp = skb_sec_path(skb);
|
||||
|
||||
if (!sp || !sp->olen || sp->len != sp->olen)
|
||||
return NULL;
|
||||
|
||||
return &sp->ovec[sp->olen - 1];
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef CONFIG_XFRM
|
||||
int __xfrm_policy_check(struct sock *, int dir, struct sk_buff *skb,
|
||||
unsigned short family);
|
||||
@ -1123,10 +1156,19 @@ static inline int __xfrm_policy_check2(struct sock *sk, int dir,
|
||||
{
|
||||
struct net *net = dev_net(skb->dev);
|
||||
int ndir = dir | (reverse ? XFRM_POLICY_MASK + 1 : 0);
|
||||
struct xfrm_offload *xo = xfrm_offload(skb);
|
||||
struct xfrm_state *x;
|
||||
|
||||
if (sk && sk->sk_policy[XFRM_POLICY_IN])
|
||||
return __xfrm_policy_check(sk, ndir, skb, family);
|
||||
|
||||
if (xo) {
|
||||
x = xfrm_input_state(skb);
|
||||
if (x->xso.type == XFRM_DEV_OFFLOAD_PACKET)
|
||||
return (xo->flags & CRYPTO_DONE) &&
|
||||
(xo->status & CRYPTO_SUCCESS);
|
||||
}
|
||||
|
||||
return __xfrm_check_nopolicy(net, skb, dir) ||
|
||||
__xfrm_check_dev_nopolicy(skb, dir, family) ||
|
||||
__xfrm_policy_check(sk, ndir, skb, family);
|
||||
@ -1529,6 +1571,23 @@ struct xfrm_state *xfrm_stateonly_find(struct net *net, u32 mark, u32 if_id,
|
||||
struct xfrm_state *xfrm_state_lookup_byspi(struct net *net, __be32 spi,
|
||||
unsigned short family);
|
||||
int xfrm_state_check_expire(struct xfrm_state *x);
|
||||
#ifdef CONFIG_XFRM_OFFLOAD
|
||||
static inline void xfrm_dev_state_update_curlft(struct xfrm_state *x)
|
||||
{
|
||||
struct xfrm_dev_offload *xdo = &x->xso;
|
||||
struct net_device *dev = xdo->dev;
|
||||
|
||||
if (x->xso.type != XFRM_DEV_OFFLOAD_PACKET)
|
||||
return;
|
||||
|
||||
if (dev && dev->xfrmdev_ops &&
|
||||
dev->xfrmdev_ops->xdo_dev_state_update_curlft)
|
||||
dev->xfrmdev_ops->xdo_dev_state_update_curlft(x);
|
||||
|
||||
}
|
||||
#else
|
||||
static inline void xfrm_dev_state_update_curlft(struct xfrm_state *x) {}
|
||||
#endif
|
||||
void xfrm_state_insert(struct xfrm_state *x);
|
||||
int xfrm_state_add(struct xfrm_state *x);
|
||||
int xfrm_state_update(struct xfrm_state *x);
|
||||
@ -1578,6 +1637,8 @@ struct xfrm_state *xfrm_find_acq_byseq(struct net *net, u32 mark, u32 seq);
|
||||
int xfrm_state_delete(struct xfrm_state *x);
|
||||
int xfrm_state_flush(struct net *net, u8 proto, bool task_valid, bool sync);
|
||||
int xfrm_dev_state_flush(struct net *net, struct net_device *dev, bool task_valid);
|
||||
int xfrm_dev_policy_flush(struct net *net, struct net_device *dev,
|
||||
bool task_valid);
|
||||
void xfrm_sad_getinfo(struct net *net, struct xfrmk_sadinfo *si);
|
||||
void xfrm_spd_getinfo(struct net *net, struct xfrmk_spdinfo *si);
|
||||
u32 xfrm_replay_seqhi(struct xfrm_state *x, __be32 net_seq);
|
||||
@ -1860,29 +1921,6 @@ static inline void xfrm_states_delete(struct xfrm_state **states, int n)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_XFRM
|
||||
static inline struct xfrm_state *xfrm_input_state(struct sk_buff *skb)
|
||||
{
|
||||
struct sec_path *sp = skb_sec_path(skb);
|
||||
|
||||
return sp->xvec[sp->len - 1];
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline struct xfrm_offload *xfrm_offload(struct sk_buff *skb)
|
||||
{
|
||||
#ifdef CONFIG_XFRM
|
||||
struct sec_path *sp = skb_sec_path(skb);
|
||||
|
||||
if (!sp || !sp->olen || sp->len != sp->olen)
|
||||
return NULL;
|
||||
|
||||
return &sp->ovec[sp->olen - 1];
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
void __init xfrm_dev_init(void);
|
||||
|
||||
#ifdef CONFIG_XFRM_OFFLOAD
|
||||
@ -1892,6 +1930,9 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
|
||||
int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
|
||||
struct xfrm_user_offload *xuo,
|
||||
struct netlink_ext_ack *extack);
|
||||
int xfrm_dev_policy_add(struct net *net, struct xfrm_policy *xp,
|
||||
struct xfrm_user_offload *xuo, u8 dir,
|
||||
struct netlink_ext_ack *extack);
|
||||
bool xfrm_dev_offload_ok(struct sk_buff *skb, struct xfrm_state *x);
|
||||
|
||||
static inline void xfrm_dev_state_advance_esn(struct xfrm_state *x)
|
||||
@ -1940,6 +1981,28 @@ static inline void xfrm_dev_state_free(struct xfrm_state *x)
|
||||
netdev_put(dev, &xso->dev_tracker);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void xfrm_dev_policy_delete(struct xfrm_policy *x)
|
||||
{
|
||||
struct xfrm_dev_offload *xdo = &x->xdo;
|
||||
struct net_device *dev = xdo->dev;
|
||||
|
||||
if (dev && dev->xfrmdev_ops && dev->xfrmdev_ops->xdo_dev_policy_delete)
|
||||
dev->xfrmdev_ops->xdo_dev_policy_delete(x);
|
||||
}
|
||||
|
||||
static inline void xfrm_dev_policy_free(struct xfrm_policy *x)
|
||||
{
|
||||
struct xfrm_dev_offload *xdo = &x->xdo;
|
||||
struct net_device *dev = xdo->dev;
|
||||
|
||||
if (dev && dev->xfrmdev_ops) {
|
||||
if (dev->xfrmdev_ops->xdo_dev_policy_free)
|
||||
dev->xfrmdev_ops->xdo_dev_policy_free(x);
|
||||
xdo->dev = NULL;
|
||||
netdev_put(dev, &xdo->dev_tracker);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static inline void xfrm_dev_resume(struct sk_buff *skb)
|
||||
{
|
||||
@ -1967,6 +2030,21 @@ static inline void xfrm_dev_state_free(struct xfrm_state *x)
|
||||
{
|
||||
}
|
||||
|
||||
static inline int xfrm_dev_policy_add(struct net *net, struct xfrm_policy *xp,
|
||||
struct xfrm_user_offload *xuo, u8 dir,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void xfrm_dev_policy_delete(struct xfrm_policy *x)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void xfrm_dev_policy_free(struct xfrm_policy *x)
|
||||
{
|
||||
}
|
||||
|
||||
static inline bool xfrm_dev_offload_ok(struct sk_buff *skb, struct xfrm_state *x)
|
||||
{
|
||||
return false;
|
||||
|
@ -519,6 +519,12 @@ struct xfrm_user_offload {
|
||||
*/
|
||||
#define XFRM_OFFLOAD_IPV6 1
|
||||
#define XFRM_OFFLOAD_INBOUND 2
|
||||
/* Two bits above are relevant for state path only, while
|
||||
* offload is used for both policy and state flows.
|
||||
*
|
||||
* In policy offload mode, they are free and can be safely reused.
|
||||
*/
|
||||
#define XFRM_OFFLOAD_PACKET 4
|
||||
|
||||
struct xfrm_userpolicy_default {
|
||||
#define XFRM_USERPOLICY_UNSPEC 0
|
||||
|
@ -132,6 +132,16 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur
|
||||
if (xo->flags & XFRM_GRO || x->xso.dir == XFRM_DEV_OFFLOAD_IN)
|
||||
return skb;
|
||||
|
||||
/* The packet was sent to HW IPsec packet offload engine,
|
||||
* but to wrong device. Drop the packet, so it won't skip
|
||||
* XFRM stack.
|
||||
*/
|
||||
if (x->xso.type == XFRM_DEV_OFFLOAD_PACKET && x->xso.dev != dev) {
|
||||
kfree_skb(skb);
|
||||
dev_core_stats_tx_dropped_inc(dev);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* This skb was already validated on the upper/virtual dev */
|
||||
if ((x->xso.dev != dev) && (x->xso.real_dev == dev))
|
||||
return skb;
|
||||
@ -229,6 +239,7 @@ int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
|
||||
struct xfrm_dev_offload *xso = &x->xso;
|
||||
xfrm_address_t *saddr;
|
||||
xfrm_address_t *daddr;
|
||||
bool is_packet_offload;
|
||||
|
||||
if (!x->type_offload) {
|
||||
NL_SET_ERR_MSG(extack, "Type doesn't support offload");
|
||||
@ -241,11 +252,13 @@ int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (xuo->flags & ~(XFRM_OFFLOAD_IPV6 | XFRM_OFFLOAD_INBOUND)) {
|
||||
if (xuo->flags &
|
||||
~(XFRM_OFFLOAD_IPV6 | XFRM_OFFLOAD_INBOUND | XFRM_OFFLOAD_PACKET)) {
|
||||
NL_SET_ERR_MSG(extack, "Unrecognized flags in offload request");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
is_packet_offload = xuo->flags & XFRM_OFFLOAD_PACKET;
|
||||
dev = dev_get_by_index(net, xuo->ifindex);
|
||||
if (!dev) {
|
||||
if (!(xuo->flags & XFRM_OFFLOAD_INBOUND)) {
|
||||
@ -260,7 +273,7 @@ int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
|
||||
x->props.family,
|
||||
xfrm_smark_get(0, x));
|
||||
if (IS_ERR(dst))
|
||||
return 0;
|
||||
return (is_packet_offload) ? -EINVAL : 0;
|
||||
|
||||
dev = dst->dev;
|
||||
|
||||
@ -271,7 +284,7 @@ int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
|
||||
if (!dev->xfrmdev_ops || !dev->xfrmdev_ops->xdo_dev_state_add) {
|
||||
xso->dev = NULL;
|
||||
dev_put(dev);
|
||||
return 0;
|
||||
return (is_packet_offload) ? -EINVAL : 0;
|
||||
}
|
||||
|
||||
if (x->props.flags & XFRM_STATE_ESN &&
|
||||
@ -291,14 +304,28 @@ int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
|
||||
else
|
||||
xso->dir = XFRM_DEV_OFFLOAD_OUT;
|
||||
|
||||
if (is_packet_offload)
|
||||
xso->type = XFRM_DEV_OFFLOAD_PACKET;
|
||||
else
|
||||
xso->type = XFRM_DEV_OFFLOAD_CRYPTO;
|
||||
|
||||
err = dev->xfrmdev_ops->xdo_dev_state_add(x);
|
||||
if (err) {
|
||||
xso->dev = NULL;
|
||||
xso->dir = 0;
|
||||
xso->real_dev = NULL;
|
||||
netdev_put(dev, &xso->dev_tracker);
|
||||
xso->type = XFRM_DEV_OFFLOAD_UNSPECIFIED;
|
||||
|
||||
if (err != -EOPNOTSUPP) {
|
||||
/* User explicitly requested packet offload mode and configured
|
||||
* policy in addition to the XFRM state. So be civil to users,
|
||||
* and return an error instead of taking fallback path.
|
||||
*
|
||||
* This WARN_ON() can be seen as a documentation for driver
|
||||
* authors to do not return -EOPNOTSUPP in packet offload mode.
|
||||
*/
|
||||
WARN_ON(err == -EOPNOTSUPP && is_packet_offload);
|
||||
if (err != -EOPNOTSUPP || is_packet_offload) {
|
||||
NL_SET_ERR_MSG(extack, "Device failed to offload this state");
|
||||
return err;
|
||||
}
|
||||
@ -308,6 +335,69 @@ int xfrm_dev_state_add(struct net *net, struct xfrm_state *x,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(xfrm_dev_state_add);
|
||||
|
||||
int xfrm_dev_policy_add(struct net *net, struct xfrm_policy *xp,
|
||||
struct xfrm_user_offload *xuo, u8 dir,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct xfrm_dev_offload *xdo = &xp->xdo;
|
||||
struct net_device *dev;
|
||||
int err;
|
||||
|
||||
if (!xuo->flags || xuo->flags & ~XFRM_OFFLOAD_PACKET) {
|
||||
/* We support only packet offload mode and it means
|
||||
* that user must set XFRM_OFFLOAD_PACKET bit.
|
||||
*/
|
||||
NL_SET_ERR_MSG(extack, "Unrecognized flags in offload request");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev = dev_get_by_index(net, xuo->ifindex);
|
||||
if (!dev)
|
||||
return -EINVAL;
|
||||
|
||||
if (!dev->xfrmdev_ops || !dev->xfrmdev_ops->xdo_dev_policy_add) {
|
||||
xdo->dev = NULL;
|
||||
dev_put(dev);
|
||||
NL_SET_ERR_MSG(extack, "Policy offload is not supported");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
xdo->dev = dev;
|
||||
netdev_tracker_alloc(dev, &xdo->dev_tracker, GFP_ATOMIC);
|
||||
xdo->real_dev = dev;
|
||||
xdo->type = XFRM_DEV_OFFLOAD_PACKET;
|
||||
switch (dir) {
|
||||
case XFRM_POLICY_IN:
|
||||
xdo->dir = XFRM_DEV_OFFLOAD_IN;
|
||||
break;
|
||||
case XFRM_POLICY_OUT:
|
||||
xdo->dir = XFRM_DEV_OFFLOAD_OUT;
|
||||
break;
|
||||
case XFRM_POLICY_FWD:
|
||||
xdo->dir = XFRM_DEV_OFFLOAD_FWD;
|
||||
break;
|
||||
default:
|
||||
xdo->dev = NULL;
|
||||
dev_put(dev);
|
||||
NL_SET_ERR_MSG(extack, "Unrecognized oflload direction");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
err = dev->xfrmdev_ops->xdo_dev_policy_add(xp);
|
||||
if (err) {
|
||||
xdo->dev = NULL;
|
||||
xdo->real_dev = NULL;
|
||||
xdo->type = XFRM_DEV_OFFLOAD_UNSPECIFIED;
|
||||
xdo->dir = 0;
|
||||
netdev_put(dev, &xdo->dev_tracker);
|
||||
NL_SET_ERR_MSG(extack, "Device failed to offload this policy");
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(xfrm_dev_policy_add);
|
||||
|
||||
bool xfrm_dev_offload_ok(struct sk_buff *skb, struct xfrm_state *x)
|
||||
{
|
||||
int mtu;
|
||||
@ -318,8 +408,9 @@ bool xfrm_dev_offload_ok(struct sk_buff *skb, struct xfrm_state *x)
|
||||
if (!x->type_offload || x->encap)
|
||||
return false;
|
||||
|
||||
if ((!dev || (dev == xfrm_dst_path(dst)->dev)) &&
|
||||
(!xdst->child->xfrm)) {
|
||||
if (x->xso.type == XFRM_DEV_OFFLOAD_PACKET ||
|
||||
((!dev || (dev == xfrm_dst_path(dst)->dev)) &&
|
||||
!xdst->child->xfrm)) {
|
||||
mtu = xfrm_state_mtu(x, xdst->child_mtu_cached);
|
||||
if (skb->len <= mtu)
|
||||
goto ok;
|
||||
@ -410,8 +501,10 @@ static int xfrm_api_check(struct net_device *dev)
|
||||
|
||||
static int xfrm_dev_down(struct net_device *dev)
|
||||
{
|
||||
if (dev->features & NETIF_F_HW_ESP)
|
||||
if (dev->features & NETIF_F_HW_ESP) {
|
||||
xfrm_dev_state_flush(dev_net(dev), dev, true);
|
||||
xfrm_dev_policy_flush(dev_net(dev), dev, true);
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
@ -492,7 +492,7 @@ static int xfrm_output_one(struct sk_buff *skb, int err)
|
||||
struct xfrm_state *x = dst->xfrm;
|
||||
struct net *net = xs_net(x);
|
||||
|
||||
if (err <= 0)
|
||||
if (err <= 0 || x->xso.type == XFRM_DEV_OFFLOAD_PACKET)
|
||||
goto resume;
|
||||
|
||||
do {
|
||||
@ -717,6 +717,16 @@ int xfrm_output(struct sock *sk, struct sk_buff *skb)
|
||||
break;
|
||||
}
|
||||
|
||||
if (x->xso.type == XFRM_DEV_OFFLOAD_PACKET) {
|
||||
if (!xfrm_dev_offload_ok(skb, x)) {
|
||||
XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR);
|
||||
kfree_skb(skb);
|
||||
return -EHOSTUNREACH;
|
||||
}
|
||||
|
||||
return xfrm_output_resume(sk, skb, 0);
|
||||
}
|
||||
|
||||
secpath_reset(skb);
|
||||
|
||||
if (xfrm_dev_offload_ok(skb, x)) {
|
||||
|
@ -425,6 +425,7 @@ void xfrm_policy_destroy(struct xfrm_policy *policy)
|
||||
if (del_timer(&policy->timer) || del_timer(&policy->polq.hold_timer))
|
||||
BUG();
|
||||
|
||||
xfrm_dev_policy_free(policy);
|
||||
call_rcu(&policy->rcu, xfrm_policy_destroy_rcu);
|
||||
}
|
||||
EXPORT_SYMBOL(xfrm_policy_destroy);
|
||||
@ -535,7 +536,7 @@ redo:
|
||||
__get_hash_thresh(net, pol->family, dir, &dbits, &sbits);
|
||||
h = __addr_hash(&pol->selector.daddr, &pol->selector.saddr,
|
||||
pol->family, nhashmask, dbits, sbits);
|
||||
if (!entry0) {
|
||||
if (!entry0 || pol->xdo.type == XFRM_DEV_OFFLOAD_PACKET) {
|
||||
hlist_del_rcu(&pol->bydst);
|
||||
hlist_add_head_rcu(&pol->bydst, ndsttable + h);
|
||||
h0 = h;
|
||||
@ -866,7 +867,7 @@ static void xfrm_policy_inexact_list_reinsert(struct net *net,
|
||||
break;
|
||||
}
|
||||
|
||||
if (newpos)
|
||||
if (newpos && policy->xdo.type != XFRM_DEV_OFFLOAD_PACKET)
|
||||
hlist_add_behind_rcu(&policy->bydst, newpos);
|
||||
else
|
||||
hlist_add_head_rcu(&policy->bydst, &n->hhead);
|
||||
@ -1347,7 +1348,7 @@ static void xfrm_hash_rebuild(struct work_struct *work)
|
||||
else
|
||||
break;
|
||||
}
|
||||
if (newpos)
|
||||
if (newpos && policy->xdo.type != XFRM_DEV_OFFLOAD_PACKET)
|
||||
hlist_add_behind_rcu(&policy->bydst, newpos);
|
||||
else
|
||||
hlist_add_head_rcu(&policy->bydst, chain);
|
||||
@ -1524,7 +1525,7 @@ static void xfrm_policy_insert_inexact_list(struct hlist_head *chain,
|
||||
break;
|
||||
}
|
||||
|
||||
if (newpos)
|
||||
if (newpos && policy->xdo.type != XFRM_DEV_OFFLOAD_PACKET)
|
||||
hlist_add_behind_rcu(&policy->bydst_inexact_list, newpos);
|
||||
else
|
||||
hlist_add_head_rcu(&policy->bydst_inexact_list, chain);
|
||||
@ -1561,9 +1562,12 @@ static struct xfrm_policy *xfrm_policy_insert_list(struct hlist_head *chain,
|
||||
break;
|
||||
}
|
||||
|
||||
if (newpos)
|
||||
if (newpos && policy->xdo.type != XFRM_DEV_OFFLOAD_PACKET)
|
||||
hlist_add_behind_rcu(&policy->bydst, &newpos->bydst);
|
||||
else
|
||||
/* Packet offload policies enter to the head
|
||||
* to speed-up lookups.
|
||||
*/
|
||||
hlist_add_head_rcu(&policy->bydst, chain);
|
||||
|
||||
return delpol;
|
||||
@ -1769,12 +1773,41 @@ xfrm_policy_flush_secctx_check(struct net *net, u8 type, bool task_valid)
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline int xfrm_dev_policy_flush_secctx_check(struct net *net,
|
||||
struct net_device *dev,
|
||||
bool task_valid)
|
||||
{
|
||||
struct xfrm_policy *pol;
|
||||
int err = 0;
|
||||
|
||||
list_for_each_entry(pol, &net->xfrm.policy_all, walk.all) {
|
||||
if (pol->walk.dead ||
|
||||
xfrm_policy_id2dir(pol->index) >= XFRM_POLICY_MAX ||
|
||||
pol->xdo.dev != dev)
|
||||
continue;
|
||||
|
||||
err = security_xfrm_policy_delete(pol->security);
|
||||
if (err) {
|
||||
xfrm_audit_policy_delete(pol, 0, task_valid);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
#else
|
||||
static inline int
|
||||
xfrm_policy_flush_secctx_check(struct net *net, u8 type, bool task_valid)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int xfrm_dev_policy_flush_secctx_check(struct net *net,
|
||||
struct net_device *dev,
|
||||
bool task_valid)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int xfrm_policy_flush(struct net *net, u8 type, bool task_valid)
|
||||
@ -1814,6 +1847,44 @@ out:
|
||||
}
|
||||
EXPORT_SYMBOL(xfrm_policy_flush);
|
||||
|
||||
int xfrm_dev_policy_flush(struct net *net, struct net_device *dev,
|
||||
bool task_valid)
|
||||
{
|
||||
int dir, err = 0, cnt = 0;
|
||||
struct xfrm_policy *pol;
|
||||
|
||||
spin_lock_bh(&net->xfrm.xfrm_policy_lock);
|
||||
|
||||
err = xfrm_dev_policy_flush_secctx_check(net, dev, task_valid);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
again:
|
||||
list_for_each_entry(pol, &net->xfrm.policy_all, walk.all) {
|
||||
dir = xfrm_policy_id2dir(pol->index);
|
||||
if (pol->walk.dead ||
|
||||
dir >= XFRM_POLICY_MAX ||
|
||||
pol->xdo.dev != dev)
|
||||
continue;
|
||||
|
||||
__xfrm_policy_unlink(pol, dir);
|
||||
spin_unlock_bh(&net->xfrm.xfrm_policy_lock);
|
||||
cnt++;
|
||||
xfrm_audit_policy_delete(pol, 1, task_valid);
|
||||
xfrm_policy_kill(pol);
|
||||
spin_lock_bh(&net->xfrm.xfrm_policy_lock);
|
||||
goto again;
|
||||
}
|
||||
if (cnt)
|
||||
__xfrm_policy_inexact_flush(net);
|
||||
else
|
||||
err = -ESRCH;
|
||||
out:
|
||||
spin_unlock_bh(&net->xfrm.xfrm_policy_lock);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL(xfrm_dev_policy_flush);
|
||||
|
||||
int xfrm_policy_walk(struct net *net, struct xfrm_policy_walk *walk,
|
||||
int (*func)(struct xfrm_policy *, int, int, void*),
|
||||
void *data)
|
||||
@ -2113,6 +2184,9 @@ static struct xfrm_policy *xfrm_policy_lookup_bytype(struct net *net, u8 type,
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ret && ret->xdo.type == XFRM_DEV_OFFLOAD_PACKET)
|
||||
goto skip_inexact;
|
||||
|
||||
bin = xfrm_policy_inexact_lookup_rcu(net, type, family, dir, if_id);
|
||||
if (!bin || !xfrm_policy_find_inexact_candidates(&cand, bin, saddr,
|
||||
daddr))
|
||||
@ -2245,6 +2319,7 @@ int xfrm_policy_delete(struct xfrm_policy *pol, int dir)
|
||||
pol = __xfrm_policy_unlink(pol, dir);
|
||||
spin_unlock_bh(&net->xfrm.xfrm_policy_lock);
|
||||
if (pol) {
|
||||
xfrm_dev_policy_delete(pol);
|
||||
xfrm_policy_kill(pol);
|
||||
return 0;
|
||||
}
|
||||
|
@ -84,6 +84,25 @@ static unsigned int xfrm_seq_hash(struct net *net, u32 seq)
|
||||
return __xfrm_seq_hash(seq, net->xfrm.state_hmask);
|
||||
}
|
||||
|
||||
#define XFRM_STATE_INSERT(by, _n, _h, _type) \
|
||||
{ \
|
||||
struct xfrm_state *_x = NULL; \
|
||||
\
|
||||
if (_type != XFRM_DEV_OFFLOAD_PACKET) { \
|
||||
hlist_for_each_entry_rcu(_x, _h, by) { \
|
||||
if (_x->xso.type == XFRM_DEV_OFFLOAD_PACKET) \
|
||||
continue; \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
if (!_x || _x->xso.type == XFRM_DEV_OFFLOAD_PACKET) \
|
||||
/* SAD is empty or consist from HW SAs only */ \
|
||||
hlist_add_head_rcu(_n, _h); \
|
||||
else \
|
||||
hlist_add_before_rcu(_n, &_x->by); \
|
||||
}
|
||||
|
||||
static void xfrm_hash_transfer(struct hlist_head *list,
|
||||
struct hlist_head *ndsttable,
|
||||
struct hlist_head *nsrctable,
|
||||
@ -100,23 +119,25 @@ static void xfrm_hash_transfer(struct hlist_head *list,
|
||||
h = __xfrm_dst_hash(&x->id.daddr, &x->props.saddr,
|
||||
x->props.reqid, x->props.family,
|
||||
nhashmask);
|
||||
hlist_add_head_rcu(&x->bydst, ndsttable + h);
|
||||
XFRM_STATE_INSERT(bydst, &x->bydst, ndsttable + h, x->xso.type);
|
||||
|
||||
h = __xfrm_src_hash(&x->id.daddr, &x->props.saddr,
|
||||
x->props.family,
|
||||
nhashmask);
|
||||
hlist_add_head_rcu(&x->bysrc, nsrctable + h);
|
||||
XFRM_STATE_INSERT(bysrc, &x->bysrc, nsrctable + h, x->xso.type);
|
||||
|
||||
if (x->id.spi) {
|
||||
h = __xfrm_spi_hash(&x->id.daddr, x->id.spi,
|
||||
x->id.proto, x->props.family,
|
||||
nhashmask);
|
||||
hlist_add_head_rcu(&x->byspi, nspitable + h);
|
||||
XFRM_STATE_INSERT(byspi, &x->byspi, nspitable + h,
|
||||
x->xso.type);
|
||||
}
|
||||
|
||||
if (x->km.seq) {
|
||||
h = __xfrm_seq_hash(x->km.seq, nhashmask);
|
||||
hlist_add_head_rcu(&x->byseq, nseqtable + h);
|
||||
XFRM_STATE_INSERT(byseq, &x->byseq, nseqtable + h,
|
||||
x->xso.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -549,6 +570,8 @@ static enum hrtimer_restart xfrm_timer_handler(struct hrtimer *me)
|
||||
int err = 0;
|
||||
|
||||
spin_lock(&x->lock);
|
||||
xfrm_dev_state_update_curlft(x);
|
||||
|
||||
if (x->km.state == XFRM_STATE_DEAD)
|
||||
goto out;
|
||||
if (x->km.state == XFRM_STATE_EXPIRED)
|
||||
@ -951,6 +974,49 @@ xfrm_init_tempstate(struct xfrm_state *x, const struct flowi *fl,
|
||||
x->props.family = tmpl->encap_family;
|
||||
}
|
||||
|
||||
static struct xfrm_state *__xfrm_state_lookup_all(struct net *net, u32 mark,
|
||||
const xfrm_address_t *daddr,
|
||||
__be32 spi, u8 proto,
|
||||
unsigned short family,
|
||||
struct xfrm_dev_offload *xdo)
|
||||
{
|
||||
unsigned int h = xfrm_spi_hash(net, daddr, spi, proto, family);
|
||||
struct xfrm_state *x;
|
||||
|
||||
hlist_for_each_entry_rcu(x, net->xfrm.state_byspi + h, byspi) {
|
||||
#ifdef CONFIG_XFRM_OFFLOAD
|
||||
if (xdo->type == XFRM_DEV_OFFLOAD_PACKET) {
|
||||
if (x->xso.type != XFRM_DEV_OFFLOAD_PACKET)
|
||||
/* HW states are in the head of list, there is
|
||||
* no need to iterate further.
|
||||
*/
|
||||
break;
|
||||
|
||||
/* Packet offload: both policy and SA should
|
||||
* have same device.
|
||||
*/
|
||||
if (xdo->dev != x->xso.dev)
|
||||
continue;
|
||||
} else if (x->xso.type == XFRM_DEV_OFFLOAD_PACKET)
|
||||
/* Skip HW policy for SW lookups */
|
||||
continue;
|
||||
#endif
|
||||
if (x->props.family != family ||
|
||||
x->id.spi != spi ||
|
||||
x->id.proto != proto ||
|
||||
!xfrm_addr_equal(&x->id.daddr, daddr, family))
|
||||
continue;
|
||||
|
||||
if ((mark & x->mark.m) != x->mark.v)
|
||||
continue;
|
||||
if (!xfrm_state_hold_rcu(x))
|
||||
continue;
|
||||
return x;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct xfrm_state *__xfrm_state_lookup(struct net *net, u32 mark,
|
||||
const xfrm_address_t *daddr,
|
||||
__be32 spi, u8 proto,
|
||||
@ -1092,6 +1158,23 @@ xfrm_state_find(const xfrm_address_t *daddr, const xfrm_address_t *saddr,
|
||||
rcu_read_lock();
|
||||
h = xfrm_dst_hash(net, daddr, saddr, tmpl->reqid, encap_family);
|
||||
hlist_for_each_entry_rcu(x, net->xfrm.state_bydst + h, bydst) {
|
||||
#ifdef CONFIG_XFRM_OFFLOAD
|
||||
if (pol->xdo.type == XFRM_DEV_OFFLOAD_PACKET) {
|
||||
if (x->xso.type != XFRM_DEV_OFFLOAD_PACKET)
|
||||
/* HW states are in the head of list, there is
|
||||
* no need to iterate further.
|
||||
*/
|
||||
break;
|
||||
|
||||
/* Packet offload: both policy and SA should
|
||||
* have same device.
|
||||
*/
|
||||
if (pol->xdo.dev != x->xso.dev)
|
||||
continue;
|
||||
} else if (x->xso.type == XFRM_DEV_OFFLOAD_PACKET)
|
||||
/* Skip HW policy for SW lookups */
|
||||
continue;
|
||||
#endif
|
||||
if (x->props.family == encap_family &&
|
||||
x->props.reqid == tmpl->reqid &&
|
||||
(mark & x->mark.m) == x->mark.v &&
|
||||
@ -1109,6 +1192,23 @@ xfrm_state_find(const xfrm_address_t *daddr, const xfrm_address_t *saddr,
|
||||
|
||||
h_wildcard = xfrm_dst_hash(net, daddr, &saddr_wildcard, tmpl->reqid, encap_family);
|
||||
hlist_for_each_entry_rcu(x, net->xfrm.state_bydst + h_wildcard, bydst) {
|
||||
#ifdef CONFIG_XFRM_OFFLOAD
|
||||
if (pol->xdo.type == XFRM_DEV_OFFLOAD_PACKET) {
|
||||
if (x->xso.type != XFRM_DEV_OFFLOAD_PACKET)
|
||||
/* HW states are in the head of list, there is
|
||||
* no need to iterate further.
|
||||
*/
|
||||
break;
|
||||
|
||||
/* Packet offload: both policy and SA should
|
||||
* have same device.
|
||||
*/
|
||||
if (pol->xdo.dev != x->xso.dev)
|
||||
continue;
|
||||
} else if (x->xso.type == XFRM_DEV_OFFLOAD_PACKET)
|
||||
/* Skip HW policy for SW lookups */
|
||||
continue;
|
||||
#endif
|
||||
if (x->props.family == encap_family &&
|
||||
x->props.reqid == tmpl->reqid &&
|
||||
(mark & x->mark.m) == x->mark.v &&
|
||||
@ -1126,8 +1226,10 @@ found:
|
||||
x = best;
|
||||
if (!x && !error && !acquire_in_progress) {
|
||||
if (tmpl->id.spi &&
|
||||
(x0 = __xfrm_state_lookup(net, mark, daddr, tmpl->id.spi,
|
||||
tmpl->id.proto, encap_family)) != NULL) {
|
||||
(x0 = __xfrm_state_lookup_all(net, mark, daddr,
|
||||
tmpl->id.spi, tmpl->id.proto,
|
||||
encap_family,
|
||||
&pol->xdo)) != NULL) {
|
||||
to_put = x0;
|
||||
error = -EEXIST;
|
||||
goto out;
|
||||
@ -1161,21 +1263,53 @@ found:
|
||||
x = NULL;
|
||||
goto out;
|
||||
}
|
||||
#ifdef CONFIG_XFRM_OFFLOAD
|
||||
if (pol->xdo.type == XFRM_DEV_OFFLOAD_PACKET) {
|
||||
struct xfrm_dev_offload *xdo = &pol->xdo;
|
||||
struct xfrm_dev_offload *xso = &x->xso;
|
||||
|
||||
xso->type = XFRM_DEV_OFFLOAD_PACKET;
|
||||
xso->dir = xdo->dir;
|
||||
xso->dev = xdo->dev;
|
||||
xso->real_dev = xdo->real_dev;
|
||||
netdev_tracker_alloc(xso->dev, &xso->dev_tracker,
|
||||
GFP_ATOMIC);
|
||||
error = xso->dev->xfrmdev_ops->xdo_dev_state_add(x);
|
||||
if (error) {
|
||||
xso->dir = 0;
|
||||
netdev_put(xso->dev, &xso->dev_tracker);
|
||||
xso->dev = NULL;
|
||||
xso->real_dev = NULL;
|
||||
xso->type = XFRM_DEV_OFFLOAD_UNSPECIFIED;
|
||||
x->km.state = XFRM_STATE_DEAD;
|
||||
to_put = x;
|
||||
x = NULL;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (km_query(x, tmpl, pol) == 0) {
|
||||
spin_lock_bh(&net->xfrm.xfrm_state_lock);
|
||||
x->km.state = XFRM_STATE_ACQ;
|
||||
list_add(&x->km.all, &net->xfrm.state_all);
|
||||
hlist_add_head_rcu(&x->bydst, net->xfrm.state_bydst + h);
|
||||
XFRM_STATE_INSERT(bydst, &x->bydst,
|
||||
net->xfrm.state_bydst + h,
|
||||
x->xso.type);
|
||||
h = xfrm_src_hash(net, daddr, saddr, encap_family);
|
||||
hlist_add_head_rcu(&x->bysrc, net->xfrm.state_bysrc + h);
|
||||
XFRM_STATE_INSERT(bysrc, &x->bysrc,
|
||||
net->xfrm.state_bysrc + h,
|
||||
x->xso.type);
|
||||
if (x->id.spi) {
|
||||
h = xfrm_spi_hash(net, &x->id.daddr, x->id.spi, x->id.proto, encap_family);
|
||||
hlist_add_head_rcu(&x->byspi, net->xfrm.state_byspi + h);
|
||||
XFRM_STATE_INSERT(byspi, &x->byspi,
|
||||
net->xfrm.state_byspi + h,
|
||||
x->xso.type);
|
||||
}
|
||||
if (x->km.seq) {
|
||||
h = xfrm_seq_hash(net, x->km.seq);
|
||||
hlist_add_head_rcu(&x->byseq, net->xfrm.state_byseq + h);
|
||||
XFRM_STATE_INSERT(byseq, &x->byseq,
|
||||
net->xfrm.state_byseq + h,
|
||||
x->xso.type);
|
||||
}
|
||||
x->lft.hard_add_expires_seconds = net->xfrm.sysctl_acq_expires;
|
||||
hrtimer_start(&x->mtimer,
|
||||
@ -1185,6 +1319,18 @@ found:
|
||||
xfrm_hash_grow_check(net, x->bydst.next != NULL);
|
||||
spin_unlock_bh(&net->xfrm.xfrm_state_lock);
|
||||
} else {
|
||||
#ifdef CONFIG_XFRM_OFFLOAD
|
||||
struct xfrm_dev_offload *xso = &x->xso;
|
||||
|
||||
if (xso->type == XFRM_DEV_OFFLOAD_PACKET) {
|
||||
xso->dev->xfrmdev_ops->xdo_dev_state_delete(x);
|
||||
xso->dir = 0;
|
||||
netdev_put(xso->dev, &xso->dev_tracker);
|
||||
xso->dev = NULL;
|
||||
xso->real_dev = NULL;
|
||||
xso->type = XFRM_DEV_OFFLOAD_UNSPECIFIED;
|
||||
}
|
||||
#endif
|
||||
x->km.state = XFRM_STATE_DEAD;
|
||||
to_put = x;
|
||||
x = NULL;
|
||||
@ -1280,22 +1426,26 @@ static void __xfrm_state_insert(struct xfrm_state *x)
|
||||
|
||||
h = xfrm_dst_hash(net, &x->id.daddr, &x->props.saddr,
|
||||
x->props.reqid, x->props.family);
|
||||
hlist_add_head_rcu(&x->bydst, net->xfrm.state_bydst + h);
|
||||
XFRM_STATE_INSERT(bydst, &x->bydst, net->xfrm.state_bydst + h,
|
||||
x->xso.type);
|
||||
|
||||
h = xfrm_src_hash(net, &x->id.daddr, &x->props.saddr, x->props.family);
|
||||
hlist_add_head_rcu(&x->bysrc, net->xfrm.state_bysrc + h);
|
||||
XFRM_STATE_INSERT(bysrc, &x->bysrc, net->xfrm.state_bysrc + h,
|
||||
x->xso.type);
|
||||
|
||||
if (x->id.spi) {
|
||||
h = xfrm_spi_hash(net, &x->id.daddr, x->id.spi, x->id.proto,
|
||||
x->props.family);
|
||||
|
||||
hlist_add_head_rcu(&x->byspi, net->xfrm.state_byspi + h);
|
||||
XFRM_STATE_INSERT(byspi, &x->byspi, net->xfrm.state_byspi + h,
|
||||
x->xso.type);
|
||||
}
|
||||
|
||||
if (x->km.seq) {
|
||||
h = xfrm_seq_hash(net, x->km.seq);
|
||||
|
||||
hlist_add_head_rcu(&x->byseq, net->xfrm.state_byseq + h);
|
||||
XFRM_STATE_INSERT(byseq, &x->byseq, net->xfrm.state_byseq + h,
|
||||
x->xso.type);
|
||||
}
|
||||
|
||||
hrtimer_start(&x->mtimer, ktime_set(1, 0), HRTIMER_MODE_REL_SOFT);
|
||||
@ -1409,9 +1559,11 @@ static struct xfrm_state *__find_acq_core(struct net *net,
|
||||
ktime_set(net->xfrm.sysctl_acq_expires, 0),
|
||||
HRTIMER_MODE_REL_SOFT);
|
||||
list_add(&x->km.all, &net->xfrm.state_all);
|
||||
hlist_add_head_rcu(&x->bydst, net->xfrm.state_bydst + h);
|
||||
XFRM_STATE_INSERT(bydst, &x->bydst, net->xfrm.state_bydst + h,
|
||||
x->xso.type);
|
||||
h = xfrm_src_hash(net, daddr, saddr, family);
|
||||
hlist_add_head_rcu(&x->bysrc, net->xfrm.state_bysrc + h);
|
||||
XFRM_STATE_INSERT(bysrc, &x->bysrc, net->xfrm.state_bysrc + h,
|
||||
x->xso.type);
|
||||
|
||||
net->xfrm.state_num++;
|
||||
|
||||
@ -1786,6 +1938,8 @@ EXPORT_SYMBOL(xfrm_state_update);
|
||||
|
||||
int xfrm_state_check_expire(struct xfrm_state *x)
|
||||
{
|
||||
xfrm_dev_state_update_curlft(x);
|
||||
|
||||
if (!x->curlft.use_time)
|
||||
x->curlft.use_time = ktime_get_real_seconds();
|
||||
|
||||
@ -2094,7 +2248,8 @@ int xfrm_alloc_spi(struct xfrm_state *x, u32 low, u32 high,
|
||||
spin_lock_bh(&net->xfrm.xfrm_state_lock);
|
||||
x->id.spi = newspi;
|
||||
h = xfrm_spi_hash(net, &x->id.daddr, x->id.spi, x->id.proto, x->props.family);
|
||||
hlist_add_head_rcu(&x->byspi, net->xfrm.state_byspi + h);
|
||||
XFRM_STATE_INSERT(byspi, &x->byspi, net->xfrm.state_byspi + h,
|
||||
x->xso.type);
|
||||
spin_unlock_bh(&net->xfrm.xfrm_state_lock);
|
||||
|
||||
err = 0;
|
||||
|
@ -956,6 +956,8 @@ static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb)
|
||||
xuo->ifindex = xso->dev->ifindex;
|
||||
if (xso->dir == XFRM_DEV_OFFLOAD_IN)
|
||||
xuo->flags = XFRM_OFFLOAD_INBOUND;
|
||||
if (xso->type == XFRM_DEV_OFFLOAD_PACKET)
|
||||
xuo->flags |= XFRM_OFFLOAD_PACKET;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1890,6 +1892,15 @@ static struct xfrm_policy *xfrm_policy_construct(struct net *net,
|
||||
if (attrs[XFRMA_IF_ID])
|
||||
xp->if_id = nla_get_u32(attrs[XFRMA_IF_ID]);
|
||||
|
||||
/* configure the hardware if offload is requested */
|
||||
if (attrs[XFRMA_OFFLOAD_DEV]) {
|
||||
err = xfrm_dev_policy_add(net, xp,
|
||||
nla_data(attrs[XFRMA_OFFLOAD_DEV]),
|
||||
p->dir, extack);
|
||||
if (err)
|
||||
goto error;
|
||||
}
|
||||
|
||||
return xp;
|
||||
error:
|
||||
*errp = err;
|
||||
@ -1929,6 +1940,7 @@ static int xfrm_add_policy(struct sk_buff *skb, struct nlmsghdr *nlh,
|
||||
xfrm_audit_policy_add(xp, err ? 0 : 1, true);
|
||||
|
||||
if (err) {
|
||||
xfrm_dev_policy_delete(xp);
|
||||
security_xfrm_policy_free(xp->security);
|
||||
kfree(xp);
|
||||
return err;
|
||||
@ -2041,6 +2053,8 @@ static int dump_one_policy(struct xfrm_policy *xp, int dir, int count, void *ptr
|
||||
err = xfrm_mark_put(skb, &xp->mark);
|
||||
if (!err)
|
||||
err = xfrm_if_id_put(skb, xp->if_id);
|
||||
if (!err && xp->xdo.dev)
|
||||
err = copy_user_offload(&xp->xdo, skb);
|
||||
if (err) {
|
||||
nlmsg_cancel(skb, nlh);
|
||||
return err;
|
||||
@ -3379,6 +3393,8 @@ static int build_acquire(struct sk_buff *skb, struct xfrm_state *x,
|
||||
err = xfrm_mark_put(skb, &xp->mark);
|
||||
if (!err)
|
||||
err = xfrm_if_id_put(skb, xp->if_id);
|
||||
if (!err && xp->xdo.dev)
|
||||
err = copy_user_offload(&xp->xdo, skb);
|
||||
if (err) {
|
||||
nlmsg_cancel(skb, nlh);
|
||||
return err;
|
||||
@ -3497,6 +3513,8 @@ static int build_polexpire(struct sk_buff *skb, struct xfrm_policy *xp,
|
||||
err = xfrm_mark_put(skb, &xp->mark);
|
||||
if (!err)
|
||||
err = xfrm_if_id_put(skb, xp->if_id);
|
||||
if (!err && xp->xdo.dev)
|
||||
err = copy_user_offload(&xp->xdo, skb);
|
||||
if (err) {
|
||||
nlmsg_cancel(skb, nlh);
|
||||
return err;
|
||||
@ -3580,6 +3598,8 @@ static int xfrm_notify_policy(struct xfrm_policy *xp, int dir, const struct km_e
|
||||
err = xfrm_mark_put(skb, &xp->mark);
|
||||
if (!err)
|
||||
err = xfrm_if_id_put(skb, xp->if_id);
|
||||
if (!err && xp->xdo.dev)
|
||||
err = copy_user_offload(&xp->xdo, skb);
|
||||
if (err)
|
||||
goto out_free_skb;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user