第三部分: ioctl管理网桥

3.1 通过ioctl系统调用创建网桥

仍然以前的配置作为例,我们分用户空间程序brctl是如何通过ioctl系统调用在kernel空间内创建上述的数据结构。创建网桥,我们不需要预知任

何网络设备信息,因此我们通过ioctl来创建网桥时不应该与任何网络设备绑定到一起。网桥模块为此ioctl函数提供了一个恰如其分的名字

br_ioctl_deviceless_stub。Brctl工具使用的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 *, unsigned int, void __user *))   {

mutex_lock(&br_ioctl_mutex);

br_ioctl_hook = hook;

mutex_unlock(&br_ioctl_mutex);

}

void brioctl_set(int (*hook) (struct net *, unsigned int, void __user *)) {

mutex_lock(&br_ioctl_mutex);

br_ioctl_hook = hook;

mutex_unlock(&br_ioctl_mutex);

}

用户空间程序使用网桥相关的命令来调用ioctl函数时,它经kernel依据命令所属的分类分派到sock_ioctl函数。在sock_ioctl函

数里面,当ioctl命令为SIOCGIFBR,SIOCSIFBR, SIOCBRADDBR

和SIOCBRDELBR,它将ioctl的请求转发到br_ioctl_deviceless_stub函数。

Br_ioctl_deviceless_stub函数代码和分析如下:

[linux-2.6.24.4/net/bridge/br_ioctl.c]

int br_ioctl_deviceless_stub(struct net *net, unsigned int 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;

}

int br_ioctl_deviceless_stub(struct net *net, unsigned int 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;

}

该函数调用br_add_bridge和br_del_brdge函数的实现新建和删除网桥的功能。由于这两个函数所完成的事情刚好相反,在此,我们只讨论br_add_bridge的代码:

[linux-2.6.24.4/net/bridge/br_if.c]

int br_add_bridge(const char *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;

}

int br_add_bridge(const char *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;

}

现在创建网桥设备的任务落到new_bridge_dev的身上。New_bridge_dev函数的功能与一般的网卡驱动初化为代码非常类似的。因为这里段代就创建一个网桥设备,从这个层面来说,这段代码也算是驱动代码,结构和真实驱动非常类似。

[linux-2.6.24.4/net/bridge/br_if.c]

static struct net_device *new_bridge_dev(const 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;

}

static struct net_device *new_bridge_dev(const 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;

}

[linux-2.6.24.4/net/bridge/br_device.c]

void br_dev_setup(struct net_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;

}

void br_dev_setup(struct net_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系统调用为网桥添加端口

仅仅创建网桥,还是不够的。实际应用中的网桥需要添加实际的端口(即物理接口),如例子中的eth1, eth2等。应用程序在使用ioctl来为网桥增加物理接口,br_dev_ioctl的代码和分析如下:

[linux-2.6.24.4/net/bridge/br_ioctl.c]

// dev 为网桥接口,ifreq 为添加/删除的物理接口的参数

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);

}

pr_debug("Bridge does not support ioctl 0x%x\n", cmd);

return -EOPNOTSUPP;

}

// dev 为网桥接口,ifreq 为添加/删除的物理接口的参数

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);

}

pr_debug("Bridge does not support ioctl 0x%x\n", cmd);

return -EOPNOTSUPP;

}

这段代码一目了然,通过add_del_if函数来控制网桥的物理接口,该函数的代码和分析如下:

[linux-2.6.24.4/net/bridge/br_ioctl.c]

// br 网桥,ifindex 添加/删除物理接口的index

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(&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;

}

// br 网桥,ifindex 添加/删除物理接口的index

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(&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;

}

具体的代码在br_add_if和br_del_if中,出于讨论的方便,我们只分析br_add_if函数。

[linux-2.6.24.4/net/bridge/br_if.c]

int br_add_if(struct net_bridge *br, struct 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;

}

int br_add_if(struct net_bridge *br, struct 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;

}

第四部分: 总结

网桥是2层的网格连接设备,它工作在协议栈的第二层。本文以简单的例子作为基础,分析网桥处理报文,更新MAC-端口映射表,和如何控制网桥和端口的功

能。文中帖上了大量的关键代码,并以代码加上注释这种贴近程序员的方式来分析代码。对于缺少kernel网络编程经验的朋友,在某些代码处,写了在背景知

识的分析和解释。

