所謂鄰居,是指在同一個IP區域網路内的主機,或者鄰居之間在三層上僅相隔一跳的距離。而鄰居子系統,則提供了三層協定位址與二層協定位址之間的映射關系,此外還提供二層首部緩存,以加速發送資料包。
在發送資料的時候,先進行路由查找,如果找到目的位址的路徑,再檢視鄰居表中是否存在相應的映射關系,如果沒有則建立鄰居項;然後判斷鄰居項是否為可用狀态,如不可用則把資料報存至發送緩存隊列後發送請求;在接收到請求應答後,将對應鄰居項置為可用,并将其緩存隊列中的資料包發送出去;如果在指定時間内未收到相應包,則将對應鄰居項置為無效狀态。
引用LINUX鄰居子系統(一)解釋:
個人感覺也沒那麼複雜:大緻意思就是L3的是邏輯位址,L2的是實體位址,需要做的就是實作這兩個位址的映射。
舉個簡單的例子:比如我要給寄出一個包裹到國外,對方的位址就是一個邏輯位址,但是我不可能直接就交到對方手上,我需要做的第一件事情就是看以下附近有沒有什麼郵局,郵局就是我的一個“鄰居關系”,而且它是可以幫助我把包裹送到國外的唯一管道,那我要做的就是查到郵局的位址,然後把這個包裹送到郵局,讓它幫忙。做如下等價:
收件人位址=邏輯位址
郵局位址=實體位址
兩者同時具備,這個包裹就可以正确送達了。
注1:其實這就是ARP協定做的功能
注2:把它做成鄰居子系統的原因就是,網絡不可能隻有IPV4,其他的協定也需要位址解析,如果為每個單獨開發,有很多重複勞動,做一個通用結構就可以減少這些勞動了
鄰居子系統的實作涉及以下檔案:
include/linux/inetdevice.h 定義IPv4專用的網絡裝置相關的結構、宏等
include/net/neighbour.h 定義鄰居項等結構、宏和函數原型
net/core/neighbour.c 鄰居子系統實作
鄰居子系統結構
neigh_table結構用來存儲與鄰居協定相關的參數、功能函數,以及鄰居項散清單,一個neigh_table結構執行個體對應一個鄰居協定,所有的執行個體都連結在全局連結清單neigh_tables中,對應ARP協定,其neigh_table結構執行個體是arp_tbl。
/*
* neighbour table manipulation
*/
struct neigh_table
{
struct neigh_table *next;
int family;
int entry_size;
int key_len;
__u32 (*hash)(const void *pkey, const struct net_device *);
int (*constructor)(struct neighbour *);
int (*pconstructor)(struct pneigh_entry *);
void (*pdestructor)(struct pneigh_entry *);
void (*proxy_redo)(struct sk_buff *skb);
char *id;
struct neigh_parms parms;
/* HACK. gc_* shoul follow parms without a gap! */
int gc_interval;
int gc_thresh1;
int gc_thresh2;
int gc_thresh3;
unsigned long last_flush;
struct delayed_work gc_work;
struct timer_list proxy_timer;
struct sk_buff_head proxy_queue;
atomic_t entries;
rwlock_t lock;
unsigned long last_rand;
struct kmem_cache *kmem_cachep;
struct neigh_statistics *stats;
struct neighbour **hash_buckets;
unsigned int hash_mask;
__u32 hash_rnd;
struct pneigh_entry **phash_buckets;
};
struct neigh_table *next
用來連接配接到neigh_tables中,該連結清單中除了ARP的arp_tbl,還有IPv6和DECnet所使用鄰居協定的鄰居表執行個體nd_tbl和dn_neigh_table等
int family
鄰居協定所屬的位址族,ARP為AF_INET
__u32 (*hash)(const void *pkey, const struct net_device *)
哈希函數,用來計算哈希值,ARP中為arp_hash()
int (*constructor)(struct neighbour *)
鄰居表項初始化函數,用于初始化一個新的neighbour結構執行個體中與協定相關的字段。在ARP中,該函數為arp_constructor(),由鄰居表項建立函數neigh_create()中調用。
char *id
用來配置設定neighbour結構執行個體的緩存池名字字元串,arp_tlb的該字段為"arp_cache"
struct neigh_parms parms
存儲一些與協定相關的可調節參數。如重傳逾時時間,proxy_queue隊列長度等
atomic_t entries
整個表中鄰居項的數目
struct kmem_cache *kmem_cachep
用來配置設定neighbour結構執行個體的slab緩存
struct neighbour **hash_buckets
用于存儲鄰居項的散清單,該散清單在配置設定鄰居項時,如果鄰居項數超出散清單容量,可動态擴容
struct pneigh_entry **phash_buckets
存儲ARP代理三層協定位址的散清單,在neigh_table_init_no_netlink()中初始化
neighbour結構
鄰居項使用neighbour結構來描述,該結構存儲了鄰居的相關資訊,包括狀态、二層和三層協定位址、提供給三層協定的函數指針,還有定時器和緩存的二層首部等。需要注意的是,一個鄰居并不代表一個主機,而是一個三層協定位址,對于配置了多接口的主機,一個主機将對應多個三層協定位址
struct neighbour
{
struct neighbour *next;
struct neigh_table *tbl;
struct neigh_parms *parms;
struct net_device *dev;
unsigned long used;
unsigned long confirmed;
unsigned long updated;
__u8 flags;
__u8 nud_state;
__u8 type;
__u8 dead;
atomic_t probes;
rwlock_t lock;
unsigned char ha[ALIGN(MAX_ADDR_LEN, sizeof(unsigned long))];
struct hh_cache *hh;
atomic_t refcnt;
int (*output)(struct sk_buff *skb);
struct sk_buff_head arp_queue;
struct timer_list timer;
const struct neigh_ops *ops;
u8 primary_key[0];
};
struct neighbour *next
通過next把鄰居項插入到散清單桶連結清單上,總在桶的前部插入新的鄰居項
struct neigh_table *tbl
指向相關協定的neigh_table結構執行個體,即該鄰居項所在的鄰居表。如果該鄰居項對應的是一個IPv4位址,則該字段指向arp_tbl。
struct net_device *dev
通過此網絡裝置可通路到該鄰居。對每個鄰居來說,隻能有一個可用來通路該鄰居的網絡裝置
__u8 nud_state
辨別鄰居項的狀态
struct hh_cache*hh
指向緩存的二層協定首部hh_cache結構執行個體連結清單
int (*output)(struct sk_buff *skb)
輸出函數,用來将封包輸出到該鄰居。在鄰居項的整個生命周期中,由于其狀态是不斷變化的,進而導緻該函數指針會指向不同的輸出函數。例如,當該鄰居可達時會調用neigh_connect()将output設定為neigh_ops->connected_output,
const struct neigh_ops*ops
指向鄰居項函數指針表執行個體。每一種鄰居協定都提供3到4種不同的鄰居項函數指針表
neigh_ops結構
鄰居項函數指針表由在鄰居的生存周期中不同時期被調用的多個函數指針組成。其中有多個函數指針式實作三層(IPv4中的IP層)與dev_queue_xmit()之間的調用橋梁,适用于不同的狀态。
struct neigh_ops
{
int family;
void (*solicit)(struct neighbour *, struct sk_buff*);
void (*error_report)(struct neighbour *, struct sk_buff*);
int (*output)(struct sk_buff*);
int (*connected_output)(struct sk_buff*);
int (*hh_output)(struct sk_buff*);
int (*queue_xmit)(struct sk_buff*);
};
void (*solicit)(struct neighbour *, struct sk_buff*)
發送請求封包函數。發送第一個封包時,需要新的鄰居項,發送封包被緩存arp_queue隊列中,然後會調用solicit()發送請求封包
void (*error_report)(struct neighbour *, struct sk_buff*)
當鄰居項緩存着未發送的封包,而該鄰居項又不可達時,被調用來向三層封包錯誤的函數
int (*output)(struct sk_buff*)
最通用的輸出函數,可用于所有情況。此輸出函數實作了完整的輸出過程,是以存在較多的校驗與操作,以確定封包輸出,是以該函數相對較消耗資源
int (*queue_xmit)(struct sk_buff*)
實際上,以上幾個輸出接口,除了hh_output外,并不真正傳輸資料包,隻是在準備好二層首部之後,調用queue_xmit接口
鄰居表的初始化
鄰居表由neigh_table_init()初始化。對arp_tbl的初始化,在ARP子產品初始化由arp_init()調用。實際上,鄰居表的初始化工作大部分是由neigh_table_init_no_netlink()完成的
void neigh_table_init_no_netlink(struct neigh_table *tbl)
{
unsigned long now = jiffies;
unsigned long phsize;
write_pnet(&tbl->parms.net, &init_net);
atomic_set(&tbl->parms.refcnt, 1);
tbl->parms.reachable_time =
neigh_rand_reach_time(tbl->parms.base_reachable_time);
if (!tbl->kmem_cachep)
tbl->kmem_cachep =
kmem_cache_create(tbl->id, tbl->entry_size, 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC,
NULL);
tbl->stats = alloc_percpu(struct neigh_statistics);
if (!tbl->stats)
panic("cannot create neighbour cache statistics");
#ifdef CONFIG_PROC_FS
if (!proc_create_data(tbl->id, 0, init_net.proc_net_stat,
&neigh_stat_seq_fops, tbl))
panic("cannot create neighbour proc dir entry");
#endif
tbl->hash_mask = 1;
tbl->hash_buckets = neigh_hash_alloc(tbl->hash_mask + 1);
phsize = (PNEIGH_HASHMASK + 1) * sizeof(struct pneigh_entry *);
tbl->phash_buckets = kzalloc(phsize, GFP_KERNEL);
if (!tbl->hash_buckets || !tbl->phash_buckets)
panic("cannot allocate neighbour cache hashes");
get_random_bytes(&tbl->hash_rnd, sizeof(tbl->hash_rnd));
rwlock_init(&tbl->lock);
INIT_DELAYED_WORK_DEFERRABLE(&tbl->gc_work, neigh_periodic_work);
schedule_delayed_work(&tbl->gc_work, tbl->parms.reachable_time);
setup_timer(&tbl->proxy_timer, neigh_proxy_process, (unsigned long)tbl);
skb_queue_head_init_class(&tbl->proxy_queue,
&neigh_table_proxy_queue_class);
tbl->last_flush = now;
tbl->last_rand = now + tbl->parms.reachable_time * 20;
}
void neigh_table_init(struct neigh_table *tbl)
{
struct neigh_table *tmp;
neigh_table_init_no_netlink(tbl);
write_lock(&neigh_tbl_lock);
for (tmp = neigh_tables; tmp; tmp = tmp->next) {
if (tmp->family == tbl->family)
break;
}
tbl->next = neigh_tables;
neigh_tables = tbl;
write_unlock(&neigh_tbl_lock);
if (unlikely(tmp)) {
printk(KERN_ERR "NEIGH: Registering multiple tables for "
"family %d\n", tbl->family);
dump_stack();
}
}