1971 lines · 44.6 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* * vrf.c: device driver to encapsulate a VRF space * * Copyright (c) 2015 Cumulus Networks. All rights reserved. * Copyright (c) 2015 Shrijeet Mukherjee <shm@cumulusnetworks.com> * Copyright (c) 2015 David Ahern <dsa@cumulusnetworks.com> * * Based on dummy, team and ipvlan drivers */ #include <linux/ethtool.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/netdevice.h>#include <linux/etherdevice.h>#include <linux/ip.h>#include <linux/init.h>#include <linux/moduleparam.h>#include <linux/netfilter.h>#include <linux/rtnetlink.h>#include <net/rtnetlink.h>#include <linux/u64_stats_sync.h>#include <linux/hashtable.h>#include <linux/spinlock_types.h> #include <linux/inetdevice.h>#include <net/arp.h>#include <net/flow.h>#include <net/ip.h>#include <net/ip_fib.h>#include <net/ip6_fib.h>#include <net/ip6_route.h>#include <net/route.h>#include <net/addrconf.h>#include <net/l3mdev.h>#include <net/fib_rules.h>#include <net/netdev_lock.h>#include <net/sch_generic.h>#include <net/netns/generic.h>#include <net/netfilter/nf_conntrack.h> #define DRV_NAME	"vrf"#define DRV_VERSION	"1.1" #define FIB_RULE_PREF  1000       /* default preference for FIB rules */ #define HT_MAP_BITS	4#define HASH_INITVAL	((u32)0xcafef00d) struct  vrf_map {	DECLARE_HASHTABLE(ht, HT_MAP_BITS);	spinlock_t vmap_lock; 	/* shared_tables:	 * count how many distinct tables do not comply with the strict mode	 * requirement.	 * shared_tables value must be 0 in order to enable the strict mode.	 *	 * example of the evolution of shared_tables:	 *                                                        | time	 * add  vrf0 --> table 100        shared_tables = 0       | t0	 * add  vrf1 --> table 101        shared_tables = 0       | t1	 * add  vrf2 --> table 100        shared_tables = 1       | t2	 * add  vrf3 --> table 100        shared_tables = 1       | t3	 * add  vrf4 --> table 101        shared_tables = 2       v t4	 *	 * shared_tables is a "step function" (or "staircase function")	 * and it is increased by one when the second vrf is associated to a	 * table.	 *	 * at t2, vrf0 and vrf2 are bound to table 100: shared_tables = 1.	 *	 * at t3, another dev (vrf3) is bound to the same table 100 but the	 * value of shared_tables is still 1.	 * This means that no matter how many new vrfs will register on the	 * table 100, the shared_tables will not increase (considering only	 * table 100).	 *	 * at t4, vrf4 is bound to table 101, and shared_tables = 2.	 *	 * Looking at the value of shared_tables we can immediately know if	 * the strict_mode can or cannot be enforced. Indeed, strict_mode	 * can be enforced iff shared_tables = 0.	 *	 * Conversely, shared_tables is decreased when a vrf is de-associated	 * from a table with exactly two associated vrfs.	 */	u32 shared_tables; 	bool strict_mode;}; struct vrf_map_elem {	struct hlist_node hnode;	struct list_head vrf_list;  /* VRFs registered to this table */ 	u32 table_id;	int users;	int ifindex;}; static unsigned int vrf_net_id; /* per netns vrf data */struct netns_vrf {	/* protected by rtnl lock */	bool add_fib_rules; 	struct vrf_map vmap;	struct ctl_table_header	*ctl_hdr;}; struct net_vrf {	struct rtable		*rth;	struct rt6_info		*rt6;#if IS_ENABLED(CONFIG_IPV6)	struct fib6_table	*fib6_table;#endif	u32                     tb_id; 	struct list_head	me_list;   /* entry in vrf_map_elem */	int			ifindex;}; static void vrf_tx_error(struct net_device *vrf_dev, struct sk_buff *skb){	vrf_dev->stats.tx_errors++;	kfree_skb(skb);} static struct vrf_map *netns_vrf_map(struct net *net){	struct netns_vrf *nn_vrf = net_generic(net, vrf_net_id); 	return &nn_vrf->vmap;} static struct vrf_map *netns_vrf_map_by_dev(struct net_device *dev){	return netns_vrf_map(dev_net(dev));} static int vrf_map_elem_get_vrf_ifindex(struct vrf_map_elem *me){	struct list_head *me_head = &me->vrf_list;	struct net_vrf *vrf; 	if (list_empty(me_head))		return -ENODEV; 	vrf = list_first_entry(me_head, struct net_vrf, me_list); 	return vrf->ifindex;} static struct vrf_map_elem *vrf_map_elem_alloc(gfp_t flags){	struct vrf_map_elem *me; 	me = kmalloc_obj(*me, flags);	if (!me)		return NULL; 	return me;} static void vrf_map_elem_free(struct vrf_map_elem *me){	kfree(me);} static void vrf_map_elem_init(struct vrf_map_elem *me, int table_id,			      int ifindex, int users){	me->table_id = table_id;	me->ifindex = ifindex;	me->users = users;	INIT_LIST_HEAD(&me->vrf_list);} static struct vrf_map_elem *vrf_map_lookup_elem(struct vrf_map *vmap,						u32 table_id){	struct vrf_map_elem *me;	u32 key; 	key = jhash_1word(table_id, HASH_INITVAL);	hash_for_each_possible(vmap->ht, me, hnode, key) {		if (me->table_id == table_id)			return me;	} 	return NULL;} static void vrf_map_add_elem(struct vrf_map *vmap, struct vrf_map_elem *me){	u32 table_id = me->table_id;	u32 key; 	key = jhash_1word(table_id, HASH_INITVAL);	hash_add(vmap->ht, &me->hnode, key);} static void vrf_map_del_elem(struct vrf_map_elem *me){	hash_del(&me->hnode);} static void vrf_map_lock(struct vrf_map *vmap) __acquires(&vmap->vmap_lock){	spin_lock(&vmap->vmap_lock);} static void vrf_map_unlock(struct vrf_map *vmap) __releases(&vmap->vmap_lock){	spin_unlock(&vmap->vmap_lock);} /* called with rtnl lock held */static intvrf_map_register_dev(struct net_device *dev, struct netlink_ext_ack *extack){	struct vrf_map *vmap = netns_vrf_map_by_dev(dev);	struct net_vrf *vrf = netdev_priv(dev);	struct vrf_map_elem *new_me, *me;	u32 table_id = vrf->tb_id;	bool free_new_me = false;	int users;	int res; 	/* we pre-allocate elements used in the spin-locked section (so that we	 * keep the spinlock as short as possible).	 */	new_me = vrf_map_elem_alloc(GFP_KERNEL);	if (!new_me)		return -ENOMEM; 	vrf_map_elem_init(new_me, table_id, dev->ifindex, 0); 	vrf_map_lock(vmap); 	me = vrf_map_lookup_elem(vmap, table_id);	if (!me) {		me = new_me;		vrf_map_add_elem(vmap, me);		goto link_vrf;	} 	/* we already have an entry in the vrf_map, so it means there is (at	 * least) a vrf registered on the specific table.	 */	free_new_me = true;	if (vmap->strict_mode) {		/* vrfs cannot share the same table */		NL_SET_ERR_MSG(extack, "Table is used by another VRF");		res = -EBUSY;		goto unlock;	} link_vrf:	users = ++me->users;	if (users == 2)		++vmap->shared_tables; 	list_add(&vrf->me_list, &me->vrf_list); 	res = 0; unlock:	vrf_map_unlock(vmap); 	/* clean-up, if needed */	if (free_new_me)		vrf_map_elem_free(new_me); 	return res;} /* called with rtnl lock held */static void vrf_map_unregister_dev(struct net_device *dev){	struct vrf_map *vmap = netns_vrf_map_by_dev(dev);	struct net_vrf *vrf = netdev_priv(dev);	u32 table_id = vrf->tb_id;	struct vrf_map_elem *me;	int users; 	vrf_map_lock(vmap); 	me = vrf_map_lookup_elem(vmap, table_id);	if (!me)		goto unlock; 	list_del(&vrf->me_list); 	users = --me->users;	if (users == 1) {		--vmap->shared_tables;	} else if (users == 0) {		vrf_map_del_elem(me); 		/* no one will refer to this element anymore */		vrf_map_elem_free(me);	} unlock:	vrf_map_unlock(vmap);} /* return the vrf device index associated with the table_id */static int vrf_ifindex_lookup_by_table_id(struct net *net, u32 table_id){	struct vrf_map *vmap = netns_vrf_map(net);	struct vrf_map_elem *me;	int ifindex; 	vrf_map_lock(vmap); 	if (!vmap->strict_mode) {		ifindex = -EPERM;		goto unlock;	} 	me = vrf_map_lookup_elem(vmap, table_id);	if (!me) {		ifindex = -ENODEV;		goto unlock;	} 	ifindex = vrf_map_elem_get_vrf_ifindex(me); unlock:	vrf_map_unlock(vmap); 	return ifindex;} /* by default VRF devices do not have a qdisc and are expected * to be created with only a single queue. */static bool qdisc_tx_is_default(const struct net_device *dev){	struct netdev_queue *txq; 	if (dev->num_tx_queues > 1)		return false; 	txq = netdev_get_tx_queue(dev, 0); 	return qdisc_txq_has_no_queue(txq);} /* Local traffic destined to local address. Reinsert the packet to rx * path, similar to loopback handling. */static int vrf_local_xmit(struct sk_buff *skb, struct net_device *dev,			  struct dst_entry *dst){	unsigned int len = skb->len; 	skb_orphan(skb); 	skb_dst_set(skb, dst); 	/* set pkt_type to avoid skb hitting packet taps twice -	 * once on Tx and again in Rx processing	 */	skb->pkt_type = PACKET_LOOPBACK; 	skb->protocol = eth_type_trans(skb, dev); 	if (likely(__netif_rx(skb) == NET_RX_SUCCESS))		dev_dstats_rx_add(dev, len);	else		dev_dstats_rx_dropped(dev); 	return NETDEV_TX_OK;} static void vrf_nf_set_untracked(struct sk_buff *skb){	if (skb_get_nfct(skb) == 0)		nf_ct_set(skb, NULL, IP_CT_UNTRACKED);} static void vrf_nf_reset_ct(struct sk_buff *skb){	if (skb_get_nfct(skb) == IP_CT_UNTRACKED)		nf_reset_ct(skb);} #if IS_ENABLED(CONFIG_IPV6)static int vrf_ip6_local_out(struct net *net, struct sock *sk,			     struct sk_buff *skb){	int err; 	vrf_nf_reset_ct(skb); 	err = nf_hook(NFPROTO_IPV6, NF_INET_LOCAL_OUT, net,		      sk, skb, NULL, skb_dst(skb)->dev, dst_output); 	if (likely(err == 1))		err = dst_output(net, sk, skb); 	return err;} static netdev_tx_t vrf_process_v6_outbound(struct sk_buff *skb,					   struct net_device *dev){	const struct ipv6hdr *iph;	struct net *net = dev_net(skb->dev);	struct flowi6 fl6;	int ret = NET_XMIT_DROP;	struct dst_entry *dst;	struct dst_entry *dst_null = &net->ipv6.ip6_null_entry->dst; 	if (!pskb_may_pull(skb, ETH_HLEN + sizeof(struct ipv6hdr)))		goto err; 	iph = ipv6_hdr(skb); 	memset(&fl6, 0, sizeof(fl6));	/* needed to match OIF rule */	fl6.flowi6_l3mdev = dev->ifindex;	fl6.flowi6_iif = LOOPBACK_IFINDEX;	fl6.daddr = iph->daddr;	fl6.saddr = iph->saddr;	fl6.flowlabel = ip6_flowinfo(iph);	fl6.flowi6_mark = skb->mark;	fl6.flowi6_proto = iph->nexthdr; 	dst = ip6_dst_lookup_flow(net, NULL, &fl6, NULL);	if (IS_ERR(dst) || dst == dst_null)		goto err; 	skb_dst_drop(skb); 	/* if dst.dev is the VRF device again this is locally originated traffic	 * destined to a local address. Short circuit to Rx path.	 */	if (dst->dev == dev)		return vrf_local_xmit(skb, dev, dst); 	skb_dst_set(skb, dst); 	/* strip the ethernet header added for pass through VRF device */	__skb_pull(skb, skb_network_offset(skb)); 	memset(IP6CB(skb), 0, sizeof(*IP6CB(skb)));	ret = vrf_ip6_local_out(net, skb->sk, skb);	if (unlikely(net_xmit_eval(ret)))		dev->stats.tx_errors++;	else		ret = NET_XMIT_SUCCESS; 	return ret;err:	vrf_tx_error(dev, skb);	return NET_XMIT_DROP;}#elsestatic netdev_tx_t vrf_process_v6_outbound(struct sk_buff *skb,					   struct net_device *dev){	vrf_tx_error(dev, skb);	return NET_XMIT_DROP;}#endif /* based on ip_local_out; can't use it b/c the dst is switched pointing to us */static int vrf_ip_local_out(struct net *net, struct sock *sk,			    struct sk_buff *skb){	int err; 	vrf_nf_reset_ct(skb); 	err = nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, net, sk,		      skb, NULL, skb_dst(skb)->dev, dst_output);	if (likely(err == 1))		err = dst_output(net, sk, skb); 	return err;} static netdev_tx_t vrf_process_v4_outbound(struct sk_buff *skb,					   struct net_device *vrf_dev){	struct iphdr *ip4h;	int ret = NET_XMIT_DROP;	struct flowi4 fl4;	struct net *net = dev_net(vrf_dev);	struct rtable *rt; 	if (!pskb_may_pull(skb, ETH_HLEN + sizeof(struct iphdr)))		goto err;

Lines 1–500 of 1971.