ipsec-next-2024-11-15

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEH7ZpcWbFyOOp6OJbrB3Eaf9PW7cFAmc3A/gACgkQrB3Eaf9P
 W7fNew//XCIhIvFYaQcP2x84T4EYB679NkGlwMATxXgn40+sp7muSwVweynEWNIu
 FltfBAwYD/MxD7g519abVPMWXs/iYI5duw3vvqnxmkOoebWLLocg2VoqFIdVXlQw
 /hj+1X/oNT4OKcaQAw/FAGRuYvkc90YB/rRG51RwAIR0tyBjRwfUsozMM8QX/zQI
 I0cLCgGAf/kylQre+dhvUkMhXaLogMF5v0qzPxhyMBD02JaUpe6+5cdHQcmKOhqa
 ksTpySYnIKIHZrLizeFGDZpinaDIph20vGaDvDXpqTYFuwvCQsZczJy02dF4otf2
 2dZz6+2La+ZM+WsGIqpALqKCNhr8fOcQxCRH3eGLPBwoXXt5CFAMgJKob8hKuonW
 FgJaYMBZOjYbgGah8WbEe/YsWq4y3uRs48pFtY+T5cn7AskNxIvUoLNjSS83Hlqu
 PJbveiKsZygig966Q/zUFATYnvj3zEgjVEcSbK6LRyBXL79Njr8l+PZ0Zoz76tc4
 bF1Xv0x+lRYmwa9rvOFaeqrP/GTe0xvlitFzuCN7HnXiN8URKnnDY2odkXYzo+Z7
 MBbP8wR/CaoiAvdMw74116nAIFOW95LPtvdGJTvlS9jAOt1P7dWQ3/mFKEpItndv
 cJjWzI7HKl0+85FcCDw+tmsDWWGbALUyPw96i8UgUcDGyqVKUgA=
 =Ioo8
 -----END PGP SIGNATURE-----

Merge tag 'ipsec-next-2024-11-15' of git://git.kernel.org/pub/scm/linux/kernel/git/klassert/ipsec-next

Steffen Klassert says:

====================

ipsec-next-11-15

1) Add support for RFC 9611 per cpu xfrm state handling.

2) Add inbound and outbound xfrm state caches to speed up
   state lookups.

3) Convert xfrm to dscp_t. From Guillaume Nault.

4) Fix error handling in build_aevent.
   From Everest K.C.

5) Replace strncpy with strscpy_pad in copy_to_user_auth.
   From Daniel Yang.

6) Fix an uninitialized symbol during acquire state insertion.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2024-11-18 11:52:49 +00:00
commit 296a681def
12 changed files with 284 additions and 40 deletions

View File

