文章目录
邻居子系统负责将L3地址翻译成L2地址,设计上分为协议无关部分和协议相关部分,后续分析中,协议无关部分以IPv4协议族的ARP为例进行分析。
文件路径 | 描述 |
---|---|
include/net/neighbour.h | 协议无关部分的头文件 |
net/core/neighbour.c | 协议无关部分实现文件 |
include/linux/arp.h | arp协议头文件 |
net/ipv4/arp.c | arp协议实现文件 |
include/linux/neighbour.h | Netlink配置邻居子系统需要的数据结构 |
数据结构
邻居协议: neigh_table
每个使用邻居协议的协议族都需要向框架注册一个邻居协议对象。它保存了一个邻居协议所需的所有信息,主要包括邻居项缓存、邻居协议配置等信息。
struct neigh_table
{
struct neigh_table *next; // 将邻居协议组织到全局链表neigh_tables中
int family;
int entry_size; // 邻居项大小
int key_len; // key的长度
__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); // 处理延时请求的回调,见neigh_proxy_process()
char *id; // 邻居协议名字
struct neigh_parms parms; // 邻居协议参数,不同协议可自行指定
int gc_interval; // 该字段未被使用
// 垃圾回收门限值,邻居项个数超过gc_thresh3必须执行同步清理,
// 超过gc_thresh2有条件的执行同步清理,gc_thresh1未被使用
int gc_thresh1;
int gc_thresh2;
int gc_thresh3;
unsigned long last_flush; // 记录上一次执行同步清理的jiffies
struct timer_list gc_timer; // 异步清理定时器,函数为neigh_periodic_timer()
struct timer_list proxy_timer; // 延迟处理代理的定时器,函数为neigh_proxy_process()
struct sk_buff_head proxy_queue; // 存储代理请求报文的队列
atomic_t entries; // 当前邻居表中邻居项个数
rwlock_t lock; // 保护邻居表
unsigned long last_rand; // 记录上一次执行reachable_time随机的jiffies
struct kmem_cache *kmem_cachep; // 分配邻居项的高速缓存
struct neigh_statistics *stats;
struct neighbour **hash_buckets; // 维护邻居项的哈希表
unsigned int hash_mask; // 邻居项哈希表桶大小减1
__u32 hash_rnd; // 用于哈希表扩容
unsigned int hash_chain_gc; // 下一次异步清理定时器要清理的哈希桶索引
struct pneigh_entry **phash_buckets; // 存储基于目的地址的代理功能
};
邻居协议参数: neigh_parms
struct neigh_parms
{
#ifdef CONFIG_NET_NS
struct net *net;
#endif
struct net_device *dev;
struct neigh_parms *next;
int (*neigh_setup)(struct neighbour *); // 定制的邻居项初始化回调,见neigh_create()
void (*neigh_cleanup)(struct neighbour *);
struct neigh_table *tbl; // 回指邻居协议
void *sysctl_table;
int dead; // 删除标记
atomic_t refcnt;
struct rcu_head rcu_head;
int base_reachable_time; // reachable_time的基准值,见neigh_rand_reach_time()
int retrans_time; // INCOMPLETE和PROBE状态下Solicitation请求的重发间隔
int gc_staletime; // 无人使用的邻居项存在多久后会被异步垃圾回收机制回收
int reachable_time; // 邻居项可达后,可以持续多长时间后才需要再次进行确认
int delay_probe_time; // 当邻居项进入DELAY状态后,延时多长时间进入PROBE状态进行可达性判定
int queue_len; // 缓存队列所能容纳的数据包个数上限
int ucast_probes; // 为验证可达性,可以发送的单播Solicitation请求次数
int app_probes; // 用户态程序可发送的Solicitation请求次数
int mcast_probes; // 为解析邻居项,可以发送的多播/广播Solicitation请求次数
int anycast_delay;
int proxy_delay; // 延迟处理代理请求的最大延时值,实际会生成一个小于该配置的随机值
int proxy_qlen; // 延时代理请求队列最大限制,配置为0会关闭延时代理功能
int locktime; // 锁定期,确保短期内收到多个响应时,只更新一次邻居项状态
};
邻居项: neighbour
一个邻居项维护了一个L3协议地址到L2地址的映射。
struct neighbour
{
struct neighbour *next; // 将邻居项接入哈希表中
struct neigh_table *tbl; // 回指邻居协议对象
struct neigh_parms *parms; // 邻居协议参数
struct net_device *dev; // 网络设备对象
unsigned long used; // 邻居项上次被使用时间戳,用于衡量邻居项闲置时长
unsigned long confirmed; // 确认邻居项可达时的jiffies
unsigned long updated; // 邻居项状态更新jiffies
__u8 flags;
__u8 nud_state; // 邻居项状态
__u8 type; // L3协议地址类型
__u8 dead; // 标记邻居项是否正在被删除
atomic_t probes; // 该邻居项剩余可发送的Solicitation请求次数
rwlock_t lock;
unsigned char ha[ALIGN(MAX_ADDR_LEN, sizeof(unsigned long))]; // L2层地址
struct hh_cache *hh; // L2层帧头部缓存,每个邻居项可以有多个L2帧头部缓存
atomic_t refcnt; // 引用计数,见neigh_hold()和neigh_release()
// 随着邻居项的状态变化,指向ops中的不同输出接口,设计该字段是为了方便协议无关代码使用
int (*output)(struct sk_buff *skb);
struct sk_buff_head arp_queue; // 数据包缓存队列
struct timer_list timer; // 状态更新定时器
struct neigh_ops *ops; // 邻居项操作集
u8 primary_key[0]; // 具体邻居协议需要扩展该字段,指向自己的key,ARP协议就是IP地址
};
所谓的key,就是指查找邻居表时所用的键,比如ARP协议是以IP地址为key,自然的,其key长度就为4。
type
type表示了L3协议地址的类型,它的取值来自路由项类型定义,如RTN_MULTICAST等,常用的有RTN_MULTICAST(代表该邻居项代表的地址为多播地址);RTN_BROADCAST(代表邻居项代表的地址为广播地址)。
邻居项操作集: neigh_ops
每个邻居项在创建时都指定了一个邻居项操作集,它决定了邻居项对数据包的发送行为,neigh_ops一旦指定就不会改变。邻居协议往往会L3协议地址类型(单播、广播等)以及网络设备类型(如是否支持ARP、是否支持L2帧头部缓存等)确定使用什么样的neigh_ops。
struct neigh_ops
{
int family;
// Solicitation请求发送回调
void (*solicit)(struct neighbour *, struct sk_buff*);
// 当邻居项无法处理缓存的报文时(邻居可达性无法确定),通过该回调报告L3协议书包发送失败
void (*error_report)(struct neighbour *, struct sk_buff*);
// 邻居项可达性无法完全可信(非NUD_REACHABLE状态)时的数据包发送回调
int (*output)(struct sk_buff*);
// 邻居项可达性完全可信(NUD_REACHABLE状态)时的快速发送回调
int (*connected_output)(struct sk_buff*);
int (*hh_output)(struct sk_buff*);
int (*queue_xmit)(struct sk_buff*);
};
系统初始化
邻居子系统框架初始化入口为neigh_init()。
static int __init neigh_init(void)
{
// 注册配置邻居项的命令
rtnl_register(PF_UNSPEC, RTM_NEWNEIGH, neigh_add, NULL);
rtnl_register(PF_UNSPEC, RTM_DELNEIGH, neigh_delete, NULL);
rtnl_register(PF_UNSPEC, RTM_GETNEIGH, NULL, neigh_dump_info);
// 注册配置邻居协议的命令(主要是上面的neigh_params中的参数)
rtnl_register(PF_UNSPEC, RTM_GETNEIGHTBL, NULL, neightbl_dump_info);
rtnl_register(PF_UNSPEC, RTM_SETNEIGHTBL, neightbl_set, NULL);
return 0;
}
subsys_initcall(neigh_init);
邻居协议的管理
邻居子系统框架将所有特定协议族的邻居协议组织成一个链表来维护。
// 框架将所有的邻居协议对象组织成链表
static struct neigh_table *neigh_tables;
// 该读写锁只保护neigh_tables的增加和删除,并不保护其中某个邻居协议实例的内容
static DEFINE_RWLOCK(neigh_tbl_lock);
注册邻居协议: neigh_table_init()
neigh_table_init()从名字上看仅仅像是对邻居协议进行初始化,实际上它还完成了邻居协议的注册。对该接口的使用示例参考arp_init()。
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;
}
// 将新的邻居协议加入到全局链表neigh_tables的首部
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();
}
}
邻居协议协议无关初始化
void neigh_table_init_no_netlink(struct neigh_table *tbl)
{
unsigned long now = jiffies;
unsigned long phsize;
tbl->parms.net = &init_net;
atomic_set(&tbl->parms.refcnt, 1);
INIT_RCU_HEAD(&tbl->parms.rcu_head);
tbl->parms.reachable_time = neigh_rand_reach_time(tbl->parms.base_reachable_time);
// 创建高速缓存用于分配邻居项,每个邻居项的大小为entry_size,该值由各个邻居协议基于
// struct neighboru扩展而来,对于arp协议,大小为sizeof(struct neighbour) + 4
if (!tbl->kmem_cachep)
tbl->kmem_cachep = kmem_cache_create(tbl->id, tbl->entry_size, 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
// 邻居协议的统计信息,每个cpu一份
tbl->stats = alloc_percpu(struct neigh_statistics);
if (!tbl->stats)
panic("cannot create neighbour cache statistics");
#ifdef CONFIG_PROC_FS
// 创建/proc/net/stat/xxx_cache(对于IPv4是arp_cache,IPv6是ndisc_cache)文件,
// 通过读该文件可以dump对应邻居协议的统计信息
tbl->pde = proc_create(tbl->id, 0, init_net.proc_net_stat, &neigh_stat_seq_fops);
if (!tbl->pde)
panic("cannot create neighbour proc dir entry");
tbl->pde->data = tbl;
#endif
// 创建邻居项哈希表,初始大小为2,后面会根据需要进行扩容
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);
// 启动异步垃圾回收定时器
setup_timer(&tbl->gc_timer, neigh_periodic_timer, (unsigned long)tbl);
tbl->gc_timer.expires = now + 1;
add_timer(&tbl->gc_timer);
// 启动处理代理的定时器
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;
}
邻居项典型操作
邻居项的查找: neigh_lookup()
struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,
struct net_device *dev)
{
struct neighbour *n;
int key_len = tbl->key_len;
u32 hash_val;
// 更新统计信息
NEIGH_CACHE_STAT_INC(tbl, lookups);
read_lock_bh(&tbl->lock);
hash_val = tbl->hash(pkey, dev); // 调用邻居协议的hash算法计算哈希值
// 遍历冲突链,寻找匹配的路由项,匹配条件就是网络设备和key,该key对于ARP就是IP地址
for (n = tbl->hash_buckets[hash_val & tbl->hash_mask]; 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;
}
还有一个邻居项查找接口__neigh_lookup_errno(),它会在没有找到的情况下尝试创建一个邻居项。
static inline struct neighbour *__neigh_lookup_errno(struct neigh_table *tbl,
const void *pkey, struct net_device *dev)
{
struct neighbour *n = neigh_lookup(tbl, pkey, dev);
if (n)
return n;
return neigh_create(tbl, pkey, dev);
}
邻居项的创建: neigh_create()
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;
struct neighbour *n1, *rc, *n = neigh_alloc(tbl); // 从高速缓存中分配一个邻居项,并做初始化
if (!n) {
rc = ERR_PTR(-ENOBUFS);
goto out;
}
// 保存key和网络设备对象
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;
}
// 设置confirm的初始值为一个过去的时间
n->confirmed = jiffies - (n->parms->base_reachable_time << 1);
write_lock_bh(&tbl->lock);
// 尝试对哈希表进行扩容
if (atomic_read(&tbl->entries) > (tbl->hash_mask + 1))
neigh_hash_grow(tbl, (tbl->hash_mask + 1) << 1);
hash_val = tbl->hash(pkey, dev) & tbl->hash_mask;
if (n->parms->dead) {
rc = ERR_PTR(-EINVAL);
goto out_tbl_unlock;
}
// 遍历哈希表冲突链,将新的表项插入到哈希表中
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;
}
}
n->next = tbl->hash_buckets[hash_val];
tbl->hash_buckets[hash_val] = n;
n->dead = 0; // 真正插入哈希表后才会把dead标记设置为0
neigh_hold(n);
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;
}
EXPORT_SYMBOL(neigh_create);
邻居项的分配: neigh_alloc()
static struct neighbour *neigh_alloc(struct neigh_table *tbl)
{
struct neighbour *n = NULL;
unsigned long now = jiffies;
int entries;
// 根据邻居表中邻居项个数,判断是否需要进行垃圾回收
entries = atomic_inc_return(&tbl->entries) - 1;
if (entries >= tbl->gc_thresh3 ||
(entries >= tbl->gc_thresh2 && time_after(now, tbl->last_flush + 5 * HZ))) {
if (!neigh_forced_gc(tbl) && entries >= tbl->gc_thresh3)
goto out_entries;
}
// 从高速缓存中分配一个邻居项
n = kmem_cache_zalloc(tbl->kmem_cachep, GFP_ATOMIC);
if (!n)
goto out_entries;
skb_queue_head_init(&n->arp_queue);
rwlock_init(&n->lock);
n->updated = n->used = now;
n->nud_state = NUD_NONE;
n->output = neigh_blackhole;
n->parms = neigh_parms_clone(&tbl->parms);
// 初始化邻居项状态更新定时器
setup_timer(&n->timer, neigh_timer_handler, (unsigned long)n);
NEIGH_CACHE_STAT_INC(tbl, allocs);
n->tbl = tbl;
atomic_set(&n->refcnt, 1);
// 初始dead标记为1,这是因为创建时并未持锁,期间其它CPU有可能已经分配了该邻居项,
// 只有在真正的将该路由项放入邻居表后,才会将dead标记设置为0
n->dead = 1;
out:
return n;
out_entries:
atomic_dec(&tbl->entries);
goto out;
}
邻居项的更新: neigh_update()
该函数能够将邻居项的状态更新成参数指定的状态,它得实现和邻居项的状态更新密切相关,其实现见邻居子系统之邻居项状态更新。