cregit-Linux how code gets into the kernel

Release 4.8 net/ipv6/sit.c

Directory: net/ipv6
/*
 *      IPv6 over IPv4 tunnel device - Simple Internet Transition (SIT)
 *      Linux INET6 implementation
 *
 *      Authors:
 *      Pedro Roque             <roque@di.fc.ul.pt>
 *      Alexey Kuznetsov        <kuznet@ms2.inr.ac.ru>
 *
 *      This program is free software; you can redistribute it and/or
 *      modify it under the terms of the GNU General Public License
 *      as published by the Free Software Foundation; either version
 *      2 of the License, or (at your option) any later version.
 *
 *      Changes:
 * Roger Venning <r.venning@telstra.com>:       6to4 support
 * Nate Thompson <nate@thebog.net>:             6to4 support
 * Fred Templin <fred.l.templin@boeing.com>:    isatap support
 */


#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/capability.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/net.h>
#include <linux/in6.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <linux/icmp.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/init.h>
#include <linux/netfilter_ipv4.h>
#include <linux/if_ether.h>

#include <net/sock.h>
#include <net/snmp.h>

#include <net/ipv6.h>
#include <net/protocol.h>
#include <net/transp_v6.h>
#include <net/ip6_fib.h>
#include <net/ip6_route.h>
#include <net/ndisc.h>
#include <net/addrconf.h>
#include <net/ip.h>
#include <net/udp.h>
#include <net/icmp.h>
#include <net/ip_tunnels.h>
#include <net/inet_ecn.h>
#include <net/xfrm.h>
#include <net/dsfield.h>
#include <net/net_namespace.h>
#include <net/netns/generic.h>

/*
   This version of net/ipv6/sit.c is cloned of net/ipv4/ip_gre.c

   For comments look at net/ipv4/ip_gre.c --ANK
 */


#define HASH_SIZE  16

#define HASH(addr) (((__force u32)addr^((__force u32)addr>>4))&0xF)


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");

static int ipip6_tunnel_init(struct net_device *dev);
static void ipip6_tunnel_setup(struct net_device *dev);
static void ipip6_dev_free(struct net_device *dev);
static bool check_6rd(struct ip_tunnel *tunnel, const struct in6_addr *v6dst,
		      __be32 *v4dst);

static struct rtnl_link_ops sit_link_ops __read_mostly;


static int sit_net_id __read_mostly;

struct sit_net {
	
struct ip_tunnel __rcu *tunnels_r_l[HASH_SIZE];
	
struct ip_tunnel __rcu *tunnels_r[HASH_SIZE];
	
struct ip_tunnel __rcu *tunnels_l[HASH_SIZE];
	
struct ip_tunnel __rcu *tunnels_wc[1];
	
struct ip_tunnel __rcu **tunnels[4];

	
struct net_device *fb_tunnel_dev;
};

/*
 * Must be invoked with rcu_read_lock
 */

