目录
1.2 注册通知链 struct notifier_block br_device_notifier
1.3 注册用户态 ioctl 接口 br_ioctl_deviceless_stub()
1 初始化 br_init()
对于网桥初始化所需要做的功能,主要有以下几项
- 调用 stp_proto_register 进行 stp 协议相关的初始化
- 调用 br_fdb_init 进行CAM表的初始化
- 调用 register_pernet_subsys,为bridge模块注册网络命名空间。而 br_net_ops 的 init函数为NULL,所以调用register_pernet_subsys并没有在/proc目录下生成任何与bridge相关的目录,如果我们想在/proc下生成bridge相关的子目录或子文件,我们可以自己写init函数。
- 调用函数 br_netfilter_init,注册网络防火墙相关的钩子函数,主要是实现ebtables相关的功能
- 调用函数 register_netdevice_notifier,向通知链中注册网桥感兴趣的信息。
- 调用函数 br_netlink_init,进行netlink的初始化
- 调用 brioctl_set,设置网桥相关的 ioctl 回调函数 br_ioctl_deviceless_stub
static int __init br_init(void)
{
int err;
err = stp_proto_register(&br_stp_proto);
if (err < 0) {
pr_err("bridge: can't register sap for STP\n");
return err;
}
err = br_fdb_init();
if (err)
goto err_out;
err = register_pernet_subsys(&br_net_ops);
if (err)
goto err_out1;
err = br_netfilter_init();
if (err)
goto err_out2;
err = register_netdevice_notifier(&br_device_notifier);
if (err)
goto err_out3;
err = br_netlink_init();
if (err)
goto err_out4;
brioctl_set(br_ioctl_deviceless_stub);
#if IS_ENABLED(CONFIG_ATM_LANE)
br_fdb_test_addr_hook = br_fdb_test_addr;
#endif
return 0;
err_out4:
unregister_netdevice_notifier(&br_device_notifier);
err_out3:
br_netfilter_fini();
err_out2:
unregister_pernet_subsys(&br_net_ops);
err_out1:
br_fdb_fini();
err_out:
stp_proto_unregister(&br_stp_proto);
return err;
}
1.1 CAM 转发表初始化 br_fdb_init()
该函数主要是调用kmem_cache_create,获取一块slab缓存br_fdb_cache
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;
}
1.2 注册通知链 struct notifier_block br_device_notifier
网桥通过调用register_netdevice_notifier,向netdev_chain中注册了网桥相关的链表元素br_device_notifier,br_device_notifier的定义如下(关于通知链的详细分析可参看 linux 内核通知链)。
struct notifier_block br_device_notifier = {
.notifier_call = br_device_event
};
进入函数br_device_event,我们可以发现网桥关心的事件有以下几个:
#define NETDEV_UP 0x0001 /* For now you can't veto a device up/down */
#define NETDEV_DOWN 0x0002
#define NETDEV_CHANGE 0x0004 /* Notify device state change */
#define NETDEV_REGISTER 0x0005
#define NETDEV_UNREGISTER 0x0006
#define NETDEV_CHANGEMTU 0x0007
#define NETDEV_CHANGEADDR 0x0008 //需要刷新CAM表
#define NETDEV_CHANGENAME 0x000A
#define NETDEV_FEAT_CHANGE 0x000B
对于NETDEV_CHANGEADDR事件,网桥需要更新CAM表;对于NETDEV_CHANGE、NETDEV_DOWN、NETDEV_UP事件,网桥需要更改网桥端口的状态。
1.3 注册用户态 ioctl 接口 br_ioctl_deviceless_stub()
通过调用 brioctl_set,将 br_ioctl_deviceless_stub() 赋值给回调函数 br_ioctl_hook,而 br_ioctl_hook 在 sock_ioctl 中使用,这样通过在应用层调用 socket 的 ioctl 函数,就能够进行网桥的添加与删除了。
而函数 br_ioctl_deviceless_stub 能够对SIOCBRADDBR、SIOCBRDELBR、BRCTL_GET_BRIDGES、BRCTL_GET_VERSION 等操作。如果我们想增加新的ioctl,用于我们新开放的功能,就可以在该函数里增加新的case即可。
int br_ioctl_deviceless_stub(struct net *net, unsigned int cmd, void __user *uarg)
{
switch (cmd) {
case SIOCGIFBR:
case SIOCSIFBR:
return old_deviceless(net, uarg);
case SIOCBRADDBR:
case SIOCBRDELBR:
{
char buf[IFNAMSIZ];
if (!ns_capable(net->user_ns, 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(net, buf);
return br_del_bridge(net, buf);
}
}
return -EOPNOTSUPP;
}