在linux代码中,对于不同的邻居项,抽象出了一个通用的模型,通用邻居层,主要是用来进行邻居项的创建、添加、删除、查找、更新等操作。
对于通用邻居层,最主要的就是邻居项的状态机的设计,本部分先介绍相应的数据结构,在分析通用邻居处理函数时,会仔细分析邻居状态机。本部分会简要介绍状态机。
首先是邻居状态的定义,在通用邻居项中定义了以下邻居状态:
#defineNUD_INCOMPLETE 0x01
#defineNUD_REACHABLE 0x02
#defineNUD_STALE 0x04
#defineNUD_DELAY 0x08
#defineNUD_PROBE 0x10
#defineNUD_FAILED 0x20
/* Dummy states*/
#defineNUD_NOARP 0x40
#defineNUD_PERMANENT 0x80
#defineNUD_NONE 0x00
其中NUD_NOARP、NUD_PERMANENT、NUD_NONE表示该邻居项不需要邻居地址的解析。
下面我们介绍NUD_IN_TIMER、NUD_VALID、NUD_CONNECTED
#define NUD_IN_TIMER (NUD_INCOMPLETE|NUD_REACHABLE|NUD_DELAY|NUD_PROBE)
#define NUD_VALID (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE|NUD_PROBE|NUD_STALE|NUD_DELAY)
#define NUD_CONNECTED (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)
对于NUD_IN_TIMER,通过名称我们就知道,当邻居项处于该状态时,则会启动定时器。下面我们一一分析这几个邻居项状态,通过分析完这几个状态,我们就基本上会理解邻居项状态机中定时器处理函数neigh_timer_handler的设计逻辑了。
1、对于NUD_INCOMPLETE,当本机发送完arp 请求包后,还未收到应答时,即会进入该状态。进入该状态,即会启动定时器,如果在定时器到期后,还没有收到应答时:如果没有到达最大发包上限时,即会重新进行发送请求报文;如果超过最大发包上限还没有收到应答,则会将状态设置为failed
2、对于收到可到达性确认后,即会进入NUD_REACHABLE,当进入NUD_REACHABLE状态。当进入NUD_REACHABLE后,即会启动一个定时器,当定时器到时前,该邻居协议没有
被使用过,就会将邻居项的状态转换为NUD_STALE
3、对于进入NUD_STALE状态的邻居项,即会启动一个定时器。如果在定时器到时前,有数据需要发送,则直接将数据包发送出去,并将状态设置为NUD_DELAY;如果在定时器到时,没有数据需要发送,且该邻居项的引用计数为1,则会通过垃圾回收机制,释放该邻居项对应的缓存
4、处于NUD_DELAY状态的邻居项,如果在定时器到时后,没有收到可到达性确认,则会进入NUD_PROBE状态;如果在定时器到达之前,收到可到达性确认,则会进入NUD_REACHABLE (在该状态下的邻居项不会发送solicit请求,而只是等待可到达性应答。主要包括对以前的solicit请求的应答或者收到一个对于本设备以前发送的一个数据包的应答)
5、处于NUD_PROBE状态的邻居项,会发送arp solicit请求,并启动一个定时器。如果在定时器到时前,收到可到达性确认,则进入NUD_REACHABLE;如果在定时器到时后,没有收到可到达性确认:
a)没有超过最大发包次数时,则继续发送solicit请求,并启动定时器
b)如果超过最大发包次数,则将邻居项状态设置为failed
在上面5个状态中,在NUD_REACHABLE、NUD_PROBE、NUD_STALE、NUD_DELAY状态时,数据包是可以正常发送的,只是发送的函数不同。这样就不难理解NUD_VALID包含NUD_PERMANENT、NUD_NOARP、NUD_REACHABLE、NUD_PROBE、NUD_STALE、NUD_DELAY了
对于#defineNUD_CONNECTED (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)
主要是表示邻居是可达的状态,对于NUD_PERMANENT、NUD_NOARP状态的邻居项,其邻居状态是不会改变的,一直是有效的,除非删除该邻居项。对于NUD_REACHABLE我们在上面已经介绍过了。
下面介绍neigh_parms、neighbour、neigh_ops、neigh_table
struct neigh_parms {
#ifdef CONFIG_NET_NS
structnet *net;
#endif
structnet_device *dev;
structneigh_parms *next;
int (*neigh_setup)(struct neighbour *);
void (*neigh_cleanup)(struct neighbour *);
structneigh_table *tbl;
void *sysctl_table;
intdead;
atomic_trefcnt;
structrcu_head rcu_head;
int base_reachable_time;//基本有效时间 ,对于arp默认为30s
int retrans_time;//solicit请求报文重发间隔时间
int gc_staletime;//闲置时间
int reachable_time;//确认有效时间超时长度,这个值每隔300s会更新一次
int delay_probe_time;//在nud_delay时,为delay的超时时间;在nud_reach状态时,用于判断是否需要进入delay状态的一个时间判断点
int queue_len;//缓存数据包的队列长度
int ucast_probes;//发送单播solicit请求的最大次数
int app_probes;// ?
int mcast_probes;//发送广播solicit请求的最大次数
int anycast_delay;
int proxy_delay;
int proxy_qlen;
int locktime;
};
struct neighbour {
structneighbour *next;
structneigh_table *tbl;//指向该邻居项所属的邻居表
structneigh_parms *parms;
structnet_device *dev;
unsignedlong used;//邻居项使用时间
unsignedlong confirmed;//connected状态确认时间
unsignedlong updated;//邻居项更新时间
__u8 flags;
__u8 nud_state;//邻居项状态值
__u8 type;//邻居项地址的类型
__u8 dead;
atomic_t probes;//记录邻居项发送的solicit请求的次数
rwlock_t lock;
unsignedchar ha[ALIGN(MAX_ADDR_LEN,sizeof(unsigned long))];//
structhh_cache *hh;//二层缓存头部指针,指向一个二层缓存头部
atomic_t refcnt;
int (*output)(struct sk_buff *skb);
structsk_buff_head arp_queue;//数据包缓存队列,当有数据包要发送,但又没有目的ip地址对应的目的mac时,则会将数据包缓存在该队列中
structtimer_list timer;
conststruct neigh_ops *ops; //neighbour项的函数指针表,包含发送solicit请求函数,以及不同状态下对应的输出函数
u8 primary_key[0];
};
struct neigh_ops {
int family;//所属的地址簇,对于arp,则为AF_INET
void (*solicit)(struct neighbour *,struct sk_buff*);//发送邻居请求的函数指针
void (*error_report)(struct neighbour*, struct sk_buff*);//当有数据要传送,且邻居项不可达时,则调用该函数向三层发送错误信息
int (*output)(struct sk_buff*);//通用输出函数
int (*connected_output)(structsk_buff*);//当邻居项可达时,使用该函数发送数据包
int (*hh_output)(structsk_buff*);//在缓存了二层头部时,调用该函数发送数据包
int (*queue_xmit)(structsk_buff*);//真正的数据传输函数,
};
struct neigh_table {
structneigh_table *next; //指向下一个邻居协议对应的邻居表
int family;//该邻居协议对应的地址簇
int entry_size;//该邻接表所能包含的邻居项的最大值
int key_len;//关键字的大小,对于arp协议,是4个字节大小
__u32 (*hash)(const void *pkey,const struct net_device *);//hash函数
int (*constructor)(struct neighbour*);//该邻居协议所对应的邻居项的邻居初始化 函数,初始化与该邻居协议相关的成员值
int (*pconstructor)(structpneigh_entry *);
void (*pdestructor)(structpneigh_entry *);
void (*proxy_redo)(struct sk_buff*skb);
char *id;
structneigh_parms parms;
/*HACK. gc_* shoul follow parms without a gap! */
int gc_interval;//垃圾回收处理邻居项的时间间隔
int gc_thresh1;//当邻居项的数量少于该值时,不会进行垃圾回收
int gc_thresh2;//如果邻居项的数目超过这个值,则在新建邻居项时,若超过5秒未进行刷新,则刷新并强制垃圾回收
int gc_thresh3;//当超过这个值时,则在创建新邻居项时,强制进行垃圾回收
unsignedlong last_flush;//记录最新一次刷新邻居表项的时间
structdelayed_work gc_work;
structtimer_list proxy_timer;
structsk_buff_head proxy_queue;
atomic_t entries;//已创建邻居表项的数据
rwlock_t lock;
unsignedlong last_rand;//记录neigh_parms中reach_time成员的最近更新时间
structkmem_cache *kmem_cachep;//用来分配邻居项的slab缓存
structneigh_statistics __percpu *stats;
structneighbour **hash_buckets;//存储邻居项的hash bucket
unsignedint hash_mask;
__u32 hash_rnd;//hash 散列表扩容时的关键字
structpneigh_entry **phash_buckets;
};
至此,将通用邻居项相关的主要数据结构即介绍完了。