@ -43,6 +43,7 @@ struct netns_xfrm {
struct hlist_head __rcu *state_bysrc; struct hlist_head __rcu *state_bysrc;
struct hlist_head __rcu *state_byspi; struct hlist_head __rcu *state_byspi;
struct hlist_head __rcu *state_byseq; struct hlist_head __rcu *state_byseq;
struct hlist_head __percpu *state_cache_input;
unsigned int state_hmask; unsigned int state_hmask;
unsigned int state_num; unsigned int state_num;
struct work_struct state_hash_work; struct work_struct state_hash_work;

View File

@ -19,6 +19,7 @@
#include <net/sock.h> #include <net/sock.h>
#include <net/dst.h> #include <net/dst.h>
#include <net/inet_dscp.h>
#include <net/ip.h> #include <net/ip.h>
#include <net/route.h> #include <net/route.h>
#include <net/ipv6.h> #include <net/ipv6.h>
@ -184,10 +185,13 @@ struct xfrm_state {
}; };
struct hlist_node byspi; struct hlist_node byspi;
struct hlist_node byseq; struct hlist_node byseq;
struct hlist_node state_cache;
struct hlist_node state_cache_input;
refcount_t refcnt; refcount_t refcnt;
spinlock_t lock; spinlock_t lock;
u32 pcpu_num;
struct xfrm_id id; struct xfrm_id id;
struct xfrm_selector sel; struct xfrm_selector sel;
struct xfrm_mark mark; struct xfrm_mark mark;
@ -351,7 +355,7 @@ void xfrm_if_unregister_cb(void);
struct xfrm_dst_lookup_params { struct xfrm_dst_lookup_params {
struct net *net; struct net *net;
int tos; dscp_t dscp;
int oif; int oif;
xfrm_address_t *saddr; xfrm_address_t *saddr;
xfrm_address_t *daddr; xfrm_address_t *daddr;
@ -536,6 +540,7 @@ struct xfrm_policy_queue {
* @xp_net: network namespace the policy lives in * @xp_net: network namespace the policy lives in
* @bydst: hlist node for SPD hash table or rbtree list * @bydst: hlist node for SPD hash table or rbtree list
* @byidx: hlist node for index hash table * @byidx: hlist node for index hash table
* @state_cache_list: hlist head for policy cached xfrm states
* @lock: serialize changes to policy structure members * @lock: serialize changes to policy structure members
* @refcnt: reference count, freed once it reaches 0 * @refcnt: reference count, freed once it reaches 0
* @pos: kernel internal tie-breaker to determine age of policy * @pos: kernel internal tie-breaker to determine age of policy
@ -566,6 +571,8 @@ struct xfrm_policy {
struct hlist_node bydst; struct hlist_node bydst;
struct hlist_node byidx; struct hlist_node byidx;
struct hlist_head state_cache_list;
/* This lock only affects elements except for entry. */ /* This lock only affects elements except for entry. */
rwlock_t lock; rwlock_t lock;
refcount_t refcnt; refcount_t refcnt;
@ -1645,6 +1652,10 @@ int xfrm_state_update(struct xfrm_state *x);
struct xfrm_state *xfrm_state_lookup(struct net *net, u32 mark, struct xfrm_state *xfrm_state_lookup(struct net *net, u32 mark,
const xfrm_address_t *daddr, __be32 spi, const xfrm_address_t *daddr, __be32 spi,
u8 proto, unsigned short family); u8 proto, unsigned short family);
struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,
const xfrm_address_t *daddr,
__be32 spi, u8 proto,
unsigned short family);
struct xfrm_state *xfrm_state_lookup_byaddr(struct net *net, u32 mark, struct xfrm_state *xfrm_state_lookup_byaddr(struct net *net, u32 mark,
const xfrm_address_t *daddr, const xfrm_address_t *daddr,
const xfrm_address_t *saddr, const xfrm_address_t *saddr,
@ -1684,7 +1695,7 @@ struct xfrmk_spdinfo {
u32 spdhmcnt; u32 spdhmcnt;
}; };
struct xfrm_state *xfrm_find_acq_byseq(struct net *net, u32 mark, u32 seq); struct xfrm_state *xfrm_find_acq_byseq(struct net *net, u32 mark, u32 seq, u32 pcpu_num);
int xfrm_state_delete(struct xfrm_state *x); int xfrm_state_delete(struct xfrm_state *x);
int xfrm_state_flush(struct net *net, u8 proto, bool task_valid, bool sync); 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_state_flush(struct net *net, struct net_device *dev, bool task_valid);
@ -1796,7 +1807,7 @@ int verify_spi_info(u8 proto, u32 min, u32 max, struct netlink_ext_ack *extack);
int xfrm_alloc_spi(struct xfrm_state *x, u32 minspi, u32 maxspi, int xfrm_alloc_spi(struct xfrm_state *x, u32 minspi, u32 maxspi,
struct netlink_ext_ack *extack); struct netlink_ext_ack *extack);
struct xfrm_state *xfrm_find_acq(struct net *net, const struct xfrm_mark *mark, struct xfrm_state *xfrm_find_acq(struct net *net, const struct xfrm_mark *mark,
u8 mode, u32 reqid, u32 if_id, u8 proto, u8 mode, u32 reqid, u32 if_id, u32 pcpu_num, u8 proto,
const xfrm_address_t *daddr, const xfrm_address_t *daddr,
const xfrm_address_t *saddr, int create, const xfrm_address_t *saddr, int create,
unsigned short family); unsigned short family);

View File

@ -322,6 +322,7 @@ enum xfrm_attr_type_t {
XFRMA_MTIMER_THRESH, /* __u32 in seconds for input SA */ XFRMA_MTIMER_THRESH, /* __u32 in seconds for input SA */
XFRMA_SA_DIR, /* __u8 */ XFRMA_SA_DIR, /* __u8 */
XFRMA_NAT_KEEPALIVE_INTERVAL, /* __u32 in seconds for NAT keepalive */ XFRMA_NAT_KEEPALIVE_INTERVAL, /* __u32 in seconds for NAT keepalive */
XFRMA_SA_PCPU, /* __u32 */
__XFRMA_MAX __XFRMA_MAX
#define XFRMA_OUTPUT_MARK XFRMA_SET_MARK /* Compatibility */ #define XFRMA_OUTPUT_MARK XFRMA_SET_MARK /* Compatibility */
@ -437,6 +438,7 @@ struct xfrm_userpolicy_info {
#define XFRM_POLICY_LOCALOK 1 /* Allow user to override global policy */ #define XFRM_POLICY_LOCALOK 1 /* Allow user to override global policy */
/* Automatically expand selector to include matching ICMP payloads. */ /* Automatically expand selector to include matching ICMP payloads. */
#define XFRM_POLICY_ICMP 2 #define XFRM_POLICY_ICMP 2
#define XFRM_POLICY_CPU_ACQUIRE 4
__u8 share; __u8 share;
}; };

View File

@ -53,9 +53,9 @@ static struct sk_buff *esp4_gro_receive(struct list_head *head,
if (sp->len == XFRM_MAX_DEPTH) if (sp->len == XFRM_MAX_DEPTH)
goto out_reset; goto out_reset;
x = xfrm_state_lookup(dev_net(skb->dev), skb->mark, x = xfrm_input_state_lookup(dev_net(skb->dev), skb->mark,
(xfrm_address_t *)&ip_hdr(skb)->daddr, (xfrm_address_t *)&ip_hdr(skb)->daddr,
spi, IPPROTO_ESP, AF_INET); spi, IPPROTO_ESP, AF_INET);
if (unlikely(x && x->dir && x->dir != XFRM_SA_DIR_IN)) { if (unlikely(x && x->dir && x->dir != XFRM_SA_DIR_IN)) {
/* non-offload path will record the error and audit log */ /* non-offload path will record the error and audit log */

View File

@ -14,6 +14,7 @@
#include <linux/inetdevice.h> #include <linux/inetdevice.h>
#include <net/dst.h> #include <net/dst.h>
#include <net/xfrm.h> #include <net/xfrm.h>
#include <net/inet_dscp.h>
#include <net/ip.h> #include <net/ip.h>
#include <net/l3mdev.h> #include <net/l3mdev.h>
@ -24,7 +25,7 @@ static struct dst_entry *__xfrm4_dst_lookup(struct flowi4 *fl4,
memset(fl4, 0, sizeof(*fl4)); memset(fl4, 0, sizeof(*fl4));
fl4->daddr = params->daddr->a4; fl4->daddr = params->daddr->a4;
fl4->flowi4_tos = params->tos; fl4->flowi4_tos = inet_dscp_to_dsfield(params->dscp);
fl4->flowi4_l3mdev = l3mdev_master_ifindex_by_index(params->net, fl4->flowi4_l3mdev = l3mdev_master_ifindex_by_index(params->net,
params->oif); params->oif);
fl4->flowi4_mark = params->mark; fl4->flowi4_mark = params->mark;

View File

@ -80,9 +80,9 @@ static struct sk_buff *esp6_gro_receive(struct list_head *head,
if (sp->len == XFRM_MAX_DEPTH) if (sp->len == XFRM_MAX_DEPTH)
goto out_reset; goto out_reset;
x = xfrm_state_lookup(dev_net(skb->dev), skb->mark, x = xfrm_input_state_lookup(dev_net(skb->dev), skb->mark,
(xfrm_address_t *)&ipv6_hdr(skb)->daddr, (xfrm_address_t *)&ipv6_hdr(skb)->daddr,
spi, IPPROTO_ESP, AF_INET6); spi, IPPROTO_ESP, AF_INET6);
if (unlikely(x && x->dir && x->dir != XFRM_SA_DIR_IN)) { if (unlikely(x && x->dir && x->dir != XFRM_SA_DIR_IN)) {
/* non-offload path will record the error and audit log */ /* non-offload path will record the error and audit log */

View File

@ -1354,7 +1354,7 @@ static int pfkey_getspi(struct sock *sk, struct sk_buff *skb, const struct sadb_
} }
if (hdr->sadb_msg_seq) { if (hdr->sadb_msg_seq) {
x = xfrm_find_acq_byseq(net, DUMMY_MARK, hdr->sadb_msg_seq); x = xfrm_find_acq_byseq(net, DUMMY_MARK, hdr->sadb_msg_seq, UINT_MAX);
if (x && !xfrm_addr_equal(&x->id.daddr, xdaddr, family)) { if (x && !xfrm_addr_equal(&x->id.daddr, xdaddr, family)) {
xfrm_state_put(x); xfrm_state_put(x);
x = NULL; x = NULL;
@ -1362,7 +1362,8 @@ static int pfkey_getspi(struct sock *sk, struct sk_buff *skb, const struct sadb_
} }
if (!x) if (!x)
x = xfrm_find_acq(net, &dummy_mark, mode, reqid, 0, proto, xdaddr, xsaddr, 1, family); x = xfrm_find_acq(net, &dummy_mark, mode, reqid, 0, UINT_MAX,
proto, xdaddr, xsaddr, 1, family);
if (x == NULL) if (x == NULL)
return -ENOENT; return -ENOENT;
@ -1417,7 +1418,7 @@ static int pfkey_acquire(struct sock *sk, struct sk_buff *skb, const struct sadb
if (hdr->sadb_msg_seq == 0 || hdr->sadb_msg_errno == 0) if (hdr->sadb_msg_seq == 0 || hdr->sadb_msg_errno == 0)
return 0; return 0;
x = xfrm_find_acq_byseq(net, DUMMY_MARK, hdr->sadb_msg_seq); x = xfrm_find_acq_byseq(net, DUMMY_MARK, hdr->sadb_msg_seq, UINT_MAX);
if (x == NULL) if (x == NULL)
return 0; return 0;

View File

@ -132,6 +132,7 @@ static const struct nla_policy compat_policy[XFRMA_MAX+1] = {
[XFRMA_MTIMER_THRESH] = { .type = NLA_U32 }, [XFRMA_MTIMER_THRESH] = { .type = NLA_U32 },
[XFRMA_SA_DIR] = NLA_POLICY_RANGE(NLA_U8, XFRM_SA_DIR_IN, XFRM_SA_DIR_OUT), [XFRMA_SA_DIR] = NLA_POLICY_RANGE(NLA_U8, XFRM_SA_DIR_IN, XFRM_SA_DIR_OUT),
[XFRMA_NAT_KEEPALIVE_INTERVAL] = { .type = NLA_U32 }, [XFRMA_NAT_KEEPALIVE_INTERVAL] = { .type = NLA_U32 },
[XFRMA_SA_PCPU] = { .type = NLA_U32 },
}; };
static struct nlmsghdr *xfrm_nlmsg_put_compat(struct sk_buff *skb, static struct nlmsghdr *xfrm_nlmsg_put_compat(struct sk_buff *skb,
@ -282,9 +283,10 @@ static int xfrm_xlate64_attr(struct sk_buff *dst, const struct nlattr *src)
case XFRMA_MTIMER_THRESH: case XFRMA_MTIMER_THRESH:
case XFRMA_SA_DIR: case XFRMA_SA_DIR:
case XFRMA_NAT_KEEPALIVE_INTERVAL: case XFRMA_NAT_KEEPALIVE_INTERVAL:
case XFRMA_SA_PCPU:
return xfrm_nla_cpy(dst, src, nla_len(src)); return xfrm_nla_cpy(dst, src, nla_len(src));
default: default:
BUILD_BUG_ON(XFRMA_MAX != XFRMA_NAT_KEEPALIVE_INTERVAL); BUILD_BUG_ON(XFRMA_MAX != XFRMA_SA_PCPU);
pr_warn_once("unsupported nla_type %d\n", src->nla_type); pr_warn_once("unsupported nla_type %d\n", src->nla_type);
return -EOPNOTSUPP; return -EOPNOTSUPP;
} }
@ -439,7 +441,7 @@ static int xfrm_xlate32_attr(void *dst, const struct nlattr *nla,
int err; int err;
if (type > XFRMA_MAX) { if (type > XFRMA_MAX) {
BUILD_BUG_ON(XFRMA_MAX != XFRMA_NAT_KEEPALIVE_INTERVAL); BUILD_BUG_ON(XFRMA_MAX != XFRMA_SA_PCPU);
NL_SET_ERR_MSG(extack, "Bad attribute"); NL_SET_ERR_MSG(extack, "Bad attribute");
return -EOPNOTSUPP; return -EOPNOTSUPP;
} }

View File

@ -572,7 +572,7 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
goto drop; goto drop;
} }
x = xfrm_state_lookup(net, mark, daddr, spi, nexthdr, family); x = xfrm_input_state_lookup(net, mark, daddr, spi, nexthdr, family);
if (x == NULL) { if (x == NULL) {
secpath_reset(skb); secpath_reset(skb);
XFRM_INC_STATS(net, LINUX_MIB_XFRMINNOSTATES); XFRM_INC_STATS(net, LINUX_MIB_XFRMINNOSTATES);

View File

@ -289,7 +289,7 @@ struct dst_entry *__xfrm_dst_lookup(int family,
EXPORT_SYMBOL(__xfrm_dst_lookup); EXPORT_SYMBOL(__xfrm_dst_lookup);
static inline struct dst_entry *xfrm_dst_lookup(struct xfrm_state *x, static inline struct dst_entry *xfrm_dst_lookup(struct xfrm_state *x,
int tos, int oif, dscp_t dscp, int oif,
xfrm_address_t *prev_saddr, xfrm_address_t *prev_saddr,
xfrm_address_t *prev_daddr, xfrm_address_t *prev_daddr,
int family, u32 mark) int family, u32 mark)
@ -312,7 +312,7 @@ static inline struct dst_entry *xfrm_dst_lookup(struct xfrm_state *x,
params.net = net; params.net = net;
params.saddr = saddr; params.saddr = saddr;
params.daddr = daddr; params.daddr = daddr;
params.tos = tos; params.dscp = dscp;
params.oif = oif; params.oif = oif;
params.mark = mark; params.mark = mark;
params.ipproto = x->id.proto; params.ipproto = x->id.proto;
@ -434,6 +434,7 @@ struct xfrm_policy *xfrm_policy_alloc(struct net *net, gfp_t gfp)
if (policy) { if (policy) {
write_pnet(&policy->xp_net, net); write_pnet(&policy->xp_net, net);
INIT_LIST_HEAD(&policy->walk.all); INIT_LIST_HEAD(&policy->walk.all);
INIT_HLIST_HEAD(&policy->state_cache_list);
INIT_HLIST_NODE(&policy->bydst); INIT_HLIST_NODE(&policy->bydst);
INIT_HLIST_NODE(&policy->byidx); INIT_HLIST_NODE(&policy->byidx);
rwlock_init(&policy->lock); rwlock_init(&policy->lock);
@ -475,6 +476,9 @@ EXPORT_SYMBOL(xfrm_policy_destroy);
static void xfrm_policy_kill(struct xfrm_policy *policy) static void xfrm_policy_kill(struct xfrm_policy *policy)
{ {
struct net *net = xp_net(policy);
struct xfrm_state *x;
xfrm_dev_policy_delete(policy); xfrm_dev_policy_delete(policy);
write_lock_bh(&policy->lock); write_lock_bh(&policy->lock);
@ -490,6 +494,13 @@ static void xfrm_policy_kill(struct xfrm_policy *policy)
if (del_timer(&policy->timer)) if (del_timer(&policy->timer))
xfrm_pol_put(policy); xfrm_pol_put(policy);
/* XXX: Flush state cache */
spin_lock_bh(&net->xfrm.xfrm_state_lock);
hlist_for_each_entry_rcu(x, &policy->state_cache_list, state_cache) {
hlist_del_init_rcu(&x->state_cache);
}
spin_unlock_bh(&net->xfrm.xfrm_state_lock);
xfrm_pol_put(policy); xfrm_pol_put(policy);
} }
@ -2576,10 +2587,10 @@ xfrm_tmpl_resolve(struct xfrm_policy **pols, int npols, const struct flowi *fl,
} }
static int xfrm_get_tos(const struct flowi *fl, int family) static dscp_t xfrm_get_dscp(const struct flowi *fl, int family)
{ {
if (family == AF_INET) if (family == AF_INET)
return fl->u.ip4.flowi4_tos & INET_DSCP_MASK; return inet_dsfield_to_dscp(fl->u.ip4.flowi4_tos);
return 0; return 0;
} }
@ -2667,13 +2678,13 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy,
int header_len = 0; int header_len = 0;
int nfheader_len = 0; int nfheader_len = 0;
int trailer_len = 0; int trailer_len = 0;
int tos;
int family = policy->selector.family; int family = policy->selector.family;
xfrm_address_t saddr, daddr; xfrm_address_t saddr, daddr;
dscp_t dscp;
xfrm_flowi_addr_get(fl, &saddr, &daddr, family); xfrm_flowi_addr_get(fl, &saddr, &daddr, family);
tos = xfrm_get_tos(fl, family); dscp = xfrm_get_dscp(fl, family);
dst_hold(dst); dst_hold(dst);
@ -2721,8 +2732,8 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy,
family = xfrm[i]->props.family; family = xfrm[i]->props.family;
oif = fl->flowi_oif ? : fl->flowi_l3mdev; oif = fl->flowi_oif ? : fl->flowi_l3mdev;
dst = xfrm_dst_lookup(xfrm[i], tos, oif, dst = xfrm_dst_lookup(xfrm[i], dscp, oif, &saddr,
&saddr, &daddr, family, mark); &daddr, family, mark);
err = PTR_ERR(dst); err = PTR_ERR(dst);
if (IS_ERR(dst)) if (IS_ERR(dst))
goto put_states; goto put_states;
@ -3275,6 +3286,7 @@ no_transform:
dst_release(dst); dst_release(dst);
dst = dst_orig; dst = dst_orig;
} }
ok: ok:
xfrm_pols_put(pols, drop_pols); xfrm_pols_put(pols, drop_pols);
if (dst && dst->xfrm && if (dst && dst->xfrm &&

View File

@ -665,6 +665,7 @@ struct xfrm_state *xfrm_state_alloc(struct net *net)
refcount_set(&x->refcnt, 1); refcount_set(&x->refcnt, 1);
atomic_set(&x->tunnel_users, 0); atomic_set(&x->tunnel_users, 0);
INIT_LIST_HEAD(&x->km.all); INIT_LIST_HEAD(&x->km.all);
INIT_HLIST_NODE(&x->state_cache);
INIT_HLIST_NODE(&x->bydst); INIT_HLIST_NODE(&x->bydst);
INIT_HLIST_NODE(&x->bysrc); INIT_HLIST_NODE(&x->bysrc);
INIT_HLIST_NODE(&x->byspi); INIT_HLIST_NODE(&x->byspi);
@ -679,6 +680,7 @@ struct xfrm_state *xfrm_state_alloc(struct net *net)
x->lft.hard_packet_limit = XFRM_INF; x->lft.hard_packet_limit = XFRM_INF;
x->replay_maxage = 0; x->replay_maxage = 0;
x->replay_maxdiff = 0; x->replay_maxdiff = 0;
x->pcpu_num = UINT_MAX;
spin_lock_init(&x->lock); spin_lock_init(&x->lock);
} }
return x; return x;
@ -743,12 +745,18 @@ int __xfrm_state_delete(struct xfrm_state *x)
if (x->km.state != XFRM_STATE_DEAD) { if (x->km.state != XFRM_STATE_DEAD) {
x->km.state = XFRM_STATE_DEAD; x->km.state = XFRM_STATE_DEAD;
spin_lock(&net->xfrm.xfrm_state_lock); spin_lock(&net->xfrm.xfrm_state_lock);
list_del(&x->km.all); list_del(&x->km.all);
hlist_del_rcu(&x->bydst); hlist_del_rcu(&x->bydst);
hlist_del_rcu(&x->bysrc); hlist_del_rcu(&x->bysrc);
if (x->km.seq) if (x->km.seq)
hlist_del_rcu(&x->byseq); hlist_del_rcu(&x->byseq);
if (!hlist_unhashed(&x->state_cache))
hlist_del_rcu(&x->state_cache);
if (!hlist_unhashed(&x->state_cache_input))
hlist_del_rcu(&x->state_cache_input);
if (x->id.spi) if (x->id.spi)
hlist_del_rcu(&x->byspi); hlist_del_rcu(&x->byspi);
net->xfrm.state_num--; net->xfrm.state_num--;
@ -1101,6 +1109,52 @@ static struct xfrm_state *__xfrm_state_lookup(struct net *net, u32 mark,
return NULL; return NULL;
} }
struct xfrm_state *xfrm_input_state_lookup(struct net *net, u32 mark,
const xfrm_address_t *daddr,
__be32 spi, u8 proto,
unsigned short family)
{
struct hlist_head *state_cache_input;
struct xfrm_state *x = NULL;
int cpu = get_cpu();
state_cache_input = per_cpu_ptr(net->xfrm.state_cache_input, cpu);
rcu_read_lock();
hlist_for_each_entry_rcu(x, state_cache_input, state_cache_input) {
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;
goto out;
}
x = __xfrm_state_lookup(net, mark, daddr, spi, proto, family);
if (x && x->km.state == XFRM_STATE_VALID) {
spin_lock_bh(&net->xfrm.xfrm_state_lock);
if (hlist_unhashed(&x->state_cache_input)) {
hlist_add_head_rcu(&x->state_cache_input, state_cache_input);
} else {
hlist_del_rcu(&x->state_cache_input);
hlist_add_head_rcu(&x->state_cache_input, state_cache_input);
}
spin_unlock_bh(&net->xfrm.xfrm_state_lock);
}
out:
rcu_read_unlock();
put_cpu();
return x;
}
EXPORT_SYMBOL(xfrm_input_state_lookup);
static struct xfrm_state *__xfrm_state_lookup_byaddr(struct net *net, u32 mark, static struct xfrm_state *__xfrm_state_lookup_byaddr(struct net *net, u32 mark,
const xfrm_address_t *daddr, const xfrm_address_t *daddr,
const xfrm_address_t *saddr, const xfrm_address_t *saddr,
@ -1155,6 +1209,12 @@ static void xfrm_state_look_at(struct xfrm_policy *pol, struct xfrm_state *x,
struct xfrm_state **best, int *acq_in_progress, struct xfrm_state **best, int *acq_in_progress,
int *error) int *error)
{ {
/* We need the cpu id just as a lookup key,
* we don't require it to be stable.
*/
unsigned int pcpu_id = get_cpu();
put_cpu();
/* Resolution logic: /* Resolution logic:
* 1. There is a valid state with matching selector. Done. * 1. There is a valid state with matching selector. Done.
* 2. Valid state with inappropriate selector. Skip. * 2. Valid state with inappropriate selector. Skip.
@ -1174,13 +1234,18 @@ static void xfrm_state_look_at(struct xfrm_policy *pol, struct xfrm_state *x,
&fl->u.__fl_common)) &fl->u.__fl_common))
return; return;
if (x->pcpu_num != UINT_MAX && x->pcpu_num != pcpu_id)
return;
if (!*best || if (!*best ||
((*best)->pcpu_num == UINT_MAX && x->pcpu_num == pcpu_id) ||
(*best)->km.dying > x->km.dying || (*best)->km.dying > x->km.dying ||
((*best)->km.dying == x->km.dying && ((*best)->km.dying == x->km.dying &&
(*best)->curlft.add_time < x->curlft.add_time)) (*best)->curlft.add_time < x->curlft.add_time))
*best = x; *best = x;
} else if (x->km.state == XFRM_STATE_ACQ) { } else if (x->km.state == XFRM_STATE_ACQ) {
*acq_in_progress = 1; if (!*best || x->pcpu_num == pcpu_id)
*acq_in_progress = 1;
} else if (x->km.state == XFRM_STATE_ERROR || } else if (x->km.state == XFRM_STATE_ERROR ||
x->km.state == XFRM_STATE_EXPIRED) { x->km.state == XFRM_STATE_EXPIRED) {
if ((!x->sel.family || if ((!x->sel.family ||
@ -1209,12 +1274,60 @@ xfrm_state_find(const xfrm_address_t *daddr, const xfrm_address_t *saddr,
unsigned short encap_family = tmpl->encap_family; unsigned short encap_family = tmpl->encap_family;
unsigned int sequence; unsigned int sequence;
struct km_event c; struct km_event c;
unsigned int pcpu_id;
bool cached = false;
/* We need the cpu id just as a lookup key,
* we don't require it to be stable.
*/
pcpu_id = get_cpu();
put_cpu();
to_put = NULL; to_put = NULL;
sequence = read_seqcount_begin(&net->xfrm.xfrm_state_hash_generation); sequence = read_seqcount_begin(&net->xfrm.xfrm_state_hash_generation);
rcu_read_lock(); rcu_read_lock();
hlist_for_each_entry_rcu(x, &pol->state_cache_list, state_cache) {
if (x->props.family == encap_family &&
x->props.reqid == tmpl->reqid &&
(mark & x->mark.m) == x->mark.v &&
x->if_id == if_id &&
!(x->props.flags & XFRM_STATE_WILDRECV) &&
xfrm_state_addr_check(x, daddr, saddr, encap_family) &&
tmpl->mode == x->props.mode &&
tmpl->id.proto == x->id.proto &&
(tmpl->id.spi == x->id.spi || !tmpl->id.spi))
xfrm_state_look_at(pol, x, fl, encap_family,
&best, &acquire_in_progress, &error);
}
if (best)
goto cached;
hlist_for_each_entry_rcu(x, &pol->state_cache_list, state_cache) {
if (x->props.family == encap_family &&
x->props.reqid == tmpl->reqid &&
(mark & x->mark.m) == x->mark.v &&
x->if_id == if_id &&
!(x->props.flags & XFRM_STATE_WILDRECV) &&
xfrm_addr_equal(&x->id.daddr, daddr, encap_family) &&
tmpl->mode == x->props.mode &&
tmpl->id.proto == x->id.proto &&
(tmpl->id.spi == x->id.spi || !tmpl->id.spi))
xfrm_state_look_at(pol, x, fl, family,
&best, &acquire_in_progress, &error);
}
cached:
cached = true;
if (best)
goto found;
else if (error)
best = NULL;
else if (acquire_in_progress) /* XXX: acquire_in_progress should not happen */
WARN_ON(1);
h = xfrm_dst_hash(net, daddr, saddr, tmpl->reqid, encap_family); h = xfrm_dst_hash(net, daddr, saddr, tmpl->reqid, encap_family);
hlist_for_each_entry_rcu(x, net->xfrm.state_bydst + h, bydst) { hlist_for_each_entry_rcu(x, net->xfrm.state_bydst + h, bydst) {
#ifdef CONFIG_XFRM_OFFLOAD #ifdef CONFIG_XFRM_OFFLOAD
@ -1282,7 +1395,10 @@ xfrm_state_find(const xfrm_address_t *daddr, const xfrm_address_t *saddr,
} }
found: found:
x = best; if (!(pol->flags & XFRM_POLICY_CPU_ACQUIRE) ||
(best && (best->pcpu_num == pcpu_id)))
x = best;
if (!x && !error && !acquire_in_progress) { if (!x && !error && !acquire_in_progress) {
if (tmpl->id.spi && if (tmpl->id.spi &&
(x0 = __xfrm_state_lookup_all(net, mark, daddr, (x0 = __xfrm_state_lookup_all(net, mark, daddr,
@ -1314,6 +1430,8 @@ found:
xfrm_init_tempstate(x, fl, tmpl, daddr, saddr, family); xfrm_init_tempstate(x, fl, tmpl, daddr, saddr, family);
memcpy(&x->mark, &pol->mark, sizeof(x->mark)); memcpy(&x->mark, &pol->mark, sizeof(x->mark));
x->if_id = if_id; x->if_id = if_id;
if ((pol->flags & XFRM_POLICY_CPU_ACQUIRE) && best)
x->pcpu_num = pcpu_id;
error = security_xfrm_state_alloc_acquire(x, pol->security, fl->flowi_secid); error = security_xfrm_state_alloc_acquire(x, pol->security, fl->flowi_secid);
if (error) { if (error) {
@ -1352,6 +1470,7 @@ found:
x->km.state = XFRM_STATE_ACQ; x->km.state = XFRM_STATE_ACQ;
x->dir = XFRM_SA_DIR_OUT; x->dir = XFRM_SA_DIR_OUT;
list_add(&x->km.all, &net->xfrm.state_all); list_add(&x->km.all, &net->xfrm.state_all);
h = xfrm_dst_hash(net, daddr, saddr, tmpl->reqid, encap_family);
XFRM_STATE_INSERT(bydst, &x->bydst, XFRM_STATE_INSERT(bydst, &x->bydst,
net->xfrm.state_bydst + h, net->xfrm.state_bydst + h,
x->xso.type); x->xso.type);
@ -1359,6 +1478,7 @@ found:
XFRM_STATE_INSERT(bysrc, &x->bysrc, XFRM_STATE_INSERT(bysrc, &x->bysrc,
net->xfrm.state_bysrc + h, net->xfrm.state_bysrc + h,
x->xso.type); x->xso.type);
INIT_HLIST_NODE(&x->state_cache);
if (x->id.spi) { if (x->id.spi) {
h = xfrm_spi_hash(net, &x->id.daddr, x->id.spi, x->id.proto, encap_family); h = xfrm_spi_hash(net, &x->id.daddr, x->id.spi, x->id.proto, encap_family);
XFRM_STATE_INSERT(byspi, &x->byspi, XFRM_STATE_INSERT(byspi, &x->byspi,
@ -1392,6 +1512,11 @@ found:
x = NULL; x = NULL;
error = -ESRCH; error = -ESRCH;
} }
/* Use the already installed 'fallback' while the CPU-specific
* SA acquire is handled*/
if (best)
x = best;
} }
out: out:
if (x) { if (x) {
@ -1402,6 +1527,15 @@ out:
} else { } else {
*err = acquire_in_progress ? -EAGAIN : error; *err = acquire_in_progress ? -EAGAIN : error;
} }
if (x && x->km.state == XFRM_STATE_VALID && !cached &&
(!(pol->flags & XFRM_POLICY_CPU_ACQUIRE) || x->pcpu_num == pcpu_id)) {
spin_lock_bh(&net->xfrm.xfrm_state_lock);
if (hlist_unhashed(&x->state_cache))
hlist_add_head_rcu(&x->state_cache, &pol->state_cache_list);
spin_unlock_bh(&net->xfrm.xfrm_state_lock);
}
rcu_read_unlock(); rcu_read_unlock();
if (to_put) if (to_put)
xfrm_state_put(to_put); xfrm_state_put(to_put);
@ -1524,12 +1658,14 @@ static void __xfrm_state_bump_genids(struct xfrm_state *xnew)
unsigned int h; unsigned int h;
u32 mark = xnew->mark.v & xnew->mark.m; u32 mark = xnew->mark.v & xnew->mark.m;
u32 if_id = xnew->if_id; u32 if_id = xnew->if_id;
u32 cpu_id = xnew->pcpu_num;
h = xfrm_dst_hash(net, &xnew->id.daddr, &xnew->props.saddr, reqid, family); h = xfrm_dst_hash(net, &xnew->id.daddr, &xnew->props.saddr, reqid, family);
hlist_for_each_entry(x, net->xfrm.state_bydst+h, bydst) { hlist_for_each_entry(x, net->xfrm.state_bydst+h, bydst) {
if (x->props.family == family && if (x->props.family == family &&
x->props.reqid == reqid && x->props.reqid == reqid &&
x->if_id == if_id && x->if_id == if_id &&
x->pcpu_num == cpu_id &&
(mark & x->mark.m) == x->mark.v && (mark & x->mark.m) == x->mark.v &&
xfrm_addr_equal(&x->id.daddr, &xnew->id.daddr, family) && xfrm_addr_equal(&x->id.daddr, &xnew->id.daddr, family) &&
xfrm_addr_equal(&x->props.saddr, &xnew->props.saddr, family)) xfrm_addr_equal(&x->props.saddr, &xnew->props.saddr, family))
@ -1552,7 +1688,7 @@ EXPORT_SYMBOL(xfrm_state_insert);
static struct xfrm_state *__find_acq_core(struct net *net, static struct xfrm_state *__find_acq_core(struct net *net,
const struct xfrm_mark *m, const struct xfrm_mark *m,
unsigned short family, u8 mode, unsigned short family, u8 mode,
u32 reqid, u32 if_id, u8 proto, u32 reqid, u32 if_id, u32 pcpu_num, u8 proto,
const xfrm_address_t *daddr, const xfrm_address_t *daddr,
const xfrm_address_t *saddr, const xfrm_address_t *saddr,
int create) int create)
@ -1569,6 +1705,7 @@ static struct xfrm_state *__find_acq_core(struct net *net,
x->id.spi != 0 || x->id.spi != 0 ||
x->id.proto != proto || x->id.proto != proto ||
(mark & x->mark.m) != x->mark.v || (mark & x->mark.m) != x->mark.v ||
x->pcpu_num != pcpu_num ||
!xfrm_addr_equal(&x->id.daddr, daddr, family) || !xfrm_addr_equal(&x->id.daddr, daddr, family) ||
!xfrm_addr_equal(&x->props.saddr, saddr, family)) !xfrm_addr_equal(&x->props.saddr, saddr, family))
continue; continue;
@ -1602,6 +1739,7 @@ static struct xfrm_state *__find_acq_core(struct net *net,
break; break;
} }
x->pcpu_num = pcpu_num;
x->km.state = XFRM_STATE_ACQ; x->km.state = XFRM_STATE_ACQ;
x->id.proto = proto; x->id.proto = proto;
x->props.family = family; x->props.family = family;
@ -1630,7 +1768,7 @@ static struct xfrm_state *__find_acq_core(struct net *net,
return x; return x;
} }
static struct xfrm_state *__xfrm_find_acq_byseq(struct net *net, u32 mark, u32 seq); static struct xfrm_state *__xfrm_find_acq_byseq(struct net *net, u32 mark, u32 seq, u32 pcpu_num);
int xfrm_state_add(struct xfrm_state *x) int xfrm_state_add(struct xfrm_state *x)
{ {
@ -1656,7 +1794,7 @@ int xfrm_state_add(struct xfrm_state *x)
} }
if (use_spi && x->km.seq) { if (use_spi && x->km.seq) {
x1 = __xfrm_find_acq_byseq(net, mark, x->km.seq); x1 = __xfrm_find_acq_byseq(net, mark, x->km.seq, x->pcpu_num);
if (x1 && ((x1->id.proto != x->id.proto) || if (x1 && ((x1->id.proto != x->id.proto) ||
!xfrm_addr_equal(&x1->id.daddr, &x->id.daddr, family))) { !xfrm_addr_equal(&x1->id.daddr, &x->id.daddr, family))) {
to_put = x1; to_put = x1;
@ -1666,7 +1804,7 @@ int xfrm_state_add(struct xfrm_state *x)
if (use_spi && !x1) if (use_spi && !x1)
x1 = __find_acq_core(net, &x->mark, family, x->props.mode, x1 = __find_acq_core(net, &x->mark, family, x->props.mode,
x->props.reqid, x->if_id, x->id.proto, x->props.reqid, x->if_id, x->pcpu_num, x->id.proto,
&x->id.daddr, &x->props.saddr, 0); &x->id.daddr, &x->props.saddr, 0);
__xfrm_state_bump_genids(x); __xfrm_state_bump_genids(x);
@ -1791,6 +1929,7 @@ static struct xfrm_state *xfrm_state_clone(struct xfrm_state *orig,
x->props.flags = orig->props.flags; x->props.flags = orig->props.flags;
x->props.extra_flags = orig->props.extra_flags; x->props.extra_flags = orig->props.extra_flags;
x->pcpu_num = orig->pcpu_num;
x->if_id = orig->if_id; x->if_id = orig->if_id;
x->tfcpad = orig->tfcpad; x->tfcpad = orig->tfcpad;
x->replay_maxdiff = orig->replay_maxdiff; x->replay_maxdiff = orig->replay_maxdiff;
@ -2066,13 +2205,14 @@ EXPORT_SYMBOL(xfrm_state_lookup_byaddr);
struct xfrm_state * struct xfrm_state *
xfrm_find_acq(struct net *net, const struct xfrm_mark *mark, u8 mode, u32 reqid, xfrm_find_acq(struct net *net, const struct xfrm_mark *mark, u8 mode, u32 reqid,
u32 if_id, u8 proto, const xfrm_address_t *daddr, u32 if_id, u32 pcpu_num, u8 proto, const xfrm_address_t *daddr,
const xfrm_address_t *saddr, int create, unsigned short family) const xfrm_address_t *saddr, int create, unsigned short family)
{ {
struct xfrm_state *x; struct xfrm_state *x;
spin_lock_bh(&net->xfrm.xfrm_state_lock); spin_lock_bh(&net->xfrm.xfrm_state_lock);
x = __find_acq_core(net, mark, family, mode, reqid, if_id, proto, daddr, saddr, create); x = __find_acq_core(net, mark, family, mode, reqid, if_id, pcpu_num,
proto, daddr, saddr, create);
spin_unlock_bh(&net->xfrm.xfrm_state_lock); spin_unlock_bh(&net->xfrm.xfrm_state_lock);
return x; return x;
@ -2207,7 +2347,7 @@ xfrm_state_sort(struct xfrm_state **dst, struct xfrm_state **src, int n,
/* Silly enough, but I'm lazy to build resolution list */ /* Silly enough, but I'm lazy to build resolution list */
static struct xfrm_state *__xfrm_find_acq_byseq(struct net *net, u32 mark, u32 seq) static struct xfrm_state *__xfrm_find_acq_byseq(struct net *net, u32 mark, u32 seq, u32 pcpu_num)
{ {
unsigned int h = xfrm_seq_hash(net, seq); unsigned int h = xfrm_seq_hash(net, seq);
struct xfrm_state *x; struct xfrm_state *x;
@ -2215,6 +2355,7 @@ static struct xfrm_state *__xfrm_find_acq_byseq(struct net *net, u32 mark, u32 s
hlist_for_each_entry_rcu(x, net->xfrm.state_byseq + h, byseq) { hlist_for_each_entry_rcu(x, net->xfrm.state_byseq + h, byseq) {
if (x->km.seq == seq && if (x->km.seq == seq &&
(mark & x->mark.m) == x->mark.v && (mark & x->mark.m) == x->mark.v &&
x->pcpu_num == pcpu_num &&
x->km.state == XFRM_STATE_ACQ) { x->km.state == XFRM_STATE_ACQ) {
xfrm_state_hold(x); xfrm_state_hold(x);
return x; return x;
@ -2224,12 +2365,12 @@ static struct xfrm_state *__xfrm_find_acq_byseq(struct net *net, u32 mark, u32 s
return NULL; return NULL;
} }
struct xfrm_state *xfrm_find_acq_byseq(struct net *net, u32 mark, u32 seq) struct xfrm_state *xfrm_find_acq_byseq(struct net *net, u32 mark, u32 seq, u32 pcpu_num)
{ {
struct xfrm_state *x; struct xfrm_state *x;
spin_lock_bh(&net->xfrm.xfrm_state_lock); spin_lock_bh(&net->xfrm.xfrm_state_lock);
x = __xfrm_find_acq_byseq(net, mark, seq); x = __xfrm_find_acq_byseq(net, mark, seq, pcpu_num);
spin_unlock_bh(&net->xfrm.xfrm_state_lock); spin_unlock_bh(&net->xfrm.xfrm_state_lock);
return x; return x;
} }
@ -2988,6 +3129,11 @@ int __net_init xfrm_state_init(struct net *net)
net->xfrm.state_byseq = xfrm_hash_alloc(sz); net->xfrm.state_byseq = xfrm_hash_alloc(sz);
if (!net->xfrm.state_byseq) if (!net->xfrm.state_byseq)
goto out_byseq; goto out_byseq;
net->xfrm.state_cache_input = alloc_percpu(struct hlist_head);
if (!net->xfrm.state_cache_input)
goto out_state_cache_input;
net->xfrm.state_hmask = ((sz / sizeof(struct hlist_head)) - 1); net->xfrm.state_hmask = ((sz / sizeof(struct hlist_head)) - 1);
net->xfrm.state_num = 0; net->xfrm.state_num = 0;
@ -2997,6 +3143,8 @@ int __net_init xfrm_state_init(struct net *net)
&net->xfrm.xfrm_state_lock); &net->xfrm.xfrm_state_lock);
return 0; return 0;
out_state_cache_input:
xfrm_hash_free(net->xfrm.state_byseq, sz);
out_byseq: out_byseq:
xfrm_hash_free(net->xfrm.state_byspi, sz); xfrm_hash_free(net->xfrm.state_byspi, sz);
out_byspi: out_byspi:
@ -3026,6 +3174,7 @@ void xfrm_state_fini(struct net *net)
xfrm_hash_free(net->xfrm.state_bysrc, sz); xfrm_hash_free(net->xfrm.state_bysrc, sz);
WARN_ON(!hlist_empty(net->xfrm.state_bydst)); WARN_ON(!hlist_empty(net->xfrm.state_bydst));
xfrm_hash_free(net->xfrm.state_bydst, sz); xfrm_hash_free(net->xfrm.state_bydst, sz);
free_percpu(net->xfrm.state_cache_input);
} }
#ifdef CONFIG_AUDITSYSCALL #ifdef CONFIG_AUDITSYSCALL

View File

@ -460,6 +460,12 @@ static int verify_newsa_info(struct xfrm_usersa_info *p,
} }
} }
if (!sa_dir && attrs[XFRMA_SA_PCPU]) {
NL_SET_ERR_MSG(extack, "SA_PCPU only supported with SA_DIR");
err = -EINVAL;
goto out;
}
out: out:
return err; return err;
} }
@ -839,6 +845,12 @@ static struct xfrm_state *xfrm_state_construct(struct net *net,
x->nat_keepalive_interval = x->nat_keepalive_interval =
nla_get_u32(attrs[XFRMA_NAT_KEEPALIVE_INTERVAL]); nla_get_u32(attrs[XFRMA_NAT_KEEPALIVE_INTERVAL]);
if (attrs[XFRMA_SA_PCPU]) {
x->pcpu_num = nla_get_u32(attrs[XFRMA_SA_PCPU]);
if (x->pcpu_num >= num_possible_cpus())
goto error;
}
err = __xfrm_init_state(x, false, attrs[XFRMA_OFFLOAD_DEV], extack); err = __xfrm_init_state(x, false, attrs[XFRMA_OFFLOAD_DEV], extack);
if (err) if (err)
goto error; goto error;
@ -1087,7 +1099,7 @@ static int copy_to_user_auth(struct xfrm_algo_auth *auth, struct sk_buff *skb)
if (!nla) if (!nla)
return -EMSGSIZE; return -EMSGSIZE;
algo = nla_data(nla); algo = nla_data(nla);
strncpy(algo->alg_name, auth->alg_name, sizeof(algo->alg_name)); strscpy_pad(algo->alg_name, auth->alg_name, sizeof(algo->alg_name));
if (redact_secret && auth->alg_key_len) if (redact_secret && auth->alg_key_len)
memset(algo->alg_key, 0, (auth->alg_key_len + 7) / 8); memset(algo->alg_key, 0, (auth->alg_key_len + 7) / 8);
@ -1294,6 +1306,11 @@ static int copy_to_user_state_extra(struct xfrm_state *x,
if (ret) if (ret)
goto out; goto out;
} }
if (x->pcpu_num != UINT_MAX) {
ret = nla_put_u32(skb, XFRMA_SA_PCPU, x->pcpu_num);
if (ret)
goto out;
}
if (x->dir) if (x->dir)
ret = nla_put_u8(skb, XFRMA_SA_DIR, x->dir); ret = nla_put_u8(skb, XFRMA_SA_DIR, x->dir);
@ -1698,6 +1715,7 @@ static int xfrm_alloc_userspi(struct sk_buff *skb, struct nlmsghdr *nlh,
u32 mark; u32 mark;
struct xfrm_mark m; struct xfrm_mark m;
u32 if_id = 0; u32 if_id = 0;
u32 pcpu_num = UINT_MAX;
p = nlmsg_data(nlh); p = nlmsg_data(nlh);
err = verify_spi_info(p->info.id.proto, p->min, p->max, extack); err = verify_spi_info(p->info.id.proto, p->min, p->max, extack);
@ -1714,8 +1732,16 @@ static int xfrm_alloc_userspi(struct sk_buff *skb, struct nlmsghdr *nlh,
if (attrs[XFRMA_IF_ID]) if (attrs[XFRMA_IF_ID])
if_id = nla_get_u32(attrs[XFRMA_IF_ID]); if_id = nla_get_u32(attrs[XFRMA_IF_ID]);
if (attrs[XFRMA_SA_PCPU]) {
pcpu_num = nla_get_u32(attrs[XFRMA_SA_PCPU]);
if (pcpu_num >= num_possible_cpus()) {
err = -EINVAL;
goto out_noput;
}
}
if (p->info.seq) { if (p->info.seq) {
x = xfrm_find_acq_byseq(net, mark, p->info.seq); x = xfrm_find_acq_byseq(net, mark, p->info.seq, pcpu_num);
if (x && !xfrm_addr_equal(&x->id.daddr, daddr, family)) { if (x && !xfrm_addr_equal(&x->id.daddr, daddr, family)) {
xfrm_state_put(x); xfrm_state_put(x);
x = NULL; x = NULL;
@ -1724,7 +1750,7 @@ static int xfrm_alloc_userspi(struct sk_buff *skb, struct nlmsghdr *nlh,
if (!x) if (!x)
x = xfrm_find_acq(net, &m, p->info.mode, p->info.reqid, x = xfrm_find_acq(net, &m, p->info.mode, p->info.reqid,
if_id, p->info.id.proto, daddr, if_id, pcpu_num, p->info.id.proto, daddr,
&p->info.saddr, 1, &p->info.saddr, 1,
family); family);
err = -ENOENT; err = -ENOENT;
@ -2524,7 +2550,8 @@ static inline unsigned int xfrm_aevent_msgsize(struct xfrm_state *x)
+ nla_total_size(sizeof(struct xfrm_mark)) + nla_total_size(sizeof(struct xfrm_mark))
+ nla_total_size(4) /* XFRM_AE_RTHR */ + nla_total_size(4) /* XFRM_AE_RTHR */
+ nla_total_size(4) /* XFRM_AE_ETHR */ + nla_total_size(4) /* XFRM_AE_ETHR */
+ nla_total_size(sizeof(x->dir)); /* XFRMA_SA_DIR */ + nla_total_size(sizeof(x->dir)) /* XFRMA_SA_DIR */
+ nla_total_size(4); /* XFRMA_SA_PCPU */
} }
static int build_aevent(struct sk_buff *skb, struct xfrm_state *x, const struct km_event *c) static int build_aevent(struct sk_buff *skb, struct xfrm_state *x, const struct km_event *c)
@ -2580,6 +2607,11 @@ static int build_aevent(struct sk_buff *skb, struct xfrm_state *x, const struct
err = xfrm_if_id_put(skb, x->if_id); err = xfrm_if_id_put(skb, x->if_id);
if (err) if (err)
goto out_cancel; goto out_cancel;
if (x->pcpu_num != UINT_MAX) {
err = nla_put_u32(skb, XFRMA_SA_PCPU, x->pcpu_num);
if (err)
goto out_cancel;
}
if (x->dir) { if (x->dir) {
err = nla_put_u8(skb, XFRMA_SA_DIR, x->dir); err = nla_put_u8(skb, XFRMA_SA_DIR, x->dir);
@ -2850,6 +2882,13 @@ static int xfrm_add_acquire(struct sk_buff *skb, struct nlmsghdr *nlh,
xfrm_mark_get(attrs, &mark); xfrm_mark_get(attrs, &mark);
if (attrs[XFRMA_SA_PCPU]) {
x->pcpu_num = nla_get_u32(attrs[XFRMA_SA_PCPU]);
err = -EINVAL;
if (x->pcpu_num >= num_possible_cpus())
goto free_state;
}
err = verify_newpolicy_info(&ua->policy, extack); err = verify_newpolicy_info(&ua->policy, extack);
if (err) if (err)
goto free_state; goto free_state;
@ -3180,6 +3219,7 @@ const struct nla_policy xfrma_policy[XFRMA_MAX+1] = {
[XFRMA_MTIMER_THRESH] = { .type = NLA_U32 }, [XFRMA_MTIMER_THRESH] = { .type = NLA_U32 },
[XFRMA_SA_DIR] = NLA_POLICY_RANGE(NLA_U8, XFRM_SA_DIR_IN, XFRM_SA_DIR_OUT), [XFRMA_SA_DIR] = NLA_POLICY_RANGE(NLA_U8, XFRM_SA_DIR_IN, XFRM_SA_DIR_OUT),
[XFRMA_NAT_KEEPALIVE_INTERVAL] = { .type = NLA_U32 }, [XFRMA_NAT_KEEPALIVE_INTERVAL] = { .type = NLA_U32 },
[XFRMA_SA_PCPU] = { .type = NLA_U32 },
}; };
EXPORT_SYMBOL_GPL(xfrma_policy); EXPORT_SYMBOL_GPL(xfrma_policy);
@ -3243,6 +3283,20 @@ static int xfrm_reject_unused_attr(int type, struct nlattr **attrs,
} }
} }
if (attrs[XFRMA_SA_PCPU]) {
switch (type) {
case XFRM_MSG_NEWSA:
case XFRM_MSG_UPDSA:
case XFRM_MSG_ALLOCSPI:
case XFRM_MSG_ACQUIRE:
break;
default:
NL_SET_ERR_MSG(extack, "Invalid attribute SA_PCPU");
return -EINVAL;
}
}
return 0; return 0;
} }
@ -3346,7 +3400,8 @@ static inline unsigned int xfrm_expire_msgsize(void)
{ {
return NLMSG_ALIGN(sizeof(struct xfrm_user_expire)) + return NLMSG_ALIGN(sizeof(struct xfrm_user_expire)) +
nla_total_size(sizeof(struct xfrm_mark)) + nla_total_size(sizeof(struct xfrm_mark)) +
nla_total_size(sizeof_field(struct xfrm_state, dir)); nla_total_size(sizeof_field(struct xfrm_state, dir)) +
nla_total_size(4); /* XFRMA_SA_PCPU */
} }
static int build_expire(struct sk_buff *skb, struct xfrm_state *x, const struct km_event *c) static int build_expire(struct sk_buff *skb, struct xfrm_state *x, const struct km_event *c)
@ -3372,6 +3427,11 @@ static int build_expire(struct sk_buff *skb, struct xfrm_state *x, const struct
err = xfrm_if_id_put(skb, x->if_id); err = xfrm_if_id_put(skb, x->if_id);
if (err) if (err)
return err; return err;
if (x->pcpu_num != UINT_MAX) {
err = nla_put_u32(skb, XFRMA_SA_PCPU, x->pcpu_num);
if (err)
return err;
}
if (x->dir) { if (x->dir) {
err = nla_put_u8(skb, XFRMA_SA_DIR, x->dir); err = nla_put_u8(skb, XFRMA_SA_DIR, x->dir);
@ -3479,6 +3539,8 @@ static inline unsigned int xfrm_sa_len(struct xfrm_state *x)
} }
if (x->if_id) if (x->if_id)
l += nla_total_size(sizeof(x->if_id)); l += nla_total_size(sizeof(x->if_id));
if (x->pcpu_num)
l += nla_total_size(sizeof(x->pcpu_num));
/* Must count x->lastused as it may become non-zero behind our back. */ /* Must count x->lastused as it may become non-zero behind our back. */
l += nla_total_size_64bit(sizeof(u64)); l += nla_total_size_64bit(sizeof(u64));
@ -3585,6 +3647,7 @@ static inline unsigned int xfrm_acquire_msgsize(struct xfrm_state *x,
+ nla_total_size(sizeof(struct xfrm_user_tmpl) * xp->xfrm_nr) + nla_total_size(sizeof(struct xfrm_user_tmpl) * xp->xfrm_nr)
+ nla_total_size(sizeof(struct xfrm_mark)) + nla_total_size(sizeof(struct xfrm_mark))
+ nla_total_size(xfrm_user_sec_ctx_size(x->security)) + nla_total_size(xfrm_user_sec_ctx_size(x->security))
+ nla_total_size(4) /* XFRMA_SA_PCPU */
+ userpolicy_type_attrsize(); + userpolicy_type_attrsize();
} }
@ -3621,6 +3684,8 @@ static int build_acquire(struct sk_buff *skb, struct xfrm_state *x,
err = xfrm_if_id_put(skb, xp->if_id); err = xfrm_if_id_put(skb, xp->if_id);
if (!err && xp->xdo.dev) if (!err && xp->xdo.dev)
err = copy_user_offload(&xp->xdo, skb); err = copy_user_offload(&xp->xdo, skb);
if (!err && x->pcpu_num != UINT_MAX)
err = nla_put_u32(skb, XFRMA_SA_PCPU, x->pcpu_num);
if (err) { if (err) {
nlmsg_cancel(skb, nlh); nlmsg_cancel(skb, nlh);
return err; return err;