主要数据结构:
1: struct neighbour
2: {
3: struct neighbour *next;
4: struct neigh_table *tbl;
5: struct neigh_parms *parms;
6: struct net_device *dev;
7: unsigned long used;
8: unsigned long confirmed;
9: unsigned long updated;
10: __u8 flags;
11: __u8 nud_state;
12: __u8 type;
13: __u8 dead;
14: atomic_t probes;
15: rwlock_t lock;
16: unsigned char ha[ALIGN(MAX_ADDR_LEN, sizeof(unsigned long))];
17: struct hh_cache *hh;
18: atomic_t refcnt;
19: int (*output)(struct sk_buff *skb);
20: struct sk_buff_head arp_queue;
21: struct timer_list timer;
22: const struct neigh_ops *ops;
23: u8 primary_key[0];
24: };
存储邻居的有关信息,例如:L2和L3地址,NUD状态,访问该邻居经过的设备等。注意,一个neighbour项不是与一台主机有关,而是与一个L3地址相关。因为一台主机可以有多个L3地址。
1: struct neigh_table
2: {
3: struct neigh_table *next;
4: int family;
5: int entry_size;
6: int key_len;
7: __u32 (*hash)(const void *pkey, const struct net_device *);
8: int (*constructor)(struct neighbour *);
9: int (*pconstructor)(struct pneigh_entry *);
10: void (*pdestructor)(struct pneigh_entry *);
11: void (*proxy_redo)(struct sk_buff *skb);
12: char *id;
13: struct neigh_parms parms;
14: /* HACK. gc_* shoul follow parms without a gap! */
15: int gc_interval;
16: int gc_thresh1;
17: int gc_thresh2;
18: int gc_thresh3;
19: unsigned long last_flush;
20: struct delayed_work gc_work;
21: struct timer_list proxy_timer;
22: struct sk_buff_head proxy_queue;
23: atomic_t entries;
24: rwlock_t lock;
25: unsigned long last_rand;
26: struct kmem_cache *kmem_cachep;
27: struct neigh_statistics *stats;
28: struct neighbour **hash_buckets;
29: unsigned int hash_mask;
30: __u32 hash_rnd;
31: struct pneigh_entry **phash_buckets;
32: };
描述一种邻居协议的参数和所用函数。每个邻居协议都有该结构的一个实例。所有实例都插入到一个由静态变量neigh_tables指向的一个全局表中,并由neigh_tbl_lock来加锁保护。该锁只保护全局表的完整性,并不对表中每个条目的内容进行保护。
1: struct neigh_parms
2: {
3: #ifdef CONFIG_NET_NS
4: struct net *net;
5: #endif
6: struct net_device *dev;
7: struct neigh_parms *next;
8: int (*neigh_setup)(struct neighbour *);
9: void (*neigh_cleanup)(struct neighbour *);
10: struct neigh_table *tbl;
11:
12: void *sysctl_table;
13:
14: int dead;
15: atomic_t refcnt;
16: struct rcu_head rcu_head;
17:
18: int base_reachable_time;
19: int retrans_time;
20: int gc_staletime;
21: int reachable_time;
22: int delay_probe_time;
23:
24: int queue_len;
25: int ucast_probes;
26: int app_probes;
27: int mcast_probes;
28: int anycast_delay;
29: int proxy_delay;
30: int proxy_qlen;
31: int locktime;
32: };
对每个设备上邻居协议进行调整的一组参数。由于在大部分接口上可以启动多个协议,所以一个net_device结构可以关联多个neigh_parms结构。
1: struct neigh_ops
2: {
3: int family;
4: void (*solicit)(struct neighbour *, struct sk_buff*);
5: void (*error_report)(struct neighbour *, struct sk_buff*);
6: int (*output)(struct sk_buff*);
7: int (*connected_output)(struct sk_buff*);
8: int (*hh_output)(struct sk_buff*);
9: int (*queue_xmit)(struct sk_buff*);
10: };
一组函数,用来表示L3协议和dev_queue_xmit之间的接口。这些虚拟函数可以根据上下文环境来改变
1: struct hh_cache
2: {
3: struct hh_cache *hh_next; /* Next entry */
4: atomic_t hh_refcnt; /* number of users */
5: /*
6: * We want hh_output, hh_len, hh_lock and hh_data be a in a separate
7: * cache line on SMP.
8: * They are mostly read, but hh_refcnt may be changed quite frequently,
9: * incurring cache line ping pongs.
10: */
11: __be16 hh_type ____cacheline_aligned_in_smp;
12: /* protocol identifier, f.e ETH_P_IP
13: * NOTE: For VLANs, this will be the
14: * encapuslated type. --BLG
15: */
16: u16 hh_len; /* length of header */
17: int (*hh_output)(struct sk_buff *skb);
18: seqlock_t hh_lock;
19:
20: /* cached hardware header; allow for machine alignment needs. */
21: #define HH_DATA_MOD 16
22: #define HH_DATA_OFF(__len) \
23: (HH_DATA_MOD - (((__len - 1) & (HH_DATA_MOD - 1)) + 1))
24: #define HH_DATA_ALIGN(__len) \
25: (((__len)+(HH_DATA_MOD-1))&~(HH_DATA_MOD - 1))
26: unsigned long hh_data[HH_DATA_ALIGN(LL_MAX_HEADER) / sizeof(long)];
27: };
缓存链路层头部信息用于加快传输速度。一次将一个缓存的头部信息复制到发送缓冲区中比按位填充头部信息要快的多。并不是所有的网络设备驱动都支持缓存头部信息。
1: struct rtable
2: {
3: union
4: {
5: struct dst_entry dst;
6: } u;
7:
8: /* Cache lookup keys */
9: struct flowi fl;
10:
11: struct in_device *idev;
12:
13: int rt_genid;
14: unsigned rt_flags;
15: __u16 rt_type;
16:
17: __be32 rt_dst; /* Path destination */
18: __be32 rt_src; /* Path source */
19: int rt_iif;
20:
21: /* Info on neighbour */
22: __be32 rt_gateway;
23:
24: /* Miscellaneous cached information */
25: __be32 rt_spec_dst; /* RFC1122 specific destination */
26: struct inet_peer *peer; /* long-living peer info */
27: };
1: struct dst_entry
2: {
3: struct rcu_head rcu_head;
4: struct dst_entry *child;
5: struct net_device *dev;
6: short error;
7: short obsolete;
8: int flags;
9: #define DST_HOST 1
10: #define DST_NOXFRM 2
11: #define DST_NOPOLICY 4
12: #define DST_NOHASH 8
13: unsigned long expires;
14:
15: unsigned short header_len; /* more space at head required */
16: unsigned short trailer_len; /* space to reserve at tail */
17:
18: unsigned int rate_tokens;
19: unsigned long rate_last; /* rate limiting for ICMP */
20:
21: struct dst_entry *path;
22:
23: struct neighbour *neighbour;
24: struct hh_cache *hh;
25: #ifdef CONFIG_XFRM
26: struct xfrm_state *xfrm;
27: #else
28: void *__pad1;
29: #endif
30: int (*input)(struct sk_buff*);
31: int (*output)(struct sk_buff*);
32:
33: struct dst_ops *ops;
34:
35: u32 metrics[RTAX_MAX];
36:
37: #ifdef CONFIG_NET_CLS_ROUTE
38: __u32 tclassid;
39: #else
40: __u32 __pad2;
41: #endif
42:
43:
44: /*
45: * Align __refcnt to a 64 bytes alignment
46: * (L1_CACHE_SIZE would be too much)
47: */
48: #ifdef CONFIG_64BIT
49: long __pad_to_align_refcnt[2];
50: #else
51: long __pad_to_align_refcnt[1];
52: #endif
53: /*
54: * __refcnt wants to be on a different cache line from
55: * input/output/ops or performance tanks badly
56: */
57: atomic_t __refcnt; /* client references */
58: int __use;
59: unsigned long lastuse;
60: union {
61: struct dst_entry *next;
62: struct rtable *rt_next;
63: struct rt6_info *rt6_next;
64: struct dn_route *dn_next;
65: };
66: };
当主机需要路由一个封包时,首先会查询自己的路由缓存中的目的主机信息,在缓存不命中时,接着才会查询路由表。每次查询路由表时,都会将结果保存在路由缓存中。
L3协议和邻居协议间的通用接口
Linux内核有个通用邻居层,通过一个虚拟函数表将L3协议和主要的L2传输函数连接起来。VFT是Linux内核最常用的机制,可以使各个子系统在不同的时间使用不同的函数。邻居子系统的VFT是由neigh_ops结构实现的。每个neighbour机构的ops字段中有一个指针,指向neigh_ops结构。
1: struct neigh_ops
2: {
3: int family;
4: void (*solicit)(struct neighbour *, struct sk_buff*);
5: void (*error_report)(struct neighbour *, struct sk_buff*);
6: int (*output)(struct sk_buff*);
7: int (*connected_output)(struct sk_buff*);
8: int (*hh_output)(struct sk_buff*);
9: int (*queue_xmit)(struct sk_buff*);
10: };
neigh_ops结构由一些函数指针组成,这些指针指向在一个neighbour项生存期内的不同时间调用的函数。这些函数大部分是虚函数,作为L3协议和dev_queue_xmit API间的接口。
family,邻居协议所表示的邻居项的地址簇。她的可能取值位于include/linux/socket.h文件中,名称都是AF_XXX的形式。对于ipv4和ipv6,对应的值分别为AF_INET和AF_INET6。
solicit,发送solicitation请求的函数
error_report,当一个邻居被认为不可到达时,要调用这个函数
output,这个是最普通的函数,用于所有的情况下。他会检查地址是否已经被解析过;在没有被解析的情况下,它会启动解析程序。如果地址还未准备好,它会把封包保存在一个临时队列中,并启动解析程序。由于该函数为了保证接收方是可到达的,它会做每一件必要的事情,因此他相对来说需要的操作比较多。
connected_output,当已经知道邻居是可到达时(邻居状态为NUD_CONNECTED态),使用该函数。因为所有需要的信息都是满足的,该函数只要简单填充一下L2帧头,因此它比output速度要快。
hh_output,当地址已被解析过,并且整个封包头已经根据上次传输结果放入帧头缓存时,就使用这个函数
queue_xmit,传输封包
neigh->out和neigh->nud_state的初始化
邻居的状态和neigh->output函数彼此相互依赖。当nud_state改变状态时,output通常也必须相应地更新。
邻居信息的更新:neigh_update:
1: int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,
2: u32 flags)
3: {
4: u8 old;
5: int err;
6: int notify = 0;
7: struct net_device *dev;
8: int update_isrouter = 0;
9:
10: write_lock_bh(&neigh->lock);
11:
12: dev = neigh->dev;
13: old = neigh->nud_state;
14: err = -EPERM;
15:
16: if (!(flags & NEIGH_UPDATE_F_ADMIN) &&
17: (old & (NUD_NOARP | NUD_PERMANENT)))
18: goto out;
19:
20: if (!(new & NUD_VALID)) {
21: neigh_del_timer(neigh);
22: if (old & NUD_CONNECTED)
23: neigh_suspect(neigh);
24: neigh->nud_state = new;
25: err = 0;
26: notify = old & NUD_VALID;
27: if ((old & (NUD_INCOMPLETE | NUD_PROBE)) &&
28: (new & NUD_FAILED)) {
29: neigh_invalidate(neigh);
30: notify = 1;
31: }
32: goto out;
33: }
34:
35: /* Compare new lladdr with cached one */
36: if (!dev->addr_len) {
37: /* First case: device needs no address. */
38: lladdr = neigh->ha;
39: } else if (lladdr) {
40: /* The second case: if something is already cached
41: and a new address is proposed:
42: - compare new & old
43: - if they are different, check override flag
44: */
45: if ((old & NUD_VALID) &&
46: !memcmp(lladdr, neigh->ha, dev->addr_len))
47: lladdr = neigh->ha;
48: } else {
49: /* No address is supplied; if we know something,
50: use it, otherwise discard the request.
51: */
52: err = -EINVAL;
53: if (!(old & NUD_VALID))
54: goto out;
55: lladdr = neigh->ha;
56: }
57:
58: if (new & NUD_CONNECTED)
59: neigh->confirmed = jiffies;
60: neigh->updated = jiffies;
61:
62: /* If entry was valid and address is not changed,
63: do not change entry state, if new one is STALE.
64: */
65: err = 0;
66: update_isrouter = flags & NEIGH_UPDATE_F_OVERRIDE_ISROUTER;
67: if (old & NUD_VALID) {
68: if (lladdr != neigh->ha && !(flags & NEIGH_UPDATE_F_OVERRIDE)) {
69: update_isrouter = 0;
70: if ((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE) &&
71: (old & NUD_CONNECTED)) {
72: lladdr = neigh->ha;
73: new = NUD_STALE;
74: } else
75: goto out;
76: } else {
77: if (lladdr == neigh->ha && new == NUD_STALE &&
78: ((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE) ||
79: (old & NUD_CONNECTED))
80: )
81: new = old;
82: }
83: }
84:
85: if (new != old) {
86: neigh_del_timer(neigh);
87: if (new & NUD_IN_TIMER)
88: neigh_add_timer(neigh, (jiffies +
89: ((new & NUD_REACHABLE) ?
90: neigh->parms->reachable_time :
91: 0)));
92: neigh->nud_state = new;
93: }
94:
95: if (lladdr != neigh->ha) {
96: memcpy(&neigh->ha, lladdr, dev->addr_len);
97: neigh_update_hhs(neigh);
98: if (!(new & NUD_CONNECTED))
99: neigh->confirmed = jiffies -
100: (neigh->parms->base_reachable_time << 1);
101: notify = 1;
102: }
103: if (new == old)
104: goto out;
105: if (new & NUD_CONNECTED)
106: neigh_connect(neigh);
107: else
108: neigh_suspect(neigh);
109: if (!(old & NUD_VALID)) {
110: struct sk_buff *skb;
111:
112: /* Again: avoid dead loop if something went wrong */
113:
114: while (neigh->nud_state & NUD_VALID &&
115: (skb = __skb_dequeue(&neigh->arp_queue)) != NULL) {
116: struct neighbour *n1 = neigh;
117: write_unlock_bh(&neigh->lock);
118: /* On shaper/eql skb->dst->neighbour != neigh :( */
119: if (skb_dst(skb) && skb_dst(skb)->neighbour)
120: n1 = skb_dst(skb)->neighbour;
121: n1->output(skb);
122: write_lock_bh(&neigh->lock);
123: }
124: skb_queue_purge(&neigh->arp_queue);
125: }
126: out:
127: if (update_isrouter) {
128: neigh->flags = (flags & NEIGH_UPDATE_F_ISROUTER) ?
129: (neigh->flags | NTF_ROUTER) :
130: (neigh->flags & ~NTF_ROUTER);
131: }
132: write_unlock_bh(&neigh->lock);
133:
134: if (notify)
135: neigh_update_notify(neigh);
136:
137: return err;
138: }
neigh,指向要更新的neighbour结构。
lladdr,新的连接层(L2)地址。lladdr并不总是初始化为一个新值。例如,当调用neigh_update来删除一个neighbour结构时,会给lladdr传递一个NULL值。
new,新的NUD状态
flags,用于传达信息,
NEIGH_UPDATE_F_ADMIN,管理性改变。意思是说改变来自用户空间命令。
NEIGH_UPDATE_F_OVERRIDE,指当前的L2地址可以被lladdr覆盖,管理性改变使用这个标志来区分replace和add命令。协议代码可以使用这个标识来给一个L2地址设定一个最小生存期
链路层地址的改变
调用neigh_update函数的原因是为了改变NUD状态,但它也可以改变目的链路层地址,通过这个地址可以到达一个邻居。如果该函数的输入参数中提供了一个新的链路层地址,并且如果输入参数flags表明允许改变地址,那么该函数就能完成改变链路层地址的动作,如果改变了链路层地址,那么所有缓存的帧头都要同步更新。这个工作由neigh_update_hhs函数完成。
若neigh_update的输入参数中没有提供链路层地址,并且当前NUD状态不是一个合法态,那么neigh_update就会丢弃输入帧skb,并返回一个错误。
通知arpd
在一些大型网络的主机中,管理ARP请求时使用称为arpd的用户空间守护进程,而不是让内核自己负责。若编译内核时选择了支持arpd,并且arpd经过适当的配置,neigh_update函数就会将下列事件通知这个进程:
- 状态从NUD_VALID态改变为无效太
- 链路层地址发生了变化
邻居基础结构的一般任务
缓存
- 邻居映射,由于任何数据都有可能被多次使用,那么缓存L3到L2的映射结果就是有意义的。不缓存错误结果(失败的地址解析)。但是会将映射失败的neighbour结构设置为NUD_FAILED态,以便垃圾回收定时器能将其清除。
- L2帧头,邻居基础结构会缓存L2帧头,这样可以缩短L3封包到L2帧的封装时间,否则,基础结构将要挨个初始话封包的L2帧头的每个字段
邻居基础结构将neighbour结构放在缓存中,每个协议一个缓存,其结构是典型的hash表结构,同一个bucket中冲突的元素被链接到一个单链表中。新元素被增加到链表的开头。Hash函数负责把元素分发到bucket中,它的输入参数是l3地址,关联的设备和一个为了降低假设的拒绝服务攻击的影响而定期重新计算的随机数。Hash表是由neigh_hash_alloc函数和neigh_hash_free函数来进行分配和释放的。在每个协议初始化时,都会建立一个hash表,其初始容量为两个元素大小。如果表中的元素数量大于bucket数目,hash表就会按照下面的方式重新组织。首先,表的容量扩大一倍,然后重新计算hashing所用的随机值。最后,表中的元素按照前面提到的变量重新分配:L3地址、设备和随机数。
1: struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,
2: struct net_device *dev)
3: {
4: struct neighbour *n;
5: int key_len = tbl->key_len;
6: u32 hash_val;
7:
8: NEIGH_CACHE_STAT_INC(tbl, lookups);
9:
10: read_lock_bh(&tbl->lock);
11: hash_val = tbl->hash(pkey, dev);
12: for (n = tbl->hash_buckets[hash_val & tbl->hash_mask]; n; n = n->next) {
13: if (dev == n->dev && !memcmp(n->primary_key, pkey, key_len)) {
14: neigh_hold(n);
15: NEIGH_CACHE_STAT_INC(tbl, hits);
16: break;
17: }
18: }
19: read_unlock_bh(&tbl->lock);
20: return n;
21: }
定时器
- 状态转移时的定时器(neighhour->timer),一些NUD状态的转移是由时间的推移而触发的,而不是由系统事件触发。这些状态转移包括
- 从NUD_REACHABLE态到NUD_DELAY态或NUD_STALE态,当在一段特定时间内没有送往或接收自某一邻居的流量,那么就发生这种状态转移;并且邻居子系统自动认为该邻居是不可达的
- 从NUD_DELAY态到NUD_PROBE态或NUD_REACHABLE状态,在邻居的可达性被怀疑后,这就是该邻居的下一个状态;此时,如果可到达性没有被外部事件所证实,那么邻居子系统必须进行一次明确的探测。该定时器只是简单地检测状态改变所要求的条件,并且处理状态的转变。
每个neighbour结构中的定时器可以控制上述三个状态转移中的两个。当用neigh_alloc函数创建一个neighbour项时,该定时器的回调函数被初始化neigh_timer_handler函数。
- 失败的solicitation请求的定时器,如果在一个给定的时间段内没有收到solicitation请求的应答,那么就再发送一个新的solicitation请求。发送solicitation请求的最大次数在neigh_parms结构的XXX_probes字段中设定。
- 垃圾回收定时器,这是一个周期性定时器,用于确保内存不会浪费在没用的数据结构上,其回调处理函数是neigh_periodic_timer
- proxy定时器,对于一个会接收到大量solicitation请求的代理来说,推迟请求的处理时有好处的。
创建一个邻居项
和大多数缓存项一样,neighbour缓存项的创建是事件驱动的:当系统需要一个邻居,并且缓存不命中时,就需要创建一个邻居实例。特别是当下列的一个事件发生时,就创建一个新的邻居项:
- 传输请求,当有一个向一台L2地址未知的主机的传输封包时,就需要对该地址进行解析。
- 收到一个solicitation请求,由于发送请求的主机在请求封包中有自己的识别信息,收到该请求的主机会假定即将有两个系统间的通信产生,于是自动创建一个缓存项。
- 手工添加,管理员可以通过ip neigh add命令创建一个邻居缓存项
1: struct neighbour *neigh_create(struct neigh_table *tbl, const void *pkey,
2: struct net_device *dev)
3: {
4: u32 hash_val;
5: int key_len = tbl->key_len;
6: int error;
7: struct neighbour *n1, *rc, *n = neigh_alloc(tbl);
8:
9: if (!n) {
10: rc = ERR_PTR(-ENOBUFS);
11: goto out;
12: }
13:
14: memcpy(n->primary_key, pkey, key_len);
15: n->dev = dev;
16: dev_hold(dev);
17:
18: /* Protocol specific setup. */
19: if (tbl->constructor && (error = tbl->constructor(n)) < 0) {
20: rc = ERR_PTR(error);
21: goto out_neigh_release;
22: }
23:
24: /* Device specific setup. */
25: if (n->parms->neigh_setup &&
26: (error = n->parms->neigh_setup(n)) < 0) {
27: rc = ERR_PTR(error);
28: goto out_neigh_release;
29: }
30:
31: n->confirmed = jiffies - (n->parms->base_reachable_time << 1);
32:
33: write_lock_bh(&tbl->lock);
34:
35: if (atomic_read(&tbl->entries) > (tbl->hash_mask + 1))
36: neigh_hash_grow(tbl, (tbl->hash_mask + 1) << 1);
37:
38: hash_val = tbl->hash(pkey, dev) & tbl->hash_mask;
39:
40: if (n->parms->dead) {
41: rc = ERR_PTR(-EINVAL);
42: goto out_tbl_unlock;
43: }
44:
45: for (n1 = tbl->hash_buckets[hash_val]; n1; n1 = n1->next) {
46: if (dev == n1->dev && !memcmp(n1->primary_key, pkey, key_len)) {
47: neigh_hold(n1);
48: rc = n1;
49: goto out_tbl_unlock;
50: }
51: }
52:
53: n->next = tbl->hash_buckets[hash_val];
54: tbl->hash_buckets[hash_val] = n;
55: n->dead = 0;
56: neigh_hold(n);
57: write_unlock_bh(&tbl->lock);
58: NEIGH_PRINTK2("neigh %p is created.\n", n);
59: rc = n;
60: out:
61: return rc;
62: out_tbl_unlock:
63: write_unlock_bh(&tbl->lock);
64: out_neigh_release:
65: neigh_release(n);
66: goto out;
67: }
删除neighbour结构的原因主要有以下三个:
- 内核企图向一个不可达的主机发送封包。发送这种情况的原因有很多:该主机已关机、或者它的网线没有插好,或者它是个无线设备但是移动到了信号范围之外或者它的网络配置出现问题、或许是有人为一台不存的主机手工建立了一个缓存项。不管是什么原因,邻居子系统觉察到了传输不成功,就将相关的neighbour结构转移到NUD_FAILED状态。
- 于该邻居结构关林的主机的l2地址改变了(可能是它换了NIC),但它的L3地址还是原来的。这样一来,该neighbour结构的L2地址就过时了
- 该邻居结构存在时间太长,且内核需要他所占用的内存。
1: static inline void neigh_release(struct neighbour *neigh)
2: {
3: if (atomic_dec_and_test(&neigh->refcnt))
4: neigh_destroy(neigh);
5: }
1: void neigh_destroy(struct neighbour *neigh)
2: {
3: struct hh_cache *hh;
4:
5: NEIGH_CACHE_STAT_INC(neigh->tbl, destroys);
6:
7: if (!neigh->dead) {
8: printk(KERN_WARNING
9: "Destroying alive neighbour %p\n", neigh);
10: dump_stack();
11: return;
12: }
13:
14: if (neigh_del_timer(neigh))
15: printk(KERN_WARNING "Impossible event.\n");
16:
17: while ((hh = neigh->hh) != NULL) {
18: neigh->hh = hh->hh_next;
19: hh->hh_next = NULL;
20:
21: write_seqlock_bh(&hh->hh_lock);
22: hh->hh_output = neigh_blackhole;
23: write_sequnlock_bh(&hh->hh_lock);
24: if (atomic_dec_and_test(&hh->hh_refcnt))
25: kfree(hh);
26: }
27:
28: skb_queue_purge(&neigh->arp_queue);
29:
30: dev_put(neigh->dev);
31: neigh_parms_put(neigh->parms);
32:
33: NEIGH_PRINTK2("neigh %p is destroyed.\n", neigh);
34:
35: atomic_dec(&neigh->tbl->entries);
36: kmem_cache_free(neigh->tbl->kmem_cachep, neigh);
37: }
- 停止所有未决的定时器。理论上,在执行neigh_destroy函数时,不应该有任何定时器是未决的,因为neigh_release调用neigh_destroy的条件是引用计数为0,而定时器运行时总要保持对要删除的数据结构的引用
- 释放所有对外部数据结构的引用
- 如果arp_queue队列非空,就要将其清空
- 将表示主机使用的neighbour项总数的全局计数器减1
- 释放该neighbour结构。
垃圾回收
垃圾回收涉及清除不再使用的资源的系统进程。和许多Linux内核子系统(网络子系统或其他子系统)类似,邻居子系统也维护者一个定时器。该定时器周期性的运行,当其过期时,就执行某个函数来清理不用的数据结构。
邻居基础结构使用的垃圾回收算法有两个主要组成部分:
- 同步清理,当邻居基础结构需要分配一个新的neighbour基础结构,并且这种结构专用的内存池已经用完时,内核就会立即执行同步清理
- 异步清理,异步清理是周期性执行的,目的是为了删除某段时间内没有用过的neighbour结构。该段时间的长短是可以配置的,其值保存在gc_staletime变量中。
1: static int neigh_forced_gc(struct neigh_table *tbl)
2: {
3: int shrunk = 0;
4: int i;
5:
6: NEIGH_CACHE_STAT_INC(tbl, forced_gc_runs);
7:
8: write_lock_bh(&tbl->lock);
9: for (i = 0; i <= tbl->hash_mask; i++) {
10: struct neighbour *n, **np;
11:
12: np = &tbl->hash_buckets[i];
13: while ((n = *np) != NULL) {
14: /* Neighbour record may be discarded if:
15: * - nobody refers to it.
16: * - it is not permanent
17: */
18: write_lock(&n->lock);
19: if (atomic_read(&n->refcnt) == 1 &&
20: !(n->nud_state & NUD_PERMANENT)) {
21: *np = n->next;
22: n->dead = 1;
23: shrunk = 1;
24: write_unlock(&n->lock);
25: neigh_cleanup_and_release(n);
26: continue;
27: }
28: write_unlock(&n->lock);
29: np = &n->next;
30: }
31: }
32:
33: tbl->last_flush = jiffies;
34:
35: write_unlock_bh(&tbl->lock);
36:
37: return shrunk;
38: }
1: struct pneigh_entry * pneigh_lookup(struct neigh_table *tbl,
2: struct net *net, const void *pkey,
3: struct net_device *dev, int creat)
4: {
5: struct pneigh_entry *n;
6: int key_len = tbl->key_len;
7: u32 hash_val = pneigh_hash(pkey, key_len);
8:
9: read_lock_bh(&tbl->lock);
10: n = __pneigh_lookup_1(tbl->phash_buckets[hash_val],
11: net, pkey, key_len, dev);
12: read_unlock_bh(&tbl->lock);
13:
14: if (n || !creat)
15: goto out;
16:
17: ASSERT_RTNL();
18:
19: n = kmalloc(sizeof(*n) + key_len, GFP_KERNEL);
20: if (!n)
21: goto out;
22:
23: write_pnet(&n->net, hold_net(net));
24: memcpy(n->key, pkey, key_len);
25: n->dev = dev;
26: if (dev)
27: dev_hold(dev);
28:
29: if (tbl->pconstructor && tbl->pconstructor(n)) {
30: if (dev)
31: dev_put(dev);
32: release_net(net);
33: kfree(n);
34: n = NULL;
35: goto out;
36: }
37:
38: write_lock_bh(&tbl->lock);
39: n->next = tbl->phash_buckets[hash_val];
40: tbl->phash_buckets[hash_val] = n;
41: write_unlock_bh(&tbl->lock);
42: out:
43: return n;
44: }
L2帧头缓存
当向一个目的地发送一个封包后,驱动程序就将其L2帧头保存在名为hh_cache的专用结构中。如果下一次若有封包发往同一个邻居,发送者就不需要在挨个填充器L2帧头的每个字段,只需要从缓存中拷贝一个帧头就可以。
1: int neigh_resolve_output(struct sk_buff *skb)
2: {
3: struct dst_entry *dst = skb_dst(skb);
4: struct neighbour *neigh;
5: int rc = 0;
6:
7: if (!dst || !(neigh = dst->neighbour))
8: goto discard;
9:
10: __skb_pull(skb, skb_network_offset(skb));
11:
12: if (!neigh_event_send(neigh, skb)) {
13: int err;
14: struct net_device *dev = neigh->dev;
15: if (dev->header_ops->cache && !dst->hh) {
16: write_lock_bh(&neigh->lock);
17: if (!dst->hh)
18: neigh_hh_init(neigh, dst, dst->ops->protocol);
19: err = dev_hard_header(skb, dev, ntohs(skb->protocol),
20: neigh->ha, NULL, skb->len);
21: write_unlock_bh(&neigh->lock);
22: } else {
23: read_lock_bh(&neigh->lock);
24: err = dev_hard_header(skb, dev, ntohs(skb->protocol),
25: neigh->ha, NULL, skb->len);
26: read_unlock_bh(&neigh->lock);
27: }
28: if (err >= 0)
29: rc = neigh->ops->queue_xmit(skb);
30: else
31: goto out_kfree_skb;
32: }
33: out:
34: return rc;
35: discard:
36: NEIGH_PRINTK1("neigh_resolve_output: dst=%p neigh=%p\n",
37: dst, dst ? dst->neighbour : NULL);
38: out_kfree_skb:
39: rc = -EINVAL;
40: kfree_skb(skb);
41: goto out;
一个缓存的帧头可能包含许多不同的字段,但是有两个字段是最有可能发生变化的并且使该缓存项失效。这两个字段就是就是源地址和目的地址字段。当一个本地设备改变了自己L2地址,所有与此地址关联的缓存帧头就过期了。当邻居子系统知道L2地址发生变化后,它就会刷新与该地址相关的说有neighbour项,从而使所有相关的L2帧头缓存项失效。当系统探测到邻居L2地址发生变化,它就调用neigh_update_hhs函数处理。这个函数会依次更新那个邻居所使用的所有缓存帧头。neigh_update_hhs函数调用设备驱动程序提供的header_cache_update函数。
协议初始化和清理
邻居层接收的事件
邻居子系统维护的邻居项中,不管什么时候只要邻居项中有一个主要元素(l3地址,l2地址或接口设备)变化了,那么该项也就失效了。此时,内核必须确保邻居协议能够知道这些信息是否发生变化。这个工作是由邻居子系统提供的两个函数完成的:
neigh_ifdown,其他的内核子系统要调用该函数,以通知邻居子系统有关设备和L3地址的变化。L3地址改变的通知由L3协议送出
neigh_changeaddr,当一个设备的L2地址发生变化时,邻居协议调用该函数来更新协议的缓存,为了的得到这些事件的通知,每个协议可以向内核注册。
1: int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev)
2: {
3: write_lock_bh(&tbl->lock);
4: neigh_flush_dev(tbl, dev);
5: pneigh_ifdown(tbl, dev);
6: write_unlock_bh(&tbl->lock);
7:
8: del_timer_sync(&tbl->proxy_timer);
9: pneigh_queue_purge(&tbl->proxy_queue);
10: return 0;
11: }
-
设备关闭,每一个邻居项都与一个设备相关联。因此,如果该设备停止运行了,那么所有与之相关的邻居项都要被删除。更确切地讲,该事件不仅表示这个设备停止运行,而且还表示要清除该设备上的L3配置,以及要报告该L3地址到L2地址的映射是无效的。相反的情况,如果系统中添加一个设备,邻居子系统对其并不关心
-
L3地址改变,如果管理员改变了接口的配置,以前通过该接口可到达的主机有可能通过它已无法到达。因此,改变接口的L3地址会触发调用neigh_ifdown函数。
-
协议关闭,如果作为模块安装的L3协议从内核中卸载了,那么所有相关的邻居项将不再有用,必须要删除。
使用neigh_changeaddr更新
netdevice通知链会跟踪大量与网络运行相关的事件。邻居协议为了从netdevice通知链得到通知,在其初始化函数中会向内核注册。对邻居子系统来说,最重要的事件是NETDEV_CHANDEADDR,在使用命令改变一个设备的L2地址时,由do_setlink函数产生。
邻居协议和L3传输函数的交互