Bridge实现
网桥原理: 传统的中继器,如HUB,是一个单纯的物理层设备,它将每一个收到的数据包,在其所有的端口上广播,由接收主机来判断这个数据包是否是给自己的。
这样,网络资源被极大的浪费掉了。
网桥之所以不同于中继器,主要在于其除了有中继的作用外,还有一个更重要的作用,就是学习MAC地址,然后根据每个数据包的目的MAC与自身端口的对应,
从关联端口发送数据,而不完全地在整个网段中进行广播。所以,网桥的实现中,有两个关键点:
、 学习MAC地址,起初,网桥是没有任何地址与端口的对应关系的,它发送数据,还是得想HUB一样,但是每发送一个数据,
它都会关心数据包的来源MAC是从自己的哪个端口来的,由于学习,建立地址-端口的对照表(CAM表)。
、 每发送一个数据包,网桥都会提取其目的MAC地址,从自己的地址-端口对照表(CAM表)中查找由哪个端口把数据包发送出去。 本文介绍2..18中bridge的实现。 数据结构介绍: 新创建的br的主结构.
struct net_bridge
{
......
struct list_head port_list; //桥组中的端口列表,所有struct net_bridge_port -> list 会添加到这个list
struct net_device * dev; //网络设备的dev, 自己的.
struct net_device_stats statistics; //网桥虚拟网卡的统计数据
spinlock_t hash_lock; //hash表的读写锁,这个表就是用于存放桥的MAC-PORT对应表
struct hlist_head hash[BR_HASH_SIZE]; //就是这张表了,也叫CAM表. struct net_bridge_fdb_entry -> hlist 会添加到这个hash table
......
//以下定义了STP协议所使用的信息
bridge_id designated_root;
......
};
可以看出,桥中有几个重要的地方:
、桥的端口成员:struct net_bridge_port *port_list;
、桥的CAM表:struct net_bridge_fdb_entry *hash[BR_HASH_SIZE];
、桥的虚拟网卡
、STP 网桥中的端口,用struct net_bridge_port结构表示,它实际上表示的是接收该数据包的网桥的端口的相关信息
在struct net_device 中成员br_port为此结构,像交换机的端口.
struct net_bridge_port
{
......
struct net_bridge * br; //当前端口(接收数据包这个)所在的桥组. 指向被添加到的bridg
struct net_device * dev; //本端口所指向的物理网卡
struct list_head list; //连接到 struct net_bridge -> port_list
......
u16 port_no; //本端口在网桥中的编号,唯一 id
port_id designated_port; //指定端口的端口ID
bridge_id designated_root; //根网桥的网桥ID
bridge_id designated_bridge; //发送当前BPDU包的网桥的ID
u32 path_cost; //此端口的路径花销
u32 designated_cost; //到根桥的链路花销
......
}; 记录端口中mac地址的数据结构
struct net_bridge_fdb_entry
{
struct hlist_node hlist; //会被连接到struct net_bridge->hash 中
struct net_bridge_port * dst; //指向相应的端口
struct rcu_head rcu; //rcu回掉处理
atomic_t use_count; //引用计数器
unsigned long ageing_timer; //处理MAC超时
mac_addr addr; //记录mac地址结构
unsigned char is_local; //是否是本机的MAC地址
unsigned char is_static; //是否是静态MAC地址
}; br_init() 在文件 br.c中,bridge的初始化函数,br_netfilter_init()主要是注册bridge的hook函数, 在文件br_netfilter.c中。
static int __init br_init(void)
{
int err;
//参看下面STP:生成树协议
br_stp_sap = llc_sap_open(LLC_SAP_BSPAN, br_stp_rcv);
if (!br_stp_sap) {
printk(KERN_ERR "bridge: can't register sap for STP\n");
return -EADDRINUSE;
} br_fdb_init(); //创建struct net_bridge_fdb_entry高速缓存 err = br_netfilter_init();
if (err)
goto err_out1;
err = register_netdevice_notifier(&br_device_notifier);
if (err)
goto err_out2; //初始化netlink,与用户程序通讯使用 br_netlink_init();
brioctl_set(br_ioctl_deviceless_stub); //注册一个函数,在添加新的br时使用 //netif_receive_skb (net/core/dev.c) 函数 会调用 br_handle_frame , 接受 sk_buff 调用相关的bridge HOOK
br_handle_frame_hook = br_handle_frame;
br_fdb_get_hook = br_fdb_get;
br_fdb_put_hook = br_fdb_put; return ;
......
}
void __init br_fdb_init(void)
{
br_fdb_cache = kmem_cache_create("bridge_fdb_cache", sizeof(struct net_bridge_fdb_entry), ,
SLAB_HWCACHE_ALIGN, NULL, NULL);
}
int br_netfilter_init(void)
{
int i;
for (i = ; i < ARRAY_SIZE(br_nf_ops); i++) {
int ret;
//注册hook操作函数
if ((ret = nf_register_hook(&br_nf_ops[i])) >= )
continue; while (i--)
nf_unregister_hook(&br_nf_ops[i]); return ret;
}
#ifdef CONFIG_SYSCTL
//注册proc配置接口
brnf_sysctl_header = register_sysctl_table(brnf_net_table, );
if (brnf_sysctl_header == NULL) {
printk(KERN_WARNING "br_netfilter: can't register to sysctl.\n");
for (i = ; i < ARRAY_SIZE(br_nf_ops); i++)
nf_unregister_hook(&br_nf_ops[i]); return -EFAULT;
}
#endif
printk(KERN_NOTICE "Bridge firewalling registered\n");
return ;
} brioctl_set(br_ioctl_deviceless_stub); 注册一个函数,在调用ioctl添加新的br时使用。
int br_ioctl_deviceless_stub(unsigned int cmd, void __user *uarg)
{
switch (cmd) {
case SIOCGIFBR:
case SIOCSIFBR:
return old_deviceless(uarg);
case SIOCBRADDBR:
case SIOCBRDELBR:
{
char buf[IFNAMSIZ];
if (!capable(CAP_NET_ADMIN))
return -EPERM;
if (copy_from_user(buf, uarg, IFNAMSIZ))
return -EFAULT;
buf[IFNAMSIZ-] = ;
if (cmd == SIOCBRADDBR)
return br_add_bridge(buf); //添加一个br
return br_del_bridge(buf);//删除一个br,很简单就不看了
}
}
return -EOPNOTSUPP;
}
我们来看添加一个br.
int br_add_bridge(const char *name)
{
struct net_device *dev;
int ret;
//一个br是一个网络设备,所以分配一个net_device
dev = new_bridge_dev(name);
if (!dev) return -ENOMEM;
rtnl_lock();
if (strchr(dev->name, '%')) { //如果有%
//给设备分配一个数字,这样就像网卡一样eth0,eth1对于桥就是br0, br1.
ret = dev_alloc_name(dev, dev->name);
if (ret < ) {
free_netdev(dev);
goto out;
}
}
//注册这个网络设备
ret = register_netdevice(dev);
if (ret)
goto out;
//在sys目录注册这个设备,参考PCI设备初始化过程
ret = br_sysfs_addbr(dev);
if (ret)
unregister_netdevice(dev);
out:
rtnl_unlock();
return ret;
}
static struct net_device *new_bridge_dev(const char *name)
{
struct net_bridge *br;
struct net_device *dev;
//分配一个网络设备
dev = alloc_netdev(sizeof(struct net_bridge), name, br_dev_setup);
if (!dev)
return NULL; br = netdev_priv(dev);
br->dev = dev; spin_lock_init(&br->lock);
INIT_LIST_HEAD(&br->port_list);
spin_lock_init(&br->hash_lock); //下面这部份代码跟stp协议相关,我们暂不关心
br->bridge_id.prio[] = 0x80;
br->bridge_id.prio[] = 0x00; //const u8 br_group_address[ETH_ALEN] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 };
memcpy(br->group_addr, br_group_address, ETH_ALEN); br->feature_mask = dev->features;
br->stp_enabled = ;
br->designated_root = br->bridge_id;
br->root_path_cost = ;
br->root_port = ;
br->bridge_max_age = br->max_age = * HZ;
br->bridge_hello_time = br->hello_time = * HZ;
br->bridge_forward_delay = br->forward_delay = * HZ;
br->topology_change = ;
br->topology_change_detected = ;
br->ageing_time = * HZ;
INIT_LIST_HEAD(&br->age_list); br_stp_timer_init(br);
return dev;
}
在br_dev_setup中还做了一些另外初始化
void br_dev_setup(struct net_device *dev)
{
memset(dev->dev_addr, , ETH_ALEN); ether_setup(dev);
//当向br添加新的端口设备时被调用
dev->do_ioctl = br_dev_ioctl;
//下面基本上是和网络设备初始化相关
dev->get_stats = br_dev_get_stats;
dev->hard_start_xmit = br_dev_xmit; //发送数据函数
dev->open = br_dev_open;
dev->set_multicast_list = br_dev_set_multicast_list;
dev->change_mtu = br_change_mtu;
dev->destructor = free_netdev;
SET_MODULE_OWNER(dev);
SET_ETHTOOL_OPS(dev, &br_ethtool_ops);
dev->stop = br_dev_stop;
dev->tx_queue_len = ;
dev->set_mac_address = br_set_mac_address;
dev->priv_flags = IFF_EBRIDGE; dev->features = NETIF_F_SG | NETIF_F_FRAGLIST | NETIF_F_HIGHDMA |
NETIF_F_TSO | NETIF_F_NO_CSUM | NETIF_F_GSO_ROBUST;
}
br_dev_ioctl -> add_del_if -> br_add_if -> br_fdb_insert 向 struct net_bridge 的hash表中插入dev的mac地址。 -> br_stp_recalculate_bridge_id(br) 重新计算br的mac地址(取最小的mac).
相关实现看下面网桥操作等函数实现. 在src/net/core/dev.c的int netif_receive_skb(struct sk_buff *skb)中:
......
handle_diverter(skb); if (handle_bridge(&skb, &pt_prev, &ret, orig_dev))
goto out; type = skb->protocol;
......
由handle_bridge函数处理网桥处理.
static __inline__ int handle_bridge(struct sk_buff **pskb, struct packet_type **pt_prev, int *ret, struct net_device *orig_dev)
{
struct net_bridge_port *port;
//如果是回环数据或者
//skb->dev->br_port : 接收该数据包的端口是网桥端口组的一员,如果接收当前数据包的接口不属于任意网桥的某一端口,则其值为NULL
if ((*pskb)->pkt_type == PACKET_LOOPBACK || (port = rcu_dereference((*pskb)->dev->br_port)) == NULL)
return ; if (*pt_prev) {
*ret = deliver_skb(*pskb, *pt_prev, orig_dev);
*pt_prev = NULL;
}
//处理网桥的hook函数,指向br_handle_frame. 看上面初始化过程
return br_handle_frame_hook(port, pskb);
}
//网桥处理函数
int br_handle_frame(struct net_bridge_port *p, struct sk_buff **pskb)
{
struct sk_buff *skb = *pskb;
const unsigned char *dest = eth_hdr(skb)->h_dest;
//源mac地址不能使广播地址或者0地址
if (!is_valid_ether_addr(eth_hdr(skb)->h_source))
goto err;
//看下面桥组多播地址
if (unlikely(is_link_local(dest))) {
skb->pkt_type = PACKET_HOST;
return NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev, NULL, br_handle_local_finish) != ;
} //网桥之所以是网桥,比HUB更智能,是因为它有一个MAC-PORT的表,这样转发数据就不用广播,而查表定端口就可以了
//每次收到一个包,网桥都会学习其来源MAC,添加进这个表. Linux中这个表叫CAM表
//如果桥的状态是LEARNING或FORWARDING(学习或转发),则学习该包的源地址skb->mac.ethernet->h_source, 将其添加到CAM表中
if (p->state == BR_STATE_FORWARDING || p->state == BR_STATE_LEARNING) {
if (br_should_route_hook) { //这个hook函数与ebtable有关
if (br_should_route_hook(pskb))
return ; skb = *pskb;
dest = eth_hdr(skb)->h_dest;
}
//mac地址为本机
if (!compare_ether_addr(p->br->dev->dev_addr, dest))
skb->pkt_type = PACKET_HOST;
//调用桥相关的hook函数,参考ip_conntrack实现
//虽然在这写了br_handle_frame_finish函数但实际不会在这调用这函数,看下面hook函数实现
NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL, br_handle_frame_finish);
return ;
}
}
//我们还是把这函数先看了
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;
int passedup = ;
//如果设备不是桥的端口或设备是关闭状态
if (!p || p->state == BR_STATE_DISABLED)
goto drop; br = p->br;
br_fdb_update(br, p, eth_hdr(skb)->h_source); ////更新cam表
//学习状态
if (p->state == BR_STATE_LEARNING)
goto drop; //如果网桥的虚拟网卡处于混杂模式,那么每个接收到的数据包都需要克隆一份
//送到AF_PACKET协议处理函数(netif_receive_skb) if (br->dev->flags & IFF_PROMISC) {
struct sk_buff *skb2; skb2 = skb_clone(skb, GFP_ATOMIC); if (skb2 != NULL) {
passedup = ;
br_pass_frame_up(br, skb2); //递交到netif_receive_skb
}
}
//目的MAC为多播,则需要向本机的上层协议栈传送这个数据包,这里
//有一个标志变量passedup,用于表示是否传送过了,如果已传送过,那就算了
if (is_multicast_ether_addr(dest)) {
br->statistics.multicast++;
br_flood_forward(br, skb, !passedup); //发送到其他端口
if (!passedup)
br_pass_frame_up(br, skb);
goto out;
}
//查询CAM表
dst = __br_fdb_get(br, dest);
if (dst != NULL && dst->is_local) { //目的地址为本地地址
if (!passedup)
br_pass_frame_up(br, skb);
else
kfree_skb(skb);
goto out; }
//不是本地地址,转发
if (dst != NULL) {
br_forward(dst->dst, skb); //主要调用__br_forward(to, skb);
goto out;
}
//在CAM表中没有查到,发送到所有的端口
br_flood_forward(br, skb, );
out:
return ;
drop:
kfree_skb(skb);
goto out;
}
更新CAM表
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)];//取出hash table头
struct net_bridge_fdb_entry *fdb; //return br->topology_change ? br->forward_delay : br->ageing_time;
//如果拓扑结构改变,用forward_delay默认15秒,否则更长默认5分钟
if (hold_time(br) == )
return; //查找
fdb = fdb_find(head, addr);
if (likely(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->dst = source; //CAM表中这项属于的端口改变
fdb->ageing_timer = jiffies;
}
} else { //没找到
spin_lock(&br->hash_lock);
if (!fdb_find(head, addr))
fdb_create(head, source, addr, ); //创建并插入一项,0表示不是本地mac spin_unlock(&br->hash_lock);
} }
static struct net_bridge_fdb_entry *fdb_create(struct hlist_head *head, struct net_bridge_port *source,
const unsigned char *addr, int is_local)
{
struct net_bridge_fdb_entry *fdb; fdb = kmem_cache_alloc(br_fdb_cache, GFP_ATOMIC);
if (fdb) {
memcpy(fdb->addr.addr, addr, ETH_ALEN);
atomic_set(&fdb->use_count, );
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;
}
static void br_pass_frame_up(struct net_bridge *br, struct sk_buff *skb)
{
struct net_device *indev; br->statistics.rx_packets++;
br->statistics.rx_bytes += skb->len; indev = skb->dev;
skb->dev = br->dev; //设置成br的dev. 注意:br->dev->br_port一般为空,如果这个br也被添加到别的br中那么就不是空了. //先走桥的NF_BR_LOCAL_IN hook然后在调用netif_receive_skb
NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL, netif_receive_skb);
}
void br_flood_forward(struct net_bridge *br, struct sk_buff *skb, int clone)
{
br_flood(br, skb, clone, __br_forward);
}
static void br_flood(struct net_bridge *br, struct sk_buff *skb, int clone, void (*__packet_hook)(const struct net_bridge_port *p,
struct sk_buff *skb))
{
struct net_bridge_port *p;
struct net_bridge_port *prev; if (clone) { //克隆这包
struct sk_buff *skb2;
if ((skb2 = skb_clone(skb, GFP_ATOMIC)) == NULL) {
br->statistics.tx_dropped++;
return;
}
skb = skb2;
}
prev = NULL;
//桥的每个端口
list_for_each_entry_rcu(p, &br->port_list, list) {
//应该转发
if (should_deliver(p, skb)) { //return (skb->dev != p->dev && p->state == BR_STATE_FORWARDING);
if (prev != NULL) {
struct sk_buff *skb2;
if ((skb2 = skb_clone(skb, GFP_ATOMIC)) == NULL) { 克隆一个
br->statistics.tx_dropped++;
kfree_skb(skb);
return;
}
__packet_hook(prev, skb2); //调用这个函数指针
}
prev = p;
}
}
if (prev != NULL) {
__packet_hook(prev, skb);
return;
}
kfree_skb(skb);
}
//函数指针指向的这个函数
static void __br_forward(const struct net_bridge_port *to, struct sk_buff *skb)
{
struct net_device *indev; indev = skb->dev;
skb->dev = to->dev; //要出去的接口设备
skb->ip_summed = CHECKSUM_NONE;
//br_forward_finish 实现为 return NF_HOOK(PF_BRIDGE, NF_BR_POST_ROUTING, skb, NULL, skb->dev, br_dev_queue_push_xmit);
//走桥的FORWARD然后走POST_ROUTING,最后在发送skb
NF_HOOK(PF_BRIDGE, NF_BR_FORWARD, skb, indev, skb->dev, br_forward_finish);
}
int br_dev_queue_push_xmit(struct sk_buff *skb)
{
/* drop mtu oversized packets except gso */
if (packet_length(skb) > skb->dev->mtu && !skb_is_gso(skb))
kfree_skb(skb);
else {
#ifdef CONFIG_BRIDGE_NETFILTER
/* ip_refrag calls ip_fragment, doesn't copy the MAC header. */
if (nf_bridge_maybe_copy_header(skb))
kfree_skb(skb);
else
#endif
{
skb_push(skb, ETH_HLEN);
dev_queue_xmit(skb); //此处调用dev设备的hard_start_xmit()函数
}
}
return ;
}
下面我们来看所有的hook函数.
static struct nf_hook_ops br_nf_ops[] = {
{ .hook = br_nf_pre_routing,
.owner = THIS_MODULE,
.pf = PF_BRIDGE,
.hooknum = NF_BR_PRE_ROUTING,
.priority = NF_BR_PRI_BRNF, },
{ .hook = br_nf_local_in,
.owner = THIS_MODULE,
.pf = PF_BRIDGE,
.hooknum = NF_BR_LOCAL_IN,
.priority = NF_BR_PRI_BRNF, },
{ .hook = br_nf_forward_ip,
.owner = THIS_MODULE,
.pf = PF_BRIDGE,
.hooknum = NF_BR_FORWARD,
.priority = NF_BR_PRI_BRNF - , },
{ .hook = br_nf_forward_arp,
.owner = THIS_MODULE,
.pf = PF_BRIDGE,
.hooknum = NF_BR_FORWARD,
.priority = NF_BR_PRI_BRNF, },
{ .hook = br_nf_local_out,
.owner = THIS_MODULE,
.pf = PF_BRIDGE,
.hooknum = NF_BR_LOCAL_OUT,
.priority = NF_BR_PRI_FIRST, },
{ .hook = br_nf_post_routing,
.owner = THIS_MODULE,
.pf = PF_BRIDGE,
.hooknum = NF_BR_POST_ROUTING,
.priority = NF_BR_PRI_LAST, },
{ .hook = ip_sabotage_in,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_PRE_ROUTING,
.priority = NF_IP_PRI_FIRST, },
{ .hook = ip_sabotage_out,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_FORWARD,
.priority = NF_IP_PRI_BRIDGE_SABOTAGE_FORWARD, },
{ .hook = ip_sabotage_out,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_LOCAL_OUT,
.priority = NF_IP_PRI_BRIDGE_SABOTAGE_LOCAL_OUT, },
{ .hook = ip_sabotage_out,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_POST_ROUTING,
.priority = NF_IP_PRI_FIRST, },
};
我去掉了所有ipv6的项,我们只看ipv4的,其实两个都是一样的函数,只不过hook点不同.
static unsigned int br_nf_pre_routing(unsigned int hook, struct sk_buff **pskb, const struct net_device *in,
const struct net_device *out, int (*okfn)(struct sk_buff *))
{
struct iphdr *iph;
__u32 len;
struct sk_buff *skb = *pskb;
if (skb->protocol == htons(ETH_P_IPV6) || IS_VLAN_IPV6(skb)) { //关于ipv6忽略 ......
}
#ifdef CONFIG_SYSCTL if (!brnf_call_iptables) //proc调节项
return NF_ACCEPT;
#endif
if (skb->protocol != htons(ETH_P_IP) && !IS_VLAN_IP(skb)) //不是ip协议
return NF_ACCEPT; //如果skb是共享的,那么克隆一个新的 if ((skb = skb_share_check(*pskb, GFP_ATOMIC)) == NULL)
goto out; if (skb->protocol == htons(ETH_P_8021Q)) { //802.1Q VLAN Extended Header
skb_pull_rcsum(skb, VLAN_HLEN);
skb->nh.raw += VLAN_HLEN; }
if (!pskb_may_pull(skb, sizeof(struct iphdr))) //检测数据包长度是否有一个标准ip头长度
goto inhdr_error; iph = skb->nh.iph;
if (iph->ihl < || iph->version != ) //头长度或版本不对
goto inhdr_error; if (!pskb_may_pull(skb, * iph->ihl)) //检测数据包长度是否有一个实际ip头长度
goto inhdr_error; iph = skb->nh.iph;
if (ip_fast_csum((__u8 *) iph, iph->ihl) != ) //校验和错误
goto inhdr_error; len = ntohs(iph->tot_len);
if (skb->len < len || len < * iph->ihl) //包长度有错误
goto inhdr_error; pskb_trim_rcsum(skb, len); //整理skb长度,如果需要从新计算校验和 nf_bridge_put(skb->nf_bridge); //如果有,释放它
if (!nf_bridge_alloc(skb)) //分配一个并把引用计数置为1
return NF_DROP;
if (!setup_pre_routing(skb)) //初始化一些值
return NF_DROP; //#define skb_origaddr(skb) (((struct bridge_skb_cb *)(skb->nf_bridge->data))->daddr.ipv4)
//#define store_orig_dstaddr(skb) (skb_origaddr(skb) = (skb)->nh.iph->daddr)
store_orig_dstaddr(skb);//保存目的地址 //调用PF_INET的hook,可以进行netfilter的工作,具体看ip_conntrack实现
NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, skb->dev, NULL, br_nf_pre_routing_finish); return NF_STOLEN; //已经被处理,不在向上递交
inhdr_error:
out:
return NF_DROP;
}
static struct net_device *setup_pre_routing(struct sk_buff *skb)
{
struct nf_bridge_info *nf_bridge = skb->nf_bridge; if (skb->pkt_type == PACKET_OTHERHOST) {
skb->pkt_type = PACKET_HOST;
nf_bridge->mask |= BRNF_PKT_TYPE;
} nf_bridge->mask |= BRNF_NF_BRIDGE_PREROUTING;
nf_bridge->physindev = skb->dev; //记录物理进入的接口
skb->dev = bridge_parent(skb->dev); //如果接口属于br,返回br的dev. return skb->dev;
}
static int br_nf_pre_routing_finish(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
struct iphdr *iph = skb->nh.iph;
struct nf_bridge_info *nf_bridge = skb->nf_bridge; if (nf_bridge->mask & BRNF_PKT_TYPE) {
skb->pkt_type = PACKET_OTHERHOST;
nf_bridge->mask ^= BRNF_PKT_TYPE;
}
nf_bridge->mask ^= BRNF_NF_BRIDGE_PREROUTING; //#define dnat_took_place(skb) (skb_origaddr(skb) != (skb)->nh.iph->daddr)
if (dnat_took_place(skb)) { //如果做过DNAT,那么要从新确定路由
if (ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, dev)) {
...... //我们先省略
} else {
if (skb->dst->dev == dev) {
bridged_dnat:
nf_bridge->mask |= BRNF_BRIDGED_DNAT;
skb->dev = nf_bridge->physindev;
if (skb->protocol == htons(ETH_P_8021Q)) {
skb_push(skb, VLAN_HLEN);
skb->nh.raw -= VLAN_HLEN; }
NF_HOOK_THRESH(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL, br_nf_pre_routing_finish_bridge, );
return ; }
memcpy(eth_hdr(skb)->h_dest, dev->dev_addr, ETH_ALEN);
skb->pkt_type = PACKET_HOST;
}
} else {
skb->dst = (struct dst_entry *)&__fake_rtable; //伪路由
dst_hold(skb->dst);//增加引用计数
}
skb->dev = nf_bridge->physindev; if (skb->protocol == htons(ETH_P_8021Q)) {
skb_push(skb, VLAN_HLEN);
skb->nh.raw -= VLAN_HLEN;
}
//又进入桥的PRE_ROUTING,在br_handle_frame函数中我们就会调用桥的PRE_ROUTING hook函数.
//但是br_handle_frame使用的是NF_HOOK
//看现在使用NF_HOOK_THRESH最后一个参数是1,看ip_conntrack实现中有详细说明
//这个hook的权限是NF_BR_PRI_BRNF=0,所以不会在调用一遍hook函数了,但是会调用br_handle_frame_finish,已经看过了在上面
NF_HOOK_THRESH(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL, br_handle_frame_finish, );
return ;
}
下面我们看NF_BR_LOCAL_IN的hook函数.
br_handle_frame_finish->br_pass_frame_up->会调用
NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL, netif_receive_skb)看上面 static unsigned int br_nf_local_in(unsigned int hook, struct sk_buff **pskb, const struct net_device *in,
const struct net_device *out, int (*okfn)(struct sk_buff *))
{
struct sk_buff *skb = *pskb; if (skb->dst == (struct dst_entry *)&__fake_rtable) { //执行一个伪路由
dst_release(skb->dst);
skb->dst = NULL;
} return NF_ACCEPT;
}
下面我们看NF_BR_FORWARD的hook函数
br_handle_frame_finish->br_forward->__br_forward->会调用
NF_HOOK(PF_BRIDGE, NF_BR_FORWARD, skb, indev, skb->dev, br_forward_finish);
br_forward_finish并不会被调用,因为这个hook返回NF_STOLEN.
static unsigned int br_nf_forward_ip(unsigned int hook, struct sk_buff **pskb, const struct net_device *in,
const struct net_device *out, int (*okfn)(struct sk_buff *))
{
struct sk_buff *skb = *pskb;
struct nf_bridge_info *nf_bridge;
struct net_device *parent;
int pf; if (!skb->nf_bridge)
return NF_ACCEPT; parent = bridge_parent(out); //取出端口所在br的dev
if (!parent)
return NF_DROP; if (skb->protocol == htons(ETH_P_IP) || IS_VLAN_IP(skb)) //判断协议
pf = PF_INET;
else
pf = PF_INET6; if (skb->protocol == htons(ETH_P_8021Q)) {
skb_pull(*pskb, VLAN_HLEN);
(*pskb)->nh.raw += VLAN_HLEN;
}
nf_bridge = skb->nf_bridge;
if (skb->pkt_type == PACKET_OTHERHOST) {
skb->pkt_type = PACKET_HOST;
nf_bridge->mask |= BRNF_PKT_TYPE;
}
nf_bridge->mask |= BRNF_BRIDGED;
nf_bridge->physoutdev = skb->dev; //保存物理外出dev
//调用PF_INET的hook,就是netfilter体系
NF_HOOK(pf, NF_IP_FORWARD, skb, bridge_parent(in), parent, br_nf_forward_finish); return NF_STOLEN; //不在继续处理这个skb
}
static int br_nf_forward_finish(struct sk_buff *skb)
{
struct nf_bridge_info *nf_bridge = skb->nf_bridge;
struct net_device *in;
//不是arp协议
if (skb->protocol != htons(ETH_P_ARP) && !IS_VLAN_ARP(skb)) {
in = nf_bridge->physindev; //取出物理进入接口dev
if (nf_bridge->mask & BRNF_PKT_TYPE) {
skb->pkt_type = PACKET_OTHERHOST;
nf_bridge->mask ^= BRNF_PKT_TYPE;
}
} else {
in = *((struct net_device **)(skb->cb));
}
if (skb->protocol == htons(ETH_P_8021Q)) {
skb_push(skb, VLAN_HLEN);
skb->nh.raw -= VLAN_HLEN;
}
//这和上面的情况一样,最后一个参数是1就不会调用hook函数了,会直接调用br_forward_finish函数,这个函数在上面已经介绍过了
NF_HOOK_THRESH(PF_BRIDGE, NF_BR_FORWARD, skb, in, skb->dev, br_forward_finish, );
return ;
}
还有一个hook是关于br处理arp协议的,看它的权限表明这个hook先于上面那个调用
static unsigned int br_nf_forward_arp(unsigned int hook, struct sk_buff **pskb, const struct net_device *in,
const struct net_device *out, int (*okfn)(struct sk_buff *))
{
struct sk_buff *skb = *pskb;
struct net_device **d = (struct net_device **)(skb->cb);
#ifdef CONFIG_SYSCTL
if (!brnf_call_arptables) //proc调节项
return NF_ACCEPT;
#endif
if (skb->protocol != htons(ETH_P_ARP)) {
if (!IS_VLAN_ARP(skb))
return NF_ACCEPT; //不是arp协议 skb_pull(*pskb, VLAN_HLEN);
(*pskb)->nh.raw += VLAN_HLEN;
}
if (skb->nh.arph->ar_pln != ) {
if (IS_VLAN_ARP(skb)) {
skb_push(*pskb, VLAN_HLEN);
(*pskb)->nh.raw -= VLAN_HLEN;
}
return NF_ACCEPT;
}
*d = (struct net_device *)in; //保存进入接口设备
//调用arp相关的hook函数,和ebtable有关,最后还是调用br_nf_forward_finish
NF_HOOK(NF_ARP, NF_ARP_FORWARD, skb, (struct net_device *)in, (struct net_device *)out, br_nf_forward_finish);
return NF_STOLEN; //不在继续递交skb
}
接着就是NF_BR_LOCAL_OUT的hook.
参看下面网桥操作等函数实现.
static unsigned int br_nf_local_out(unsigned int hook, struct sk_buff **pskb, const struct net_device *in,
const struct net_device *out, int (*okfn)(struct sk_buff *))
{
struct net_device *realindev, *realoutdev;
struct sk_buff *skb = *pskb;
struct nf_bridge_info *nf_bridge;
int pf; if (!skb->nf_bridge)
return NF_ACCEPT;
if (skb->protocol == htons(ETH_P_IP) || IS_VLAN_IP(skb)) //判断协议
pf = PF_INET;
else
pf = PF_INET6;
nf_bridge = skb->nf_bridge;
nf_bridge->physoutdev = skb->dev;
realindev = nf_bridge->physindev; if (nf_bridge->mask & BRNF_BRIDGED_DNAT) {
if (nf_bridge->mask & BRNF_PKT_TYPE) {
skb->pkt_type = PACKET_OTHERHOST;
nf_bridge->mask ^= BRNF_PKT_TYPE;
} if (skb->protocol == htons(ETH_P_8021Q)) {
skb_push(skb, VLAN_HLEN);
skb->nh.raw -= VLAN_HLEN;
}
//调用转发hook
NF_HOOK(PF_BRIDGE, NF_BR_FORWARD, skb, realindev, skb->dev, br_forward_finish);
goto out;
}
realoutdev = bridge_parent(skb->dev);
if (!realoutdev)
return NF_DROP;
#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
/* iptables should match -o br0.x */
if (nf_bridge->netoutdev)
realoutdev = nf_bridge->netoutdev;
#endif if (skb->protocol == htons(ETH_P_8021Q)) {
skb_pull(skb, VLAN_HLEN);
(*pskb)->nh.raw += VLAN_HLEN;
}
//转发数据包有物理输入设备,本地发送的包则没有
if (realindev != NULL) {
if (!(nf_bridge->mask & BRNF_DONT_TAKE_PARENT)) {
struct net_device *parent = bridge_parent(realindev);
if (parent)
realindev = parent; }
//用PF_INET/PF_INET6族的FORWARD点的hook链表进行过滤,这样在FORWARD链制定的规则在此生效
//优先级阈值是NF_IP_PRI_BRIDGE_SABOTAGE_FORWARD + 1,也就是在连接跟踪
//之后的所有hook点都生效,包括mangle, nat, filter等hook
//因为数据包这时已经进行了连接跟踪了,不用重新跟踪
NF_HOOK_THRESH(pf, NF_IP_FORWARD, skb, realindev, realoutdev, br_nf_local_out_finish, NF_IP_PRI_BRIDGE_SABOTAGE_FORWARD + ); } else { //本地发送 //用PF_INET/PF_INET6族的OUTPUT点的hook链表进行过滤,这样在OUTPUT链制定的规则在此生效
//优先级阈值是NF_IP_PRI_BRIDGE_SABOTAGE_OUT + 1,也就是在目的NAT
//之后的所有hook点都起效,包括filter, 源NAT等hook
NF_HOOK_THRESH(pf, NF_IP_LOCAL_OUT, skb, realindev, realoutdev, br_nf_local_out_finish, NF_IP_PRI_BRIDGE_SABOTAGE_LOCAL_OUT + );
}
out:
return NF_STOLEN;
}
然后会调用
static int br_nf_local_out_finish(struct sk_buff *skb)
{
if (skb->protocol == htons(ETH_P_8021Q)) {
skb_push(skb, VLAN_HLEN);
skb->nh.raw -= VLAN_HLEN;
}
//因为优先级是NF_BR_PRI_FIRST + 1,所以不会在重复调用br_nf_local_out,看下面网桥操作等函数实现
NF_HOOK_THRESH(PF_BRIDGE, NF_BR_LOCAL_OUT, skb, NULL, skb->dev, br_forward_finish, NF_BR_PRI_FIRST + );
return ;
} 接着看NF_BR_POST_ROUTING的hook.
br_forward_finish会调用
NF_HOOK(PF_BRIDGE, NF_BR_POST_ROUTING, skb, NULL, skb->dev, br_dev_queue_push_xmit);
static unsigned int br_nf_post_routing(unsigned int hook, struct sk_buff **pskb, const struct net_device *in,
const struct net_device *out, int (*okfn)(struct sk_buff *))
{
struct sk_buff *skb = *pskb;
struct nf_bridge_info *nf_bridge = (*pskb)->nf_bridge;
struct net_device *realoutdev = bridge_parent(skb->dev); //取出br->dev
int pf; if (!nf_bridge)
return NF_ACCEPT; if (!realoutdev)
return NF_DROP; if (skb->protocol == htons(ETH_P_IP) || IS_VLAN_IP(skb))
pf = PF_INET;
else
pf = PF_INET6; if (skb->pkt_type == PACKET_OTHERHOST) {
skb->pkt_type = PACKET_HOST;
nf_bridge->mask |= BRNF_PKT_TYPE;
} if (skb->protocol == htons(ETH_P_8021Q)) {
skb_pull(skb, VLAN_HLEN);
skb->nh.raw += VLAN_HLEN;
} nf_bridge_save_header(skb);
#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
if (nf_bridge->netoutdev)
realoutdev = nf_bridge->netoutdev;
#endif
//调用PF_IENT的POST_ROUTING hook函数
NF_HOOK(pf, NF_IP_POST_ROUTING, skb, NULL, realoutdev, br_nf_dev_queue_xmit);
return NF_STOLEN;
}
static inline void nf_bridge_save_header(struct sk_buff *skb)
{
int header_size = ;
if (skb->protocol == htons(ETH_P_8021Q))
header_size = ;
memcpy(skb->nf_bridge->data, skb->data - header_size, header_size);
}
static int br_nf_dev_queue_xmit(struct sk_buff *skb)
{
if (skb->protocol == htons(ETH_P_IP) && skb->len > skb->dev->mtu && !skb_is_gso(skb))
return ip_fragment(skb, br_dev_queue_push_xmit); //需要进行ip分片
else
return br_dev_queue_push_xmit(skb);
}
还有一些桥添加到PF_INET中的hook
没进行什么数据包操作,就是自身的输入输出包不通过桥处理,要短路掉
static unsigned int ip_sabotage_in(unsigned int hook, struct sk_buff **pskb, const struct net_device *in,
const struct net_device *out, int (*okfn)(struct sk_buff *))
{
//setup_pre_routing函数会设置这个标志
if ((*pskb)->nf_bridge && !((*pskb)->nf_bridge->mask & BRNF_NF_BRIDGE_PREROUTING)) {
return NF_STOP;
} return NF_ACCEPT;
}
static unsigned int ip_sabotage_out(unsigned int hook, struct sk_buff **pskb, const struct net_device *in,
const struct net_device *out, int (*okfn)(struct sk_buff *))
{ if ((out->hard_start_xmit == br_dev_xmit && okfn != br_nf_forward_finish && okfn != br_nf_local_out_finish && okfn != br_nf_dev_queue_xmit)
#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
|| ((out->priv_flags & IFF_802_1Q_VLAN) && VLAN_DEV_INFO(out)->real_dev->hard_start_xmit == br_dev_xmit)
#endif
) { struct nf_bridge_info *nf_bridge;
if (!skb->nf_bridge) {
#ifdef CONFIG_SYSCTL //proc配置项
struct iphdr *ip = skb->nh.iph;
if (ip->version == && !brnf_call_iptables)
return NF_ACCEPT;
else if (ip->version == && !brnf_call_ip6tables)
return NF_ACCEPT;
else if (!brnf_deferred_hooks)
return NF_ACCEPT;
#endif
if (hook == NF_IP_POST_ROUTING)
return NF_ACCEPT;
if (!nf_bridge_alloc(skb)) //分配一个struct nf_bridge_info结构
return NF_DROP;
}
nf_bridge = skb->nf_bridge;
if (hook == NF_IP_FORWARD && nf_bridge->physindev == NULL) {
nf_bridge->mask |= BRNF_DONT_TAKE_PARENT;
nf_bridge->physindev = (struct net_device *)in;
}
#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
/* the iptables outdev is br0.x, not br0 */
if (out->priv_flags & IFF_802_1Q_VLAN)
nf_bridge->netoutdev = (struct net_device *)out;
#endif
return NF_STOP;
}
return NF_ACCEPT;
}
到现在为止我们的hook点全部看完了. [桥组多播地址]
/* const u8 br_group_address[ETH_ALEN] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 };
* Does address match the link local multicast address. 01:80:c2:00:00:0X
*/
/*
* STP协议的BPDU包的目的MAC采用的是多播目标MAC地址:
* 01-80-c2-00-00-00(Bridge_group_addr:网桥组多播地址)
* 比较目的地址前5位
* 是否与多播目标MAC地址相同:memcmp(dest, bridge_ula, 5) == 0
* 如果相同,地址第6项也符合 : (dest[5] & 0xF0) == 0
*/
static inline int is_link_local(const unsigned char *dest)
{
return memcmp(dest, br_group_address, ) == && (dest[] & 0xf0) == ;
}
[/桥组多播地址] [网桥操作等函数实现]
//ioctl操作
int br_dev_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
struct net_bridge *br = netdev_priv(dev); switch(cmd) {
case SIOCDEVPRIVATE:
return old_dev_ioctl(dev, rq, cmd);
case SIOCBRADDIF:
case SIOCBRDELIF:
return add_del_if(br, rq->ifr_ifindex, cmd == SIOCBRADDIF);
}
return -EOPNOTSUPP;
}
添加删除都由这个函数来处理
static int add_del_if(struct net_bridge *br, int ifindex, int isadd)
{
struct net_device *dev;
int ret; if (!capable(CAP_NET_ADMIN))
return -EPERM; dev = dev_get_by_index(ifindex);
if (dev == NULL)
return -EINVAL; if (isadd)
ret = br_add_if(br, dev);
else
ret = br_del_if(br, dev); dev_put(dev);
return ret;
}
删除,参考下面添加
int br_del_if(struct net_bridge *br, struct net_device *dev)
{
struct net_bridge_port *p = dev->br_port; if (!p || p->br != br)
return -EINVAL; del_nbp(p); spin_lock_bh(&br->lock);
br_stp_recalculate_bridge_id(br);
br_features_recompute(br);
spin_unlock_bh(&br->lock); return ;
}
添加
int br_add_if(struct net_bridge *br, struct net_device *dev)
{
struct net_bridge_port *p;
int err = ;
//回环或者非以及网接口
if (dev->flags & IFF_LOOPBACK || dev->type != ARPHRD_ETHER)
return -EINVAL; //还不能把一个br添加到另一个br中
if (dev->hard_start_xmit == br_dev_xmit)
return -ELOOP; //此接口已经被添加过
if (dev->br_port != NULL)
return -EBUSY; //创建此设备的网桥接口
p = new_nbp(br, dev);
if (IS_ERR(p)) return PTR_ERR(p); err = kobject_add(&p->kobj);
if (err)
goto err0; //更新br的CAM hash表
err = br_fdb_insert(br, p, dev->dev_addr); //调用fdb_insert函数
if (err)
goto err1; err = br_sysfs_addif(p); //在sys文件系统中添加一项
if (err)
goto err2; rcu_assign_pointer(dev->br_port, p);
dev_set_promiscuity(dev, ); //设置接口为混杂模式 list_add_rcu(&p->list, &br->port_list); //添加到br的端口连表 spin_lock_bh(&br->lock);
br_stp_recalculate_bridge_id(br); //重新选择br的mac地址
br_features_recompute(br); //从新设置br->dev->feature字段 //#define BR_PORT_DEBOUNCE (HZ/10)
schedule_delayed_work(&p->carrier_check, BR_PORT_DEBOUNCE);//时间到后开始调用工作队列函数
spin_unlock_bh(&br->lock); dev_set_mtu(br->dev, br_min_mtu(br)); //从新设置br设备的mtu,选则一个最小的
kobject_uevent(&p->kobj, KOBJ_ADD); return ;
err2:
br_fdb_delete_by_port(br, p);
err1:
kobject_del(&p->kobj);
err0:
kobject_put(&p->kobj);
return err;
}
static struct net_bridge_port *new_nbp(struct net_bridge *br, struct net_device *dev)
{
int index;
struct net_bridge_port *p; //为这个端口分配一个唯一的id(对于这个br)
index = find_portno(br);
if (index < )
return ERR_PTR(index); p = kzalloc(sizeof(*p), GFP_KERNEL);
if (p == NULL) return ERR_PTR(-ENOMEM); p->br = br;
dev_hold(dev);
p->dev = dev;
p->path_cost = port_cost(dev);
p->priority = 0x8000 >> BR_PORT_BITS;
p->port_no = index;
br_init_port(p);
p->state = BR_STATE_DISABLED; //开始的状态是关闭的.
INIT_WORK(&p->carrier_check, port_carrier_check, dev); //初始化工作队列
br_stp_port_timer_init(p); //初始化一些定时器 kobject_init(&p->kobj);
kobject_set_name(&p->kobj, SYSFS_BRIDGE_PORT_ATTR);
p->kobj.ktype = &brport_ktype;
p->kobj.parent = &(dev->class_dev.kobj);
p->kobj.kset = NULL; return p;
}
void br_stp_port_timer_init(struct net_bridge_port *p)
{
setup_timer(&p->message_age_timer, br_message_age_timer_expired, (unsigned long) p);
setup_timer(&p->forward_delay_timer, br_forward_delay_timer_expired, (unsigned long) p);
setup_timer(&p->hold_timer, br_hold_timer_expired, (unsigned long) p);
}
//static const unsigned char br_mac_zero[6]; 全是0
//选择mac地址
void br_stp_recalculate_bridge_id(struct net_bridge *br)
{
const unsigned char *addr = br_mac_zero;
struct net_bridge_port *p; list_for_each_entry(p, &br->port_list, list) { //遍历所有端口,找一个最小的
if (addr == br_mac_zero || memcmp(p->dev->dev_addr, addr, ETH_ALEN) < )
addr = p->dev->dev_addr;
} if (compare_ether_addr(br->bridge_id.addr, addr)) //和原来的不同
br_stp_change_bridge_id(br, addr);
}
void br_stp_change_bridge_id(struct net_bridge *br, const unsigned char *addr)
{
unsigned char oldaddr[];
struct net_bridge_port *p;
int wasroot; wasroot = br_is_root_bridge(br); memcpy(oldaddr, br->bridge_id.addr, ETH_ALEN);
//修改mac地址
memcpy(br->bridge_id.addr, addr, ETH_ALEN);
memcpy(br->dev->dev_addr, addr, ETH_ALEN); list_for_each_entry(p, &br->port_list, list) {
if (!compare_ether_addr(p->designated_bridge.addr, oldaddr))
memcpy(p->designated_bridge.addr, addr, ETH_ALEN); if (!compare_ether_addr(p->designated_root.addr, oldaddr))
memcpy(p->designated_root.addr, addr, ETH_ALEN);
} br_configuration_update(br);
br_port_state_selection(br);
if (br_is_root_bridge(br) && !wasroot)
br_become_root_bridge(br);
}
工作队列函数
static void port_carrier_check(void *arg)
{
} //发送数据包
int 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; br->statistics.tx_packets++;
br->statistics.tx_bytes += skb->len; skb->mac.raw = skb->data;
skb_pull(skb, ETH_HLEN); if (dest[] & ) //多播
br_flood_deliver(br, skb, ); //实现为br_flood(br, skb, clone, __br_deliver);
else if ((dst = __br_fdb_get(br, dest)) != NULL) //单播
br_deliver(dst->dst, skb);
else
br_flood_deliver(br, skb, ); //广播 return ;
}
上面三个流程最后都会调用到函数
static void __br_deliver(const struct net_bridge_port *to, struct sk_buff *skb)
{
skb->dev = to->dev; //出去的接口dev,这个dev就是具体物理接口的dev了
NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_OUT, skb, NULL, skb->dev, br_forward_finish);
}
[\网桥操作等函数实现] [STP:生成树协议]
生成树协议 Spanning Tree 定义在 IEEE 802.1D 中,是一种桥到桥的链路管理协议,它在防止产生自循环的基础上提供路径冗余。为使以太网更好地工作,两个工作站之间只能有一条活动路径。网络环路的发生有多种原因,最常见的一种是故意生成的冗余,万一一个链路或交换机失败,会有另一个链路或交换机替代。 STP 是一种桥到桥的通信技术,提供发现网络物理环路的服务。该协议规定了网桥创建无环回loop - free 逻辑拓朴结构的算法。换句话说,STP 提供了一个生成整个第二层网络的无环回树结构。 生成树协议操作对终端站透明,也就是说,终端站并不知道它们自己是否连接在某单个局域网或多交换局域网中。当两个网桥相互连接在相同的由两台计算机组成的网络中时,生成树协议支持两网桥之间相互交换信息,这样只需要其中一个网桥处理两台计算机之间发送的信息。 桥接设备之间通过使用网桥协议数据单元(Bridge Protocol Data Unit,BPDU)交换各自状态信息。生成树协议通过发送 BPDU 信息为交换网络配置根交换和根端口,并为每个交换网路区段(switched segment)配置根端口和指定端口。 网桥中的生成树算法可以用来决定如何使用生成树协议,该算法的优点在于能够避免网桥环路,并确保在多路径情形下网桥能够选择一条最有效的路径。如果最佳路径选择失败,可以使用该算法重新计算网络路径并找出下一条最佳路径。 利用生成树算法可以决定网络路径(哪台计算机主机在哪个区段),并通过 BPDU 信息交换以上数据。该过程主要分为以下两个步骤: 步骤1:通过评估网桥接收的配置信息以及选择最佳选项,再利用生成树算法来决定网桥发送的最佳信息。
步骤2:一旦选定某发送信息,网桥将该信息与来自无根(non-root)连接的可能配置信息相比较。如果步骤1中选择的最佳选项并不优于可能配置信息,便删除该端口。 协议结构 网桥协议数据单元(BPDU): Protocol ID () Version () Type () Flags () Rood ID () Root Path ()
Sender BID () Port ID () M-Age () Max Age () Hello () FD ( Bytes) Protocol ID ― 恒为0。
Version ― 恒为0。
Type ― 决定该帧中所包含的两种 BPDU 格式类型(配置 BPDU 或 TCN BPDU)。
Flags ― 标志活动拓朴中的变化,包含在拓朴变化通知(Topology Change Notifications)的下一部分中。
Root BID ― 包括有根网桥的网桥 ID。会聚后的网桥网络中,所有配置 BPDU 中的该字段都应该具有相同值(单个 VLAN)。
NetXRay 可以细分为两个 BID 子字段:网桥优先级和网桥 MAC 地址。
Root Path Cost ― 通向有根网桥(Root Bridge)的所有链路的积累资本。
Sender BID ― 创建当前 BPDU 的网桥 BID。对于单交换机(单个 VLAN)发送的所有 BPDU 而言,该字段值都相同,
而对于交换机与交换机之间发送的 BPDU 而言,该字段值不同)
Port ID ― 每个端口值都是唯一的。端口1/1值为0×,而端口1/ 值为0×。
Message Age ― 记录 Root Bridge 生成当前 BPDU 起源信息的所消耗时间。
Max Age ― 保存 BPDU 的最长时间,也反映了拓朴变化通知(Topology Change Notification)过程中的网桥表生存时间情况。
Hello Time ― 指周期性配置 BPDU 间的时间。
Forward Delay ― 用于在 Listening 和 Learning 状态的时间,也反映了拓朴变化通知(Topology Change Notification)过程中的时间情况。 struct br_config_bpdu
{
unsigned topology_change:; //拓朴改变标志
unsigned topology_change_ack:;//拓朴改变回应标志
bridge_id root;//根ID,用于会聚后的网桥网络中,所有配置 BPDU 中的该字段都应该具有相同值(同VLAN),
//又可分为两个 BID 子字段:网桥优先级和网桥 MAC 地址
int root_path_cost;//路径开销,通向有根网桥(Root Bridge)的所有链路的积累资本
bridge_id bridge_id;//创建当前 BPDU 的网桥 BID。对于单交换机(单个 VLAN)发送的所有 BPDU 而言,
//该字段值都相同,而对于交换机与交换机之间发送的 BPDU 而言,该字段值不同)
port_id port_id; //端口ID,每个端口值都是唯一的。端口1/1值为0×8001,而端口1/2 值为0×8002
int message_age;//记录 Root Bridge 生成当前 BPDU 起源信息的所消耗时间
int max_age;//保存 BPDU 的最长时间,也反映了拓朴变化通知(Topology Change Notification)过程中的网桥表生存时间情况
int hello_time;//指周期性配置 BPDU 间的时间
int forward_delay;//用于在Listening和Learning状态的时间,也反映了拓朴变化通知(Topology Change Notification)过程中的时间情况
};
STP的处理函数,在br_init中设置
int br_stp_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
const struct llc_pdu_un *pdu = llc_pdu_un_hdr(skb);
const unsigned char *dest = eth_hdr(skb)->h_dest;
struct net_bridge_port *p = rcu_dereference(dev->br_port);
struct net_bridge *br;
const unsigned char *buf; if (!p)
goto err; //协议是否正确
if (pdu->ssap != LLC_SAP_BSPAN || pdu->dsap != LLC_SAP_BSPAN || pdu->ctrl_1 != LLC_PDU_TYPE_U)
goto err;
if (!pskb_may_pull(skb, ))
goto err; /* compare of protocol id and version */
//协议和版本号都必须为0
buf = skb->data;
if (buf[] != || buf[] != || buf[] != )
goto err;
br = p->br;
spin_lock(&br->lock); //检测网桥和端口的状态
if (p->state == BR_STATE_DISABLED || !br->stp_enabled || !(br->dev->flags & IFF_UP))
goto out;
//比较mac地址,参考上面桥组多播地址
if (compare_ether_addr(dest, br->group_addr) != )
goto out; buf = skb_pull(skb, ); //协议2字节,版本1字节 //BPDU包有两类,由TYPE字段标志,分为配置和TCN(Topology Change Notification,拓朴改变通告)
if (buf[] == BPDU_TYPE_CONFIG) {
//内核中用struct br_config_bpdu描述一个BPDU包
struct br_config_bpdu bpdu; if (!pskb_may_pull(skb, ))
goto out; buf = skb->data;
bpdu.topology_change = (buf[] & 0x01) ? : ;
bpdu.topology_change_ack = (buf[] & 0x80) ? : ; bpdu.root.prio[] = buf[];
bpdu.root.prio[] = buf[];
bpdu.root.addr[] = buf[];
bpdu.root.addr[] = buf[];
bpdu.root.addr[] = buf[];
bpdu.root.addr[] = buf[];
bpdu.root.addr[] = buf[];
bpdu.root.addr[] = buf[];
bpdu.root_path_cost = (buf[] << ) | (buf[] << ) | (buf[] << ) | buf[]; bpdu.bridge_id.prio[] = buf[];
bpdu.bridge_id.prio[] = buf[];
bpdu.bridge_id.addr[] = buf[];
bpdu.bridge_id.addr[] = buf[];
bpdu.bridge_id.addr[] = buf[];
bpdu.bridge_id.addr[] = buf[];
bpdu.bridge_id.addr[] = buf[];
bpdu.bridge_id.addr[] = buf[];
bpdu.port_id = (buf[] << ) | buf[]; bpdu.message_age = br_get_ticks(buf+);
bpdu.max_age = br_get_ticks(buf+);
bpdu.hello_time = br_get_ticks(buf+);
bpdu.forward_delay = br_get_ticks(buf+);
br_received_config_bpdu(p, &bpdu); //调用配置函数
} else if (buf[] == BPDU_TYPE_TCN) {
br_received_tcn_bpdu(p); //调用TCN函数
}
out:
spin_unlock(&br->lock);
err:
kfree_skb(skb);
return ;
}
STP的运作流程:
STP需要确定root bridge,root port,designate port,
) 选举1个根网桥:每个VLAN中只能有1个网桥担当根网桥,在根网桥上,所有的端口都担当指定端口。指定端口不仅能够发送和接收流量,
而且还可以发送和接收配置消息或BPDU;
) 选择所有非根网桥的根端口:STP协议在每个非根网桥上建立1个根端口,根端口是非根网桥到根网桥的最低开销路径;
如果有多条等价路径,那么非根网桥将选择连接到最低网桥ID的端口,再次再选最低端口ID的端口;
) 在各个网段,STP在网桥上建立一个指定端口,到达根网桥的路径开销最低;
备注:每一个广播网络只能有一个根桥,每个非根桥只能有一个根端口,每个网段只能有一个指定端口,非指定端口和非根端口将被阻塞。
) 网桥ID包含2个字节的优先级和6个字节的MAC地址,网桥ID的数值越低,成为根的概率也就越高。
) 在确定无环路拓扑的时候,交换机将运行如下5种标准:
确定最低的根网桥ID;
确定最低的到达根网桥的路径开销;
确定最低的发送方网桥ID;
确定最低的端口优先级;
确定最低的端口ID.
所以,需要在确定之间进行判断,判断的原则是:
. 最小的root BID(所有交换机中有最小BID的成为root bridge)
. 最小的到root bridge路径开销(确定root port)
. 最小的发送BID(确定指向端口)
. 最小的端口ID(如果其他标准都相同,根据端口ID确定选择标准,较小的优先)
所以,网桥需要在每收到一个BPDU包的时候,将包中的这些值,与自己原先保存的值相对比,对应的函数是:
br_supersedes_port_info 在确定好这些值后,就需要根据这些值进行选举root bridge,root port,designate port,
运作流程是:
. 选择root bridge,选举范围是整个网络,选择的流程是交换机相互交换BPDU,
选择依据是根据BID判断谁的BID比较小(优先级小,桥MAC小)
. 选择root port,选举范围是每个nonbridge的和其他交换机相连的端口之间(同一个交换机上的连接其他交换机的端口)
选择依据是path cost较小,每个nonbridge一个root port,可以收发数据。
. 选择designate port,选择范围是连接每个网段之间的端口(端口在不同交换机上)
选择依据也是path cost较小,如果相同,进一步比较BID,designate port每个网段一个,可以收发数据。
. 通过上述选择,没有成为任何角色的端口称作nondesignate port,端口设置为block状态,可以接收数据,但不转发数据。 前面三步是选择的过程,对应函数是br_configuration_update,
第四步是根据选举后的结果,决定端口的状态,对应的函数是:br_port_state_selection 开启STP的交换机端口可能处于5种状态:
. Block:
一个端口处于阻塞状态将会有如下特征:
丢弃所有连接的网段上收到的数据帧,或者通过交换而来内部转发的帧.
接收到的BPDU直接传给系统模块.
没有地址数据库。
不传递从系统模块收到的BPDU.
接收响应网络管理消息,但不传递他们.
. Listening:
如果一个网桥在启动或者在一定时间没有收到 BPDU 后立即认为自己是根交换机,端口进入侦听状态,
侦听状态是一种不传用户数据的STP状态,仅在端口发送和接收BPDU报文。
努力确定一个活动的拓扑,该状态有如下特征:
丢弃所有连接的网段上收到的数据帧,或者通过交换而来内部转发的帧。
接收到的BPDU直接传给系统模块。
没有地址数据库。
不传递从系统模块收到的BPDU
接收响应网络管理消息
选举根桥,根端口和指定端口发生在侦听状态期间。
在指定端口选举中失败的端口成为非指定端口,并回到阻塞状态,剩下的指定端口或者根端口在 15s 后进入学习状态。
. Learning:
学习状态是网桥不传递用户数据帧但构建桥街表并收集诸如数据帧源vlan等信息的一种STP状态。
当网桥收到一个帧,他将源 MAC 地址和端口放入桥接表,当数据转发开始后学习状态减少了所需的泛洪次数需求。
学习状态的生存时间同样受转发延迟定时器的控制,默认为 15s
一个处于学习状态的端口特性:
丢弃所有连接的网段上收到的数据帧。
丢弃从其它端口交换来的需要转发的帧
接收到的BPDU 直接传给系统模块。
接收,处理传递从系统模块收到的BPDU
接收响应网络管理消息
. Forward:转发状态,转发数据。
处于一个学习状态端口在转发延迟定时器超时后仍然是根端口或者指定端口,则将进入转发状态,
转发状态特性如下
转发所有连接的网段上收到的数据帧。
转发从其它端口交换来的需要转发的帧
将位置状态信息包含进自己的地址数据库
接收到的BPDU直接传给系统模块。
处理从系统模块收到的BPDU.
接收响应网络管理消息
. Disable:禁用状态,既不参与STP计算,也不转发数据。
在进行选举之前,需要先用传送过来的BPUD中的相关值,更新自己对应的相关值,对应的函数是:br_record_config_information 生成树算法就是利用上述四个参数在判断,判断过程总是相同的:
、确定根桥,桥ID最小的(即把包中的桥ID,同自己以前记录的那个最小的桥ID相比,机器加电时,总是以自己的桥ID为根桥ID)的为根桥;
、确定最小路径开销;
、确定最小发送方ID;
、确定最小的端口ID: 、根桥ID
我们配置了网桥后,用brctl命令会发现8000.XXXXXX这样一串,这就是网桥的ID号,用一标识每一个网桥,后面的XXXX一般 的桥的MAC地址,这样ID值就不会重复。根桥ID,是指网络中所有网桥的ID值最小的那一个,对应的具有根桥ID的桥,当然也是网络的根桥了;
、最小路径开销
动态路由中也类似这个概念,不过这里用的不是跳数(局域网不比广域网,不一定跳数大就慢,比如跳数小,是10M链路,跳数大的却是千兆链路),最初的开销 定义为1000M/链种带宽,当然,这种方式不适用于万兆网了……所以后来又有一个新的,对每一种链路定义一个常数值——详请请查阅相关资料;
、发送方ID
网桥之前要收敛出一个无环状拓朴,就需要互相发送BPDU包,当然需要把自己的ID告诉对方,这样对方好拿来互相比较;
、端口ID
端口ID由优先级+端口编号组成,用于标识某个桥的某个端口,后面比较时好用。 这四步非常地重要,后面的所有比较都是这四个步骤. 选举根交换机
STP要求每个网桥分配一个唯一的标识(BID), BID通常由优先级(2bytes)和网桥MAC地址(6bytes)
构成。根据 IEEE802.1d 规定,优先级值为 -,缺省的优先级为 (0x8000)。当交换机最初
启动时,它假定自己就是根交换机,并发送次优的 BPDU,当交换机接收到一个更低的 BID 时,它会把
自己正在发送的BPDU的根BDI替换为这个最低的根BID,所有的网桥都会接收到这些BPDU,并且判定
具有最小BID 值的网桥作为根网桥。如下图所示,假定 A,B的优先级均为32, C 的优先级为40,. 根据选举规则, 选择较小的优先级的交换机,则选择出Cat-A和Cat-B。 在A , B优先级相同的时候,
查找最小的MAC地址AA-AA-AA-AA-AA-AA.于是Cat-A被选举成为根交换机. 选举根端口
在选举根桥结束后,将选举根端口,一个网桥的根端口是按照路径开销最靠近根交换机的端口。每一个非根交换机都将选出一个根端口。
其选择过程如下:
. 根交换机Cat-A发送BPDU,他们所包含的根路径开销为0,当Cat-B收到这些BPDU后,迅速将端口 / 的路径开销累加到所收到 BPDU 的根路径开销。
假定为FastEthernet,则加上端口1/1的开销19,Cat-B / 到根路径的开销为19。
. Cat-B使用内部值19 ,并从端口1/2发送一个根路径开销为19 的BPDU
. 当Cat-C从Cat-B收到这些BPDU将计算自己到根网桥的开销为38(+)。
. Cat-C也在1/ 上收到来自Cat-A的BPDU。同时计算1/1到根网桥的开销为19。
. 根据最靠近根桥原则,C选出根端口为1/。
. Cat-C将继续向下游交换机公布其到根端口的开销为19 选举指定端口
指定端口是定义在一个网段(Segment)上的概念。在选举根端口的同时,基于到根网桥的的根路径成本的累加值的指定端口选择过程也在进行。
包含某一网段指定端口的网桥称为该网段的指定网桥。根网桥的所有活动端口都成为指定端口。
这条规则的例外是:当根网桥自身存在第一层物理环路的情况。
例如:根网桥的两个端口连接到了同一台集线器上,或者两个端口通过交叉线连接到了一起。
指定端口选举过程如下: Segment 中,根交换机 Cat-A 上 / 的路径开销为 ,Cat-B 上 / 的开销为 。故 Cat-A 的 /1为指定端口。
Segment 中,同样 Cat-A 上的 / 被选举为指定端口。
在 Segment 中。Cat-B 和 Cat-C上的1/2口,端口路径开销均为19。
此时将根据最小发送者的BID来确定,此时确定B的端口为指定端口,Cat-B为该网段的指定网桥
在某些情况下,例如Cisco的交换机每个vlan一个生成树实例,此时,将会出现BID相同的情况,
则最后比较端口ID,端口ID在同一台交换机上定义是必定不相同的,最小端口ID的端口被定义为指定端口。
注意:接入端口在指定端口过程中不扮演任何角色,所有的指定端口选举均仅在中继端口中交互,接入端口仅用于连接主机和路由器。 配置函数
void br_received_config_bpdu(struct net_bridge_port *p, struct br_config_bpdu *bpdu)
{
struct net_bridge *br;
int was_root; br = p->br; //return !memcmp(&br->bridge_id, &br->designated_root, 8);
//自己是根桥吗?用自己的br_ID和BPDU包中的根ID相比较
was_root = br_is_root_bridge(br); //把包中的值,同先前指定的对应值进行判断和比较,经确定是否需要更新
if (br_supersedes_port_info(p, bpdu)) {
br_record_config_information(p, bpdu);//刷新自己的相关信息
br_configuration_update(br);//进行root_bridge、port的选举
br_port_state_selection(br);//设置端口状态 //如果因为这个BPDU导致拓朴变化了,如自己以前是根桥,现在不是了,需要发送TCN包,进行通告
if (!br_is_root_bridge(br) && was_root) {
del_timer(&br->hello_timer);
if (br->topology_change_detected) {
del_timer(&br->topology_change_timer);
br_transmit_tcn(br);
mod_timer(&br->tcn_timer, jiffies + br->bridge_hello_time);
}
}
//如果是根端口,需要把这个BPDU包继续转发下去
//(如果自己收到数据的端口是根端口的话,那么就有可能有许多交换机(网桥)串在自己的指定端口下边,
//总得把这个包能过指定端口再发给它们吧,否则交换机就不叫交换机了)
if (p->port_no == br->root_port) {
br_record_config_timeout_values(br, bpdu);
br_config_bpdu_generation(br);
if (bpdu->topology_change_ack)
br_topology_change_acknowledged(br);
}
} else if (br_is_designated_port(p)) {//如果当前端口是designate port,则根据当前配置信息,生成BPDU,发送出去
br_reply(p); //实现为 br_transmit_config(p);
}
} 确定是否更新
static int br_supersedes_port_info(struct net_bridge_port *p, struct br_config_bpdu *bpdu)
{
int t;
//确定根桥,桥ID最小的(即把包中的桥ID,同自己以前记录的那个最小的桥ID相比,机器加电时,总是以自己的桥ID为根桥ID)的为根桥
t = memcmp(&bpdu->root, &p->designated_root, );
if (t < )
return ;
else if (t > )
return ;
//确定最小路径开销
if (bpdu->root_path_cost < p->designated_cost)
return ;
else if (bpdu->root_path_cost > p->designated_cost)
return ;
//确定最小发送方ID,要同两个桥ID比:已记录的最小发送ID和自己的ID
t = memcmp(&bpdu->bridge_id, &p->designated_bridge, );
if (t < )
return ;
else if (t > )
return ;
if (memcmp(&bpdu->bridge_id, &p->br->bridge_id, ))
return ;
//确定最小的端口ID
if (bpdu->port_id <= p->designated_port)
return ;
return ;
}
看当前桥是否就是指定的根桥,并且当前port是否就是designate port
static inline int br_is_designated_port(const struct net_bridge_port *p)
{
return !memcmp(&p->designated_bridge, &p->br->bridge_id, ) && (p->designated_port == p->port_id);
}
如果检测到有变动,则刷新自己的记录
static inline void br_record_config_information(struct net_bridge_port *p, const struct br_config_bpdu *bpdu)
{
p->designated_root = bpdu->root; //指定的根网桥的网桥ID
p->designated_cost = bpdu->root_path_cost;//指定的到根桥的链路花销
p->designated_bridge = bpdu->bridge_id;//指定的发送当前BPDU包的网桥的ID
p->designated_port = bpdu->port_id;//指定的发送当前BPDU包的网桥的端口的ID
//设置时间戳
mod_timer(&p->message_age_timer, jiffies + (p->br->max_age - bpdu->message_age));
}
根桥的选举不是在这里进行,这里进行根端口和指定端口的选举
void br_configuration_update(struct net_bridge *br)
{
br_root_selection(br);//选举根端口
br_designated_port_selection(br);//选举指定端口
}
根端口的选举br_root_selection根端口的选举同样是以上四个步骤,只是有一点小技巧:
它逐个遍历桥的每一个所属端口,找出一个符合条件的,保存下来,再用下一个来与之做比较,用变量root_port来标志
static void br_root_selection(struct net_bridge *br)
{
struct net_bridge_port *p;
u16 root_port = ; list_for_each_entry(p, &br->port_list, list) {
if (br_should_become_root_port(p, root_port))
root_port = p->port_no;
}
br->root_port = root_port; //找完了还没有找到,则认为自己就是根桥
if (!root_port) {
br->designated_root = br->bridge_id;
br->root_path_cost = ;
} else {//否则记录相应的值
p = br_get_port(br, root_port);
br->designated_root = p->designated_root;
br->root_path_cost = p->designated_cost + p->path_cost;
}
}
判断端口p是否应该变成根端口
static int br_should_become_root_port(const struct net_bridge_port *p, u16 root_port)
{
struct net_bridge *br;
struct net_bridge_port *rp;
int t; br = p->br;
//若当前端口是关闭状态或为一个指定端口,则不参与选举,返回
if (p->state == BR_STATE_DISABLED || br_is_designated_port(p))
return ;
//在根端口的选举中,根桥是没有选举权的
if (memcmp(&br->bridge_id, &p->designated_root, ) <= )
return ; if (!root_port)//没有指定等比较的端口ID(因为第一次它初始化为0的)
return ;
rp = br_get_port(br, root_port);//获取待比较的根端口 //又是四步
t = memcmp(&p->designated_root, &rp->designated_root, );
if (t < )
return ;
else if (t > )
return ;
if (p->designated_cost + p->path_cost < rp->designated_cost + rp->path_cost)
return ;
else if (p->designated_cost + p->path_cost > rp->designated_cost + rp->path_cost)
return ;
t = memcmp(&p->designated_bridge, &rp->designated_bridge, );
if (t < )
return ;
else if (t > )
return ;
if (p->designated_port < rp->designated_port)
return ;
else if (p->designated_port > rp->designated_port)
return ; if (p->port_id < rp->port_id)
return ;
return ;
}
指定端口的选举,这个过程与根端口的选举过程极为类似
static void br_designated_port_selection(struct net_bridge *br)
{
struct net_bridge_port *p;
list_for_each_entry(p, &br->port_list, list) {
if (p->state != BR_STATE_DISABLED && br_should_become_designated_port(p))
br_become_designated_port(p);
}
}
static int br_should_become_designated_port(const struct net_bridge_port *p)
{
struct net_bridge *br;
int t;
br = p->br;
if (br_is_designated_port(p))
return ; if (memcmp(&p->designated_root, &br->designated_root, ))
return ; if (br->root_path_cost < p->designated_cost)
return ;
else if (br->root_path_cost > p->designated_cost)
return ; //最小发送者的BID来确定
t = memcmp(&br->bridge_id, &p->designated_bridge, );
if (t < )
return ;
else if (t > )
return ; //最后比较端口ID,端口ID在同一台交换机上定义是必定不相同
if (p->port_id < p->designated_port)
return ; return ;
}
void br_become_designated_port(struct net_bridge_port *p)
{
struct net_bridge *br;
br = p->br;
p->designated_root = br->designated_root;
p->designated_cost = br->root_path_cost;
p->designated_bridge = br->bridge_id;
p->designated_port = p->port_id;
}
端口状态选择
void br_port_state_selection(struct net_bridge *br)
{
struct net_bridge_port *p;
list_for_each_entry(p, &br->port_list, list) { if (p->state != BR_STATE_DISABLED) {
if (p->port_no == br->root_port) {//如果端口是根端口
p->config_pending = ;
p->topology_change_ack = ;
br_make_forwarding(p);//让它forwarding
} else if (br_is_designated_port(p)) {//或者是指定端口
del_timer(&p->message_age_timer);
br_make_forwarding(p);//让它forwarding
} else {//否则
p->config_pending = ;
p->topology_change_ack = ;
br_make_blocking(p);//让它blocking
}
}
}
}
设置p->state 相应状态位就可以了
static void br_make_forwarding(struct net_bridge_port *p)
{
if (p->state == BR_STATE_BLOCKING) {
if (p->br->stp_enabled) {
p->state = BR_STATE_LISTENING;
} else {
p->state = BR_STATE_LEARNING;
}
br_log_state(p);
mod_timer(&p->forward_delay_timer, jiffies + p->br->forward_delay);
}
}
设置p->state 相应状态位就可以了
static void br_make_blocking(struct net_bridge_port *p)
{
if (p->state != BR_STATE_DISABLED && p->state != BR_STATE_BLOCKING) {
if (p->state == BR_STATE_FORWARDING || p->state == BR_STATE_LEARNING)
br_topology_change_detection(p->br); p->state = BR_STATE_BLOCKING;
br_log_state(p);
del_timer(&p->forward_delay_timer);
}
}
遍历桥的所有端口,如果是指定端口,就发送一个config类型的BPDU包
void br_config_bpdu_generation(struct net_bridge *br)
{
struct net_bridge_port *p; list_for_each_entry(p, &br->port_list, list) {
if (p->state != BR_STATE_DISABLED && br_is_designated_port(p))
br_transmit_config(p);
}
}
接收到一个tsn包
void br_received_tcn_bpdu(struct net_bridge_port *p)
{
if (br_is_designated_port(p)) { //是指定端口
pr_info("%s: received tcn bpdu on port %i(%s)\n", p->br->dev->name, p->port_no, p->dev->name);
br_topology_change_detection(p->br);
br_topology_change_acknowledge(p); //发送一个config类型的BPDU包
}
}
[/STP:生成树协议]
Bridge实现的更多相关文章
- PHP设计模式(八)桥接模式(Bridge For PHP)
一.概述 桥接模式:将两个原本不相关的类结合在一起,然后利用两个类中的方法和属性,输出一份新的结果. 二.案例 1.模拟毛笔(转) 需求:现在需要准备三种粗细(大中小),并且有五种颜色的比 如果使用蜡 ...
- Configure a bridge interface over a VLAN tagged bonded interface
SOLUTION VERIFIED February 5 2014 KB340153 Environment Red Hat Enterprise Linux 6 (All Versions) Red ...
- Create a bridge using a tagged vlan (8021.q) interface
SOLUTION VERIFIED April 27 2013 KB26727 Environment Red Hat Enterprise Linux 5 Red Hat Enterprise Li ...
- Configure bridge on a team interface using NetworkManager in RHEL 7
SOLUTION IN PROGRESS February 29 2016 KB2181361 environment Red Hat Enterprise Linux 7 Teaming,Bridg ...
- 理解 neutron(15):Neutron linux-bridge-agent 创建 linux bridge 的简要过程
学习 Neutron 系列文章: (1)Neutron 所实现的虚拟化网络 (2)Neutron OpenvSwitch + VLAN 虚拟网络 (3)Neutron OpenvSwitch + GR ...
- KVM 虚拟机联网方式:NAT 和 Bridge
KVM 客户机网络连接有两种方式: 用户网络(User Networking):让虚拟机访问主机.互联网或本地网络上的资源的简单方法,但是不能从网络或其他的客户机访问客户机,性能上也需要大的调整.NA ...
- 桥接模式/bridge模式/对象结构型
意图 将抽象部分与它的实现部分分离,使它们都可以独立的变化. 动机 当一个抽象类有多个实现时,通常用继承来协调它们.但是继承机制将抽象和实现固定,难以对抽象部分和实现部分独立地进行修改.扩充和重用. ...
- The network bridge on device VMnet0 is not running
The network bridge on device VMnet0 is not running. The virtual machine will not be able to communic ...
- Net设计模式实例之桥接模式( Bridge Pattern)
一.桥接模式简介(Brief Introduction) 桥接模式(Bridge Pattern),将抽象部分与它的实现部分分离,使的抽象和实现都可以独立地变化. Decouple an abstra ...
- Neutron 理解(14):Neutron ML2 + Linux bridge + VxLAN 组网
学习 Neutron 系列文章: (1)Neutron 所实现的虚拟化网络 (2)Neutron OpenvSwitch + VLAN 虚拟网络 (3)Neutron OpenvSwitch + GR ...
随机推荐
- Spring、struts、webwork2三者MVC的比较
http://blog.sina.com.cn/s/blog_4a69fa43010005il.html 在web应用方面,Spring有独立的MVC实现,与struts和webwork2相比毫不逊色 ...
- SQL SERVER HTTP请求
--开启Sql Server 通讯配置-- sp_configure ; GO RECONFIGURE WITH OVERRIDE; GO sp_configure ; GO RECONFIGURE ...
- UITabBarController 初学
纯代码编写UITabBarController, 不多说,直接见代码 RViewController1 *vc1 = [[RViewController1 alloc]init]; UINavigat ...
- ACM——A + B Problem (3)
Home Problems 1086 A + B Problem (3) 时间限制(普通/Java):1000MS/3000MS 运行内存限制:65536KByte总提交:2317 ...
- MVC小系列(二十二)【MVC的Session超时,导致的跳转问题】
由于mvc内部跳转机制的问题,它只在当前的action所渲染的view上进行跳转,如果希望在当前页面跳,需要将mvc方法改为js方法: filterContext.Result = new Redir ...
- html常用标签 第二节
直接上代码了 <html> <head> <title>我的第二个网页</title> </head> <body> <h ...
- javascript格式化指定的日期对象
/* * 格式化Date对象为:“2015-04-17 10:20:00” * var dateObj = new Date(); */ function formartDate(dateObj){ ...
- CCPC网络赛,HDU_5842 Lweb and String
Problem Description Lweb has a string $S$. Oneday, he decided to transform this string to a new sequ ...
- ISO 学习笔记 2015-03-15
Objective--C 一 关键字 @property 定义变量函数 @synthesize 实现变量函数 二 函数 alloc 分配内存 init 初始化 new 替代上面两个函数 分配内存,并且 ...
- Cron运行原理
from:http://blog.chinaunix.net/uid-20682147-id-4977039.html 目录 目录 1 1. 前言 1 2. 示例 1 3. 工作过程 2 4. 一个诡 ...