转载一篇Bridge的数据在内核处理流程,文章写的不错啊!
(2013-07-05 16:08:48)分类: linux学习文档 |
作者:林海枫
本文地址: http://blog.csdn.net/linyt/archive/2010/01/14/5191512.aspx
注:本文由作者所拥用,欢迎转载,但请全文转载并注明作者,请勿用于 任何商途。
本文分析的kernel版本为:2.6.24.4,网桥代码目录为:linux-2.6.24.4/net/bridge。
本文着重分析网桥的基本功能,关于STP的功能,我想从另写一篇文章进行分析。由于时间仓促,分析可能存在不足之外。
第一部分: 网桥的报文处理功能分析
1.1
Linux网桥的配置实例
http://p.blog.csdn.net/images/p_blog_csdn_net/linyt/644232/o_clip_image002_thumb_633991920335773750.jpg
Brctl addbr br0 (建立一个网桥br0, 同时在Linux内核里面创建虚拟网卡br0)
Brctl addif br0 eth1
Brctl addif br0 eth2
Brctl addif br0 eth3 (分别为网桥br0添加接口eth1, eth2和eth3)
1.2 网桥的数据结构
http://p.blog.csdn.net/images/p_blog_csdn_net/linyt/644232/o_clip_image004_thumb.jpg
1.3 网桥数据包入口
[linux-2.6.24.4/net/core/dev.c]
- int
netif_receive_skb( structsk_buff *skb) -
{
-
//当网络设备收到网络数据包时,最终会在软件中断环境里调用此函数 -
//检查该数据包是否有packet socket来接收该包,如果有则往该socket -
//拷贝一份,由deliver_skb来完成。 -
list_for_each_entry_rcu(ptype, &ptype_all, list) { -
if (!ptype->dev || ptype->dev == skb->dev) { -
if (pt_prev) -
ret = deliver_skb(skb, pt_prev, orig_dev); -
pt_prev = ptype; -
} -
} -
// 先试着将该数据包让网桥函数来处理,如果该数据包的入口接口确实是网桥接口, -
// 则按网桥方式来处理,并且handle_bridge返回NULL,表示网桥已处理了。 -
// 如果不是网桥接口的数据包,则不应该让网桥来处理,handle_bridge返回skb, -
// 后面代码会让协议栈来处理上层协议。 -
skb = handle_bridge(skb, &pt_prev, &ret, orig_dev); -
if (!skb) -
goto out; -
skb = handle_macvlan(skb, &pt_prev, &ret, orig_dev); -
if (!skb) -
goto out; -
//对该数据包转达到它L3协议的处理函数 -
type = skb->protocol; -
list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&15], list) { -
if (ptype->type == type && -
(!ptype->dev || ptype->dev == skb->dev)) { -
if (pt_prev) -
ret = deliver_skb(skb, pt_prev, orig_dev); -
pt_prev = ptype; -
} -
} -
}
1.4
handle_bridge处理函数
[linux-2.6.24.4/net/core/dev.c]
- static
inline struct sk_buff struct*handle_bridge( sk_buff *skb, -
struct packet_type int**pt_prev, *ret, -
struct net_device *orig_dev) -
{
-
struct net_bridge_port *port; -
//如果该数据包产生于本机,而目标同时为本机。 -
if (skb->pkt_type == PACKET_LOOPBACK || -
//如果该数据包的输入接口不是网桥接口 -
(port = rcu_dereference(skb->dev->br_port)) == NULL) -
// 以上两种情况都需要让上层协议进行处理 -
return skb; -
if (*pt_prev) { -
*ret = deliver_skb(skb, *pt_prev, orig_dev); -
*pt_prev = NULL; -
} -
//数据包的入口接口是网桥接口。下面将按网桥逻辑进行处理。 -
//如假包换,数据包转达到真正的网桥处理函数 -
//br_handle_frame_hook在网桥模块的init函数被初始化为 -
//br_handle_frame -
return br_handle_frame_hook(port, skb); -
}
1.5 网桥处理逻辑
[linux-2.6.24.4/net/bridge/br_input.c]
- struct
sk_buff struct*br_handle_frame( net_bridge_port struct*p, sk_buff *skb) -
{
-
//所有网桥通信的数据包都会进入到这里,谓之为网桥处理函数 -
const unsigned char*dest = eth_hdr(skb)->h_dest; -
int (*rhook)( structsk_buff *skb); -
-
if (!is_valid_ether_addr(eth_hdr(skb)->h_source)) -
goto drop; -
//如果skb是share的,则拷贝一份 -
skb = skb_share_check(skb, GFP_ATOMIC); -
if (!skb) -
return NULL; -
if (unlikely(is_link_local(dest))) { -
-
if (skb->protocol == htons(ETH_P_PAUSE)) -
goto drop; -
//如果该数据包的目标地址为STP协议的组播地址,并且该网桥启用STP功能, -
//则,结束该数据包的处理,它将会在第(2)处理得到处理 -
if (p->br->stp_enabled != BR_NO_STP) { -
if (NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev, -
NULL, br_handle_local_finish)) -
return NULL; -
else -
return skb; -
} -
// 如果该包是发往网桥组播的,但该网桥没有启用STP功能,则在下面处理, -
// 并返回已处理的标识(返回NULL)来通知代码(2)处不需再处理。 -
} -
switch (p->state) { -
case BR_STATE_FORWARDING: -
rhook = rcu_dereference(br_should_route_hook); -
if (rhook != NULL) { -
if (rhook(skb)) -
// 如果该接口处于Forwarding状态,并且该报文必需要走L3层 -
// 进行转发,则直接返回,让代码(2)进行处理。 -
// br_should_route_hook钩子函数在ebtable里面设置为ebt_broute函数, -
//它根据用户的规则来决定该报文是否要能通过L3来转发。 -
return skb; -
dest = eth_hdr(skb)->h_dest; -
} -
-
case BR_STATE_LEARNING: -
if (!compare_ether_addr(p->br->dev->dev_addr, dest)) -
//当用内核创建一个网桥的同时也会创建一个虚拟的网络设备,它的名字 -
//为网桥的名字,保存在p->br->dev指针里。P->br->dev和port_list里面的 -
//接口共同组成一个网桥。如果该报文是要发往此接,则标记skb->pkt_type为 -
//PACKET_HOST。因为报文最终是要发送到p->br->dev的输送队列里面, -
//正如一般的网卡驱动程序将数据包送往到某个net_device的输入队列一样, -
//这样bridge功能充当了虚拟网卡(如例子中的br0)驱动,应当设置 -
//skb->pkt_type -
//为PACKET_HOST,表明数据包是要发送该接口,而非是因为打开混杂模式 -
//而接收到的。 -
skb->pkt_type = PACKET_HOST; -
// 接着由br_handle_frame_finish函数继续处理。 -
NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL, -
br_handle_frame_finish); -
break; -
default: -
//其它状态下的端口,不能处理数据包,直接丢弃。 -
drop: -
kfree_skb(skb); -
} -
// 该数据包要么被网桥处理了,要么处理时出错,不需要上层协议处理, -
// 返回NULL,代码(2)处不会处理该报文。 -
return NULL; -
}
1.6
br_handle_frame_finish函数
[linux-2.6.24.4/net/bridge/br_input.c]
- int
br_handle_frame_finish( structsk_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; -
if (!p || p->state == BR_STATE_DISABLED) -
goto drop; -
//对所有报的源MAC地址进行学习,这是网桥的特点之一, -
//通过对源地址的学习来建立MAC地址到端口的映射。 -
br = p->br; -
br_fdb_update(br, p, eth_hdr(skb)->h_source); -
if (p->state == BR_STATE_LEARNING) -
goto drop; -
// skb2指针表明,有数据要发往本机的网络接口,即p->br->dev接口。 -
skb2 = NULL; -
// 如果应用程序要dump本机接口的数据,那么该数据包应往主机发一份, -
// 一个明显的例子就是在用户在运行tcpdump –I br0或类似的程序。 -
if (br->dev->flags & IFF_PROMISC) -
skb2 = skb; -
dst = NULL; -
if (is_multicast_ether_addr(dest)) { -
// 如果该报文是一个L2多播报文(如arp请求),那么它应该转发到 -
// 该网桥的所有接口。 -
// 这同样是网桥的一个特点,广播和组播报文要转发到它的所有接口。 -
br->statistics.multicast++; -
skb2 = skb; -
} else if ((dst = __br_fdb_get(br, dest)) && dst->is_local) { -
// __br_fdb_get函数先查MAC-端口映射表,这一步是网桥的关键。 -
// 这个报文应从哪个接口转发出去就看它了。 -
// 如果这个报文应发往本机,那么skb置空。不需要再转发了, -
// 因为发往本机接口从逻辑上来说本身就是一个转发。 -
skb2 = skb; -
skb = NULL; -
} -
if (skb2 == skb) -
skb2 = skb_clone(skb, GFP_ATOMIC); -
// skb2不为空,表明要发往本机,br_pass_frame_up函数来完成发往 -
// 本机的工作。 -
if (skb2) -
br_pass_frame_up(br, skb2); -
if (skb) { -
if (dst) -
// 由br_forward函数从dst所指向的端口将该报文发出去。 -
br_forward(dst->dst, skb); -
else -
// 此报文是广播或组播报文,由br_flood_forward函数把报文向所有 -
// 端口转发出去。 -
br_flood_forward(br, skb); -
} -
out: -
return 0; -
drop: -
kfree_skb(skb); -
goto out; -
}
1.7
通过br_pass_frame_up函数将报文发往本机接口。
[linux-2.6.24.4/net/bridge/br_input.c]
- static
void br_pass_frame_up( structnet_bridge struct*br, 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是本机和网桥相连的接口。当报文经网桥处理后,发现 -
//该报文应该发往本机,那就使用netif_receive_skb函数将该报文向上层 -
//协议投递。并且要将skb->dev设置为本机接口即br->dev,并且所有数据在 -
//它的入口接口indev的驱动中已处理完毕,因此可直接通知上层协议来处理。 -
NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL, -
netif_receive_skb); -
}
1.8
通过br_forward函数将报文从另一个端口转发出去
- void
br_forward( conststruct net_bridge_port struct*to, sk_buff *skb) -
{
-
if (should_deliver(to, skb)) { -
__br_forward(to, skb); -
return; -
} -
kfree_skb(skb); -
}
[linux-2.6.24.4/net/bridge/br_forward.c]
- static
inline int should_deliver( conststruct net_bridge_port *p, -
const struct sk_buff *skb) -
{
-
//1) 入口端口和出口端口不能相同,如果是相同的话,那么源主机和目标 -
// 主机在同一端口的子网段中,也即源主机和目标主机在同一广播域里面, -
// 目标主机和网桥都会同时收到该报文,因此网桥无需多此一举。 -
//2) 如果出口端口的状态不是Forwarding,则不能转发出去。如果一个网桥 -
// 没有启用STP功能,并且网络接口的状态为UP,那么它网桥端口的状态 -
// 为Forwarding。如果启用STP,每个端口都有一个严格的状态,规定那些 -
// 端口在什么情况下才能成为Forwarding状态,否则容易造成环路,产生 -
// 网络风暴。 -
return (skb->dev != p->dev && p->state == BR_STATE_FORWARDING); -
}
[linux-2.6.24.4/net/bridge/br_forward.c]
- static
void __br_forward( conststruct net_bridge_port struct*to, sk_buff *skb) -
{
-
struct net_device *indev; -
indev = skb->dev; -
skb->dev = to->dev; -
skb_forward_csum(skb); -
// 通过br_forward_finish函数最终完成转发功能 -
NF_HOOK(PF_BRIDGE, NF_BR_FORWARD, skb, indev, skb->dev, -
br_forward_finish); -
}
[linux-2.6.24.4/net/bridge/br_forward.c]
- int
br_forward_finish( structsk_buff *skb) -
{
-
return NF_HOOK(PF_BRIDGE, NF_BR_POST_ROUTING, skb, NULL, skb->dev, br_dev_queue_push_xmit); -
}
-
[linux-2.6.24.4/net/bridge/br_forward.c]
- int
br_dev_queue_push_xmit( structsk_buff *skb) -
{
-
-
if (packet_length(skb) > skb->dev->mtu && !skb_is_gso(skb)) -
kfree_skb(skb); -
else { -
-
if (nf_bridge_maybe_copy_header(skb)) -
kfree_skb(skb); -
else { -
// 网桥在处理数据包里,只需拆包来获得目标MAC地址,而不需要 -
// 更改数据包的任何内容。但在入口网卡的驱动中已将以太网头部 -
// 剥掉,现在需要将它套上。Skb_push函数实现这一功能。 -
skb_push(skb, ETH_HLEN); -
// 放到网卡输出队列里,该网卡驱动将它送出去。 -
dev_queue_xmit(skb); -
} -
} -
return 0; -
}
1.9
br_flood_forward 函数把报文转发到网桥所有出口端口
[linux-2.6.24.4/net/bridge/br_forward.c]
- void
br_flood_forward( structnet_bridge struct*br, sk_buff *skb) -
{
-
br_flood(br, skb, __br_forward); -
}
[linux-2.6.24.4/net/bridge/br_forward.c]
- static
void br_flood( structnet_bridge struct*br, sk_buff *skb, -
void (*__packet_hook)( conststruct net_bridge_port *p, -
struct sk_buff *skb)) -
{
-
struct net_bridge_port *p; -
struct net_bridge_port *prev; -
prev = NULL; -
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->statistics.tx_dropped++; -
kfree_skb(skb); -
return; -
} -
__packet_hook(prev, skb2); -
} -
prev = p; -
} -
} -
if (prev != NULL) { -
__packet_hook(prev, skb); -
return; -
} -
kfree_skb(skb); -
}
第二部分:网桥转发数据库的维护
2.1 数据库的创建和销毁
[linux-2.6.24.4/net/bridge/br_fdb.c]
-
int __init void)br_fdb_init( -
{
-
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; -
}
-
[linux-2.6.24.4/net/bridge/br_fdb.c]
- void
br_fdb_fini( void) -
{
-
kmem_cache_destroy(br_fdb_cache); -
}
2.2 数据库更新
[linux-2.6.24.4/net/bridge/br_fdb.c]
- void
br_fdb_update( structnet_bridge struct*br, net_bridge_port *source, -
const unsigned char*addr) -
{
-
// br_mac_hash函数是hash表中的hash函数,具体算法过程可参阅该函数代码。 -
// br->hash就是数据库的hash表,每个hash值对应一个链表。数据库的每项为 -
// net_bridge_fdb_entry结构。 -
struct hlist_head *head = &br->hash[br_mac_hash(addr)]; -
struct net_bridge_fdb_entry *fdb; -
-
if (hold_time(br) == 0) -
return; -
-
if (!(source->state == BR_STATE_LEARNING || -
source->state == BR_STATE_FORWARDING)) -
return; -
fdb = fdb_find(head, addr); -
if (likely(fdb)) { -
// 接收到的MAC地址竟然是自己端口的MAC地址,确实不应该有这样的 -
// 事情发生。 -
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 { -
// 收到该MAC地址的报文,更新它的年龄。 -
fdb->dst = source; -
fdb->ageing_timer = jiffies; -
} -
} else { -
spin_lock(&br->hash_lock); -
if (!fdb_find(head, addr)) -
// 这是新的MAC地址,在数据库里为之创建一个数据项。 -
fdb_create(head, source, addr, 0); -
-
spin_unlock(&br->hash_lock); -
} -
}
2.3 创建数据项
[linux-2.6.24.4/net/bridge/br_fdb.c]
-
static struct net_bridge_fdb_entry struct*fdb_create( 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, 1); -
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; -
}
2.4 查找数据项。
[linux-2.6.24.4/net/bridge/br_fdb.c]
- static
inline struct net_bridge_fdb_entry struct*fdb_find( hlist_head *head, -
const unsigned char*addr) -
{
-
struct hlist_node *h; -
struct net_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; -
}
- struct
net_bridge_fdb_entry struct*__br_fdb_get( net_bridge *br, -
const unsigned char*addr) -
{
-
struct hlist_node *h; -
struct net_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; -
}
[linux-2.6.24.4/net/bridge/br_fdb.c]
- static
inline int has_expired( conststruct net_bridge *br, -
const struct net_bridge_fdb_entry *fdb) -
{
-
// 如果该数据项是静态的,即不是学习过来的,它永远不会过期。因为它就是 -
// 网桥自己端口的地址。 -
// 如果现在时间,与该数据项的最近更新时间和可保留时间之和相等, -
// 或者更早,则为过期。 -
return !fdb->is_static -
&& time_before_eq(fdb->ageing_timer + hold_time(br), jiffies); -
}
- //
数据项的可保留时间根据拓扑结构是否改变来决定,改变则为forward_delay, - //
否则为ageing_time。 - static
inline unsigned longhold_time( conststruct net_bridge *br) -
{
-
return br->topology_change ? br->forward_delay : br->ageing_time; -
}
第三部分: ioctl管理网桥
3.1 通过ioctl系统调用创建网桥
[linux-2.6.24.4/net/bridge/br.c]
brioctl_set(br_ioctl_deviceless_stub);
[linux-2.6.24.4/net/socket.c]
- void
brioctl_set( int(*hook) struct( net int,*, unsigned void __user *)) -
{
-
mutex_lock(&br_ioctl_mutex); -
br_ioctl_hook = hook; -
mutex_unlock(&br_ioctl_mutex); -
}
Br_ioctl_deviceless_stub函数代码和分析如下:
[linux-2.6.24.4/net/bridge/br_ioctl.c]
- int
br_ioctl_deviceless_stub( structnet int*net, unsigned cmd, void__user *uarg) -
{
-
switch (cmd) { -
case SIOCGIFBR: -
case SIOCSIFBR: -
// 这两个网桥命令是比较老式的,我们在这里不作讨论 -
return old_deviceless(uarg); -
// 新式的网桥ioctl命令有两个,添加新网桥和删除现有的网桥 -
// 需要用户空间提供网桥的名字。 -
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-1] = 0; -
if (cmd == SIOCBRADDBR) -
return br_add_bridge(buf); -
return br_del_bridge(buf); -
} -
} -
return -EOPNOTSUPP; -
}
[linux-2.6.24.4/net/bridge/br_if.c]
- int
br_add_bridge( constchar *name) -
{
-
struct net_device *dev; -
int ret; -
// 创建网桥的核心工作,创建一个与网桥同名的网络设备。 -
// 可以通过该设备分配的IP地址来管理该网桥。 同时该设备 -
// 是虚拟的设备,它的接收包和发送包处理函数与一般的真实网卡 -
// 设备不同。 -
dev = new_bridge_dev(name); -
if (!dev) -
return -ENOMEM; -
rtnl_lock(); -
if (strchr(dev->name, '%')){ -
ret = dev_alloc_name(dev, dev->name); -
if (ret < 0) { -
free_netdev(dev); -
goto out; -
} -
} -
// 向kernel注册该网桥设备,这样在用户空间就以使用 -
// ifconfig来为之分配IP,或通ioctl来对该网桥添加新的接口。 -
ret = register_netdevice(dev); -
if (ret) -
goto out; -
ret = br_sysfs_addbr(dev); -
if (ret) -
unregister_netdevice(dev); -
out:
-
rtnl_unlock(); -
return ret; -
}
[linux-2.6.24.4/net/bridge/br_if.c]
- static
struct net_device const*new_bridge_dev( char *name) -
{
-
struct net_bridge *br; -
struct net_device *dev; -
// 分配net_device结构,它的priv数据为net_bridge结构体。 -
// br_dev_setup函数初化了net_device结构的很多函数指针。 -
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); -
br->bridge_id.prio[0] = 0x80; -
br->bridge_id.prio[1] = 0x00; -
…. -
return dev; -
}
- void
br_dev_setup( structnet_device *dev) -
{
-
// 为该网桥设备随机分配MAC地址 -
random_ether_addr(dev->dev_addr); -
// 初始化dev的部分函数指针,因为目前网桥设备主适用于以及网 -
// 以太网的部分功能对它也适用。 -
ether_setup(dev); -
// 设置设备的ioctl函数为br_dev_ioctl。下面可以看到通过该ioctl函数 -
// 来为网桥添加网络接口。 -
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_ETHTOOL_OPS(dev, &br_ethtool_ops); -
dev->stop = br_dev_stop; -
dev->tx_queue_len = 0; -
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_GSO_MASK | NETIF_F_NO_CSUM | NETIF_F_LLTX; -
}
3.2 通过ioctl系统调用为网桥添加端口
[linux-2.6.24.4/net/bridge/br_ioctl.c]
- //
dev 为网桥接口,ifreq 为添加/删除的物理接口的参数 - int
br_dev_ioctl( structnet_device struct*dev, ifreq int*rq, 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); -
} -
pr_debug("Bridge does ,not support ioctl 0x%x/n" cmd); -
return -EOPNOTSUPP; -
}
[linux-2.6.24.4/net/bridge/br_ioctl.c]
- //
br 网桥,ifindex 添加/删除物理接口的index - static
int add_del_if( structnet_bridge int*br, ifindex, intisadd) -
{
-
struct net_device *dev; -
int ret; -
-
if (!capable(CAP_NET_ADMIN)) -
return -EPERM; -
dev = dev_get_by_index(&init_net, 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; -
}
[linux-2.6.24.4/net/bridge/br_if.c]
- int
br_add_if( structnet_bridge struct*br, net_device *dev) -
{
-
struct net_bridge_port *p; -
int err = 0; -
// Kernel仅支持以太网网桥 -
if (dev->flags & IFF_LOOPBACK || dev->type != ARPHRD_ETHER) -
return -EINVAL; -
// 把网桥接口当作物理接口加入到另一个网桥中,是不行的。 -
// 逻辑和代码上都会出现 loop -
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; -
// 将该接口的物理地址写入到 MAC-端口映射表中。 -
// 该MAC是属于网桥内部端口的固定MAC地址, -
// 它在fdb中的记录是固定的,不会失效(agged) -
err = br_fdb_insert(br, p, dev->dev_addr); -
if (err) -
goto err1; -
err = br_sysfs_addif(p); -
-
if (err) -
goto err2; -
rcu_assign_pointer(dev->br_port, p); -
// 打开该接口的混杂模式,网桥中的各个端口必须处于 -
// 混杂模式,网桥才能正确工作。 -
dev_set_promiscuity(dev, 1); -
// 加到端口列表 -
list_add_rcu(&p->list, &br->port_list); -
spin_lock_bh(&br->lock); -
br_stp_recalculate_bridge_id(br); -
br_features_recompute(br); -
if ((dev->flags & IFF_UP) && netif_carrier_ok(dev) && -
(br->dev->flags & IFF_UP)) -
br_stp_enable_port(p); -
spin_unlock_bh(&br->lock); -
br_ifinfo_notify(RTM_NEWLINK, p); -
dev_set_mtu(br->dev, br_min_mtu(br)); -
kobject_uevent(&p->kobj, KOBJ_ADD); -
return 0; -
err2:
-
br_fdb_delete_by_port(br, p, 1); -
err1:
-
kobject_del(&p->kobj); -
err0:
-
kobject_put(&p->kobj); -
return err; -
}
第四部分: 总结