当前位置:文档之家› 网桥的原理及在linux内核中的实现

网桥的原理及在linux内核中的实现

而有时候 eth0、eth1 也可能会作为报文的源地址或目的地址,直接参与报文的发送与接收(从而绕过网桥)。
2.3 网桥的功能
概括来说,网桥实现最重要的两点:
1. MAC 学习:学习 MAC 地址,起初,网桥是没有任何地址与端口的对应关系的,它发送数据,还是得想 HUB 一样,但是 每发送一个数据,它都会关心数据包的来源 MAC 是从自己的哪个端口来的,由于学习,建立地址-端口的对照表(CAM 表)。
4
goto err2; /* network device kobject is not setup until * after rtnl_unlock does it's hotplug magic. * so hold reference to avoid race. */ dev_hold(dev); rtnl_unlock(); //在 sysfs 中建立相关信息
ret = br_sysfs_addbr(dev); dev_put(dev);
if (ret) unregister_netdev(dev);
out: return ret; err2:
free_netdev(dev); err1:
rtnl_unlock(); goto out; } 网桥是一个虚拟的设备,它的注册跟实际的物理网络设备注册是一样的。我们关心的是网桥对应的 net_device 结构是什么样 的,继续跟踪进 new_bridge_dev: static struct net_device *new_bridge_dev(const char *name) { struct net_bridge *br; struct net_device *dev;
brioctl_set(br_ioctl_deviceless_stub); //设置 ioctl 钩子函数:br_ioctl_hook br_handle_frame_hook = br_handle_frame;//设置报文处理钩子:br_ioctl_hook //网桥数据库处理钩子 br_fdb_get_hook = br_fdb_get; br_fdb_put_hook = br_fdb_put; //在 netdev_chain 通知链表上注册 register_netdevice_notifier(&br_device_notifier); return 0; } 4.2 新建网桥 前面说到通过 brctl addbr br0 命令建立网桥,此处用户控件调用的 brctl 命令最终对应到内核中的 br_ioctl_deviceless_stub 处 理函数: 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))
2.报文转发:每发送一个数据包,网桥都会提取其目的 MAC 地址,从自己的地址-端口对照表(CAM 表)中查找由哪个端口 把数据包发送出去。
3 网桥的配置
在 Linux 里面使用网桥非常简单,仅需要做两件事情就可以配置了。其一是在编译内核里把 CONFIG_BRIDGE 或 CONDIG_BRIDGE_MODULE 编译选项打开;其二是安装 brctl 工具。第一步是使内核协议栈支持网桥,第二步是安装用户空间 工具,通过一系列的 ioctl 调用来配置网桥。下面以一个相对简单的实例来贯穿全文,以便分析代码。
//私区结构中的 dev 字段指向设备本身 br->dev = dev;
spin_lock_init(&br->lock); //队列初始化。在 port_list 中保存了这个桥上的端口列表 INIT_LIST_HEAD(&br->port_list); spin_lock_init(&br->hash_lock); //下面这部份代码跟 stp 协议相关,我们暂不关心 br->bridge_id.prio[0] = 0x80; br->bridge_id.prio[1] = 0x00; memset(br->bridge_id.addr, 0, ETH_ALEN); br->stp_enabled = 0; 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; INIT_LIST_HEAD(&br->age_list); br_stp_timer_init(br); return dev; } 在 br_dev_setup 中还做了一些另外在函数指针初始化: void br_dev_setup(struct net_device *dev) { //将桥的 MAC 地址设为零 memset(dev->dev_addr, 0, ETH_ALEN);
网桥的原理及在 linux 内核中的实现 2.1 桥接的概念 简单来说,桥接就是把一台机器上的若干个网络接口“连接”起来。其结果是,其中一个网口收到的报文会被复制给其他网 口并发送出去。以使得网口之间的报文能够互相转发。
交换机就是这样一个设备,它有若干个网口,并且这些网口是桥接起来的。于是,与交换机相连的若干主机就能够通过交换 机的报文转发而互相通信。 如下图:主机 A 发送的报文被送到交换机 S1 的 eth0 口,由于 eth0 与 eth1、eth2 桥接在一起,故而报文被复制到 eth1 和 eth2,并且发送出去,然后被主机 B 和交换机 S2 接收到。而 S2 又会将报文转发给主机 C、D。
1
网桥设备 br0 绑定了 eth0 和 eth1。对于网络协议栈的上层来说,只看得到 br0,因为桥接是在数据链路层实现的,上层不需 要关心桥接的细节。于是协议栈上层需要发送的报文被送到 br0,网桥设备的处理代码再来判断报文该被转发到 eth0 或是 eth1,或者两者皆是;反过来,从 eth0 或从 eth1 接收到的报文被提交给网桥的处理代码,在这里会判断报文该转发、丢弃、 或提交到协议栈上层。
//分配 net_device dev = alloc_netdev(sizeof(struct net_bridge), name,
br_dev_setup); if (!dev) return Nridge br = netdev_priv(dev);
4 网桥的实现
在内核,网桥是以模块的方式存在,注册源码路径:\net\brige\br.c:
2
4.1 初始化 static int __init br_init(void) {
br_fdb_init(); //网桥数据库初始化,分配 slab 缓冲区 #ifdef CONFIG_BRIDGE_NETFILTER if (br_netfilter_init()) //netfilter 钩子初始化 return 1; #endif
3
return -EPERM; //copy_from_user:把用户空间的数据拷入内核空间
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);
Linux 机器有 4 个网卡,分别是 eth0~eth4,其中 eth0 用于连接外网,而 eth1, eth2, eth3 都连接到一台 PC 机,用于配置网桥。 只需要用下面的命令就可以完成网桥的配置:
Brctl addbr br0 (建立一个网桥 br0, 同时在 Linux 内核里面创建虚拟网卡 br0)
交换机在报文转发的过程中并不会篡改报文数据,只是做原样复制。然而桥接却并不是在物理层实现的,而是在数据链路层。 交换机能够理解数据链路层的报文,所以实际上桥接却又不是单纯的报文转发。 交换机会关心填写在报文的数据链路层头部中的 Mac 地址信息(包括源地址和目的地址),以便了解每个 Mac 地址所代表 的主机都在什么位置(与本交换机的哪个网口相连)。在报文转发时,交换机就只需要向特定的网口转发即可,从而避免不 必要的网络交互。这个就是交换机的“地址学习”。但是如果交换机遇到一个自己未学习到的地址,就不会知道这个报文应 该从哪个网口转发,则只好将报文转发给所有网口(接收报文的那个网口除外)。 比如主机 C 向主机 A 发送一个报文,报文来到了交换机 S1 的 eth2 网口上。假设 S1 刚刚启动,还没有学习到任何地址,则 它会将报文转发给 eth0 和 eth1。同时,S1 会根据报文的源 Mac 地址,记录下“主机 C 是通过 eth2 网口接入的”。于是当 主机 A 向 C 发送报文时,S1 只需要将报文转发到 eth2 网口即可。而当主机 D 向 C 发送报文时,假设交换机 S2 将报文转发 到了 S1 的 eth2 网口(实际上 S2 也多半会因为地址学习而不这么做),则 S1 会直接将报文丢弃而不做转发(因为主机 C 就 是从 eth2 接入的)。 然而,网络拓扑不可能是永不改变的。假设我们将主机 B 和主机 C 换个位置,当主机 C 发出报文时(不管发给谁),交换 机 S1 的 eth1 口收到报文,于是交换机 S1 会更新其学习到的地址,将原来的“主机 C 是通过 eth2 网口接入的”改为“主机 C 是通过 eth1 网口接入的”。 但是如果主机 C 一直不发送报文呢?S1 将一直认为“主机 C 是通过 eth2 网口接入的”,于是将其他主机发送给 C 的报文都 从 eth2 转发出去,结果报文就发丢了。所以交换机的地址学习需要有超时策略。对于交换机 S1 来说,如果距离最后一次收 到主机 C 的报文已经过去一定时间了(默认为 5 分钟),则 S1 需要忘记“主机 C 是通过 eth2 网口接入的”这件事情。这样 一来,发往主机 C 的报文又会被转发到所有网口上去,而其中从 eth1 转发出去的报文将被主机 C 收到。 2.2 linux 的桥接实现 linux 内核支持网口的桥接(目前只支持以太网接口)。但是与单纯的交换机不同,交换机只是一个二层设备,对于接收到 的报文,要么转发、要么丢弃。小型的交换机里面只需要一块交换芯片即可,并不需要 CPU。而运行着 linux 内核的机器本 身就是一台主机,有可能就是网络报文的目的地。其收到的报文除了转发和丢弃,还可能被送到网络协议栈的上层(网络 层),从而被自己消化。linux 内核是通过一个虚拟的网桥设备来实现桥接的。这个虚拟设备可以绑定若干个以太网接口设 备,从而将它们桥接起来。如下图
相关主题