📄 fib_hash.c
字号:
/* * INET An implementation of the TCP/IP protocol suite for the LINUX * operating system. INET is implemented using the BSD Socket * interface as the means of communication with the user level. * * IPv4 FIB: lookup engine and maintenance routines. * * Version: $Id: fib_hash.c,v 1.13 2001/10/31 21:55:54 davem Exp $ * * Authors: 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. */#include <asm/uaccess.h>#include <asm/system.h>#include <linux/bitops.h>#include <linux/types.h>#include <linux/kernel.h>#include <linux/mm.h>#include <linux/string.h>#include <linux/socket.h>#include <linux/sockios.h>#include <linux/errno.h>#include <linux/in.h>#include <linux/inet.h>#include <linux/inetdevice.h>#include <linux/netdevice.h>#include <linux/if_arp.h>#include <linux/proc_fs.h>#include <linux/skbuff.h>#include <linux/netlink.h>#include <linux/init.h>#include <net/net_namespace.h>#include <net/ip.h>#include <net/protocol.h>#include <net/route.h>#include <net/tcp.h>#include <net/sock.h>#include <net/ip_fib.h>#include "fib_lookup.h"static struct kmem_cache *fn_hash_kmem __read_mostly;static struct kmem_cache *fn_alias_kmem __read_mostly;struct fib_node { struct hlist_node fn_hash; struct list_head fn_alias; __be32 fn_key;};struct fn_zone { struct fn_zone *fz_next; /* Next not empty zone */ struct hlist_head *fz_hash; /* Hash table pointer */ int fz_nent; /* Number of entries */ int fz_divisor; /* Hash divisor */ u32 fz_hashmask; /* (fz_divisor - 1) */#define FZ_HASHMASK(fz) ((fz)->fz_hashmask) int fz_order; /* Zone order */ __be32 fz_mask;#define FZ_MASK(fz) ((fz)->fz_mask)};/* NOTE. On fast computers evaluation of fz_hashmask and fz_mask * can be cheaper than memory lookup, so that FZ_* macros are used. */struct fn_hash { struct fn_zone *fn_zones[33]; struct fn_zone *fn_zone_list;};static inline u32 fn_hash(__be32 key, struct fn_zone *fz){ u32 h = ntohl(key)>>(32 - fz->fz_order); h ^= (h>>20); h ^= (h>>10); h ^= (h>>5); h &= FZ_HASHMASK(fz); return h;}static inline __be32 fz_key(__be32 dst, struct fn_zone *fz){ return dst & FZ_MASK(fz);}static DEFINE_RWLOCK(fib_hash_lock);static unsigned int fib_hash_genid;#define FZ_MAX_DIVISOR ((PAGE_SIZE<<MAX_ORDER) / sizeof(struct hlist_head))static struct hlist_head *fz_hash_alloc(int divisor){ unsigned long size = divisor * sizeof(struct hlist_head); if (size <= PAGE_SIZE) { return kmalloc(size, GFP_KERNEL); } else { return (struct hlist_head *) __get_free_pages(GFP_KERNEL, get_order(size)); }}/* The fib hash lock must be held when this is called. */static inline void fn_rebuild_zone(struct fn_zone *fz, struct hlist_head *old_ht, int old_divisor){ int i; for (i = 0; i < old_divisor; i++) { struct hlist_node *node, *n; struct fib_node *f; hlist_for_each_entry_safe(f, node, n, &old_ht[i], fn_hash) { struct hlist_head *new_head; hlist_del(&f->fn_hash); new_head = &fz->fz_hash[fn_hash(f->fn_key, fz)]; hlist_add_head(&f->fn_hash, new_head); } }}static void fz_hash_free(struct hlist_head *hash, int divisor){ unsigned long size = divisor * sizeof(struct hlist_head); if (size <= PAGE_SIZE) kfree(hash); else free_pages((unsigned long)hash, get_order(size));}static void fn_rehash_zone(struct fn_zone *fz){ struct hlist_head *ht, *old_ht; int old_divisor, new_divisor; u32 new_hashmask; old_divisor = fz->fz_divisor; switch (old_divisor) { case 16: new_divisor = 256; break; case 256: new_divisor = 1024; break; default: if ((old_divisor << 1) > FZ_MAX_DIVISOR) { printk(KERN_CRIT "route.c: bad divisor %d!\n", old_divisor); return; } new_divisor = (old_divisor << 1); break; } new_hashmask = (new_divisor - 1);#if RT_CACHE_DEBUG >= 2 printk("fn_rehash_zone: hash for zone %d grows from %d\n", fz->fz_order, old_divisor);#endif ht = fz_hash_alloc(new_divisor); if (ht) { memset(ht, 0, new_divisor * sizeof(struct hlist_head)); write_lock_bh(&fib_hash_lock); old_ht = fz->fz_hash; fz->fz_hash = ht; fz->fz_hashmask = new_hashmask; fz->fz_divisor = new_divisor; fn_rebuild_zone(fz, old_ht, old_divisor); fib_hash_genid++; write_unlock_bh(&fib_hash_lock); fz_hash_free(old_ht, old_divisor); }}static inline void fn_free_node(struct fib_node * f){ kmem_cache_free(fn_hash_kmem, f);}static inline void fn_free_alias(struct fib_alias *fa){ fib_release_info(fa->fa_info); kmem_cache_free(fn_alias_kmem, fa);}static struct fn_zone *fn_new_zone(struct fn_hash *table, int z){ int i; struct fn_zone *fz = kzalloc(sizeof(struct fn_zone), GFP_KERNEL); if (!fz) return NULL; if (z) { fz->fz_divisor = 16; } else { fz->fz_divisor = 1; } fz->fz_hashmask = (fz->fz_divisor - 1); fz->fz_hash = fz_hash_alloc(fz->fz_divisor); if (!fz->fz_hash) { kfree(fz); return NULL; } memset(fz->fz_hash, 0, fz->fz_divisor * sizeof(struct hlist_head *)); fz->fz_order = z; fz->fz_mask = inet_make_mask(z); /* Find the first not empty zone with more specific mask */ for (i=z+1; i<=32; i++) if (table->fn_zones[i]) break; write_lock_bh(&fib_hash_lock); if (i>32) { /* No more specific masks, we are the first. */ fz->fz_next = table->fn_zone_list; table->fn_zone_list = fz; } else { fz->fz_next = table->fn_zones[i]->fz_next; table->fn_zones[i]->fz_next = fz; } table->fn_zones[z] = fz; fib_hash_genid++; write_unlock_bh(&fib_hash_lock); return fz;}static intfn_hash_lookup(struct fib_table *tb, const struct flowi *flp, struct fib_result *res){ int err; struct fn_zone *fz; struct fn_hash *t = (struct fn_hash*)tb->tb_data; read_lock(&fib_hash_lock); for (fz = t->fn_zone_list; fz; fz = fz->fz_next) { struct hlist_head *head; struct hlist_node *node; struct fib_node *f; __be32 k = fz_key(flp->fl4_dst, fz); head = &fz->fz_hash[fn_hash(k, fz)]; hlist_for_each_entry(f, node, head, fn_hash) { if (f->fn_key != k) continue; err = fib_semantic_match(&f->fn_alias, flp, res, f->fn_key, fz->fz_mask, fz->fz_order); if (err <= 0) goto out; } } err = 1;out: read_unlock(&fib_hash_lock); return err;}static int fn_hash_last_dflt=-1;static voidfn_hash_select_default(struct fib_table *tb, const struct flowi *flp, struct fib_result *res){ int order, last_idx; struct hlist_node *node; struct fib_node *f; struct fib_info *fi = NULL; struct fib_info *last_resort; struct fn_hash *t = (struct fn_hash*)tb->tb_data; struct fn_zone *fz = t->fn_zones[0]; if (fz == NULL) return; last_idx = -1; last_resort = NULL; order = -1; read_lock(&fib_hash_lock); hlist_for_each_entry(f, node, &fz->fz_hash[0], fn_hash) { struct fib_alias *fa; list_for_each_entry(fa, &f->fn_alias, fa_list) { struct fib_info *next_fi = fa->fa_info; if (fa->fa_scope != res->scope || fa->fa_type != RTN_UNICAST) continue; if (next_fi->fib_priority > res->fi->fib_priority) break; if (!next_fi->fib_nh[0].nh_gw || next_fi->fib_nh[0].nh_scope != RT_SCOPE_LINK) continue; fa->fa_state |= FA_S_ACCESSED; if (fi == NULL) { if (next_fi != res->fi) break; } else if (!fib_detect_death(fi, order, &last_resort, &last_idx, &fn_hash_last_dflt)) { if (res->fi) fib_info_put(res->fi); res->fi = fi; atomic_inc(&fi->fib_clntref); fn_hash_last_dflt = order; goto out; } fi = next_fi; order++; } } if (order <= 0 || fi == NULL) { fn_hash_last_dflt = -1; goto out; } if (!fib_detect_death(fi, order, &last_resort, &last_idx, &fn_hash_last_dflt)) { if (res->fi) fib_info_put(res->fi); res->fi = fi; atomic_inc(&fi->fib_clntref); fn_hash_last_dflt = order; goto out; } if (last_idx >= 0) { if (res->fi) fib_info_put(res->fi); res->fi = last_resort; if (last_resort) atomic_inc(&last_resort->fib_clntref); } fn_hash_last_dflt = last_idx;out: read_unlock(&fib_hash_lock);}/* Insert node F to FZ. */static inline void fib_insert_node(struct fn_zone *fz, struct fib_node *f){ struct hlist_head *head = &fz->fz_hash[fn_hash(f->fn_key, fz)]; hlist_add_head(&f->fn_hash, head);}/* Return the node in FZ matching KEY. */static struct fib_node *fib_find_node(struct fn_zone *fz, __be32 key){ struct hlist_head *head = &fz->fz_hash[fn_hash(key, fz)]; struct hlist_node *node; struct fib_node *f; hlist_for_each_entry(f, node, head, fn_hash) { if (f->fn_key == key) return f; } return NULL;}static int fn_hash_insert(struct fib_table *tb, struct fib_config *cfg){ struct fn_hash *table = (struct fn_hash *) tb->tb_data; struct fib_node *new_f, *f; struct fib_alias *fa, *new_fa; struct fn_zone *fz; struct fib_info *fi; u8 tos = cfg->fc_tos; __be32 key; int err; if (cfg->fc_dst_len > 32) return -EINVAL; fz = table->fn_zones[cfg->fc_dst_len]; if (!fz && !(fz = fn_new_zone(table, cfg->fc_dst_len))) return -ENOBUFS; key = 0; if (cfg->fc_dst) { if (cfg->fc_dst & ~FZ_MASK(fz)) return -EINVAL; key = fz_key(cfg->fc_dst, fz); } fi = fib_create_info(cfg); if (IS_ERR(fi)) return PTR_ERR(fi); if (fz->fz_nent > (fz->fz_divisor<<1) && fz->fz_divisor < FZ_MAX_DIVISOR && (cfg->fc_dst_len == 32 || (1 << cfg->fc_dst_len) > fz->fz_divisor)) fn_rehash_zone(fz); f = fib_find_node(fz, key); if (!f) fa = NULL; else fa = fib_find_alias(&f->fn_alias, tos, fi->fib_priority); /* Now fa, if non-NULL, points to the first fib alias * with the same keys [prefix,tos,priority], if such key already * exists or to the node before which we will insert new one. * * If fa is NULL, we will need to allocate a new one and * insert to the head of f. * * If f is NULL, no fib node matched the destination key * and we need to allocate a new one of those as well. */ if (fa && fa->fa_tos == tos && fa->fa_info->fib_priority == fi->fib_priority) { struct fib_alias *fa_orig; err = -EEXIST; if (cfg->fc_nlflags & NLM_F_EXCL) goto out; if (cfg->fc_nlflags & NLM_F_REPLACE) { struct fib_info *fi_drop; u8 state; if (fi->fib_treeref > 1) goto out; write_lock_bh(&fib_hash_lock); fi_drop = fa->fa_info; fa->fa_info = fi; fa->fa_type = cfg->fc_type; fa->fa_scope = cfg->fc_scope; state = fa->fa_state; fa->fa_state &= ~FA_S_ACCESSED; fib_hash_genid++; write_unlock_bh(&fib_hash_lock); fib_release_info(fi_drop); if (state & FA_S_ACCESSED) rt_cache_flush(-1); rtmsg_fib(RTM_NEWROUTE, key, fa, cfg->fc_dst_len, tb->tb_id, &cfg->fc_nlinfo, NLM_F_REPLACE); return 0; } /* Error if we find a perfect match which * uses the same scope, type, and nexthop * information. */ fa_orig = fa; fa = list_entry(fa->fa_list.prev, struct fib_alias, fa_list); list_for_each_entry_continue(fa, &f->fn_alias, fa_list) { if (fa->fa_tos != tos) break; if (fa->fa_info->fib_priority != fi->fib_priority) break; if (fa->fa_type == cfg->fc_type && fa->fa_scope == cfg->fc_scope && fa->fa_info == fi) goto out; } if (!(cfg->fc_nlflags & NLM_F_APPEND)) fa = fa_orig; } err = -ENOENT; if (!(cfg->fc_nlflags & NLM_F_CREATE)) goto out; err = -ENOBUFS; new_fa = kmem_cache_alloc(fn_alias_kmem, GFP_KERNEL); if (new_fa == NULL) goto out; new_f = NULL; if (!f) { new_f = kmem_cache_alloc(fn_hash_kmem, GFP_KERNEL); if (new_f == NULL) goto out_free_new_fa; INIT_HLIST_NODE(&new_f->fn_hash); INIT_LIST_HEAD(&new_f->fn_alias); new_f->fn_key = key; f = new_f; } new_fa->fa_info = fi; new_fa->fa_tos = tos; new_fa->fa_type = cfg->fc_type; new_fa->fa_scope = cfg->fc_scope; new_fa->fa_state = 0; /* * Insert new entry to the list. */ write_lock_bh(&fib_hash_lock); if (new_f) fib_insert_node(fz, new_f); list_add_tail(&new_fa->fa_list, (fa ? &fa->fa_list : &f->fn_alias)); fib_hash_genid++; write_unlock_bh(&fib_hash_lock); if (new_f) fz->fz_nent++; rt_cache_flush(-1); rtmsg_fib(RTM_NEWROUTE, key, new_fa, cfg->fc_dst_len, tb->tb_id, &cfg->fc_nlinfo, 0); return 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -