网桥在内核的实现

我们知道netdevice有一个priv域,这个域用来保存设备的私有数据,当这个设备是一个网桥的时候,priv域也就指向一个struct net_bridge.

接下来看net_bridge以及相关的数据结构:
struct net_bridge
{
///自旋锁
spinlock_t lock;
///网桥所有端口的链表,其中每个元素都是一个net_bridge_port结构。
struct list_head port_list;
///加到这个网桥的物理设备
struct net_device *dev;
///这个锁是用来保护下面的那个hash链表。
spinlock_t hash_lock;
///保存forwarding database的一个hash链表(这个也就是地址学习的东东,所以通过hash能 快速定位),这里每个元素都是一个net_bridge_fsb_entry结构
struct hlist_head hash[BR_HASH_SIZE];
///这个结构没有被使用
struct list_head age_list;
unsigned long feature_mask;
#ifdef CONFIG_BRIDGE_NETFILTER
struct rtable fake_rtable;
#endif
unsigned long flags;
#define BR_SET_MAC_ADDR 0x00000001

///stp相关的一些东西
bridge_id designated_root;
bridge_id bridge_id;
u32 root_path_cost;
unsigned long max_age;
unsigned long hello_time;
unsigned long forward_delay;
unsigned long bridge_max_age;
unsigned long ageing_time;
unsigned long bridge_hello_time;
unsigned long bridge_forward_delay;

u8 group_addr[ETH_ALEN];
u16 root_port;
///当前使用的协议。
enum {
BR_NO_STP, /* no spanning tree */
BR_KERNEL_STP, /* old STP in kernel */
BR_USER_STP, /* new RSTP in userspace */
} stp_enabled;

unsigned char topology_change;
unsigned char topology_change_detected;

///stp要用的一些定时器列表。
struct timer_list hello_timer;
struct timer_list tcn_timer;
struct timer_list topology_change_timer;
struct timer_list gc_timer;
struct kobject *ifobj;
};


struct net_bridge_port
{
///从属于的网桥设备
struct net_bridge *br;
///表示链接到这个端口的物理设备
struct net_device *dev;
struct list_head list;
///stp相关的一些参数。
u8 priority;
u8 state;
u16 port_no;
unsigned char topology_change_ack;
unsigned char config_pending;
port_id port_id;
port_id designated_port;
bridge_id designated_root;
bridge_id designated_bridge;
u32 path_cost;
u32 designated_cost;
///端口定时器,也就是stp控制超时的一些定时器列表.(详细的需要去看stp的协议).
struct timer_list forward_delay_timer;
struct timer_list hold_timer;
struct timer_list message_age_timer;
struct kobject kobj;
struct rcu_head rcu;
};


struct net_bridge_fdb_entry
{
struct hlist_node hlist;
///桥的端口(最主要的两个域就是这个域和下面的mac地址域)
struct net_bridge_port *dst;
///当使用RCU策略,才用到
struct rcu_head rcu;
///引用计数
atomic_t use_count;
unsigned long ageing_timer;
///mac地址。
mac_addr addr;
unsigned char is_local;
unsigned char is_static;
};

通过下面的图能更好的理解这个结构:

[img]/upload/attachment/114524/d0eb9518-ed40-3df1-9cab-6a269b0d7f9a.jpg[/img]

接下来简要的介绍一下网桥的初始化。

网桥的初始化和一般网络设备的初始化很相似,只不过由于它是虚拟设备,因此这里还有一点不同。

首先来看内核的网络模块的初始化br_init,也就是初始化上面介绍的数据结构:

static int __init br_init(void)
{
int err;
///stp的注册。
err = stp_proto_register(&br_stp_proto);
if (err < 0) {
printk(KERN_ERR "bridge: can't register sap for STP\n");
return err;
}

///forwarding database的初始化
err = br_fdb_init();
if (err)
goto err_out;
///网桥的netfilter钩子函数的初始化。
err = br_netfilter_init();
if (err)
goto err_out1;
///注册到netdevice的通知链上
err = register_netdevice_notifier(&br_device_notifier);
if (err)
goto err_out2;

err = br_netlink_init();
if (err)
goto err_out3;
///安装网络设备的do_ioctl函数,也就是提供给用户空间ioctl接口。
brioctl_set(br_ioctl_deviceless_stub);
br_handle_frame_hook = br_handle_frame;

br_fdb_get_hook = br_fdb_get;
br_fdb_put_hook = br_fdb_put;

return 0;
.........................................
return err;
}


我们新建一个网桥,使用br_add_bridge,在这个函数中,主要是调用new_bridge_dev函数,下面我们主要就来看这个函数:

static struct net_device *new_bridge_dev(const char *name)
{
struct net_bridge *br;
struct net_device *dev;

///这里看到setup回调函数,是br_dev_setup(也就是网桥设备专用的)。setup函数的用途,可以看我以前写的网络设备初始化的blog。
dev = alloc_netdev(sizeof(struct net_bridge), name,
br_dev_setup);

if (!dev)
return NULL;
///得到priv数据。
br = netdev_priv(dev);

///接下来初始化br数据结构。
br->dev = dev;

spin_lock_init(&br->lock);
INIT_LIST_HEAD(&br->port_list);
spin_lock_init(&br->hash_lock);

///网桥优先级 32768(也就是默认是0x8000)
br->bridge_id.prio[0] = 0x80;
br->bridge_id.prio[1] = 0x00;

memcpy(br->group_addr, br_group_address, ETH_ALEN);

br->feature_mask = dev->features;
br->stp_enabled = BR_NO_STP;
br->designated_root = br->bridge_id;
br->root_path_cost = 0;
br->root_port = 0;
br->bridge_max_age = br->max_age = 20 * HZ;
br->bridge_hello_time = br->hello_time = 2 * HZ;
br->bridge_forward_delay = br->forward_delay = 15 * HZ;
br->topology_change = 0;
br->topology_change_detected = 0;
br->ageing_time = 300 * HZ;
///初始化网桥设备的netfilter相关域。
br_netfilter_rtable_init(br);

INIT_LIST_HEAD(&br->age_list);

br_stp_timer_init(br);

return dev;
}


加一个新端口到一个网桥使用br_add_if方法。这里就不详细介绍这个方法了,不过这里要注意,他会在sys文件系统下,生成一些相关的东西。要看sysfs的介绍,去看kernel的文档。

最后来看一下网桥的子系统在这个网络子系统的位置:


[img]/upload/attachment/114518/b254a548-47d2-3405-8abe-5b325bd94ff1.jpg[/img]

可以看到这里有很多的hook在ip层,基本都是netfilter子系统的东西。

这里网桥的输入帧的处理是通过br_handle_frame来处理的。而网桥的输出帧是通过br_dev_xmit来处理的。

当网络帧通过NIC的设备驱动被接收了之后,skb->dev被实例化为真实的设备,然后这个帧被放入网络栈,然后当be_handle_frame_finish之后调用br_pass_frame_up。我们来看这个函数的实现:

[quote]static void br_pass_frame_up(struct net_bridge *br, struct sk_buff *skb)
{
struct net_device *indev, *brdev = br->dev;

brdev->stats.rx_packets++;
brdev->stats.rx_bytes += skb->len;

indev = skb->dev;
///这步将真实的物理设备替换为虚拟的网桥设备。因此对3层来说就完全不知道物理设备的存在了。
skb->dev = brdev;
///调用netfiltel的相关hook。
NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL,
netif_receive_skb);
}[/quote]

当这个函数执行完毕后,会再次调用netif_receive_skb,他则会再次调用handle_bridge,而此时由于设备已经替换为虚拟的网桥设备,因此就会直接将包发往下层正确的协议处理。


[img]/upload/attachment/114538/e3be20e3-fa99-3465-badb-8c18611b11b3.jpg[/img]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值