123
分类: LINUX
2015-01-18 12:44:58
原文地址:三、Linux上桥的实现 作者:guanglongxishui
Linux 内核中桥要实现的主要功能分析:
1、可以创建/删除一个桥,并且可以让用户往桥里添加/删除二层口
2、可以进行MAC地址学习
3、维护FDB表项及表项的老化
4、可以根据MAC地址进行二层转发
5、每个桥需要提供一个三层口,用于虚拟桥设备和三层口的通信。
1、每个桥里有一个bridge port 链表,该链表连接了所有加入该桥的二层端口。
每个port结构体使用rcu来释放。遍历该链表时使用rcu 读锁来保护。插入链表新节点时使用bridge->lock自旋锁来保护。
2、每个桥里有一个FDB的hash表,该表里的每个FDB表项使用rcu来释放。读该hash表时使用rcu读锁来保护。插入新的表项时使用bridge->hash_lock自旋锁来保护。
首先我们来看看桥的创建过程。Bridge 模块个用户空间是使用ioctl进行通信的。Bridge相关的ioctl处理函数为br_ioctl_deviceless_stub调用br_add_bridge来进行bridge的创建,这里我们分析一下主要的bridge创建过程
创建桥过程,首先会创建一个网桥设备struct net_device 实例。该net_device 的priv数据区里存放桥的配置信息(struct net_bridge)。
调用br_dev_setup()来设置网桥设备。这里我们重点介绍一下网桥设备的发送函数br_dev_xmit()。通过网桥的三层口往外发送报文时,调用该函数
netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct net_bridge *br = netdev_priv(dev);
const unsigned char *dest = skb->data;
struct net_bridge_fdb_entry *dst;
dev->stats.tx_packets++;
dev->stats.tx_bytes += skb->len;
skb_reset_mac_header(skb);
skb_pull(skb, ETH_HLEN);
/*如果是组播或广播报文,则在桥里面泛洪发送该报文*/
if (dest[0] & 1)
br_flood_deliver(br, skb);
/*如果是单播报文,在桥的FDB表中查找,如果找到对应表项,
怎根据表项发送报文*/
else if ((dst = __br_fdb_get(br, dest)) != NULL)
br_deliver(dst->dst, skb);
/*如果是单播,并且没有找到相应的FDB,就进行泛洪发送*/
else
br_flood_deliver(br, skb);
return NETDEV_TX_OK;
}
int br_add_if(struct net_bridge *br, struct net_device *dev)
{
struct net_bridge_port *p;
int err = 0;
/* Device is already being bridged */
/*如果该二层端口已经加入了bridge,则不能在加入bridge了,
一个端口只能加入一个bridge*/
if (dev->br_port != NULL)
return -EBUSY;
/*创建一个新的net_bridge_port 并初始化*/
p = new_nbp(br, dev);
if (IS_ERR(p))
return PTR_ERR(p);
/*把端口设置成混杂模式*/
err = dev_set_promiscuity(dev, 1);
if (err)
goto put_back;
/*把该端口的MAC地址加入到FDB表中,并设置为local FDB*/
err = br_fdb_insert(br, p, dev->dev_addr);
if (err)
goto err1;
/*关联dev和bridge_port*/
rcu_assign_pointer(dev->br_port, p);
/*把bridge_port链到bridge 的port链表上*/
list_add_rcu(&p->list, &br->port_list);
。。。。。。
return err;
}
如图,这里就不对每个处理函数就行讲解了。
上送网络层进行处理
static void br_pass_frame_up(struct net_bridge *br, struct sk_buff *skb)
{
struct net_device *indev, *brdev = br->dev;
skb->dev = brdev; //把报文的接收dev修改为bridge dev
NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL,
netif_receive_skb);
}
泛洪发送
static void br_flood(struct net_bridge *br, struct sk_buff *skb,
void (*__packet_hook)(const struct net_bridge_port *p,
struct sk_buff *skb))
{
struct net_bridge_port *p;
struct net_bridge_port *prev;
prev = NULL;
/*遍历bridge下加入的二层端口,从每个二层端口发送一份出去*/
list_for_each_entry_rcu(p, &br->port_list, list)
{
if (should_deliver(p, skb))
{
if (prev != NULL)
{
struct sk_buff *skb2;
/*把报文拷贝一份后从二层端口发送出去*/
if ((skb2 = skb_clone(skb, GFP_ATOMIC)) == NULL)
{
br->dev->stats.tx_dropped++;
kfree_skb(skb);
return;
}
__packet_hook(prev, skb2);
}
prev = p;
}
}
if (prev != NULL) {
__packet_hook(prev, skb);
return;
}
kfree_skb(skb);
}
bridge每收到一个报文,就会根据报文的源MAC来更新FDB表项。
int br_handle_frame_finish(struct sk_buff *skb)
{
const unsigned char *dest = eth_hdr(skb)->h_dest;
struct net_bridge_port *p = rcu_dereference(skb->dev->br_port);
struct net_bridge *br;
struct net_bridge_fdb_entry *dst;
struct sk_buff *skb2;
br = p->br;
br_fdb_update(br, p, eth_hdr(skb)->h_source);
}
void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
const unsigned char *addr)
{
struct hlist_head *head = &br->hash[br_mac_hash(addr)];
struct net_bridge_fdb_entry *fdb;
fdb = fdb_find(head, addr);
/*如果FDB表项已经存在,则更新*/
if (likely(fdb))
{
/* attempt to update an entry for a local interface */
/*如果是本机FDB,表示接收错误*/
if (unlikely(fdb->is_local))
{
if (net_ratelimit())
printk(KERN_WARNING "%s: received packet with "
"own address as source address\n",
source->dev->name);
}
else
{
/*更新FDB表项,同时更新FDB的老化时间戳*/
fdb->dst = source;
fdb->ageing_timer = jiffies;
}
}
/*如果FDB表项不存在,就新建一个FDB*/
else
{
spin_lock(&br->hash_lock);
if (!fdb_find(head, addr))
fdb_create(head, source, addr, 0);
spin_unlock(&br->hash_lock);
}
}
每次FDB表项被命中更新时,会更新FDB表项的老化时间戳。Bridge起了一个定时器,来定期的清理长时间不用的FDB表项。
初始化定时器
void br_stp_timer_init(struct net_bridge *br)
{
。。。。。。
setup_timer(&br->gc_timer, br_fdb_cleanup, (unsigned long) br);
}
void br_fdb_cleanup(unsigned long _data)
{
struct net_bridge *br = (struct net_bridge *)_data;
unsigned long delay = hold_time(br);
unsigned long next_timer = jiffies + br->forward_delay;
int i;
/*加锁进行保护*/
spin_lock_bh(&br->hash_lock);
/*遍历FDB hash表中的每一项,判断FDB表项是否过期,如果过期,就删除*/
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 long this_timer;
/*跳过静态的FDB表项*/
if (f->is_static)
continue;
this_timer = f->ageing_timer + delay;
/*如果FDB表项过期,就删除该FDB表项*/
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);
/*遍历完后重新设置老化定时器*/
mod_timer(&br->gc_timer, round_jiffies(next_timer + HZ/4));
}