2396 lines · 58.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* *	GRE over IPv6 protocol decoder. * *	Authors: Dmitry Kozlov (xeb@mail.ru) */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/capability.h>#include <linux/module.h>#include <linux/types.h>#include <linux/kernel.h>#include <linux/slab.h>#include <linux/uaccess.h>#include <linux/skbuff.h>#include <linux/netdevice.h>#include <linux/in.h>#include <linux/tcp.h>#include <linux/udp.h>#include <linux/if_arp.h>#include <linux/init.h>#include <linux/in6.h>#include <linux/inetdevice.h>#include <linux/igmp.h>#include <linux/netfilter_ipv4.h>#include <linux/etherdevice.h>#include <linux/if_ether.h>#include <linux/hash.h>#include <linux/if_tunnel.h>#include <linux/ip6_tunnel.h> #include <net/sock.h>#include <net/ip.h>#include <net/ip_tunnels.h>#include <net/icmp.h>#include <net/protocol.h>#include <net/addrconf.h>#include <net/arp.h>#include <net/checksum.h>#include <net/dsfield.h>#include <net/inet_ecn.h>#include <net/xfrm.h>#include <net/net_namespace.h>#include <net/netns/generic.h>#include <net/netdev_lock.h>#include <net/rtnetlink.h> #include <net/ipv6.h>#include <net/ip6_fib.h>#include <net/ip6_route.h>#include <net/ip6_tunnel.h>#include <net/gre.h>#include <net/erspan.h>#include <net/dst_metadata.h>  static bool log_ecn_error = true;module_param(log_ecn_error, bool, 0644);MODULE_PARM_DESC(log_ecn_error, "Log packets received with corrupted ECN"); #define IP6_GRE_HASH_SIZE_SHIFT  5#define IP6_GRE_HASH_SIZE (1 << IP6_GRE_HASH_SIZE_SHIFT) static unsigned int ip6gre_net_id __read_mostly;struct ip6gre_net {	struct ip6_tnl __rcu *tunnels[4][IP6_GRE_HASH_SIZE]; 	struct ip6_tnl __rcu *collect_md_tun;	struct ip6_tnl __rcu *collect_md_tun_erspan;	struct net_device *fb_tunnel_dev;}; static struct rtnl_link_ops ip6gre_link_ops __read_mostly;static struct rtnl_link_ops ip6gre_tap_ops __read_mostly;static struct rtnl_link_ops ip6erspan_tap_ops __read_mostly;static int ip6gre_tunnel_init(struct net_device *dev);static void ip6gre_tunnel_setup(struct net_device *dev);static void ip6gre_tunnel_link(struct ip6gre_net *ign, struct ip6_tnl *t);static void ip6gre_tnl_link_config(struct ip6_tnl *t, int set_mtu);static void ip6erspan_tnl_link_config(struct ip6_tnl *t, int set_mtu); /* Tunnel hash table */ /*   4 hash tables:    3: (remote,local)   2: (remote,*)   1: (*,local)   0: (*,*)    We require exact key match i.e. if a key is present in packet   it will match only tunnel with the same key; if it is not present,   it will match only keyless tunnel.    All keysless packets, if not matched configured keyless tunnels   will match fallback tunnel. */ #define HASH_KEY(key) (((__force u32)key^((__force u32)key>>4))&(IP6_GRE_HASH_SIZE - 1))static u32 HASH_ADDR(const struct in6_addr *addr){	u32 hash = ipv6_addr_hash(addr); 	return hash_32(hash, IP6_GRE_HASH_SIZE_SHIFT);} #define tunnels_r_l	tunnels[3]#define tunnels_r	tunnels[2]#define tunnels_l	tunnels[1]#define tunnels_wc	tunnels[0] static bool ip6gre_tunnel_match(struct ip6_tnl *t, int dev_type, int link,				int *cand_score, struct ip6_tnl **ret){	int score = 0; 	if (t->dev->type != ARPHRD_IP6GRE &&	    t->dev->type != dev_type)		return false; 	if (t->parms.link != link)		score |= 1;	if (t->dev->type != dev_type)		score |= 2;	if (score == 0) {		*ret = t;		return true;	} 	if (score < *cand_score) {		*ret = t;		*cand_score = score;	}	return false;} /* Given src, dst and key, find appropriate for input tunnel. */static struct ip6_tnl *ip6gre_tunnel_lookup(struct net_device *dev,		const struct in6_addr *remote, const struct in6_addr *local,		__be32 key, __be16 gre_proto){	struct net *net = dev_net(dev);	int link = dev->ifindex;	unsigned int h0 = HASH_ADDR(remote);	unsigned int h1 = HASH_KEY(key);	struct ip6_tnl *t, *cand = NULL;	struct ip6gre_net *ign = net_generic(net, ip6gre_net_id);	int dev_type = (gre_proto == htons(ETH_P_TEB) ||			gre_proto == htons(ETH_P_ERSPAN) ||			gre_proto == htons(ETH_P_ERSPAN2)) ?		       ARPHRD_ETHER : ARPHRD_IP6GRE;	struct net_device *ndev;	int cand_score = 4; 	for_each_ip_tunnel_rcu(t, ign->tunnels_r_l[h0 ^ h1]) {		if (!ipv6_addr_equal(local, &t->parms.laddr) ||		    !ipv6_addr_equal(remote, &t->parms.raddr) ||		    key != t->parms.i_key ||		    !(t->dev->flags & IFF_UP))			continue; 		if (ip6gre_tunnel_match(t, dev_type, link, &cand_score, &cand))			return cand;	} 	for_each_ip_tunnel_rcu(t, ign->tunnels_r[h0 ^ h1]) {		if (!ipv6_addr_equal(remote, &t->parms.raddr) ||		    key != t->parms.i_key ||		    !(t->dev->flags & IFF_UP))			continue; 		if (ip6gre_tunnel_match(t, dev_type, link, &cand_score, &cand))			return cand;	} 	for_each_ip_tunnel_rcu(t, ign->tunnels_l[h1]) {		if ((!ipv6_addr_equal(local, &t->parms.laddr) &&			  (!ipv6_addr_equal(local, &t->parms.raddr) ||				 !ipv6_addr_is_multicast(local))) ||		    key != t->parms.i_key ||		    !(t->dev->flags & IFF_UP))			continue; 		if (ip6gre_tunnel_match(t, dev_type, link, &cand_score, &cand))			return cand;	} 	for_each_ip_tunnel_rcu(t, ign->tunnels_wc[h1]) {		if (t->parms.i_key != key ||		    !(t->dev->flags & IFF_UP))			continue; 		if (ip6gre_tunnel_match(t, dev_type, link, &cand_score, &cand))			return cand;	} 	if (cand)		return cand; 	if (gre_proto == htons(ETH_P_ERSPAN) ||	    gre_proto == htons(ETH_P_ERSPAN2))		t = rcu_dereference(ign->collect_md_tun_erspan);	else		t = rcu_dereference(ign->collect_md_tun); 	if (t && t->dev->flags & IFF_UP)		return t; 	ndev = READ_ONCE(ign->fb_tunnel_dev);	if (ndev && ndev->flags & IFF_UP)		return netdev_priv(ndev); 	return NULL;} static struct ip6_tnl __rcu **__ip6gre_bucket(struct ip6gre_net *ign,		const struct __ip6_tnl_parm *p){	const struct in6_addr *remote = &p->raddr;	const struct in6_addr *local = &p->laddr;	unsigned int h = HASH_KEY(p->i_key);	int prio = 0; 	if (!ipv6_addr_any(local))		prio |= 1;	if (!ipv6_addr_any(remote) && !ipv6_addr_is_multicast(remote)) {		prio |= 2;		h ^= HASH_ADDR(remote);	} 	return &ign->tunnels[prio][h];} static void ip6gre_tunnel_link_md(struct ip6gre_net *ign, struct ip6_tnl *t){	if (t->parms.collect_md)		rcu_assign_pointer(ign->collect_md_tun, t);} static void ip6erspan_tunnel_link_md(struct ip6gre_net *ign, struct ip6_tnl *t){	if (t->parms.collect_md)		rcu_assign_pointer(ign->collect_md_tun_erspan, t);} static void ip6gre_tunnel_unlink_md(struct ip6gre_net *ign, struct ip6_tnl *t){	if (t->parms.collect_md)		rcu_assign_pointer(ign->collect_md_tun, NULL);} static void ip6erspan_tunnel_unlink_md(struct ip6gre_net *ign,				       struct ip6_tnl *t){	if (t->parms.collect_md)		rcu_assign_pointer(ign->collect_md_tun_erspan, NULL);} static inline struct ip6_tnl __rcu **ip6gre_bucket(struct ip6gre_net *ign,		const struct ip6_tnl *t){	return __ip6gre_bucket(ign, &t->parms);} static void ip6gre_tunnel_link(struct ip6gre_net *ign, struct ip6_tnl *t){	struct ip6_tnl __rcu **tp = ip6gre_bucket(ign, t); 	rcu_assign_pointer(t->next, rtnl_dereference(*tp));	rcu_assign_pointer(*tp, t);} static void ip6gre_tunnel_unlink(struct ip6gre_net *ign, struct ip6_tnl *t){	struct ip6_tnl __rcu **tp;	struct ip6_tnl *iter; 	for (tp = ip6gre_bucket(ign, t);	     (iter = rtnl_dereference(*tp)) != NULL;	     tp = &iter->next) {		if (t == iter) {			rcu_assign_pointer(*tp, t->next);			break;		}	}} static struct ip6_tnl *ip6gre_tunnel_find(struct net *net,					   const struct __ip6_tnl_parm *parms,					   int type){	const struct in6_addr *remote = &parms->raddr;	const struct in6_addr *local = &parms->laddr;	__be32 key = parms->i_key;	int link = parms->link;	struct ip6_tnl *t;	struct ip6_tnl __rcu **tp;	struct ip6gre_net *ign = net_generic(net, ip6gre_net_id); 	for (tp = __ip6gre_bucket(ign, parms);	     (t = rtnl_dereference(*tp)) != NULL;	     tp = &t->next)		if (ipv6_addr_equal(local, &t->parms.laddr) &&		    ipv6_addr_equal(remote, &t->parms.raddr) &&		    key == t->parms.i_key &&		    link == t->parms.link &&		    type == t->dev->type)			break; 	return t;} static struct ip6_tnl *ip6gre_tunnel_locate(struct net *net,		const struct __ip6_tnl_parm *parms, int create){	struct ip6_tnl *t, *nt;	struct net_device *dev;	char name[IFNAMSIZ];	struct ip6gre_net *ign = net_generic(net, ip6gre_net_id); 	t = ip6gre_tunnel_find(net, parms, ARPHRD_IP6GRE);	if (t && create)		return NULL;	if (t || !create)		return t; 	if (parms->name[0]) {		if (!dev_valid_name(parms->name))			return NULL;		strscpy(name, parms->name);	} else {		strscpy(name, "ip6gre%d");	}	dev = alloc_netdev(sizeof(*t), name, NET_NAME_UNKNOWN,			   ip6gre_tunnel_setup);	if (!dev)		return NULL; 	dev_net_set(dev, net); 	nt = netdev_priv(dev);	nt->parms = *parms;	dev->rtnl_link_ops = &ip6gre_link_ops; 	nt->dev = dev;	nt->net = dev_net(dev); 	if (register_netdevice(dev) < 0)		goto failed_free; 	ip6gre_tnl_link_config(nt, 1);	ip6gre_tunnel_link(ign, nt);	return nt; failed_free:	free_netdev(dev);	return NULL;} static void ip6erspan_tunnel_uninit(struct net_device *dev){	struct ip6_tnl *t = netdev_priv(dev);	struct ip6gre_net *ign = net_generic(t->net, ip6gre_net_id); 	ip6erspan_tunnel_unlink_md(ign, t);	ip6gre_tunnel_unlink(ign, t);	dst_cache_reset(&t->dst_cache);	netdev_put(dev, &t->dev_tracker);} static void ip6gre_tunnel_uninit(struct net_device *dev){	struct ip6_tnl *t = netdev_priv(dev);	struct ip6gre_net *ign = net_generic(t->net, ip6gre_net_id); 	ip6gre_tunnel_unlink_md(ign, t);	ip6gre_tunnel_unlink(ign, t);	if (ign->fb_tunnel_dev == dev)		WRITE_ONCE(ign->fb_tunnel_dev, NULL);	dst_cache_reset(&t->dst_cache);	netdev_put(dev, &t->dev_tracker);}  static int ip6gre_err(struct sk_buff *skb, struct inet6_skb_parm *opt,		       u8 type, u8 code, int offset, __be32 info){	struct net *net = dev_net(skb->dev);	const struct ipv6hdr *ipv6h;	struct tnl_ptk_info tpi;	struct ip6_tnl *t; 	if (gre_parse_header(skb, &tpi, NULL, htons(ETH_P_IPV6),			     offset) < 0)		return -EINVAL; 	ipv6h = (const struct ipv6hdr *)skb->data;	t = ip6gre_tunnel_lookup(skb->dev, &ipv6h->daddr, &ipv6h->saddr,				 tpi.key, tpi.proto);	if (!t)		return -ENOENT; 	switch (type) {	case ICMPV6_DEST_UNREACH:		net_dbg_ratelimited("%s: Path to destination invalid or inactive!\n",				    t->parms.name);		if (code != ICMPV6_PORT_UNREACH)			break;		return 0;	case ICMPV6_TIME_EXCEED:		if (code == ICMPV6_EXC_HOPLIMIT) {			net_dbg_ratelimited("%s: Too small hop limit or routing loop in tunnel!\n",					    t->parms.name);			break;		}		return 0;	case ICMPV6_PARAMPROB: {		struct ipv6_tlv_tnl_enc_lim *tel;		__u32 teli; 		teli = 0;		if (code == ICMPV6_HDR_FIELD)			teli = ip6_tnl_parse_tlv_enc_lim(skb, skb->data); 		if (teli && teli == be32_to_cpu(info) - 2) {			tel = (struct ipv6_tlv_tnl_enc_lim *) &skb->data[teli];			if (tel->encap_limit == 0) {				net_dbg_ratelimited("%s: Too small encapsulation limit or routing loop in tunnel!\n",						    t->parms.name);			}		} else {			net_dbg_ratelimited("%s: Recipient unable to parse tunneled packet!\n",					    t->parms.name);		}		return 0;	}	case ICMPV6_PKT_TOOBIG:		ip6_update_pmtu(skb, net, info, 0, 0, sock_net_uid(net, NULL));		return 0;	case NDISC_REDIRECT:		ip6_redirect(skb, net, skb->dev->ifindex, 0,			     sock_net_uid(net, NULL));		return 0;	} 	if (time_before(jiffies, READ_ONCE(t->err_time) + IP6TUNNEL_ERR_TIMEO))		WRITE_ONCE(t->err_count, READ_ONCE(t->err_count) + 1);	else		WRITE_ONCE(t->err_count, 1);	WRITE_ONCE(t->err_time, jiffies); 	return 0;} static int ip6gre_rcv(struct sk_buff *skb, const struct tnl_ptk_info *tpi){	const struct ipv6hdr *ipv6h;	struct ip6_tnl *tunnel; 	ipv6h = ipv6_hdr(skb);	tunnel = ip6gre_tunnel_lookup(skb->dev,				      &ipv6h->saddr, &ipv6h->daddr, tpi->key,				      tpi->proto);	if (tunnel) {		if (tunnel->parms.collect_md) {			IP_TUNNEL_DECLARE_FLAGS(flags);			struct metadata_dst *tun_dst;			__be64 tun_id; 			ip_tunnel_flags_copy(flags, tpi->flags);			tun_id = key32_to_tunnel_id(tpi->key); 			tun_dst = ipv6_tun_rx_dst(skb, flags, tun_id, 0);			if (!tun_dst)				return PACKET_REJECT; 			ip6_tnl_rcv(tunnel, skb, tpi, tun_dst, log_ecn_error);		} else {			ip6_tnl_rcv(tunnel, skb, tpi, NULL, log_ecn_error);		} 		return PACKET_RCVD;	} 	return PACKET_REJECT;} static int ip6erspan_rcv(struct sk_buff *skb,			 struct tnl_ptk_info *tpi,			 int gre_hdr_len){	struct erspan_base_hdr *ershdr;	const struct ipv6hdr *ipv6h;	struct erspan_md2 *md2;	struct ip6_tnl *tunnel;	u8 ver; 	if (unlikely(!pskb_may_pull(skb, sizeof(*ershdr))))

Lines 1–500 of 2396.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t