linux内核网桥源码,Linux-kernel网桥代码分析(二)相关推荐

  1. linux内核io源码,Linux Kernel do_io_submit()函数整数溢出漏洞

    发布日期:2010-09-21 更新日期:2010-09-27 受影响系统: Linux kernel 2.6.x 不受影响系统: Linux kernel 2.6.36-rc4 描述: ------ ...

  2. 第十期-Linux内核补丁源码分析(2)

    作者:罗宇哲,中国科学院软件研究所智能软件研究中心 在上一期中,我们通过CAKE系统的实例介绍了一种对Linux内核补丁的初步分析方法,这一期我们将继续通过CAKE系统的例子介绍一种对补丁文件源码的分 ...

  3. mybatis源码之执行insert代码分析

    系列文档: mybatis源码之创建SqlSessionFactory代码分析 mybatis源码之创建SqlSessionFactory代码分析 - mapper xml解析 mybatis源码之执 ...

  4. linux 循环缓冲区 源码,Linux中的循环缓冲区

    在学习到 并发和竞态 时,其中的提到了缓冲区,用于实现免锁算法,这里转载的是大神有关循环缓冲区做的一些操作. 其中源代码在最下面的附件中,有关作者的讲解感觉很清晰,很好,不过这里说一下自己的见解: 点 ...

  5. linux下free源码,linux命令free源码解读:Procps free.c

    linux命令free源码解读 linux命令free源码解读:Procps free.c 作者:isayme 发布时间:September 26, 2011 分类:Linux 我们讨论的是linux ...

  6. linux c free源码,linux命令free源码解读:Procps free.c

    linux命令free源码解读 linux命令free源码解读:Procps free.c 作者:isayme 发布时间:September 26, 2011 分类:Linux 我们讨论的是linux ...

  7. Linux环境下用OpenJTAG实现Linux内核的源码级调试

    1.通过U-boot将uzImage格式的内核加载到内存中(可以从Flash中读取,也可以从U盘.SD卡读取,还可以通过网络): 2.登陆到OpenOCD上,在内核中__turn_mmu_on打上断点 ...

  8. 【华为云技术分享】Linux内核补丁源码分析(1)

    在上一期中,我们介绍了Linux内核编程环境,在这一期中,我们将通过实例来介绍如何分析Linux内核的补丁. 一.Linux内核补丁 在"Linux内核发展史"中,我们简要介绍了L ...

  9. orangepi升级linux内核,orangePi源码编译教程

    8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? #环境 ubuntu 12.04.5 64位 8G内存 jdk-6u45-linux-x64.bin 64位JDK6 板 ...

  10. linux 虚拟文件系统 源码,Linux内核源代码情状分析-虚拟文件系统

    Linux内核源代码情景分析-虚拟文件系统 我们先来看两张图: 第一张是VFS与具体文件系统的关系示意图: 第二张是Linux文件系统的层次结构: 特殊文件:用来实现"管道"的文件 ...

最新文章

  1. ‘百度杯’十月场web ---login
  2. JavaScript - 数据类型和变量
  3. ntu课程笔记7454 期中复习
  4. uni-app 请求封装
  5. 输入参数的数目不足_机器学习算法—KMEANS算法原理及阿里云PAI平台算法模块参数说明...
  6. 7种CSS圆角框解决方案
  7. 跑python gpu利用率低_训练效率低?GPU利用率上不去?快来看看别人家的tricks吧~...
  8. Python应用实战-pandas绘制图形
  9. linux修改容器内的mysql端口_Linux系统下修改phpstudy集成环境中的MySQL端口号的步骤...
  10. java数字转换为日期_Java 日期字符串date与数字long之间的转换
  11. azw3转换为pdf_怎么合并几个PDF为一个?快用这个PDF转换器!
  12. 一键免费自动 AI 抠图,效果连 PS 大哥也点赞! | 程序员硬核评测
  13. julia的几种画图方法
  14. 常见的前端视频播放格式
  15. 游戏资源提取常用工具索引
  16. 终于,高考的三只靴子落下了...
  17. 数据结构课程笔记1-水王问题
  18. unreal歌曲百度云下载_Unreal_Engine_4_1
  19. 中南大学计算机学院复试2021,34所自划线院校2021考研复试分数线-2021中南大学考研分数线已公布...
  20. python中print函数的输出问题(空格,制表符)

热门文章

  1. (个人笔记)英语语法之动词时态
  2. UVA12676 Inverting Huffman
  3. python生成exe文件与exe文件的反编译
  4. excel怎么设置打印区域_学会Excel分页符设置,打印区域自由选择
  5. Pr 视频效果:过时
  6. Android 11.0 app添加校验锁(输入密码才能进入app)
  7. python初步学习笔记(上)
  8. 解决:蓝奏云下载链接没法打开问题
  9. 微信公众号服务器图文消息发送规则,微信公众服务号怎么一天发一条图文消息?...
  10. 中英文对照的文档、分离有妙招