static struct ip_tunnel *ipip6_tunnel_lookup(struct net *net, struct net_device *dev, __be32 remote, __be32 local) { unsigned int h0 = HASH(remote); unsigned int h1 = HASH(local); struct ip_tunnel *t; struct sit_net *sitn = net_generic(net, sit_net_id); for_each_ip_tunnel_rcu(t, sitn->tunnels_r_l[h0 ^ h1]) { if (local == t->parms.iph.saddr && remote == t->parms.iph.daddr && (!dev || !t->parms.link || dev->ifindex == t->parms.link) && (t->dev->flags & IFF_UP)) return t; } for_each_ip_tunnel_rcu(t, sitn->tunnels_r[h0]) { if (remote == t->parms.iph.daddr && (!dev || !t->parms.link || dev->ifindex == t->parms.link) && (t->dev->flags & IFF_UP)) return t; } for_each_ip_tunnel_rcu(t, sitn->tunnels_l[h1]) { if (local == t->parms.iph.saddr && (!dev || !t->parms.link || dev->ifindex == t->parms.link) && (t->dev->flags & IFF_UP)) return t; } t = rcu_dereference(sitn->tunnels_wc[0]); if (t && (t->dev->flags & IFF_UP)) return t; return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git14755.26%220.00%
sascha hlusiaksascha hlusiak7227.07%110.00%
pavel emelianovpavel emelianov197.14%220.00%
eric dumazeteric dumazet176.39%220.00%
americo wangamerico wang62.26%110.00%
shmulik ladkanishmulik ladkani31.13%110.00%
al viroal viro20.75%110.00%
Total266100.00%10100.00%


static struct ip_tunnel __rcu **__ipip6_bucket(struct sit_net *sitn, struct ip_tunnel_parm *parms) { __be32 remote = parms->iph.daddr; __be32 local = parms->iph.saddr; unsigned int h = 0; int prio = 0; if (remote) { prio |= 2; h ^= HASH(remote); } if (local) { prio |= 1; h ^= HASH(local); } return &sitn->tunnels[prio][h]; }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git7983.16%116.67%
pavel emelianovpavel emelianov77.37%233.33%
hideaki yoshifujihideaki yoshifuji55.26%116.67%
al viroal viro22.11%116.67%
eric dumazeteric dumazet22.11%116.67%
Total95100.00%6100.00%


static inline struct ip_tunnel __rcu **ipip6_bucket(struct sit_net *sitn, struct ip_tunnel *t) { return __ipip6_bucket(sitn, &t->parms); }

Contributors

PersonTokensPropCommitsCommitProp
hideaki yoshifujihideaki yoshifuji2475.00%133.33%
pavel emelianovpavel emelianov721.88%133.33%
eric dumazeteric dumazet13.12%133.33%
Total32100.00%3100.00%


static void ipip6_tunnel_unlink(struct sit_net *sitn, struct ip_tunnel *t) { struct ip_tunnel __rcu **tp; struct ip_tunnel *iter; for (tp = ipip6_bucket(sitn, t); (iter = rtnl_dereference(*tp)) != NULL; tp = &iter->next) { if (t == iter) { rcu_assign_pointer(*tp, t->next); break; } } }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git5367.09%240.00%
eric dumazeteric dumazet1924.05%240.00%
pavel emelianovpavel emelianov78.86%120.00%
Total79100.00%5100.00%


static void ipip6_tunnel_link(struct sit_net *sitn, struct ip_tunnel *t) { struct ip_tunnel __rcu **tp = ipip6_bucket(sitn, t); rcu_assign_pointer(t->next, rtnl_dereference(*tp)); rcu_assign_pointer(*tp, t); }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git3364.71%233.33%
eric dumazeteric dumazet1121.57%350.00%
pavel emelianovpavel emelianov713.73%116.67%
Total51100.00%6100.00%


static void ipip6_tunnel_clone_6rd(struct net_device *dev, struct sit_net *sitn) { #ifdef CONFIG_IPV6_SIT_6RD struct ip_tunnel *t = netdev_priv(dev); if (t->dev == sitn->fb_tunnel_dev) { ipv6_addr_set(&t->ip6rd.prefix, htonl(0x20020000), 0, 0, 0); t->ip6rd.relay_prefix = 0; t->ip6rd.prefixlen = 16; t->ip6rd.relay_prefixlen = 0; } else { struct ip_tunnel *t0 = netdev_priv(sitn->fb_tunnel_dev); memcpy(&t->ip6rd, &t0->ip6rd, sizeof(t->ip6rd)); } #endif }

Contributors

PersonTokensPropCommitsCommitProp
hideaki yoshifujihideaki yoshifuji123100.00%2100.00%
Total123100.00%2100.00%


static int ipip6_tunnel_create(struct net_device *dev) { struct ip_tunnel *t = netdev_priv(dev); struct net *net = dev_net(dev); struct sit_net *sitn = net_generic(net, sit_net_id); int err; memcpy(dev->dev_addr, &t->parms.iph.saddr, 4); memcpy(dev->broadcast, &t->parms.iph.daddr, 4); if ((__force u16)t->parms.i_flags & SIT_ISATAP) dev->priv_flags |= IFF_ISATAP; dev->rtnl_link_ops = &sit_link_ops; err = register_netdevice(dev); if (err < 0) goto out; ipip6_tunnel_clone_6rd(dev, sitn); dev_hold(dev); ipip6_tunnel_link(sitn, t); return 0; out: return err; }

Contributors

PersonTokensPropCommitsCommitProp
nicolas dichtelnicolas dichtel11575.66%250.00%
steffen klassertsteffen klassert3019.74%125.00%
thadeu lima de souza cascardothadeu lima de souza cascardo74.61%125.00%
Total152100.00%4100.00%


static struct ip_tunnel *ipip6_tunnel_locate(struct net *net, struct ip_tunnel_parm *parms, int create) { __be32 remote = parms->iph.daddr; __be32 local = parms->iph.saddr; struct ip_tunnel *t, *nt; struct ip_tunnel __rcu **tp; struct net_device *dev; char name[IFNAMSIZ]; struct sit_net *sitn = net_generic(net, sit_net_id); for (tp = __ipip6_bucket(sitn, parms); (t = rtnl_dereference(*tp)) != NULL; tp = &t->next) { if (local == t->parms.iph.saddr && remote == t->parms.iph.daddr && parms->link == t->parms.link) { if (create) return NULL; else return t; } } if (!create) goto failed; if (parms->name[0]) strlcpy(name, parms->name, IFNAMSIZ); else strcpy(name, "sit%d"); dev = alloc_netdev(sizeof(*t), name, NET_NAME_UNKNOWN, ipip6_tunnel_setup); if (!dev) return NULL; dev_net_set(dev, net); nt = netdev_priv(dev); nt->parms = *parms; if (ipip6_tunnel_create(dev) < 0) goto failed_free; return nt; failed_free: ipip6_dev_free(dev); failed: return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git13551.33%419.05%
stephen hemmingerstephen hemminger4115.59%14.76%
pavel emelianovpavel emelianov3312.55%314.29%
sascha hlusiaksascha hlusiak207.60%29.52%
eric dumazeteric dumazet145.32%29.52%
linus torvaldslinus torvalds41.52%14.76%
hideaki yoshifujihideaki yoshifuji41.52%14.76%
patrick mchardypatrick mchardy31.14%14.76%
al viroal viro20.76%14.76%
tom gundersentom gundersen20.76%14.76%
david s. millerdavid s. miller20.76%14.76%
stephen rothwellstephen rothwell10.38%14.76%
ian morrisian morris10.38%14.76%
nicolas dichtelnicolas dichtel10.38%14.76%
Total263100.00%21100.00%

#define for_each_prl_rcu(start) \ for (prl = rcu_dereference(start); \ prl; \ prl = rcu_dereference(prl->next))
static struct ip_tunnel_prl_entry * __ipip6_tunnel_locate_prl(struct ip_tunnel *t, __be32 addr) { struct ip_tunnel_prl_entry *prl; for_each_prl_rcu(t->prl) if (prl->addr == addr) break; return prl; }

Contributors

PersonTokensPropCommitsCommitProp
fred templinfred templin1848.65%120.00%
pre-gitpre-git1129.73%240.00%
eric dumazeteric dumazet718.92%120.00%
hideaki yoshifujihideaki yoshifuji12.70%120.00%
Total37100.00%5100.00%


static int ipip6_tunnel_get_prl(struct ip_tunnel *t, struct ip_tunnel_prl __user *a) { struct ip_tunnel_prl kprl, *kp; struct ip_tunnel_prl_entry *prl; unsigned int cmax, c = 0, ca, len; int ret = 0; if (copy_from_user(&kprl, a, sizeof(kprl))) return -EFAULT; cmax = kprl.datalen / sizeof(kprl); if (cmax > 1 && kprl.addr != htonl(INADDR_ANY)) cmax = 1; /* For simple GET or for root users, * we try harder to allocate. */ kp = (cmax <= 1 || capable(CAP_NET_ADMIN)) ? kcalloc(cmax, sizeof(*kp), GFP_KERNEL) : NULL; rcu_read_lock(); ca = t->prl_count < cmax ? t->prl_count : cmax; if (!kp) { /* We don't try hard to allocate much memory for * non-root users. * For root users, retry allocating enough memory for * the answer. */ kp = kcalloc(ca, sizeof(*kp), GFP_ATOMIC); if (!kp) { ret = -ENOMEM; goto out; } } c = 0; for_each_prl_rcu(t->prl) { if (c >= cmax) break; if (kprl.addr != htonl(INADDR_ANY) && prl->addr != kprl.addr) continue; kp[c].addr = prl->addr; kp[c].flags = prl->flags; c++; if (kprl.addr != htonl(INADDR_ANY)) break; } out: rcu_read_unlock(); len = sizeof(*kp) * c; ret = 0; if ((len && copy_to_user(a + 1, kp, len)) || put_user(len, &a->datalen)) ret = -EFAULT; kfree(kp); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
hideaki yoshifujihideaki yoshifuji30397.12%250.00%
eric dumazeteric dumazet82.56%125.00%
sascha hlusiaksascha hlusiak10.32%125.00%
Total312100.00%4100.00%


static int ipip6_tunnel_add_prl(struct ip_tunnel *t, struct ip_tunnel_prl *a, int chg) { struct ip_tunnel_prl_entry *p; int err = 0; if (a->addr == htonl(INADDR_ANY)) return -EINVAL; ASSERT_RTNL(); for (p = rtnl_dereference(t->prl); p; p = rtnl_dereference(p->next)) { if (p->addr == a->addr) { if (chg) { p->flags = a->flags; goto out; } err = -EEXIST; goto out; } } if (chg) { err = -ENXIO; goto out; } p = kzalloc(sizeof(struct ip_tunnel_prl_entry), GFP_KERNEL); if (!p) { err = -ENOBUFS; goto out; } p->next = t->prl; p->addr = a->addr; p->flags = a->flags; t->prl_count++; rcu_assign_pointer(t->prl, p); out: return err; }

Contributors

PersonTokensPropCommitsCommitProp
hideaki yoshifujihideaki yoshifuji6533.68%323.08%
fred templinfred templin6131.61%17.69%
eric dumazeteric dumazet3618.65%430.77%
pre-gitpre-git2814.51%323.08%
kazunori miyazawakazunori miyazawa21.04%17.69%
stephen rothwellstephen rothwell10.52%17.69%
Total193100.00%13100.00%


static void prl_list_destroy_rcu(struct rcu_head *head) { struct ip_tunnel_prl_entry *p, *n; p = container_of(head, struct ip_tunnel_prl_entry, rcu_head); do { n = rcu_dereference_protected(p->next, 1); kfree(p); p = n; } while (p); }

Contributors

PersonTokensPropCommitsCommitProp
eric dumazeteric dumazet59100.00%2100.00%
Total59100.00%2100.00%


static int ipip6_tunnel_del_prl(struct ip_tunnel *t, struct ip_tunnel_prl *a) { struct ip_tunnel_prl_entry *x; struct ip_tunnel_prl_entry __rcu **p; int err = 0; ASSERT_RTNL(); if (a && a->addr != htonl(INADDR_ANY)) { for (p = &t->prl; (x = rtnl_dereference(*p)) != NULL; p = &x->next) { if (x->addr == a->addr) { *p = x->next; kfree_rcu(x, rcu_head); t->prl_count--; goto out; } } err = -ENXIO; } else { x = rtnl_dereference(t->prl); if (x) { t->prl_count = 0; call_rcu(&x->rcu_head, prl_list_destroy_rcu); t->prl = NULL; } } out: return err; }

Contributors

PersonTokensPropCommitsCommitProp
fred templinfred templin7947.88%110.00%
eric dumazeteric dumazet3923.64%330.00%
hideaki yoshifujihideaki yoshifuji3118.79%330.00%
pre-gitpre-git137.88%110.00%
paul e. mckenneypaul e. mckenney21.21%110.00%
sascha hlusiaksascha hlusiak10.61%110.00%
Total165100.00%10100.00%


static int isatap_chksrc(struct sk_buff *skb, const struct iphdr *iph, struct ip_tunnel *t) { struct ip_tunnel_prl_entry *p; int ok = 1; rcu_read_lock(); p = __ipip6_tunnel_locate_prl(t, iph->saddr); if (p) { if (p->flags & PRL_DEFAULT) skb->ndisc_nodetype = NDISC_NODETYPE_DEFAULT; else skb->ndisc_nodetype = NDISC_NODETYPE_NODEFAULT; } else { const struct in6_addr *addr6 = &ipv6_hdr(skb)->saddr; if (ipv6_addr_is_isatap(addr6) && (addr6->s6_addr32[3] == iph->saddr) && ipv6_chk_prefix(addr6, t->dev)) skb->ndisc_nodetype = NDISC_NODETYPE_HOST; else ok = 0; } rcu_read_unlock(); return ok; }

Contributors

PersonTokensPropCommitsCommitProp
fred templinfred templin11383.09%116.67%
hideaki yoshifujihideaki yoshifuji128.82%233.33%
eric dumazeteric dumazet64.41%233.33%
pre-gitpre-git53.68%116.67%
Total136100.00%6100.00%


static void ipip6_tunnel_uninit(struct net_device *dev) { struct ip_tunnel *tunnel = netdev_priv(dev); struct sit_net *sitn = net_generic(tunnel->net, sit_net_id); if (dev == sitn->fb_tunnel_dev) { RCU_INIT_POINTER(sitn->tunnels_wc[0], NULL); } else { ipip6_tunnel_unlink(sitn, tunnel); ipip6_tunnel_del_prl(tunnel, NULL); } dst_cache_reset(&tunnel->dst_cache); dev_put(dev); }

Contributors

PersonTokensPropCommitsCommitProp
fred templinfred templin4147.13%19.09%
pavel emelianovpavel emelianov2629.89%327.27%
nicolas dichtelnicolas dichtel1112.64%218.18%
paolo abenipaolo abeni44.60%19.09%
eric dumazeteric dumazet22.30%19.09%
hideaki yoshifujihideaki yoshifuji11.15%19.09%
stephen hemmingerstephen hemminger11.15%19.09%
pre-gitpre-git11.15%19.09%
Total87100.00%11100.00%


static int ipip6_err(struct sk_buff *skb, u32 info) { const struct iphdr *iph = (const struct iphdr *)skb->data; const int type = icmp_hdr(skb)->type; const int code = icmp_hdr(skb)->code; unsigned int data_len = 0; struct ip_tunnel *t; int err; switch (type) { default: case ICMP_PARAMETERPROB: return 0; case ICMP_DEST_UNREACH: switch (code) { case ICMP_SR_FAILED: /* Impossible event. */ return 0; default: /* All others are translated to HOST_UNREACH. rfc2003 contains "deep thoughts" about NET_UNREACH, I believe they are just ether pollution. --ANK */ break; } break; case ICMP_TIME_EXCEEDED: if (code != ICMP_EXC_TTL) return 0; data_len = icmp_hdr(skb)->un.reserved[1] * 4; /* RFC 4884 4.1 */ break; case ICMP_REDIRECT: break; } err = -ENOENT; t = ipip6_tunnel_lookup(dev_net(skb->dev), skb->dev, iph->daddr, iph->saddr); if (!t) goto out; if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) { ipv4_update_pmtu(skb, dev_net(skb->dev), info, t->parms.link, 0, iph->protocol, 0); err = 0; goto out; } if (type == ICMP_REDIRECT) { ipv4_redirect(skb, dev_net(skb->dev), t->parms.link, 0, iph->protocol, 0); err = 0; goto out; } err = 0; if (!ip6_err_gen_icmpv6_unreach(skb, iph->ihl * 4, type, data_len)) goto out; if (t->parms.iph.daddr == 0) goto out; if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED) goto out; if (time_before(jiffies, t->err_time + IPTUNNEL_ERR_TIMEO)) t->err_count++; else t->err_count = 1; t->err_time = jiffies; out: return err; }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git9226.06%15.56%
david s. millerdavid s. miller8423.80%211.11%
fred templinfred templin7420.96%15.56%
eric dumazeteric dumazet5114.45%422.22%
oussama ghorbeloussama ghorbel102.83%15.56%
arnaldo carvalho de meloarnaldo carvalho de melo82.27%15.56%
pavel emelianovpavel emelianov71.98%211.11%
dmitry popovdmitry popov61.70%15.56%
simon hormansimon horman61.70%15.56%
kazunori miyazawakazunori miyazawa51.42%15.56%
wei yongjunwei yongjun51.42%15.56%
sascha hlusiaksascha hlusiak41.13%15.56%
ian morrisian morris10.28%15.56%
Total353100.00%18100.00%


static inline bool is_spoofed_6rd(struct ip_tunnel *tunnel, const __be32 v4addr, const struct in6_addr *v6addr) { __be32 v4embed = 0; if (check_6rd(tunnel, v6addr, &v4embed) && v4addr != v4embed) return true; return false; }

Contributors

PersonTokensPropCommitsCommitProp
hannes frederic sowahannes frederic sowa49100.00%1100.00%
Total49100.00%1100.00%

/* Checks if an address matches an address on the tunnel interface. * Used to detect the NAT of proto 41 packets and let them pass spoofing test. * Long story: * This function is called after we considered the packet as spoofed * in is_spoofed_6rd. * We may have a router that is doing NAT for proto 41 packets * for an internal station. Destination a.a.a.a/PREFIX:bbbb:bbbb * will be translated to n.n.n.n/PREFIX:bbbb:bbbb. And is_spoofed_6rd * function will return true, dropping the packet. * But, we can still check if is spoofed against the IP * addresses associated with the interface. */
static bool only_dnatted(const struct ip_tunnel *tunnel, const struct in6_addr *v6dst) { int prefix_len; #ifdef CONFIG_IPV6_SIT_6RD prefix_len = tunnel->ip6rd.prefixlen + 32 - tunnel->ip6rd.relay_prefixlen; #else prefix_len = 48; #endif return ipv6_chk_custom_prefix(v6dst, prefix_len, tunnel->dev); }

Contributors

PersonTokensPropCommitsCommitProp
catalin boiecatalin boie60100.00%1100.00%
Total60100.00%1100.00%

/* Returns true if a packet is spoofed */
static bool packet_is_spoofed(struct sk_buff *skb, const struct iphdr *iph, struct ip_tunnel *tunnel) { const struct ipv6hdr *ipv6h; if (tunnel->dev->priv_flags & IFF_ISATAP) { if (!isatap_chksrc(skb, iph, tunnel)) return true; return false; } if (tunnel->dev->flags & IFF_POINTOPOINT) return false; ipv6h = ipv6_hdr(skb); if (unlikely(is_spoofed_6rd(tunnel, iph->saddr, &ipv6h->saddr))) { net_warn_ratelimited("Src spoofed %pI4/%pI6c -> %pI4/%pI6c\n", &iph->saddr, &ipv6h->saddr, &iph->daddr, &ipv6h->daddr); return true; } if (likely(!is_spoofed_6rd(tunnel, iph->daddr, &ipv6h->daddr))) return false; if (only_dnatted(tunnel, &ipv6h->daddr)) return false; net_warn_ratelimited("Dst spoofed %pI4/%pI6c -> %pI4/%pI6c\n", &iph->saddr, &ipv6h->saddr, &iph->daddr