cregit-Linux how code gets into the kernel

Release 4.8 net/core/neighbour.c

Directory: net/core
/*
 *      Generic address resolution entity
 *
 *      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.
 *
 *      Fixes:
 *      Vitaly E. Lavrov        releasing NULL neighbor in neigh_add.
 *      Harald Welte            Add neighbour cache statistics like rtstat
 */


#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/slab.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/socket.h>
#include <linux/netdevice.h>
#include <linux/proc_fs.h>
#ifdef CONFIG_SYSCTL
#include <linux/sysctl.h>
#endif
#include <linux/times.h>
#include <net/net_namespace.h>
#include <net/neighbour.h>
#include <net/dst.h>
#include <net/sock.h>
#include <net/netevent.h>
#include <net/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/random.h>
#include <linux/string.h>
#include <linux/log2.h>
#include <linux/inetdevice.h>
#include <net/addrconf.h>


#define DEBUG

#define NEIGH_DEBUG 1

#define neigh_dbg(level, fmt, ...)		\
do {                                            \
        if (level <= NEIGH_DEBUG)               \
                pr_debug(fmt, ##__VA_ARGS__);   \
} while (0)


#define PNEIGH_HASHMASK		0xF

static void neigh_timer_handler(unsigned long arg);
static void __neigh_notify(struct neighbour *n, int type, int flags);
static void neigh_update_notify(struct neighbour *neigh);
static int pneigh_ifdown(struct neigh_table *tbl, struct net_device *dev);

#ifdef CONFIG_PROC_FS

static const struct file_operations neigh_stat_seq_fops;
#endif

/*
   Neighbour hash table buckets are protected with rwlock tbl->lock.

   - All the scans/updates to hash buckets MUST be made under this lock.
   - NOTHING clever should be made under this lock: no callbacks
     to protocol backends, no attempts to send something to network.
     It will result in deadlocks, if backend/driver wants to use neighbour
     cache.
   - If the entry requires some non-trivial actions, increase
     its reference count and release table lock.

   Neighbour entries are protected:
   - with reference count.
   - with rwlock neigh->lock

   Reference count prevents destruction.

   neigh->lock mainly serializes ll address data and its validity state.
   However, the same lock is used to protect another entry fields:
    - timer
    - resolution queue

   Again, nothing clever shall be made under neigh->lock,
   the most complicated procedure, which we allow is dev->hard_header.
   It is supposed, that dev->hard_header is simplistic and does
   not make callbacks to neighbour tables.
 */


static int neigh_blackhole(struct neighbour *neigh, struct sk_buff *skb) { kfree_skb(skb); return -ENETDOWN; }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git2080.00%266.67%
david s. millerdavid s. miller520.00%133.33%
Total25100.00%3100.00%


static void neigh_cleanup_and_release(struct neighbour *neigh) { if (neigh->parms->neigh_cleanup) neigh->parms->neigh_cleanup(neigh); __neigh_notify(neigh, RTM_DELNEIGH, 0); neigh_release(neigh); }

Contributors

PersonTokensPropCommitsCommitProp
thomas grafthomas graf42100.00%2100.00%
Total42100.00%2100.00%

/* * It is random distribution in the interval (1/2)*base...(3/2)*base. * It corresponds to default IPv6 settings and is not overridable, * because it is really reasonable choice. */
unsigned long neigh_rand_reach_time(unsigned long base) { return base ? (prandom_u32() % base) + (base >> 1) : 0; }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git2278.57%133.33%
hideaki yoshifujihideaki yoshifuji517.86%133.33%
aruna-hewapathiranearuna-hewapathirane13.57%133.33%
Total28100.00%3100.00%

EXPORT_SYMBOL(neigh_rand_reach_time);
static int neigh_forced_gc(struct neigh_table *tbl) { int shrunk = 0; int i; struct neigh_hash_table *nht; NEIGH_CACHE_STAT_INC(tbl, forced_gc_runs); write_lock_bh(&tbl->lock); nht = rcu_dereference_protected(tbl->nht, lockdep_is_held(&tbl->lock)); for (i = 0; i < (1 << nht->hash_shift); i++) { struct neighbour *n; struct neighbour __rcu **np; np = &nht->hash_buckets[i]; while ((n = rcu_dereference_protected(*np, lockdep_is_held(&tbl->lock))) != NULL) { /* Neighbour record may be discarded if: * - nobody refers to it. * - it is not permanent */ write_lock(&n->lock); if (atomic_read(&n->refcnt) == 1 && !(n->nud_state & NUD_PERMANENT)) { rcu_assign_pointer(*np, rcu_dereference_protected(n->next, lockdep_is_held(&tbl->lock))); n->dead = 1; shrunk = 1; write_unlock(&n->lock); neigh_cleanup_and_release(n); continue; } write_unlock(&n->lock); np = &n->next; } } tbl->last_flush = jiffies; write_unlock_bh(&tbl->lock); return shrunk; }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git14763.36%433.33%
eric dumazeteric dumazet5423.28%216.67%
david s. millerdavid s. miller3012.93%541.67%
thomas grafthomas graf10.43%18.33%
Total232100.00%12100.00%


static void neigh_add_timer(struct neighbour *n, unsigned long when) { neigh_hold(n); if (unlikely(mod_timer(&n->timer, when))) { printk("NEIGH: BUG, double timer add, state is %x\n", n->nud_state); dump_stack(); } }

Contributors

PersonTokensPropCommitsCommitProp
pavel emelianovpavel emelianov49100.00%1100.00%
Total49100.00%1100.00%


static int neigh_del_timer(struct neighbour *n) { if ((n->nud_state & NUD_IN_TIMER) && del_timer(&n->timer)) { neigh_release(n); return 1; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git3992.86%150.00%
arnaldo carvalho de meloarnaldo carvalho de melo37.14%150.00%
Total42100.00%2100.00%


static void pneigh_queue_purge(struct sk_buff_head *list) { struct sk_buff *skb; while ((skb = skb_dequeue(list)) != NULL) { dev_put(skb->dev); kfree_skb(skb); } }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git43100.00%1100.00%
Total43100.00%1100.00%


static void neigh_flush_dev(struct neigh_table *tbl, struct net_device *dev) { int i; struct neigh_hash_table *nht; nht = rcu_dereference_protected(tbl->nht, lockdep_is_held(&tbl->lock)); for (i = 0; i < (1 << nht->hash_shift); i++) { struct neighbour *n; struct neighbour __rcu **np = &nht->hash_buckets[i]; while ((n = rcu_dereference_protected(*np, lockdep_is_held(&tbl->lock))) != NULL) { if (dev && n->dev != dev) { np = &n->next; continue; } rcu_assign_pointer(*np, rcu_dereference_protected(n->next, lockdep_is_held(&tbl->lock))); write_lock(&n->lock); neigh_del_timer(n); n->dead = 1; if (atomic_read(&n->refcnt) != 1) { /* The most unpleasant situation. We must destroy neighbour entry, but someone still uses it. The destroy will be delayed until the last user releases us, but we must kill timers etc. and move it to safe state. */ __skb_queue_purge(&n->arp_queue); n->arp_queue_len_bytes = 0; n->output = neigh_blackhole; if (n->nud_state & NUD_VALID) n->nud_state = NUD_NOARP; else n->nud_state = NUD_NONE; neigh_dbg(2, "neigh %p is stray\n", n); } write_unlock(&n->lock); neigh_cleanup_and_release(n); } } }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git16567.62%635.29%
eric dumazeteric dumazet6125.00%423.53%
david s. millerdavid s. miller72.87%211.76%
joe perchesjoe perches41.64%15.88%
herbert xuherbert xu31.23%15.88%
arnaldo carvalho de meloarnaldo carvalho de melo20.82%15.88%
tommi virtanentommi virtanen10.41%15.88%
thomas grafthomas graf10.41%15.88%
Total244100.00%17100.00%


void neigh_changeaddr(struct neigh_table *tbl, struct net_device *dev) { write_lock_bh(&tbl->lock); neigh_flush_dev(tbl, dev); write_unlock_bh(&tbl->lock); }

Contributors

PersonTokensPropCommitsCommitProp
herbert xuherbert xu38100.00%1100.00%
Total38100.00%1100.00%

EXPORT_SYMBOL(neigh_changeaddr);
int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev) { write_lock_bh(&tbl->lock); neigh_flush_dev(tbl, dev); pneigh_ifdown(tbl, dev); write_unlock_bh(&tbl->lock); del_timer_sync(&tbl->proxy_timer); pneigh_queue_purge(&tbl->proxy_queue); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git3554.69%685.71%
herbert xuherbert xu2945.31%114.29%
Total64100.00%7100.00%

EXPORT_SYMBOL(neigh_ifdown);
static struct neighbour *neigh_alloc(struct neigh_table *tbl, struct net_device *dev) { struct neighbour *n = NULL; unsigned long now = jiffies; int entries; entries = atomic_inc_return(&tbl->entries) - 1; if (entries >= tbl->gc_thresh3 || (entries >= tbl->gc_thresh2 && time_after(now, tbl->last_flush + 5 * HZ))) { if (!neigh_forced_gc(tbl) && entries >= tbl->gc_thresh3) { net_info_ratelimited("%s: neighbor table overflow!\n", tbl->id); NEIGH_CACHE_STAT_INC(tbl, table_fulls); goto out_entries; } } n = kzalloc(tbl->entry_size + dev->neigh_priv_len, GFP_ATOMIC); if (!n) goto out_entries; __skb_queue_head_init(&n->arp_queue); rwlock_init(&n->lock); seqlock_init(&n->ha_lock); n->updated = n->used = now; n->nud_state = NUD_NONE; n->output = neigh_blackhole; seqlock_init(&n->hh.hh_lock); n->parms = neigh_parms_clone(&tbl->parms); setup_timer(&n->timer, neigh_timer_handler, (unsigned long)n); NEIGH_CACHE_STAT_INC(tbl, allocs); n->tbl = tbl; atomic_set(&n->refcnt, 1); n->dead = 1; out: return n; out_entries: atomic_dec(&tbl->entries); goto out; }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git15156.77%527.78%
herbert xuherbert xu3613.53%211.11%
david s. millerdavid s. miller2910.90%422.22%
rick jonesrick jones186.77%15.56%
arnaldo carvalho de meloarnaldo carvalho de melo103.76%15.56%
eric dumazeteric dumazet93.38%211.11%
hideaki yoshifujihideaki yoshifuji51.88%15.56%
pavel emelianovpavel emelianov41.50%15.56%
andrew mortonandrew morton41.50%15.56%
Total266100.00%18100.00%


static void neigh_get_hash_rnd(u32 *x) { get_random_bytes(x, sizeof(*x)); *x |= 1; }

Contributors

PersonTokensPropCommitsCommitProp
david s. millerdavid s. miller26100.00%1100.00%
Total26100.00%1100.00%


static struct neigh_hash_table *neigh_hash_alloc(unsigned int shift) { size_t size = (1 << shift) * sizeof(struct neighbour *); struct neigh_hash_table *ret; struct neighbour __rcu **buckets; int i; ret = kmalloc(sizeof(*ret), GFP_ATOMIC); if (!ret) return NULL; if (size <= PAGE_SIZE) buckets = kzalloc(size, GFP_ATOMIC); else buckets = (struct neighbour __rcu **) __get_free_pages(GFP_ATOMIC | __GFP_ZERO, get_order(size)); if (!buckets) { kfree(ret); return NULL; } ret->hash_buckets = buckets; ret->hash_shift = shift; for (i = 0; i < NEIGH_NUM_HASH_RND; i++) neigh_get_hash_rnd(&ret->hash_rnd[i]); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
david s. millerdavid s. miller8856.77%457.14%
eric dumazeteric dumazet6441.29%228.57%
andrew mortonandrew morton31.94%114.29%
Total155100.00%7100.00%


static void neigh_hash_free_rcu(struct rcu_head *head) { struct neigh_hash_table *nht = container_of(head, struct neigh_hash_table, rcu); size_t size = (1 << nht->hash_shift) * sizeof(struct neighbour *); struct neighbour __rcu **buckets = nht->hash_buckets; if (size <= PAGE_SIZE) kfree(buckets); else free_pages((unsigned long)buckets, get_order(size)); kfree(nht); }

Contributors

PersonTokensPropCommitsCommitProp
david s. millerdavid s. miller4552.33%250.00%
eric dumazeteric dumazet4147.67%250.00%
Total86100.00%4100.00%


static struct neigh_hash_table *neigh_hash_grow(struct neigh_table *tbl, unsigned long new_shift) { unsigned int i, hash; struct neigh_hash_table *new_nht, *old_nht; NEIGH_CACHE_STAT_INC(tbl, hash_grows); old_nht = rcu_dereference_protected(tbl->nht, lockdep_is_held(&tbl->lock)); new_nht = neigh_hash_alloc(new_shift); if (!new_nht) return old_nht; for (i = 0; i < (1 << old_nht->hash_shift); i++) { struct neighbour *n, *next; for (n = rcu_dereference_protected(old_nht->hash_buckets[i], lockdep_is_held(&tbl->lock)); n != NULL; n = next) { hash = tbl->hash(n->primary_key, n->dev, new_nht->hash_rnd); hash >>= (32 - new_nht->hash_shift); next = rcu_dereference_protected(n->next, lockdep_is_held(&tbl->lock)); rcu_assign_pointer(n->next, rcu_dereference_protected( new_nht->hash_buckets[hash], lockdep_is_held(&tbl->lock))); rcu_assign_pointer(new_nht->hash_buckets[hash], n); } } rcu_assign_pointer(tbl->nht, new_nht); call_rcu(&old_nht->rcu, neigh_hash_free_rcu); return new_nht; }

Contributors

PersonTokensPropCommitsCommitProp
david s. millerdavid s. miller12452.54%466.67%
eric dumazeteric dumazet11247.46%233.33%
Total236100.00%6100.00%


struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey, struct net_device *dev) { struct neighbour *n; NEIGH_CACHE_STAT_INC(tbl, lookups); rcu_read_lock_bh(); n = __neigh_lookup_noref(tbl, pkey, dev); if (n) { if (!atomic_inc_not_zero(&n->refcnt)) n = NULL; NEIGH_CACHE_STAT_INC(tbl, hits); } rcu_read_unlock_bh(); return n; }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git4251.22%444.44%
eric dumazeteric dumazet2024.39%222.22%
david s. millerdavid s. miller1417.07%111.11%
eric w. biedermaneric w. biederman33.66%111.11%
pavel emelianovpavel emelianov33.66%111.11%
Total82100.00%9100.00%

EXPORT_SYMBOL(neigh_lookup);
struct neighbour *neigh_lookup_nodev(struct neigh_table *tbl, struct net *net, const void *pkey) { struct neighbour *n; int key_len = tbl->key_len; u32 hash_val; struct neigh_hash_table *nht; NEIGH_CACHE_STAT_INC(tbl, lookups); rcu_read_lock_bh(); nht = rcu_dereference_bh(tbl->nht); hash_val = tbl->hash(pkey, NULL, nht->hash_rnd) >> (32 - nht->hash_shift); for (n = rcu_dereference_bh(nht->hash_buckets[hash_val]); n != NULL; n = rcu_dereference_bh(n->next)) { if (!memcmp(n->primary_key, pkey, key_len) && net_eq(dev_net(n->dev), net)) { if (!atomic_inc_not_zero(&n->refcnt)) n = NULL; NEIGH_CACHE_STAT_INC(tbl, hits); break; } } rcu_read_unlock_bh(); return n; }

Contributors

PersonTokensPropCommitsCommitProp
david s. millerdavid s. miller10159.41%333.33%
eric dumazeteric dumazet4325.29%222.22%
eric w. biedermaneric w. biederman95.29%111.11%
pavel emelianovpavel emelianov95.29%111.11%
hideaki yoshifujihideaki yoshifuji84.71%222.22%
Total170100.00%9100.00%

EXPORT_SYMBOL(neigh_lookup_nodev);
struct neighbour *__neigh_create(struct neigh_table *tbl, const void *pkey, struct net_device *dev, bool want_ref) { u32 hash_val; int key_len = tbl->key_len; int error; struct neighbour *n1, *rc, *n = neigh_alloc(tbl, dev); struct neigh_hash_table *nht; if (!n) { rc = ERR_PTR(-ENOBUFS); goto out; } memcpy(n->primary_key, pkey, key_len); n->dev = dev; dev_hold(dev); /* Protocol specific setup. */ if (tbl->constructor && (error = tbl->constructor(n)) < 0) { rc = ERR_PTR(error); goto out_neigh_release; } if (dev->netdev_ops->ndo_neigh_construct) { error = dev->netdev_ops->ndo_neigh_construct(dev, n); if (error < 0) { rc = ERR_PTR(error); goto out_neigh_release; } } /* Device specific setup. */ if (n->parms->neigh_setup && (error = n->parms->neigh_setup(n)) < 0) { rc = ERR_PTR(error); goto out_neigh_release; } n->confirmed = jiffies - (NEIGH_VAR(n->parms, BASE_REACHABLE_TIME) << 1); write_lock_bh(&tbl->lock); nht = rcu_dereference_protected(tbl->nht, lockdep_is_held(&tbl->lock)); if (atomic_read(&tbl->entries) > (1 << nht->hash_shift)) nht = neigh_hash_grow(tbl, nht->hash_shift + 1); hash_val = tbl->hash(pkey, dev, nht->hash_rnd) >> (32 - nht->hash_shift); if (n->parms->dead) { rc = ERR_PTR(-EINVAL); goto out_tbl_unlock; } for (n1 = rcu_dereference_protected(nht->hash_buckets[hash_val], lockdep_is_held(&tbl->lock)); n1 != NULL; n1 = rcu_dereference_protected(n1->next, lockdep_is_held(&tbl->lock))) { if (dev == n1->dev && !memcmp(n1->primary_key, pkey, key_len)) { if (want_ref) neigh_hold(n1); rc = n1; goto out_tbl_unlock; } } n->dead = 0; if (want_ref) neigh_hold(n); rcu_assign_pointer(n->next, rcu_dereference_protected(nht->hash_buckets[hash_val], lockdep_is_held(&tbl->lock))); rcu_assign_pointer(nht->hash_buckets[hash_val], n); write_unlock_bh(&tbl->lock); neigh_dbg(2, "neigh %p is created\n", n); rc = n; out: return rc; out_tbl_unlock: write_unlock_bh(&tbl->lock); out_neigh_release: neigh_release(n); goto out; }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git20039.22%627.27%
david s. millerdavid s. miller10821.18%731.82%
eric dumazeteric dumazet8717.06%29.09%
herbert xuherbert xu5510.78%313.64%
arnaldo carvalho de meloarnaldo carvalho de melo499.61%14.55%
jiri pirkojiri pirko71.37%29.09%
joe perchesjoe perches40.78%14.55%
Total510100.00%22100.00%

EXPORT_SYMBOL(__neigh_create);
static u32 pneigh_hash(const void *pkey, int key_len) { u32 hash_val = *(u32 *)(pkey + key_len - 4); hash_val ^= (hash_val >> 16); hash_val ^= hash_val >> 8; hash_val ^= hash_val >> 4; hash_val &= PNEIGH_HASHMASK; return hash_val; }

Contributors

PersonTokensPropCommitsCommitProp
pavel emelianovpavel emelianov4985.96%150.00%
hideaki yoshifujihideaki yoshifuji814.04%150.00%
Total57100.00%2100.00%


static struct pneigh_entry *__pneigh_lookup_1(struct pneigh_entry *n, struct net *net, const void *pkey, int key_len, struct net_device *dev) { while (n) { if (!memcmp(n->key, pkey, key_len) && net_eq(pneigh_net(n), net) && (n->dev == dev || !n->dev)) return n; n = n->next; } return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
hideaki yoshifujihideaki yoshifuji4754.65%133.33%
pavel emelianovpavel emelianov3641.86%133.33%
david s. millerdavid s. miller33.49%133.33%
Total86100.00%3100.00%


struct pneigh_entry *__pneigh_lookup(struct neigh_table *tbl, struct net *net, const void *pkey, struct net_device *dev) { int key_len = tbl->key_len; u32 hash_val = pneigh_hash(pkey, key_len); return __pneigh_lookup_1(tbl->phash_buckets[hash_val], net, pkey, key_len, dev); }

Contributors

PersonTokensPropCommitsCommitProp
hideaki yoshifujihideaki yoshifuji6298.41%150.00%
pavel emelianovpavel emelianov11.59%150.00%
Total63100.00%2100.00%

EXPORT_SYMBOL_GPL(__pneigh_lookup);
struct pneigh_entry * pneigh_lookup(struct neigh_table *tbl, struct net *net, const void *pkey, struct net_device *dev, int creat) { struct pneigh_entry *n; int key_len = tbl->key_len; u32 hash_val = pneigh_hash(pkey, key_len); read_lock_bh(&tbl->lock); n = __pneigh_lookup_1(tbl->phash_buckets[hash_val], net, pkey, key_len, dev); read_unlock_bh(&tbl->lock); if (n || !creat) goto out; ASSERT_RTNL(); n = kmalloc(sizeof(*n) + key_len, GFP_KERNEL); if (!n) goto out; write_pnet(&n->net, net); memcpy(n->key, pkey, key_len); n->dev = dev; if (dev) dev_hold(dev); if (tbl->pconstructor && tbl->pconstructor(n)) { if (dev) dev_put(dev); kfree(n); n = NULL; goto out; } write_lock_bh(&tbl->lock); n->next = tbl->phash_buckets[hash_val]; tbl->phash_buckets[hash_val] = n; write_unlock_bh(&tbl->lock); out: return n; }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git17272.57%436.36%
hideaki yoshifujihideaki yoshifuji2912.24%327.27%
arnaldo carvalho de meloarnaldo carvalho de melo187.59%19.09%
eric w. biedermaneric w. biederman114.64%19.09%
eric dumazeteric dumazet41.69%19.09%
pavel emelianovpavel emelianov31.27%19.09%
Total237100.00%11100.00%

EXPORT_SYMBOL(pneigh_lookup);