xref: /openbmc/linux/net/ipv6/ila/ila_xlat.c (revision 9144f784f852f9a125cabe9927b986d909bfa439)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
27f00feafSTom Herbert #include <linux/jhash.h>
37f00feafSTom Herbert #include <linux/netfilter.h>
47f00feafSTom Herbert #include <linux/rcupdate.h>
57f00feafSTom Herbert #include <linux/rhashtable.h>
67f00feafSTom Herbert #include <linux/vmalloc.h>
77f00feafSTom Herbert #include <net/genetlink.h>
87f00feafSTom Herbert #include <net/netns/generic.h>
97f00feafSTom Herbert #include <uapi/linux/genetlink.h>
107f00feafSTom Herbert #include "ila.h"
117f00feafSTom Herbert 
127f00feafSTom Herbert struct ila_xlat_params {
137f00feafSTom Herbert 	struct ila_params ip;
147f00feafSTom Herbert 	int ifindex;
157f00feafSTom Herbert };
167f00feafSTom Herbert 
177f00feafSTom Herbert struct ila_map {
18351596aaSTom Herbert 	struct ila_xlat_params xp;
197f00feafSTom Herbert 	struct rhash_head node;
207f00feafSTom Herbert 	struct ila_map __rcu *next;
217f00feafSTom Herbert 	struct rcu_head rcu;
227f00feafSTom Herbert };
237f00feafSTom Herbert 
24b8932817STom Herbert #define MAX_LOCKS 1024
257f00feafSTom Herbert #define	LOCKS_PER_CPU 10
267f00feafSTom Herbert 
alloc_ila_locks(struct ila_net * ilan)277f00feafSTom Herbert static int alloc_ila_locks(struct ila_net *ilan)
287f00feafSTom Herbert {
29ad68147eSTom Herbert 	return alloc_bucket_spinlocks(&ilan->xlat.locks, &ilan->xlat.locks_mask,
30b8932817STom Herbert 				      MAX_LOCKS, LOCKS_PER_CPU,
31344476e1SKees Cook 				      GFP_KERNEL);
327f00feafSTom Herbert }
337f00feafSTom Herbert 
347f00feafSTom Herbert static u32 hashrnd __read_mostly;
__ila_hash_secret_init(void)357f00feafSTom Herbert static __always_inline void __ila_hash_secret_init(void)
367f00feafSTom Herbert {
377f00feafSTom Herbert 	net_get_random_once(&hashrnd, sizeof(hashrnd));
387f00feafSTom Herbert }
397f00feafSTom Herbert 
ila_locator_hash(struct ila_locator loc)40642c2c95STom Herbert static inline u32 ila_locator_hash(struct ila_locator loc)
417f00feafSTom Herbert {
42642c2c95STom Herbert 	u32 *v = (u32 *)loc.v32;
437f00feafSTom Herbert 
440db47e3dSArnd Bergmann 	__ila_hash_secret_init();
457f00feafSTom Herbert 	return jhash_2words(v[0], v[1], hashrnd);
467f00feafSTom Herbert }
477f00feafSTom Herbert 
ila_get_lock(struct ila_net * ilan,struct ila_locator loc)48351596aaSTom Herbert static inline spinlock_t *ila_get_lock(struct ila_net *ilan,
49642c2c95STom Herbert 				       struct ila_locator loc)
507f00feafSTom Herbert {
51ad68147eSTom Herbert 	return &ilan->xlat.locks[ila_locator_hash(loc) & ilan->xlat.locks_mask];
527f00feafSTom Herbert }
537f00feafSTom Herbert 
ila_cmp_wildcards(struct ila_map * ila,struct ila_addr * iaddr,int ifindex)54351596aaSTom Herbert static inline int ila_cmp_wildcards(struct ila_map *ila,
55642c2c95STom Herbert 				    struct ila_addr *iaddr, int ifindex)
567f00feafSTom Herbert {
57642c2c95STom Herbert 	return (ila->xp.ifindex && ila->xp.ifindex != ifindex);
587f00feafSTom Herbert }
597f00feafSTom Herbert 
ila_cmp_params(struct ila_map * ila,struct ila_xlat_params * xp)60351596aaSTom Herbert static inline int ila_cmp_params(struct ila_map *ila,
61351596aaSTom Herbert 				 struct ila_xlat_params *xp)
627f00feafSTom Herbert {
63642c2c95STom Herbert 	return (ila->xp.ifindex != xp->ifindex);
647f00feafSTom Herbert }
657f00feafSTom Herbert 
ila_cmpfn(struct rhashtable_compare_arg * arg,const void * obj)667f00feafSTom Herbert static int ila_cmpfn(struct rhashtable_compare_arg *arg,
677f00feafSTom Herbert 		     const void *obj)
687f00feafSTom Herbert {
697f00feafSTom Herbert 	const struct ila_map *ila = obj;
707f00feafSTom Herbert 
71642c2c95STom Herbert 	return (ila->xp.ip.locator_match.v64 != *(__be64 *)arg->key);
727f00feafSTom Herbert }
737f00feafSTom Herbert 
ila_order(struct ila_map * ila)747f00feafSTom Herbert static inline int ila_order(struct ila_map *ila)
757f00feafSTom Herbert {
767f00feafSTom Herbert 	int score = 0;
777f00feafSTom Herbert 
78351596aaSTom Herbert 	if (ila->xp.ifindex)
797f00feafSTom Herbert 		score += 1 << 1;
807f00feafSTom Herbert 
817f00feafSTom Herbert 	return score;
827f00feafSTom Herbert }
837f00feafSTom Herbert 
847f00feafSTom Herbert static const struct rhashtable_params rht_params = {
857f00feafSTom Herbert 	.nelem_hint = 1024,
867f00feafSTom Herbert 	.head_offset = offsetof(struct ila_map, node),
87642c2c95STom Herbert 	.key_offset = offsetof(struct ila_map, xp.ip.locator_match),
887f00feafSTom Herbert 	.key_len = sizeof(u64), /* identifier */
897f00feafSTom Herbert 	.max_size = 1048576,
907f00feafSTom Herbert 	.min_size = 256,
917f00feafSTom Herbert 	.automatic_shrinking = true,
927f00feafSTom Herbert 	.obj_cmpfn = ila_cmpfn,
937f00feafSTom Herbert };
947f00feafSTom Herbert 
parse_nl_config(struct genl_info * info,struct ila_xlat_params * xp)957f00feafSTom Herbert static int parse_nl_config(struct genl_info *info,
96351596aaSTom Herbert 			   struct ila_xlat_params *xp)
977f00feafSTom Herbert {
98351596aaSTom Herbert 	memset(xp, 0, sizeof(*xp));
997f00feafSTom Herbert 
1007f00feafSTom Herbert 	if (info->attrs[ILA_ATTR_LOCATOR])
101351596aaSTom Herbert 		xp->ip.locator.v64 = (__force __be64)nla_get_u64(
1027f00feafSTom Herbert 			info->attrs[ILA_ATTR_LOCATOR]);
1037f00feafSTom Herbert 
1047f00feafSTom Herbert 	if (info->attrs[ILA_ATTR_LOCATOR_MATCH])
105351596aaSTom Herbert 		xp->ip.locator_match.v64 = (__force __be64)nla_get_u64(
1067f00feafSTom Herbert 			info->attrs[ILA_ATTR_LOCATOR_MATCH]);
1077f00feafSTom Herbert 
10890bfe662STom Herbert 	if (info->attrs[ILA_ATTR_CSUM_MODE])
10990bfe662STom Herbert 		xp->ip.csum_mode = nla_get_u8(info->attrs[ILA_ATTR_CSUM_MODE]);
11084287bb3STom Herbert 	else
11184287bb3STom Herbert 		xp->ip.csum_mode = ILA_CSUM_NO_ACTION;
11290bfe662STom Herbert 
11370d5aef4STom Herbert 	if (info->attrs[ILA_ATTR_IDENT_TYPE])
11470d5aef4STom Herbert 		xp->ip.ident_type = nla_get_u8(
11570d5aef4STom Herbert 				info->attrs[ILA_ATTR_IDENT_TYPE]);
11670d5aef4STom Herbert 	else
11770d5aef4STom Herbert 		xp->ip.ident_type = ILA_ATYPE_USE_FORMAT;
11870d5aef4STom Herbert 
1197f00feafSTom Herbert 	if (info->attrs[ILA_ATTR_IFINDEX])
120351596aaSTom Herbert 		xp->ifindex = nla_get_s32(info->attrs[ILA_ATTR_IFINDEX]);
1217f00feafSTom Herbert 
1227f00feafSTom Herbert 	return 0;
1237f00feafSTom Herbert }
1247f00feafSTom Herbert 
1257f00feafSTom Herbert /* Must be called with rcu readlock */
ila_lookup_wildcards(struct ila_addr * iaddr,int ifindex,struct ila_net * ilan)126351596aaSTom Herbert static inline struct ila_map *ila_lookup_wildcards(struct ila_addr *iaddr,
1277f00feafSTom Herbert 						   int ifindex,
1287f00feafSTom Herbert 						   struct ila_net *ilan)
1297f00feafSTom Herbert {
1307f00feafSTom Herbert 	struct ila_map *ila;
1317f00feafSTom Herbert 
132ad68147eSTom Herbert 	ila = rhashtable_lookup_fast(&ilan->xlat.rhash_table, &iaddr->loc,
133351596aaSTom Herbert 				     rht_params);
1347f00feafSTom Herbert 	while (ila) {
135642c2c95STom Herbert 		if (!ila_cmp_wildcards(ila, iaddr, ifindex))
1367f00feafSTom Herbert 			return ila;
1377f00feafSTom Herbert 		ila = rcu_access_pointer(ila->next);
1387f00feafSTom Herbert 	}
1397f00feafSTom Herbert 
1407f00feafSTom Herbert 	return NULL;
1417f00feafSTom Herbert }
1427f00feafSTom Herbert 
1437f00feafSTom Herbert /* Must be called with rcu readlock */
ila_lookup_by_params(struct ila_xlat_params * xp,struct ila_net * ilan)144351596aaSTom Herbert static inline struct ila_map *ila_lookup_by_params(struct ila_xlat_params *xp,
1457f00feafSTom Herbert 						   struct ila_net *ilan)
1467f00feafSTom Herbert {
1477f00feafSTom Herbert 	struct ila_map *ila;
1487f00feafSTom Herbert 
149ad68147eSTom Herbert 	ila = rhashtable_lookup_fast(&ilan->xlat.rhash_table,
150642c2c95STom Herbert 				     &xp->ip.locator_match,
1517f00feafSTom Herbert 				     rht_params);
1527f00feafSTom Herbert 	while (ila) {
153351596aaSTom Herbert 		if (!ila_cmp_params(ila, xp))
1547f00feafSTom Herbert 			return ila;
1557f00feafSTom Herbert 		ila = rcu_access_pointer(ila->next);
1567f00feafSTom Herbert 	}
1577f00feafSTom Herbert 
1587f00feafSTom Herbert 	return NULL;
1597f00feafSTom Herbert }
1607f00feafSTom Herbert 
ila_release(struct ila_map * ila)1617f00feafSTom Herbert static inline void ila_release(struct ila_map *ila)
1627f00feafSTom Herbert {
1637f00feafSTom Herbert 	kfree_rcu(ila, rcu);
1647f00feafSTom Herbert }
1657f00feafSTom Herbert 
ila_free_node(struct ila_map * ila)166b6e71bdeSTom Herbert static void ila_free_node(struct ila_map *ila)
1677f00feafSTom Herbert {
168b6e71bdeSTom Herbert 	struct ila_map *next;
1697f00feafSTom Herbert 
1707f00feafSTom Herbert 	/* Assume rcu_readlock held */
1717f00feafSTom Herbert 	while (ila) {
1727f00feafSTom Herbert 		next = rcu_access_pointer(ila->next);
1737f00feafSTom Herbert 		ila_release(ila);
1747f00feafSTom Herbert 		ila = next;
1757f00feafSTom Herbert 	}
1767f00feafSTom Herbert }
1777f00feafSTom Herbert 
ila_free_cb(void * ptr,void * arg)178b6e71bdeSTom Herbert static void ila_free_cb(void *ptr, void *arg)
179b6e71bdeSTom Herbert {
180b6e71bdeSTom Herbert 	ila_free_node((struct ila_map *)ptr);
181b6e71bdeSTom Herbert }
182b6e71bdeSTom Herbert 
18384287bb3STom Herbert static int ila_xlat_addr(struct sk_buff *skb, bool sir2ila);
1847f00feafSTom Herbert 
1857f00feafSTom Herbert static unsigned int
ila_nf_input(void * priv,struct sk_buff * skb,const struct nf_hook_state * state)1867f00feafSTom Herbert ila_nf_input(void *priv,
1877f00feafSTom Herbert 	     struct sk_buff *skb,
1887f00feafSTom Herbert 	     const struct nf_hook_state *state)
1897f00feafSTom Herbert {
190707a2ca4STom Herbert 	ila_xlat_addr(skb, false);
1917f00feafSTom Herbert 	return NF_ACCEPT;
1927f00feafSTom Herbert }
1937f00feafSTom Herbert 
194591bb278SFlorian Westphal static const struct nf_hook_ops ila_nf_hook_ops[] = {
1957f00feafSTom Herbert 	{
1967f00feafSTom Herbert 		.hook = ila_nf_input,
1977f00feafSTom Herbert 		.pf = NFPROTO_IPV6,
1987f00feafSTom Herbert 		.hooknum = NF_INET_PRE_ROUTING,
1997f00feafSTom Herbert 		.priority = -1,
2007f00feafSTom Herbert 	},
2017f00feafSTom Herbert };
2027f00feafSTom Herbert 
203*17e8fa89SEric Dumazet static DEFINE_MUTEX(ila_mutex);
204*17e8fa89SEric Dumazet 
ila_add_mapping(struct net * net,struct ila_xlat_params * xp)205351596aaSTom Herbert static int ila_add_mapping(struct net *net, struct ila_xlat_params *xp)
2067f00feafSTom Herbert {
2077f00feafSTom Herbert 	struct ila_net *ilan = net_generic(net, ila_net_id);
2087f00feafSTom Herbert 	struct ila_map *ila, *head;
209642c2c95STom Herbert 	spinlock_t *lock = ila_get_lock(ilan, xp->ip.locator_match);
2107f00feafSTom Herbert 	int err = 0, order;
2117f00feafSTom Herbert 
212*17e8fa89SEric Dumazet 	if (!READ_ONCE(ilan->xlat.hooks_registered)) {
2137f00feafSTom Herbert 		/* We defer registering net hooks in the namespace until the
2147f00feafSTom Herbert 		 * first mapping is added.
2157f00feafSTom Herbert 		 */
216*17e8fa89SEric Dumazet 		mutex_lock(&ila_mutex);
217*17e8fa89SEric Dumazet 		if (!ilan->xlat.hooks_registered) {
2187f00feafSTom Herbert 			err = nf_register_net_hooks(net, ila_nf_hook_ops,
2197f00feafSTom Herbert 						ARRAY_SIZE(ila_nf_hook_ops));
220*17e8fa89SEric Dumazet 			if (!err)
221*17e8fa89SEric Dumazet 				WRITE_ONCE(ilan->xlat.hooks_registered, true);
222*17e8fa89SEric Dumazet 		}
223*17e8fa89SEric Dumazet 		mutex_unlock(&ila_mutex);
2247f00feafSTom Herbert 		if (err)
2257f00feafSTom Herbert 			return err;
2267f00feafSTom Herbert 	}
2277f00feafSTom Herbert 
2287f00feafSTom Herbert 	ila = kzalloc(sizeof(*ila), GFP_KERNEL);
2297f00feafSTom Herbert 	if (!ila)
2307f00feafSTom Herbert 		return -ENOMEM;
2317f00feafSTom Herbert 
23290bfe662STom Herbert 	ila_init_saved_csum(&xp->ip);
2337f00feafSTom Herbert 
23490bfe662STom Herbert 	ila->xp = *xp;
2357f00feafSTom Herbert 
2367f00feafSTom Herbert 	order = ila_order(ila);
2377f00feafSTom Herbert 
2387f00feafSTom Herbert 	spin_lock(lock);
2397f00feafSTom Herbert 
240ad68147eSTom Herbert 	head = rhashtable_lookup_fast(&ilan->xlat.rhash_table,
241642c2c95STom Herbert 				      &xp->ip.locator_match,
2427f00feafSTom Herbert 				      rht_params);
2437f00feafSTom Herbert 	if (!head) {
2447f00feafSTom Herbert 		/* New entry for the rhash_table */
245ad68147eSTom Herbert 		err = rhashtable_lookup_insert_fast(&ilan->xlat.rhash_table,
2467f00feafSTom Herbert 						    &ila->node, rht_params);
2477f00feafSTom Herbert 	} else {
2487f00feafSTom Herbert 		struct ila_map *tila = head, *prev = NULL;
2497f00feafSTom Herbert 
2507f00feafSTom Herbert 		do {
251351596aaSTom Herbert 			if (!ila_cmp_params(tila, xp)) {
2527f00feafSTom Herbert 				err = -EEXIST;
2537f00feafSTom Herbert 				goto out;
2547f00feafSTom Herbert 			}
2557f00feafSTom Herbert 
2567f00feafSTom Herbert 			if (order > ila_order(tila))
2577f00feafSTom Herbert 				break;
2587f00feafSTom Herbert 
2597f00feafSTom Herbert 			prev = tila;
2607f00feafSTom Herbert 			tila = rcu_dereference_protected(tila->next,
2617f00feafSTom Herbert 				lockdep_is_held(lock));
2627f00feafSTom Herbert 		} while (tila);
2637f00feafSTom Herbert 
2647f00feafSTom Herbert 		if (prev) {
2657f00feafSTom Herbert 			/* Insert in sub list of head */
2667f00feafSTom Herbert 			RCU_INIT_POINTER(ila->next, tila);
2677f00feafSTom Herbert 			rcu_assign_pointer(prev->next, ila);
2687f00feafSTom Herbert 		} else {
2697f00feafSTom Herbert 			/* Make this ila new head */
2707f00feafSTom Herbert 			RCU_INIT_POINTER(ila->next, head);
271ad68147eSTom Herbert 			err = rhashtable_replace_fast(&ilan->xlat.rhash_table,
2727f00feafSTom Herbert 						      &head->node,
2737f00feafSTom Herbert 						      &ila->node, rht_params);
2747f00feafSTom Herbert 			if (err)
2757f00feafSTom Herbert 				goto out;
2767f00feafSTom Herbert 		}
2777f00feafSTom Herbert 	}
2787f00feafSTom Herbert 
2797f00feafSTom Herbert out:
2807f00feafSTom Herbert 	spin_unlock(lock);
2817f00feafSTom Herbert 
2827f00feafSTom Herbert 	if (err)
2837f00feafSTom Herbert 		kfree(ila);
2847f00feafSTom Herbert 
2857f00feafSTom Herbert 	return err;
2867f00feafSTom Herbert }
2877f00feafSTom Herbert 
ila_del_mapping(struct net * net,struct ila_xlat_params * xp)288351596aaSTom Herbert static int ila_del_mapping(struct net *net, struct ila_xlat_params *xp)
2897f00feafSTom Herbert {
2907f00feafSTom Herbert 	struct ila_net *ilan = net_generic(net, ila_net_id);
2917f00feafSTom Herbert 	struct ila_map *ila, *head, *prev;
292642c2c95STom Herbert 	spinlock_t *lock = ila_get_lock(ilan, xp->ip.locator_match);
2937f00feafSTom Herbert 	int err = -ENOENT;
2947f00feafSTom Herbert 
2957f00feafSTom Herbert 	spin_lock(lock);
2967f00feafSTom Herbert 
297ad68147eSTom Herbert 	head = rhashtable_lookup_fast(&ilan->xlat.rhash_table,
298642c2c95STom Herbert 				      &xp->ip.locator_match, rht_params);
2997f00feafSTom Herbert 	ila = head;
3007f00feafSTom Herbert 
3017f00feafSTom Herbert 	prev = NULL;
3027f00feafSTom Herbert 
3037f00feafSTom Herbert 	while (ila) {
304351596aaSTom Herbert 		if (ila_cmp_params(ila, xp)) {
3057f00feafSTom Herbert 			prev = ila;
3067f00feafSTom Herbert 			ila = rcu_dereference_protected(ila->next,
3077f00feafSTom Herbert 							lockdep_is_held(lock));
3087f00feafSTom Herbert 			continue;
3097f00feafSTom Herbert 		}
3107f00feafSTom Herbert 
3117f00feafSTom Herbert 		err = 0;
3127f00feafSTom Herbert 
3137f00feafSTom Herbert 		if (prev) {
3147f00feafSTom Herbert 			/* Not head, just delete from list */
3157f00feafSTom Herbert 			rcu_assign_pointer(prev->next, ila->next);
3167f00feafSTom Herbert 		} else {
3177f00feafSTom Herbert 			/* It is the head. If there is something in the
3187f00feafSTom Herbert 			 * sublist we need to make a new head.
3197f00feafSTom Herbert 			 */
3207f00feafSTom Herbert 			head = rcu_dereference_protected(ila->next,
3217f00feafSTom Herbert 							 lockdep_is_held(lock));
3227f00feafSTom Herbert 			if (head) {
3237f00feafSTom Herbert 				/* Put first entry in the sublist into the
3247f00feafSTom Herbert 				 * table
3257f00feafSTom Herbert 				 */
3267f00feafSTom Herbert 				err = rhashtable_replace_fast(
327ad68147eSTom Herbert 					&ilan->xlat.rhash_table, &ila->node,
3287f00feafSTom Herbert 					&head->node, rht_params);
3297f00feafSTom Herbert 				if (err)
3307f00feafSTom Herbert 					goto out;
3317f00feafSTom Herbert 			} else {
3327f00feafSTom Herbert 				/* Entry no longer used */
333ad68147eSTom Herbert 				err = rhashtable_remove_fast(
334ad68147eSTom Herbert 						&ilan->xlat.rhash_table,
335ad68147eSTom Herbert 						&ila->node, rht_params);
3367f00feafSTom Herbert 			}
3377f00feafSTom Herbert 		}
3387f00feafSTom Herbert 
3397f00feafSTom Herbert 		ila_release(ila);
3407f00feafSTom Herbert 
3417f00feafSTom Herbert 		break;
3427f00feafSTom Herbert 	}
3437f00feafSTom Herbert 
3447f00feafSTom Herbert out:
3457f00feafSTom Herbert 	spin_unlock(lock);
3467f00feafSTom Herbert 
3477f00feafSTom Herbert 	return err;
3487f00feafSTom Herbert }
3497f00feafSTom Herbert 
ila_xlat_nl_cmd_add_mapping(struct sk_buff * skb,struct genl_info * info)350ad68147eSTom Herbert int ila_xlat_nl_cmd_add_mapping(struct sk_buff *skb, struct genl_info *info)
3517f00feafSTom Herbert {
3527f00feafSTom Herbert 	struct net *net = genl_info_net(info);
3537f00feafSTom Herbert 	struct ila_xlat_params p;
3547f00feafSTom Herbert 	int err;
3557f00feafSTom Herbert 
3567f00feafSTom Herbert 	err = parse_nl_config(info, &p);
3577f00feafSTom Herbert 	if (err)
3587f00feafSTom Herbert 		return err;
3597f00feafSTom Herbert 
3607f00feafSTom Herbert 	return ila_add_mapping(net, &p);
3617f00feafSTom Herbert }
3627f00feafSTom Herbert 
ila_xlat_nl_cmd_del_mapping(struct sk_buff * skb,struct genl_info * info)363ad68147eSTom Herbert int ila_xlat_nl_cmd_del_mapping(struct sk_buff *skb, struct genl_info *info)
3647f00feafSTom Herbert {
3657f00feafSTom Herbert 	struct net *net = genl_info_net(info);
366351596aaSTom Herbert 	struct ila_xlat_params xp;
3677f00feafSTom Herbert 	int err;
3687f00feafSTom Herbert 
369351596aaSTom Herbert 	err = parse_nl_config(info, &xp);
3707f00feafSTom Herbert 	if (err)
3717f00feafSTom Herbert 		return err;
3727f00feafSTom Herbert 
373351596aaSTom Herbert 	ila_del_mapping(net, &xp);
3747f00feafSTom Herbert 
3757f00feafSTom Herbert 	return 0;
3767f00feafSTom Herbert }
3777f00feafSTom Herbert 
lock_from_ila_map(struct ila_net * ilan,struct ila_map * ila)378b6e71bdeSTom Herbert static inline spinlock_t *lock_from_ila_map(struct ila_net *ilan,
379b6e71bdeSTom Herbert 					    struct ila_map *ila)
380b6e71bdeSTom Herbert {
381b6e71bdeSTom Herbert 	return ila_get_lock(ilan, ila->xp.ip.locator_match);
382b6e71bdeSTom Herbert }
383b6e71bdeSTom Herbert 
ila_xlat_nl_cmd_flush(struct sk_buff * skb,struct genl_info * info)384b6e71bdeSTom Herbert int ila_xlat_nl_cmd_flush(struct sk_buff *skb, struct genl_info *info)
385b6e71bdeSTom Herbert {
386b6e71bdeSTom Herbert 	struct net *net = genl_info_net(info);
387b6e71bdeSTom Herbert 	struct ila_net *ilan = net_generic(net, ila_net_id);
388b6e71bdeSTom Herbert 	struct rhashtable_iter iter;
389b6e71bdeSTom Herbert 	struct ila_map *ila;
390b6e71bdeSTom Herbert 	spinlock_t *lock;
3914ef595cbSHerbert Xu 	int ret = 0;
392b6e71bdeSTom Herbert 
3936c4128f6SHerbert Xu 	rhashtable_walk_enter(&ilan->xlat.rhash_table, &iter);
394b6e71bdeSTom Herbert 	rhashtable_walk_start(&iter);
395b6e71bdeSTom Herbert 
396b6e71bdeSTom Herbert 	for (;;) {
397b6e71bdeSTom Herbert 		ila = rhashtable_walk_next(&iter);
398b6e71bdeSTom Herbert 
399b6e71bdeSTom Herbert 		if (IS_ERR(ila)) {
400b6e71bdeSTom Herbert 			if (PTR_ERR(ila) == -EAGAIN)
401b6e71bdeSTom Herbert 				continue;
402b6e71bdeSTom Herbert 			ret = PTR_ERR(ila);
403b6e71bdeSTom Herbert 			goto done;
404b6e71bdeSTom Herbert 		} else if (!ila) {
405b6e71bdeSTom Herbert 			break;
406b6e71bdeSTom Herbert 		}
407b6e71bdeSTom Herbert 
408b6e71bdeSTom Herbert 		lock = lock_from_ila_map(ilan, ila);
409b6e71bdeSTom Herbert 
410b6e71bdeSTom Herbert 		spin_lock(lock);
411b6e71bdeSTom Herbert 
412b6e71bdeSTom Herbert 		ret = rhashtable_remove_fast(&ilan->xlat.rhash_table,
413b6e71bdeSTom Herbert 					     &ila->node, rht_params);
414b6e71bdeSTom Herbert 		if (!ret)
415b6e71bdeSTom Herbert 			ila_free_node(ila);
416b6e71bdeSTom Herbert 
417b6e71bdeSTom Herbert 		spin_unlock(lock);
418b6e71bdeSTom Herbert 
419b6e71bdeSTom Herbert 		if (ret)
420b6e71bdeSTom Herbert 			break;
421b6e71bdeSTom Herbert 	}
422b6e71bdeSTom Herbert 
423b6e71bdeSTom Herbert done:
424b6e71bdeSTom Herbert 	rhashtable_walk_stop(&iter);
425b5f9bd15SHerbert Xu 	rhashtable_walk_exit(&iter);
426b6e71bdeSTom Herbert 	return ret;
427b6e71bdeSTom Herbert }
428b6e71bdeSTom Herbert 
ila_fill_info(struct ila_map * ila,struct sk_buff * msg)4297f00feafSTom Herbert static int ila_fill_info(struct ila_map *ila, struct sk_buff *msg)
4307f00feafSTom Herbert {
431642c2c95STom Herbert 	if (nla_put_u64_64bit(msg, ILA_ATTR_LOCATOR,
432351596aaSTom Herbert 			      (__force u64)ila->xp.ip.locator.v64,
433f13a82d8SNicolas Dichtel 			      ILA_ATTR_PAD) ||
434f13a82d8SNicolas Dichtel 	    nla_put_u64_64bit(msg, ILA_ATTR_LOCATOR_MATCH,
435351596aaSTom Herbert 			      (__force u64)ila->xp.ip.locator_match.v64,
436f13a82d8SNicolas Dichtel 			      ILA_ATTR_PAD) ||
43790bfe662STom Herbert 	    nla_put_s32(msg, ILA_ATTR_IFINDEX, ila->xp.ifindex) ||
43870d5aef4STom Herbert 	    nla_put_u8(msg, ILA_ATTR_CSUM_MODE, ila->xp.ip.csum_mode) ||
43970d5aef4STom Herbert 	    nla_put_u8(msg, ILA_ATTR_IDENT_TYPE, ila->xp.ip.ident_type))
4407f00feafSTom Herbert 		return -1;
4417f00feafSTom Herbert 
4427f00feafSTom Herbert 	return 0;
4437f00feafSTom Herbert }
4447f00feafSTom Herbert 
ila_dump_info(struct ila_map * ila,u32 portid,u32 seq,u32 flags,struct sk_buff * skb,u8 cmd)4457f00feafSTom Herbert static int ila_dump_info(struct ila_map *ila,
4467f00feafSTom Herbert 			 u32 portid, u32 seq, u32 flags,
4477f00feafSTom Herbert 			 struct sk_buff *skb, u8 cmd)
4487f00feafSTom Herbert {
4497f00feafSTom Herbert 	void *hdr;
4507f00feafSTom Herbert 
4517f00feafSTom Herbert 	hdr = genlmsg_put(skb, portid, seq, &ila_nl_family, flags, cmd);
4527f00feafSTom Herbert 	if (!hdr)
4537f00feafSTom Herbert 		return -ENOMEM;
4547f00feafSTom Herbert 
4557f00feafSTom Herbert 	if (ila_fill_info(ila, skb) < 0)
4567f00feafSTom Herbert 		goto nla_put_failure;
4577f00feafSTom Herbert 
4587f00feafSTom Herbert 	genlmsg_end(skb, hdr);
4597f00feafSTom Herbert 	return 0;
4607f00feafSTom Herbert 
4617f00feafSTom Herbert nla_put_failure:
4627f00feafSTom Herbert 	genlmsg_cancel(skb, hdr);
4637f00feafSTom Herbert 	return -EMSGSIZE;
4647f00feafSTom Herbert }
4657f00feafSTom Herbert 
ila_xlat_nl_cmd_get_mapping(struct sk_buff * skb,struct genl_info * info)466ad68147eSTom Herbert int ila_xlat_nl_cmd_get_mapping(struct sk_buff *skb, struct genl_info *info)
4677f00feafSTom Herbert {
4687f00feafSTom Herbert 	struct net *net = genl_info_net(info);
4697f00feafSTom Herbert 	struct ila_net *ilan = net_generic(net, ila_net_id);
4707f00feafSTom Herbert 	struct sk_buff *msg;
471351596aaSTom Herbert 	struct ila_xlat_params xp;
4727f00feafSTom Herbert 	struct ila_map *ila;
4737f00feafSTom Herbert 	int ret;
4747f00feafSTom Herbert 
475351596aaSTom Herbert 	ret = parse_nl_config(info, &xp);
4767f00feafSTom Herbert 	if (ret)
4777f00feafSTom Herbert 		return ret;
4787f00feafSTom Herbert 
4797f00feafSTom Herbert 	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
4807f00feafSTom Herbert 	if (!msg)
4817f00feafSTom Herbert 		return -ENOMEM;
4827f00feafSTom Herbert 
4837f00feafSTom Herbert 	rcu_read_lock();
4847f00feafSTom Herbert 
485693aa2c0SEric Dumazet 	ret = -ESRCH;
486351596aaSTom Herbert 	ila = ila_lookup_by_params(&xp, ilan);
4877f00feafSTom Herbert 	if (ila) {
4887f00feafSTom Herbert 		ret = ila_dump_info(ila,
4897f00feafSTom Herbert 				    info->snd_portid,
4907f00feafSTom Herbert 				    info->snd_seq, 0, msg,
4917f00feafSTom Herbert 				    info->genlhdr->cmd);
4927f00feafSTom Herbert 	}
4937f00feafSTom Herbert 
4947f00feafSTom Herbert 	rcu_read_unlock();
4957f00feafSTom Herbert 
4967f00feafSTom Herbert 	if (ret < 0)
4977f00feafSTom Herbert 		goto out_free;
4987f00feafSTom Herbert 
4997f00feafSTom Herbert 	return genlmsg_reply(msg, info);
5007f00feafSTom Herbert 
5017f00feafSTom Herbert out_free:
5027f00feafSTom Herbert 	nlmsg_free(msg);
5037f00feafSTom Herbert 	return ret;
5047f00feafSTom Herbert }
5057f00feafSTom Herbert 
5067f00feafSTom Herbert struct ila_dump_iter {
5077f00feafSTom Herbert 	struct rhashtable_iter rhiter;
508f7a2ba5aSTom Herbert 	int skip;
5097f00feafSTom Herbert };
5107f00feafSTom Herbert 
ila_xlat_nl_dump_start(struct netlink_callback * cb)511ad68147eSTom Herbert int ila_xlat_nl_dump_start(struct netlink_callback *cb)
5127f00feafSTom Herbert {
5137f00feafSTom Herbert 	struct net *net = sock_net(cb->skb->sk);
5147f00feafSTom Herbert 	struct ila_net *ilan = net_generic(net, ila_net_id);
515f7a2ba5aSTom Herbert 	struct ila_dump_iter *iter;
5161913540aSTom Herbert 
5171913540aSTom Herbert 	iter = kmalloc(sizeof(*iter), GFP_KERNEL);
5181913540aSTom Herbert 	if (!iter)
5191913540aSTom Herbert 		return -ENOMEM;
5201913540aSTom Herbert 
5216c4128f6SHerbert Xu 	rhashtable_walk_enter(&ilan->xlat.rhash_table, &iter->rhiter);
5227f00feafSTom Herbert 
523f7a2ba5aSTom Herbert 	iter->skip = 0;
524f7a2ba5aSTom Herbert 	cb->args[0] = (long)iter;
525f7a2ba5aSTom Herbert 
5266c4128f6SHerbert Xu 	return 0;
5277f00feafSTom Herbert }
5287f00feafSTom Herbert 
ila_xlat_nl_dump_done(struct netlink_callback * cb)529ad68147eSTom Herbert int ila_xlat_nl_dump_done(struct netlink_callback *cb)
5307f00feafSTom Herbert {
5311913540aSTom Herbert 	struct ila_dump_iter *iter = (struct ila_dump_iter *)cb->args[0];
5327f00feafSTom Herbert 
5337f00feafSTom Herbert 	rhashtable_walk_exit(&iter->rhiter);
5347f00feafSTom Herbert 
5351913540aSTom Herbert 	kfree(iter);
5361913540aSTom Herbert 
5377f00feafSTom Herbert 	return 0;
5387f00feafSTom Herbert }
5397f00feafSTom Herbert 
ila_xlat_nl_dump(struct sk_buff * skb,struct netlink_callback * cb)540ad68147eSTom Herbert int ila_xlat_nl_dump(struct sk_buff *skb, struct netlink_callback *cb)
5417f00feafSTom Herbert {
5421913540aSTom Herbert 	struct ila_dump_iter *iter = (struct ila_dump_iter *)cb->args[0];
5437f00feafSTom Herbert 	struct rhashtable_iter *rhiter = &iter->rhiter;
544f7a2ba5aSTom Herbert 	int skip = iter->skip;
5457f00feafSTom Herbert 	struct ila_map *ila;
5467f00feafSTom Herbert 	int ret;
5477f00feafSTom Herbert 
54897a6ec4aSTom Herbert 	rhashtable_walk_start(rhiter);
5497f00feafSTom Herbert 
550f7a2ba5aSTom Herbert 	/* Get first entry */
551f7a2ba5aSTom Herbert 	ila = rhashtable_walk_peek(rhiter);
5527f00feafSTom Herbert 
553f7a2ba5aSTom Herbert 	if (ila && !IS_ERR(ila) && skip) {
554f7a2ba5aSTom Herbert 		/* Skip over visited entries */
555f7a2ba5aSTom Herbert 
556f7a2ba5aSTom Herbert 		while (ila && skip) {
557f7a2ba5aSTom Herbert 			/* Skip over any ila entries in this list that we
558f7a2ba5aSTom Herbert 			 * have already dumped.
559f7a2ba5aSTom Herbert 			 */
560f7a2ba5aSTom Herbert 			ila = rcu_access_pointer(ila->next);
561f7a2ba5aSTom Herbert 			skip--;
562f7a2ba5aSTom Herbert 		}
563f7a2ba5aSTom Herbert 	}
564f7a2ba5aSTom Herbert 
565f7a2ba5aSTom Herbert 	skip = 0;
566f7a2ba5aSTom Herbert 
567f7a2ba5aSTom Herbert 	for (;;) {
5687f00feafSTom Herbert 		if (IS_ERR(ila)) {
5697f00feafSTom Herbert 			ret = PTR_ERR(ila);
570f7a2ba5aSTom Herbert 			if (ret == -EAGAIN) {
571f7a2ba5aSTom Herbert 				/* Table has changed and iter has reset. Return
572f7a2ba5aSTom Herbert 				 * -EAGAIN to the application even if we have
573f7a2ba5aSTom Herbert 				 * written data to the skb. The application
574f7a2ba5aSTom Herbert 				 * needs to deal with this.
575f7a2ba5aSTom Herbert 				 */
576f7a2ba5aSTom Herbert 
577f7a2ba5aSTom Herbert 				goto out_ret;
578f7a2ba5aSTom Herbert 			} else {
579f7a2ba5aSTom Herbert 				break;
580f7a2ba5aSTom Herbert 			}
5817f00feafSTom Herbert 		} else if (!ila) {
582f7a2ba5aSTom Herbert 			ret = 0;
5837f00feafSTom Herbert 			break;
5847f00feafSTom Herbert 		}
5857f00feafSTom Herbert 
5867f00feafSTom Herbert 		while (ila) {
5877f00feafSTom Herbert 			ret =  ila_dump_info(ila, NETLINK_CB(cb->skb).portid,
5887f00feafSTom Herbert 					     cb->nlh->nlmsg_seq, NLM_F_MULTI,
5897f00feafSTom Herbert 					     skb, ILA_CMD_GET);
5907f00feafSTom Herbert 			if (ret)
591f7a2ba5aSTom Herbert 				goto out;
5927f00feafSTom Herbert 
593f7a2ba5aSTom Herbert 			skip++;
5947f00feafSTom Herbert 			ila = rcu_access_pointer(ila->next);
5957f00feafSTom Herbert 		}
596f7a2ba5aSTom Herbert 
597f7a2ba5aSTom Herbert 		skip = 0;
598f7a2ba5aSTom Herbert 		ila = rhashtable_walk_next(rhiter);
5997f00feafSTom Herbert 	}
6007f00feafSTom Herbert 
601f7a2ba5aSTom Herbert out:
602f7a2ba5aSTom Herbert 	iter->skip = skip;
603f7a2ba5aSTom Herbert 	ret = (skb->len ? : ret);
6047f00feafSTom Herbert 
605f7a2ba5aSTom Herbert out_ret:
6067f00feafSTom Herbert 	rhashtable_walk_stop(rhiter);
6077f00feafSTom Herbert 	return ret;
6087f00feafSTom Herbert }
6097f00feafSTom Herbert 
ila_xlat_init_net(struct net * net)610ad68147eSTom Herbert int ila_xlat_init_net(struct net *net)
6117f00feafSTom Herbert {
6127f00feafSTom Herbert 	struct ila_net *ilan = net_generic(net, ila_net_id);
613ad68147eSTom Herbert 	int err;
6147f00feafSTom Herbert 
6157f00feafSTom Herbert 	err = alloc_ila_locks(ilan);
6167f00feafSTom Herbert 	if (err)
6177f00feafSTom Herbert 		return err;
6187f00feafSTom Herbert 
619f04ed7d2SMichelleJin 	err = rhashtable_init(&ilan->xlat.rhash_table, &rht_params);
620f04ed7d2SMichelleJin 	if (err) {
621f04ed7d2SMichelleJin 		free_bucket_spinlocks(ilan->xlat.locks);
622f04ed7d2SMichelleJin 		return err;
623f04ed7d2SMichelleJin 	}
6247f00feafSTom Herbert 
6257f00feafSTom Herbert 	return 0;
6267f00feafSTom Herbert }
6277f00feafSTom Herbert 
ila_xlat_pre_exit_net(struct net * net)62818a5a169SEric Dumazet void ila_xlat_pre_exit_net(struct net *net)
62918a5a169SEric Dumazet {
63018a5a169SEric Dumazet 	struct ila_net *ilan = net_generic(net, ila_net_id);
63118a5a169SEric Dumazet 
63218a5a169SEric Dumazet 	if (ilan->xlat.hooks_registered)
63318a5a169SEric Dumazet 		nf_unregister_net_hooks(net, ila_nf_hook_ops,
63418a5a169SEric Dumazet 					ARRAY_SIZE(ila_nf_hook_ops));
63518a5a169SEric Dumazet }
63618a5a169SEric Dumazet 
ila_xlat_exit_net(struct net * net)637ad68147eSTom Herbert void ila_xlat_exit_net(struct net *net)
6387f00feafSTom Herbert {
6397f00feafSTom Herbert 	struct ila_net *ilan = net_generic(net, ila_net_id);
6407f00feafSTom Herbert 
641ad68147eSTom Herbert 	rhashtable_free_and_destroy(&ilan->xlat.rhash_table, ila_free_cb, NULL);
6427f00feafSTom Herbert 
643ad68147eSTom Herbert 	free_bucket_spinlocks(ilan->xlat.locks);
6447f00feafSTom Herbert }
6457f00feafSTom Herbert 
ila_xlat_addr(struct sk_buff * skb,bool sir2ila)64684287bb3STom Herbert static int ila_xlat_addr(struct sk_buff *skb, bool sir2ila)
6477f00feafSTom Herbert {
6487f00feafSTom Herbert 	struct ila_map *ila;
6497f00feafSTom Herbert 	struct ipv6hdr *ip6h = ipv6_hdr(skb);
6507f00feafSTom Herbert 	struct net *net = dev_net(skb->dev);
6517f00feafSTom Herbert 	struct ila_net *ilan = net_generic(net, ila_net_id);
652351596aaSTom Herbert 	struct ila_addr *iaddr = ila_a2i(&ip6h->daddr);
6537f00feafSTom Herbert 
6547f00feafSTom Herbert 	/* Assumes skb contains a valid IPv6 header that is pulled */
6557f00feafSTom Herbert 
65670d5aef4STom Herbert 	/* No check here that ILA type in the mapping matches what is in the
65770d5aef4STom Herbert 	 * address. We assume that whatever sender gaves us can be translated.
65870d5aef4STom Herbert 	 * The checksum mode however is relevant.
65970d5aef4STom Herbert 	 */
6607f00feafSTom Herbert 
6617f00feafSTom Herbert 	rcu_read_lock();
6627f00feafSTom Herbert 
663642c2c95STom Herbert 	ila = ila_lookup_wildcards(iaddr, skb->dev->ifindex, ilan);
6647f00feafSTom Herbert 	if (ila)
66584287bb3STom Herbert 		ila_update_ipv6_locator(skb, &ila->xp.ip, sir2ila);
6667f00feafSTom Herbert 
6677f00feafSTom Herbert 	rcu_read_unlock();
6687f00feafSTom Herbert 
6697f00feafSTom Herbert 	return 0;
6707f00feafSTom Herbert }
671