网络子系统14_邻居子系统通用接口
//创建一个新的邻居项 //参考 深入理解linux网络技术内幕 // 1.邻居子系统为具体的邻居协议,提供通用的功能接口 // 2.系统中所有的邻居协议被链接在neigh_tables链表中 // 3.neigh_table代表一个具体的邻居协议 // 4.具体邻居协议在运行时的行为,可以通过struct neigh_parms调节, // neigh_params与设备关联,每个邻居协议neigh_table提供一个默认的neigh_params。 //注册一个邻居协议到系统中 // 1.与邻居协议neigh_table有关的定时器:垃圾回收定时器gc_timer,代理定时器proxy_timer 1.1 void neigh_table_init(struct neigh_table *tbl) { unsigned long now = jiffies; unsigned long phsize; //设置引用计数为1 atomic_set(&tbl->parms.refcnt, 1); //tbl->parms 的类型为 struct neigh_parms,用于调整邻居协议的行为 INIT_RCU_HEAD(&tbl->parms.rcu_head); //reachable_time表示一个时间间隔,从上一次收到可到达性认证时间t到t+reachable_time之间,都认为邻居可达 //reachable_time = base_reachable_time/2 + rand(0,base_reachable_time) tbl->parms.reachable_time = neigh_rand_reach_time(tbl->parms.base_reachable_time);// //每个邻居协议私有的neighbour缓存 if (!tbl->kmem_cachep) tbl->kmem_cachep = kmem_cache_create(tbl->id, tbl->entry_size, 0, SLAB_HWCACHE_ALIGN, NULL, NULL); if (!tbl->kmem_cachep) panic("cannot create neighbour cache"); //per-cpu统计变量 tbl->stats = alloc_percpu(struct neigh_statistics); if (!tbl->stats) panic("cannot create neighbour cache statistics"); //1.邻居项和代理项的hash_buckets以链表指针的形式组织 //2.每一个bucket的链表头为struct neighbour的指针 //3.neighbour通过next域链接在同一个bucket //邻居项hash表,2个bucket tbl->hash_mask = 1; //hash_buckets组织成指针数组的形式,每一个struct neighbour通过next域进行链接 tbl->hash_buckets = neigh_hash_alloc(tbl->hash_mask + 1); //代理 phsize = (PNEIGH_HASHMASK + 1) * sizeof(struct pneigh_entry *); tbl->phash_buckets = kmalloc(phsize, GFP_KERNEL); if (!tbl->hash_buckets || !tbl->phash_buckets) panic("cannot allocate neighbour cache hashes"); memset(tbl->phash_buckets, 0, phsize); //hash函数的随机性 get_random_bytes(&tbl->hash_rnd, sizeof(tbl->hash_rnd)); //初始化协议垃圾回收定时器 rwlock_init(&tbl->lock); init_timer(&tbl->gc_timer); tbl->gc_timer.data = (unsigned long)tbl; tbl->gc_timer.function = neigh_periodic_timer;//异步回收函数 tbl->gc_timer.expires = now + 1; add_timer(&tbl->gc_timer); //代理相关的定时器 init_timer(&tbl->proxy_timer); tbl->proxy_timer.data = (unsigned long)tbl; tbl->proxy_timer.function = neigh_proxy_process; skb_queue_head_init(&tbl->proxy_queue); tbl->last_flush = now;//neigh_forced_gc最近执行一次的时间 tbl->last_rand = now + tbl->parms.reachable_time * 20; write_lock(&neigh_tbl_lock); tbl->next = neigh_tables;//将协议挂接到neigh_tables头部,所有的邻居协议通过neigh_tables列表链接起来 neigh_tables = tbl; write_unlock(&neigh_tbl_lock); } //为邻居协议分配hash表 1.2 static struct neighbour **neigh_hash_alloc(unsigned int entries) { unsigned long size = entries * sizeof(struct neighbour *); struct neighbour **ret; //大小少于一页 if (size <= PAGE_SIZE) { ret = kmalloc(size, GFP_ATOMIC); } else { //多于一页,分配连续的物理页 ret = (struct neighbour **) __get_free_pages(GFP_ATOMIC, get_order(size)); } if (ret) memset(ret, 0, size); return ret; } //邻居项的创建是由事件驱动的,当系统需要一个邻居项,并且缓存不命中时,就创建一个邻居项 // 创建新邻居项的时机: // 1.传输请求,当有一个向一台l2地址未知的主机传输请求时,就需要对该地址进行解析, // 当目的主机不是与发送方直连时,解析的l2地址就是下一条网关的地址 // 2.收到solicitation请求,由于发送请求的主机在请求封包中有自己的识别信息,收到该 // 请求的主机会假定即将有两个系统之间的通信 // 3.手工添加,ip neigh add命令创建一个邻居项 // 创建新邻居项的过程: // 1.通过slab缓存分配struct neighbour结构 // 2.通过neigh_table->constructor协议提供的初始化函数,初始化新邻居项 // 3.在neigh_table->constructor会设置neighbour->params,使用协议默认提供,或者驱动 // 提供的协议调整参数(neigh_params),通过neighbour->params->setup更新邻居项行为。 // 4.更新邻居项可到达性确认时间戳,使新邻居项比正常邻居项提前半个base_reachable_time到期 // ,从而对该邻居项进行一次主动的可到达性确认 // 5.添加到neigh_table->hash_buckets中,可能会引起hash表的扩容。 2.1 struct neighbour *neigh_create(struct neigh_table *tbl, const void *pkey, struct net_device *dev) { u32 hash_val; int key_len = tbl->key_len; int error; //从邻居协议的neighbour缓存中分配一个邻居项 struct neighbour *n1, *rc, *n = neigh_alloc(tbl); if (!n) { rc = ERR_PTR(-ENOBUFS); goto out; } //拷贝hash的key到邻居项中,arp为ip地址 memcpy(n->primary_key, pkey, key_len); n->dev = dev;//到达该邻居项使用的接口 dev_hold(dev);//增加该接口的引用计数 //如果邻居协议提供了邻居项的初始化函数,则调用 if (tbl->constructor && (error = tbl->constructor(n)) < 0) { rc = ERR_PTR(error); goto out_neigh_release; } //如果设备驱动提供了初始化邻居项的回调函数,则调用 if (n->parms->neigh_setup && (error = n->parms->neigh_setup(n)) < 0) { rc = ERR_PTR(error); goto out_neigh_release; } //邻居项可到达性确认的时间戳 //当前时间-base_reachable_time/2,已保证新邻居项比正常邻居项提前base_reachable_time/2时间到期,执行可到达性检测 n->confirmed = jiffies - (n->parms->base_reachable_time << 1); write_lock_bh(&tbl->lock); //如果协议表中的邻居数大于协议表hash表的长度 if (atomic_read(&tbl->entries) > (tbl->hash_mask + 1)) neigh_hash_grow(tbl, (tbl->hash_mask + 1) << 1);//hash表增加一半当前大小 //使用协议提供的hash函数,对key,dev做hash hash_val = tbl->hash(pkey, dev) & tbl->hash_mask;//保证最终范围在hash表大小内 if (n->parms->dead) {//如果此邻居项的配置信息标记为不在使用 rc = ERR_PTR(-EINVAL); goto out_tbl_unlock;//则不添加新邻居项 } //遍历hash表中的bucket链表 for (n1 = tbl->hash_buckets[hash_val]; n1; n1 = n1->next) { //是否已存在该邻居 if (dev == n1->dev && !memcmp(n1->primary_key, pkey, key_len)) { neigh_hold(n1);//增加已有邻居的引用计数 rc = n1; goto out_tbl_unlock; } } //将邻居添加到bucket的list中 n->next = tbl->hash_buckets[hash_val]; tbl->hash_buckets[hash_val] = n; n->dead = 0;//标记此neighbour为可使用 neigh_hold(n);//增加neighbour的引用计数 write_unlock_bh(&tbl->lock); NEIGH_PRINTK2("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; } //删除邻居项 // 邻居项被删除的原因: // 1.内核企图向一个不可到达的主机发送封包,当邻居子系统察觉到传输不成功, // 就将neighbour标记为NUD_FAILED状态,可以是异步垃圾回收机制清除该neighbour. // 2.与该邻居结构关联的主机的l2地址改变了,但它的l3地址还是原来的,一个 // 过时的邻居项必须进入NUD_FAILED态,然后重新创建一个新邻居项. // 邻居项被删除的过程: // 1.停用与邻居项有关的定时器 // 2.更新与邻居项有关的l2帧头缓存,设置器hh_cache->output为blackhole函数, // 丢弃通过此l2帧头缓存发送的数据帧,递减hh_cache的引用计数,如果为0,则释放 // 3.调用邻居项提供的删除回调函数,在neigh_table->constructor或者neigh_table->params->setup中 // 被设置。 // 4.清空该邻居项的arp_queue,此队列中缓存的skb为与此邻居相关的,l2地址待解析的skb。 // 5.更新邻居协议的邻居项个数。 3.1 void neigh_destroy(struct neighbour *neigh) { struct hh_cache *hh; //增加协议统计信息,邻居项被删除的次数 NEIGH_CACHE_STAT_INC(neigh->tbl, destroys); //邻居项没有被标记为不在使用,则说明内核有错BUG() if (!neigh->dead) { printk(KERN_WARNING "Destroying alive neighbour %p\n", neigh); dump_stack(); return; } //删除邻居项的定时器 if (neigh_del_timer(neigh)) printk(KERN_WARNING "Impossible event.\n"); //该邻居项缓存的l2帧头链表 //从一台主机发往另一台主机的所有封包的l2帧头都是相同的,当向一个目的地发送第一个封包后, //驱动程序就将其l2帧头保存在hh_cache的结构中,如果下一次有相同的封包发往 //同一个邻居,则只需从缓存中拷贝一个帧头即可,每个邻居项可以缓存多个帧头,但通常只缓存一个。 while ((hh = neigh->hh) != NULL) { neigh->hh = hh->hh_next; //将l2地址缓存从列表上摘下来 hh->hh_next = NULL; write_lock_bh(&hh->hh_lock); //替换此l2地址的输出函数为neigh_blackhole,以丢弃发往此目的l2地址的所有skb hh->hh_output = neigh_blackhole; write_unlock_bh(&hh->hh_lock); if (atomic_dec_and_test(&hh->hh_refcnt)) kfree(hh); } //邻居项提供了删除函数 if (neigh->ops && neigh->ops->destructor) (neigh->ops->destructor)(neigh); //清空该邻居项的arp缓存队列 skb_queue_purge(&neigh->arp_queue); //释放对dev的引用 dev_put(neigh->dev); //释放对协议调整参数的引用 neigh_parms_put(neigh->parms); NEIGH_PRINTK2("neigh %p is destroyed.\n", neigh); //递减协议中邻居项的个数 atomic_dec(&neigh->tbl->entries); //释放缓存 kmem_cache_free(neigh->tbl->kmem_cachep, neigh); } // 查询l3地址对应的邻居项 // 参数: // tbl,被查询的协议表,arp为arp_tbl // pkey,查询的key,arp为ip // dev,到达此邻居项使用的出口设备 4.1 struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey, struct net_device *dev) { struct neighbour *n; int key_len = tbl->key_len; //hash pkey u32 hash_val = tbl->hash(pkey, dev) & tbl->hash_mask; //更新统计信息,递增查询个数 NEIGH_CACHE_STAT_INC(tbl, lookups); read_lock_bh(&tbl->lock); //找到对应的bucket头链表 for (n = tbl->hash_buckets[hash_val]; n; n = n->next) { //遍历链表中的每个元素 if (dev == n->dev && !memcmp(n->primary_key, pkey, key_len)) { //增加邻居项的引用计数 neigh_hold(n); //更新统计信息,递增命中率 NEIGH_CACHE_STAT_INC(tbl, hits); break; } } read_unlock_bh(&tbl->lock); return n; } //用于更新一个邻居项 /* Generic update routine. -- lladdr is new lladdr or NULL, if it is not supplied. -- new is new state. -- flags NEIGH_UPDATE_F_OVERRIDE allows to override existing lladdr, if it is different. NEIGH_UPDATE_F_WEAK_OVERRIDE will suspect existing "connected" lladdr instead of overriding it if it is different. It also allows to retain current state if lladdr is unchanged. NEIGH_UPDATE_F_ADMIN means that the change is administrative. NEIGH_UPDATE_F_OVERRIDE_ISROUTER allows to override existing NTF_ROUTER flag. NEIGH_UPDATE_F_ISROUTER indicates if the neighbour is known as a router. Caller MUST hold reference count on the entry. */ //可能的更新策略: //1.新状态没有可用的l2地址,删除定时器,怀疑邻居项,退出 //2.新状态有可用的l2地址: // 2.1旧状态有可用l2地址, // 2.1.1新地址与旧地址不同并且没提供更新标志 // 2.1.1.1 但允许怀疑邻居项并且就状态可达,仍然使用旧地址,标示邻居项需要进行可达性确认 // 2.1.1.2 不允许怀疑邻居项并且就状态不可达,退出 // 2.2新旧地址相同,并且新状态为进行可到达性确认,允许去怀疑或者就状态为可到达,依然保持就状态 //3.新旧状态不同,删除定时器,如果新状态需要定时器,更新定时器。更新邻居状态 //4.新地址与旧地址不同,拷贝新l2地址到邻居项的ha字段,更新l2缓存 //5.如果新旧状态相同,退出 //6.更新邻居项的输出回调函数 //7.如果旧状态没有可用的l2地址 // 7.1 当前状态有可用的l2地址,将arp_queue中的所有skb发送 // 7.2 否则,清空arp_queue 5.1 int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new, u32 flags) { u8 old; int err; ... struct net_device *dev; int update_isrouter = 0; //获取此邻居项的锁 write_lock_bh(&neigh->lock); //此邻居项的状态 dev = neigh->dev; old = neigh->nud_state; err = -EPERM; //如果没有admin,并且就状态为手动设置 if (!(flags & NEIGH_UPDATE_F_ADMIN) && (old & (NUD_NOARP | NUD_PERMANENT))) goto out;//退出 //新状态没有可用的l2地址 if (!(new & NUD_VALID)) { neigh_del_timer(neigh);//删除邻居项的定时器 if (old & NUD_CONNECTED)//旧状态为可达的 neigh_suspect(neigh);//怀疑邻居项 neigh->nud_state = new;//更新状态为新状态 err = 0; goto out; } //运行到此处,说明有新状态有可用的l2地址,开始检查此地址 //设备没有提供l2地址 if (!dev->addr_len) { //设置提供的l2地址为邻居项的l2地址 lladdr = neigh->ha; } else if (lladdr) {//提供了l2地址 //如果旧状态有l2地址 if ((old & NUD_VALID) && !memcmp(lladdr, neigh->ha, dev->addr_len))//比较新旧地址 lladdr = neigh->ha;//如果一样的话,lladdr指向邻居的l2的地址 } else { err = -EINVAL;//没有提供地址 if (!(old & NUD_VALID))//并且旧状态没有可用的l2地址 goto out;//返回错误 lladdr = neigh->ha; } //新状态为可达状态 if (new & NUD_CONNECTED) neigh->confirmed = jiffies;//更新邻居项上一次被确认的时间为当前 neigh->updated = jiffies;//上一次更新时间为当前 err = 0; update_isrouter = flags & NEIGH_UPDATE_F_OVERRIDE_ISROUTER;//是否允许去更新NTF_ROUTER if (old & NUD_VALID) {//旧状态有l2地址 if (lladdr != neigh->ha && !(flags & NEIGH_UPDATE_F_OVERRIDE)) {//(设备具有l2地址,或者新地址与旧地址不相同) && 没有提供更新地址的指示标志 update_isrouter = 0; if ((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE) &&//允许怀疑 (old & NUD_CONNECTED)) {//旧状态可达 lladdr = neigh->ha;//改变提供的地址为邻居项的旧地址 new = NUD_STALE;//指示邻居项需要进行可达性确认 } else//新旧地址不同,但是不允许更新,则直接退出 goto out; } else { if (lladdr == neigh->ha && new == NUD_STALE &&//新地址等于旧地址,并且新状态要求进行可达性确认 ((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE) ||//允许去怀疑就状态 (old & NUD_CONNECTED))//旧状态为可达的 ) new = old;//新状态改变为旧状态 } } if (new != old) {//如果新旧状态不同 neigh_del_timer(neigh);//删除定时器 if (new & NUD_IN_TIMER) { neigh_hold(neigh);//添加新的定时器 neigh->timer.expires = jiffies + ((new & NUD_REACHABLE) ? neigh->parms->reachable_time : 0); add_timer(&neigh->timer); } neigh->nud_state = new; } if (lladdr != neigh->ha) {//新地址与旧地址不同 memcpy(&neigh->ha, lladdr, dev->addr_len);//拷贝新l2地址到邻居项的ha字段 neigh_update_hhs(neigh);//更新l2地址缓存 if (!(new & NUD_CONNECTED)) neigh->confirmed = jiffies - (neigh->parms->base_reachable_time << 1); } if (new == old)//如果新旧状态相同 goto out; if (new & NUD_CONNECTED)//新状态表示邻居可达 neigh_connect(neigh);// else neigh_suspect(neigh);//否则怀疑邻居项 if (!(old & NUD_VALID)) {//如果旧状态没有可用的l2地址 struct sk_buff *skb; while (neigh->nud_state & NUD_VALID &&//并且新地址为有可用的l2地址 (skb = __skb_dequeue(&neigh->arp_queue)) != NULL) {//从arp_queue中取出所有等待l2地址解析的skb struct neighbour *n1 = neigh; write_unlock_bh(&neigh->lock); if (skb->dst && skb->dst->neighbour)//如果skb有路由缓存 n1 = skb->dst->neighbour; n1->output(skb);//则通过邻居项提供的output完成传输 write_lock_bh(&neigh->lock); } skb_queue_purge(&neigh->arp_queue);//否则清空所有待解析l2地址的skb } out: if (update_isrouter) { neigh->flags = (flags & NEIGH_UPDATE_F_ISROUTER) ? (neigh->flags | NTF_ROUTER) : (neigh->flags & ~NTF_ROUTER); } write_unlock_bh(&neigh->lock); #ifdef CONFIG_ARPD if (notify && neigh->parms->app_probes) neigh_app_notify(neigh); #endif return err; } //邻居状态为怀疑状态,即需要可达性确认 5.2 static void neigh_suspect(struct neighbour *neigh) { struct hh_cache *hh; NEIGH_PRINTK2("neigh %p is suspected.\n", neigh); //更新邻居的输出函数为最通用的输出函数 neigh->output = neigh->ops->output; //更新所有l2缓存的输出函数为最通用的输出函数 for (hh = neigh->hh; hh; hh = hh->hh_next) hh->hh_output = neigh->ops->output; } //邻居状态为可达状态 5.3 static void neigh_connect(struct neighbour *neigh) { struct hh_cache *hh; NEIGH_PRINTK2("neigh %p is connected.\n", neigh); //使用设备驱动程序填充l2头的输出方式 neigh->output = neigh->ops->connected_output; //使用帧头缓存的方式,快速发送报文 for (hh = neigh->hh; hh; hh = hh->hh_next) hh->hh_output = neigh->ops->hh_output; } //当设备的硬件地址发生改变时,调用此函数 //标记使用此设备的邻居项不在使用,并且停用此邻居项的所有定时器 6.1 void neigh_changeaddr(struct neigh_table *tbl, struct net_device *dev) { int i; write_lock_bh(&tbl->lock); //邻居协议表hash表的长度 for (i=0; i <= tbl->hash_mask; i++) { struct neighbour *n, **np; //遍历每一个bucket np = &tbl->hash_buckets[i]; while ((n = *np) != NULL) { //遍历直到传入设备等于邻居协议所在的设备 if (dev && n->dev != dev) { np = &n->next; continue; } *np = n->next; write_lock_bh(&n->lock); //将邻居标记为dead n->dead = 1; //删除邻居的定时器 neigh_del_timer(n);//垃圾回收进程负责处理停用项 write_unlock_bh(&n->lock); //递减引用计数 neigh_release(n); } } write_unlock_bh(&tbl->lock); } //查找代理地址数据库 //pkey为目的ip //dev为用于代理ip的设备 //creat指示在查找失败时,是否创建 7.1 struct pneigh_entry * pneigh_lookup(struct neigh_table *tbl, const void *pkey, struct net_device *dev, int creat) { struct pneigh_entry *n; int key_len = tbl->key_len; u32 hash_val = *(u32 *)(pkey + key_len - 4);//通用性,对于arp,hash_val=ip //hash计算 hash_val ^= (hash_val >> 16); hash_val ^= hash_val >> 8; hash_val ^= hash_val >> 4; hash_val &= PNEIGH_HASHMASK; read_lock_bh(&tbl->lock); //代理hash表 for (n = tbl->phash_buckets[hash_val]; n; n = n->next) { //被代理的邻居的l3地址和提供的目标l3地址相同 if (!memcmp(n->key, pkey, key_len) && (n->dev == dev || !n->dev)) {//当邻居项放在表示代理hash表中,neighbour->dev的语义就发生了变化 read_unlock_bh(&tbl->lock);//在一般情况下,其表示通过此设备可以访问到这个邻居,转义后,表示,由哪个设备代理此目的ip,到达此目的 goto out;//ip的设备,通过路由表查找得到 } } read_unlock_bh(&tbl->lock); n = NULL; //没有找到代理邻居项 if (!creat) goto out; n = kmalloc(sizeof(*n) + key_len, GFP_KERNEL); if (!n) goto out; //创建邻居项,并添加到hash表中 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; } //将代理的solicitation放入延迟队列,延迟处理 8.1 void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p, struct sk_buff *skb) { unsigned long now = jiffies; unsigned long sched_next = now + (net_random() % p->proxy_delay);//延时时间 if (tbl->proxy_queue.qlen > p->proxy_qlen) {//延迟队列超过限制 kfree_skb(skb);//直接丢弃封包 return; } skb->stamp.tv_sec = LOCALLY_ENQUEUED;//标示入队时间,在出队后,通过此字段可以判断出此skb是否曾经被入过队,防止循环入队 skb->stamp.tv_usec = sched_next; spin_lock(&tbl->proxy_queue.lock); if (del_timer(&tbl->proxy_timer)) {// if (time_before(tbl->proxy_timer.expires, sched_next)) sched_next = tbl->proxy_timer.expires;//设置定时器的到期时间为需要最早被处理solicitation的时间,因此通过一个定时器就可以管理整个队列 } dst_release(skb->dst); skb->dst = NULL; dev_hold(skb->dev); __skb_queue_tail(&tbl->proxy_queue, skb);//放入tbl->proxy_queue中 mod_timer(&tbl->proxy_timer, sched_next); spin_unlock(&tbl->proxy_queue.lock); } //被动学习 //接收到ARP_REQUEST封包的目的主机从请求封包中也知道了发送方的地址 //该函数会负责检查是否有一个邻居项和请求者关联,然后就会更新这个存在的邻居项,如果不存在,则创建一个新的邻居项 9.1 struct neighbour *neigh_event_ns(struct neigh_table *tbl, u8 *lladdr, void *saddr, struct net_device *dev) { //查找邻居项 struct neighbour *neigh = __neigh_lookup(tbl, saddr, dev, lladdr || !dev->addr_len); //如果存在,则更新为NUD_STALE状态,表示有l2地址可用 if (neigh) neigh_update(neigh, lladdr, NUD_STALE, NEIGH_UPDATE_F_OVERRIDE); return neigh; }
最后更新:2017-04-03 15:21:56