四、对网桥转发数据库的操作
对于网桥转发数据库,我们一般需要对数据库的初始化、数据库的插入、数据库的查找、数据库的更新、数据库的删除等操作,下面我们就以这几个方向分析fdb相关的代码
1、br_fdb_init
该函数调用kmem_cache_create 来创建一个新缓存(这个函数并没有向缓存分配
任何内存)
int __init br_fdb_init(void)
{
br_fdb_cache =kmem_cache_create("bridge_fdb_cache",
sizeof(struct net_bridge_fdb_entry),
0,
SLAB_HWCACHE_ALIGN, NULL);
if (!br_fdb_cache)
return -ENOMEM;
get_random_bytes(&fdb_salt,sizeof(fdb_salt));
return 0;
}
2、br_fdb_insert
该函数只是fdb_insert的封装,增加了自旋锁机制
int br_fdb_insert(struct net_bridge *br, struct net_bridge_port*source,
const unsigned char *addr)
{
int ret;
spin_lock_bh(&br->hash_lock);
ret = fdb_insert(br,source, addr);
spin_unlock_bh(&br->hash_lock);
return ret;
}
下面我们分析fdb_insert
该函数用于向fdb数据库中增加一个fdb entry
1、首先判断mac地址是否有效
2、从fdb表中查找是否已经存在该mac地址相关的表项,
a) 若存在,则判断该fdbentry是否是本地的
i)若是本地的,则直接返回
ii)若不是本地的,则调用fdb_delete删除该fdb entry,然后再重新
创建fdb entry
b)若不存在,则调用fdb_create,创建fdb entry表项,并插入到相应
的hash表中
static int fdb_insert(struct net_bridge *br, structnet_bridge_port *source,
const unsigned char *addr)
{
struct hlist_head*head = &br->hash[br_mac_hash(addr)];
structnet_bridge_fdb_entry *fdb;
if(!is_valid_ether_addr(addr))
return -EINVAL;
fdb = fdb_find(head,addr);
if (fdb) {
/* it is okay tohave multiple ports with same
* address, just use the first one.
*/
if(fdb->is_local)
return 0;
printk(KERN_WARNING"%s adding interface with same address "
"as a received packet\n",
source->dev->name);
fdb_delete(fdb);
}
if (!fdb_create(head,source, addr, 1))
return -ENOMEM;
return 0;
}
该函数本身实现的功能也就是对要加入的mac地址进行有效性判断以及fdb数据库中是否已存在该mac地址相关的fdb entry。当这些检查完成后就会调用fdb_create,实现fdb entry的创建及添加。
下面我们继续分析fdb_create
该函数的逻辑也比较简单
首先调用kmem_cache_alloc为该fdb entry申请缓存
然后将该fdb添加到hash表头 head中
最后设置fdb entry的dst port等其他成员并返回fdb entry
*/
static struct net_bridge_fdb_entry *fdb_create(structhlist_head *head,
struct net_bridge_port *source,
const unsigned char *addr,
int is_local)
{
structnet_bridge_fdb_entry *fdb;
fdb =kmem_cache_alloc(br_fdb_cache, GFP_ATOMIC);
if (fdb) {
memcpy(fdb->addr.addr,addr, ETH_ALEN);
hlist_add_head_rcu(&fdb->hlist,head);
fdb->dst =source;
fdb->is_local =is_local;
fdb->is_static= is_local;
fdb->ageing_timer= jiffies;
}
return fdb;
}
3、fdb entry的查找
该函数的作用是从转发数据库中查找fdb entry
该函数与__br_fdb_get的作用类似,唯一不同的是,
该函数没有判断查找到的fdb entry是否过期
*/
static inline struct net_bridge_fdb_entry *fdb_find(structhlist_head *head,
const unsigned char *addr)
{
struct hlist_node *h;
structnet_bridge_fdb_entry *fdb;
hlist_for_each_entry_rcu(fdb,h, head, hlist) {
if(!compare_ether_addr(fdb->addr.addr, addr))
return fdb;
}
return NULL;
}
该函数只是用来判断fdb数据库中是否存在与指定mac地址相关联的fdb entry,其主要是被fdb_insert、br_fdb_update调用,用来确定是否需要创建新的fdb entry。
那如果当网桥接收到数据后,需要知道是否对数据包进行扩散或者转发到指定网桥端口,其是调用哪个函数获取fdb entry呢?
其是通过函数__br_fdb_get来实现的。下面是该函数的实现代码,我们发现与fdb_find相比,其会判断fdb entry是否过期,对于数据转发来说,首先就需要判断 fdb entry的有效性。
下面是该函数的分析:
1、首先根据br_mc_hash,生成hash值,从而找到相应的hash表
2、调用hlist_for_each_entry_rcu,遍历hash表中的每一个fdb entry,并
进行如下判断:
a)判断mac地址是否相同:
i)若mac地址相同,则判断该fdb entry是否过期,若过期则返回NULL;
否则则返回查找到的fdb entry.
ii)若不相同则继续遍历
struct net_bridge_fdb_entry *__br_fdb_get(struct net_bridge*br,
const unsigned char *addr)
{
struct hlist_node *h;
structnet_bridge_fdb_entry *fdb;
hlist_for_each_entry_rcu(fdb,h, &br->hash[br_mac_hash(addr)], hlist) {
if(!compare_ether_addr(fdb->addr.addr, addr)) {
if(unlikely(has_expired(br, fdb)))
break;
return fdb;
}
}
return NULL;
}
fdb entry更新
该功能主要是有函数br_fdb_update来实现的,而该函数主要是由入口数据包处理函数br_handle_frame处理入口流量时,对于源mac地址进行学习时,就会调用fdb_update更新fdb数据库,下面对该函数进行分析,该函数执行的功能大致如下:
1、如果br网桥想要一直处于flood状态,则不对mac地址进行学习
2、如果网桥的状态不是处于learning或者forward状态则直接返回
3、调用fdb_find,查找该mac地址是否已经在fdb表中
若在fdb表中,如果该fdb entry为local则不更新;
若不在fdb表中,则调用fdb_create,创建一个非本地的表项
void br_fdb_update(struct net_bridge *br, structnet_bridge_port *source,
const unsigned char *addr)
{
struct hlist_head*head = &br->hash[br_mac_hash(addr)];
structnet_bridge_fdb_entry *fdb;
/* some users want toalways flood. */
if (hold_time(br) ==0)
return;
/* ignore packetsunless we are using this port */
if (!(source->state== BR_STATE_LEARNING ||
source->state == BR_STATE_FORWARDING))
return;
fdb = fdb_find(head,addr);
if (likely(fdb)) {
/* attempt toupdate an entry for a local interface */
if(unlikely(fdb->is_local)) {
if(net_ratelimit())
printk(KERN_WARNING"%s: received packet with "
"own address as sourceaddress\n",
source->dev->name);
} else {
/* fastpath: update of existing entry */
fdb->dst =source;
fdb->ageing_timer= jiffies;
}
} else {
spin_lock(&br->hash_lock);
if(!fdb_find(head, addr))
fdb_create(head,source, addr, 0);
/* else we lose race and someone else inserts
* itfirst, don't bother updating
*/
spin_unlock(&br->hash_lock);
}
}
疑问:同一个网桥端口可以与多个fdb entry关联吗?
通过对fdb_insert、fdb_find这些函数的理解我们知道,调用调用
fdb_find,查找fdb数据库中是否存在某个表项时,是根据mac地址
的,而并不是根据网桥端口。这从侧面说明在一个转发数据库
中,网桥端口并不仅仅与一个fdbentry关联。又通过分析
br_fdb_delete_by_port,我们发现,在以网桥端口为依据删除与网桥端口相关联的fdb entry时,在发现一个符合条件的fdb entry后,函数并不是释放该fdb entry后就直接返回了,而是继续循环并查找是否有继续符合条件的fdbentry。这就强有力的证明了一个网桥端口可以与多个fdb entry关联。
对于函数br_fdb_update来说,提供的有效信息并不是简单的更新fdb entry那么简单。
首先对于fdb entry建立的分类,我们分析一下:
1、local标签的fdb entry添加
我们知道,在添加网桥端口时,我们会调用br_fdb_insert创建一个local的fdb entry。如果
一个网桥下有4个网桥端口,而4个网桥端口的mac地址是相同的,则在执行brctl addif
时,只会将第一个网桥端口与该本地mac地址对应的fdb entry相关联。
2、非local标签的fdb entry添加
这类fdb entry是通过网桥的mac地址学习得到的。是由处理入口流量的br_handle_frame
间接调用生成的。
然后,我们再回到br_fdb_update,在该函数里,若发现要update的mac地址所对应的
fdb entry已经存在,函数还会判断这个fdb entry是否是local的。若是local的,说明什么了
呢?说明br_handle_frame处理的入口数据包的mac地址是属于网桥端口的,这就说明了
该网桥下的桥接端口出现了环路。这就是该函数的另一大功能,通过该函数
我们能判断网桥下的端口是否环路了。
还有一个函数br_fdb_changeaddr用于更新网桥端口所对应的fdb entry的mac地址,并且其只更新local类型的fdbentry。主要是用来处理通知链的接口函数。
当一个桥端口的mac地址改变以后,会调用该函数更新fdb表
1、调用hlist_for_each遍历br中hash数组中的每一个hash表,查找符合条件的fdb_entry
2、若找到符合条件的fdb_entry,则需要遍历bridge_port_list,判断是否有别的端口的mac地址
与该表项中的mac地址相等?若相等,则更新该表项的dst值为该桥接端口,并调用
fdb_insert,为已更改mac地址的桥端口,重新加入一个fdb表项;若没有符合条件的桥端口,则将找到的fdb_entry从hash表中删除并调用fdb_insert将更改后的值重新添加到hash表中。
void br_fdb_changeaddr(struct net_bridge_port *p, constunsigned char *newaddr)
{
struct net_bridge *br= p->br;
int i;
spin_lock_bh(&br->hash_lock);
/* Search all chainssince old address/hash is unknown */
for (i = 0; i <BR_HASH_SIZE; i++) {
struct hlist_node*h;
hlist_for_each(h,&br->hash[i]) {
structnet_bridge_fdb_entry *f;
f =hlist_entry(h, struct net_bridge_fdb_entry, hlist);
if (f->dst== p && f->is_local) {
/* maybeanother port has same hw addr? */
structnet_bridge_port *op;
list_for_each_entry(op,&br->port_list, list) {
if (op!= p &&
!compare_ether_addr(op->dev->dev_addr,
f->addr.addr)){
f->dst= op;
gotoinsert;
}
}
/* deleteold one */
fdb_delete(f);
gotoinsert;
}
}
}
insert:
/* insert newaddress, may fail if invalid address ordup. */
fdb_insert(br, p,newaddr);
spin_unlock_bh(&br->hash_lock);
}
fdb entry删除
该功能主要是由函数br_fdb_cleanup来实现,该函数是br->gc_timer的定时器处理函数。
br_fdb_cleanup用于桥周期性检查是否有转发数据表项过期的处理函数在网桥初始化时,当调用br_stp_timer_init进行定时器初始化化时,就会建立br->gc_timer的定时器处理函数,定时调用br_fdb_cleanup,清理CAM表缓存
下面分析该函数的执行流程:
1、调用spin_lock_bh进行上锁操作
2、循环br网桥的所有hash表(hash表的大小为BR_HASH_SIZE 目前大小为256)
3、对于每一个hash表,调用hlist_for_each_entry_safe遍历该hash表里的所有hash
表项。(因为遍历的同时会删除指针,所以此处会调用函数hlist_for_each_entry_safe
关于hash list的详细信息可参看前面的文档)
调用time_before_eq判断该fdb是否过期,若过期,则调用fdb_delete进行删除操作。
4、更新桥检查定时器
void br_fdb_cleanup(unsigned long _data)
{
struct net_bridge *br= (struct net_bridge *)_data;
unsigned long delay =hold_time(br);/*获取mac地址的可保留时间*/
unsigned longnext_timer = jiffies + br->forward_delay;
int i;
spin_lock_bh(&br->hash_lock);
for (i = 0; i <BR_HASH_SIZE; i++) {
struct net_bridge_fdb_entry*f;
struct hlist_node*h, *n;
hlist_for_each_entry_safe(f,h, n, &br->hash[i], hlist) {
unsigned longthis_timer;
if(f->is_static)
continue;
this_timer =f->ageing_timer + delay;
if(time_before_eq(this_timer, jiffies))
fdb_delete(f);
else if(time_before(this_timer, next_timer))
next_timer= this_timer;
}
}
spin_unlock_bh(&br->hash_lock);
/* Add HZ/4 to ensurewe round the jiffies upwards to be after the next
* timer, otherwise we might round down andwill have no-op run. */
mod_timer(&br->gc_timer,round_jiffies(next_timer + HZ/4));
}
删除fdb entry的主要工作是由函数fdb_delete实现的,下面分析下函数fdb_delete的实现流程。
1、将该net_bridge_fdb_entry所对应的
2、通过调用fdb_rcu_free,释放该数据表项所占用的缓存
static inline void fdb_delete(struct net_bridge_fdb_entry *f)
{
hlist_del_rcu(&f->hlist);
call_rcu(&f->rcu,fdb_rcu_free);
}
而fdb_rcu_free则会实现slab缓存的释放
/*
1、跟据rcu_head,调用container_of,获取net_bridge_fdb_entry的地址
2、调用kmem_cache_free,将一个缓存释放给br_fdb_cache缓存中
*/
static void fdb_rcu_free(struct rcu_head *head)
{
structnet_bridge_fdb_entry *ent
=container_of(head, struct net_bridge_fdb_entry, rcu);
kmem_cache_free(br_fdb_cache,ent);
}
还有一个函数br_fdb_flush,其会释放转发数据库中的所有表项
下面分析下这个函数,这个函数的逻辑很简单,主要执行以下操作:
1、遍历每一个hash表中的所有转发数据表项
对于每一个转发数据表项,若该转发数据表项不是静态的,则
调用fdb_delete,释放该表项所占用的缓存
void br_fdb_flush(struct net_bridge *br)
{
int i;
spin_lock_bh(&br->hash_lock);
for (i = 0; i <BR_HASH_SIZE; i++) {
structnet_bridge_fdb_entry *f;
struct hlist_node*h, *n;
hlist_for_each_entry_safe(f,h, n, &br->hash[i], hlist) {
if(!f->is_static)
fdb_delete(f);
}
}
spin_unlock_bh(&br->hash_lock);
}