Linux支持虚拟网桥(bridge)与虚拟网卡(tun/tap)。利用虚拟网桥和虚拟网卡,结合iptables可以做很多事情。
首先,写一个小程序, 这个程序会创建一个tap(或tun)设备, 然后不停从读取数据, 并保存成pcap格式。
假设创建的tap设备名为tap0。
[root@localhost read_tap]# ./a.out -t tap tap0 -s test1-ping.pcap
name: tap0
type: 0
配置tap0的地址为
[root@localhost read_tap]# ip addr add 7.7.7.7/24 dev tap0
[root@localhost read_tap]# ip link set tap0 up
这时在tap0中已经收取到报文了:
[root@localhost read_tap]# ./a.out -t tap tap0 -s test1-ping.pcap
name: tap0
type: 0
Read Bytes: 90
33 33 00 00 00 16 46 41 -- 65 57 4e 3b 86 dd 60 00
00 00 00 24 00 01 00 00 -- 00 00 00 00 00 00 00 00
00 00 00 00 00 00 ff 02 -- 00 00 00 00 00 00 00 00
00 00 00 00 00 16 3a 00 -- 05 02 00 00 01 00 8f 00
20 f8 00 00 00 01 04 00 -- 00 00 ff 02 00 00 00 00
00 00 00 00 00 01 ff 57 -- 4e 3b 00
Quit[q] Conti[c] Next[n]:
在这里键入c, 让a.out持续的读取, 最后我们到test1-ping.pcap中查看有没有收到我们发送的报文。
首先从外部ping 192.168.187.104, 可以ping通,但是a.out没有读到icmp报文。
使用tcpdump在tap0上抓包也是什么都没有读到。
加上这一条路由:
ip route add 10.0.4.0/24 via 192.168.187.104 dev tap0
然后在本机上ping 10.0.4.33 10.0.4.44, 这是a.out和tcpdump都收到了相应的arp报文。
那么问题来了, 为什么发送到192.168.187.104的报文没有发送到tap0网卡上???!!!
也就是说通过tap0发送出去的包可以读取到,但是发送到tap0的地址的包读取不到, 只有一种可能:
内核没有把接收的包转发给tap0!!!
报文被物理网卡接收后,直接被提交到内核中处理, 不会像虚拟网卡tap0中写入。
推断: 当在本地给虚拟网卡配置IP时, 该IP地址就被认为是本机的IP, 那么接收到发送到这个IP的报文 被直接上交到上层, 而不是转发到tap网卡。
通过抓包发现, arp报文也不会被发送到tap网卡。tap网卡只是本地虚拟网卡, 通过tap发送的报文可以 从tap中读取到, 而目标是tap网卡的IP的报文, 则绕过了tap网卡, 由物理网卡直接递交到上层。因为tap 是一个虚拟网卡, 没有物理网线的。所以外来的包肯定不经过tap网卡, 只有发出的包可以经过tap网卡中转一下。
有意思的是, Linux上还有一个虚拟网桥的功能(brctl), 本机上的多个网卡(包括tap)都可以被接入到虚拟网桥中。
我们发现将tap接入到虚拟网桥后,无论tap网卡是否有IP, tap网卡可以收到arp报文。
[root@localhost read_tap]# brctl addbr br0
[root@localhost read_tap]# brctl addif br0 enp0s8
[root@localhost read_tap]# ifconfig enp0s8 0 up
[root@localhost read_tap]# brctl addif br0 tap0
[root@localhost read_tap]# ifconfig br0 192.168.187.105 up
[root@localhost read_tap]# ip link set tap0 up
[root@localhost read_tap]# brctl show
bridge name bridge id STP enabled interfaces
br0 8000.08002753bf0c no enp0s8
tap0
但是从外部ping tap网卡的IP时, 可以ping通, 但是在tap网卡上读不到icmp包。
经过分析icmp回应包应当是通过路由策略直接从br0发送出去了。
//回应包通过br0发出
[root@localhost ~]# ip route
192.168.187.0/24 dev br0 proto kernel scope link src 192.168.187.105
192.168.187.0/24 dev tap0 proto kernel scope link src 192.168.187.78
但是如果删除了经过br0的路由,只保留经过tap0出去的路由, 整个网络就不通了。
[root@localhost read_tap]# ip route
192.168.187.0/24 dev tap0 proto kernel scope link src 192.168.187.78
修改了路由优先级也不行:
[root@localhost read_tap]# ip route
192.168.187.0/25 dev tap0 proto kernel scope link
192.168.187.0/24 dev br0 proto kernel scope link
//网络还是不通
也就是说tap0不能直接与外部通信
在测试1和上面的分析中,得出结论, 当tap网卡分配有ip地址的时候, 在本地通过路由策略经tap发送报文时:
1. 不在网桥中的tap收取不到物理链路上过来任何报文, arp也收不到。
2. 网桥中的tap只能收到从物理链路上过来的arp等广播报文。
3. 目标地址为tap的IP报文, 直接被协议栈上层接收,不会被转发到tap网卡。
> 注意: 以上是tap网卡具有本地IP是的情况。
测试2中分析发现,网桥中tap无法将报文送出到物理链路上,那么是否可以将其转向通过其它的网口发出呢?
首先我们要将tap设置一个不同于br0的网段,避免报文直接通过br0出去。 然后通过iptables修改这个网端的报文,使其可以通过br0发送出去。
tap0: 172.10.10.2
br0: 192.168.187.7
br0: 192.168.187.6
//发送到172.10.10.0/24网段的报文通过tap0发出
[root@localhost read_tap]# ip route
172.10.10.0/24 dev tap0 scope link
192.168.187.0/24 dev br0 proto kernel scope link src 192.168.187.102
这时候在本地ping 172.10.10.34, 可以从tap0上读取到arp报文, 从br0上没有到读到对应的arp报文。
创建一个新的虚拟网卡tap1, 也加入bridge中, 从tap1中同样也读取不到arp报文。
通过arp条目将ip对应mac修改为br0或tap1的mac, 报文还是没有从br0或tap1上抓取到。
arp -i tap0 -s 172.10.10.36 08:00:27:53:bf:0c
百思不得其解,后来灵机一动,直接在a.out中直接向tap中write了一个arp报文:
char arp[1024] ={0xff,0xff,0xff,0xff,0xff,0xff,0xc2,0xa0, 0x36,0xaa,0x48,0x3a,0x08,0x06,0x00,0x01,
0x08,0x00,0x06,0x04,0x00,0x01,0xc2,0xa0, 0x36,0xaa,0x48,0x3a,0xac,0x0a,0x0a,0x02,
0x00,0x00,0x00,0x00,0x00,0x00,0xac,0x0a, 0x0a,0x2d,0x00};
ret = write(fd, arp, 42)
这时从br0和tap0上都读取到我们发送的arp报文, 在与br0位于同一个网络的外部网络中也收到了这个arp报文。
但是tap1上用tcpdump没有抓到这个arp报文, 这里又是一个问题!(答案见测试4)
我们尝试开启linux的转发功能:
echo 1 >/proc/sys/net/ipv4/ip_forward
//配置tap0 ip
ip addr add 77.77.77.1/24 dev tap0
//77.77.77.1/24的路由:
77.77.77.0/24 dev tap0 proto kernel scope link src 77.77.77.1
//用于辅助分析的iptables规则
iptables -A PREROUTING -d 77.77.77.36/32 -j DNAT --to-destination 192.168.187.1
iptables -A POSTROUTING -s 77.77.77.0/24 -o tap0 -j SNAT --to-source 66.66.66.66
//添加arp条目, 将77.77.77.36的host mac设置为br0的mac
arp -i tap0 -s 77.77.77.36 08:00:27:53:bf:0c
结果还是只能在tap0中抓取到icmp报文。
但是源地址被修改为66.66.66.66, 也就是说报文确实通过tap0发出去了。
(目的地址没有变, 说明出去的报文没有经过PREROUTING, 直接通过OUTPUT->POSTROUTING)
但是br0上没有抓取到, 那就只有一个解释了:
1. 通过本地路由策略发送到tap0的报文,不会在tap0所在的虚拟bridge中传播。
2. 只有直接向tap0中写入的报文,才会在bridge中传播。
在前面的测试,一直试图在本地通过路由等方式将报文发送到tap设备中,但是因为某种还不是很确定的原因, 一直没有出现我们应当期待的结果,只能推断这种方式执行的路线是不通的。因此,测试4我们直接操作tap设备。
为此,将前面的用到的程序拆分成两个程序,一个在tap中写入数据,一个从tap中读取数据。
//在tap0中写入数据
./write_tap -t tap tap0 -s write.cap
//从tap1中接收数据
./read_tap -t tap tap1 -s read.cap
//将tap0和tap1加入到同一个bridge中
[root@localhost read_tap]# brctl show
bridge name bridge id STP enabled interfaces
br0 8000.08002753bf0c yes enp0s8
tap0
tap1
这时候终于出现了我们期待的结果, 从tap1和br0中成功的收到了向tap0中写入的arp广播报文。
Openstack早先的Nova-net采用的就是这种方式nova-net
下面的内容没太大实用意义, 只是相当于在机器上分配了两个IP地址,一个给物理网卡,一个给虚拟网卡 这种方式根本用不着虚拟网桥, 直接给虚拟网卡配置IP就行了。本意是想现有的网络上创建一个虚拟网络, 这部分内容在其它部分再说。 下面完全是刚开始学习时的臆断.
Linux中的brctl和tunctl分别提供了创建虚拟网桥和虚拟网卡的功能。
例如机器A与机器B相互通信的物理网卡地址分别是:
MachineA
enp0s3:192.168.187.5/24
MachineB
enp0s8:192.168.187.101/24
现在要在机器A与机器B之间,建立一个虚拟的网络10.0.0.0/24
首先在机器A和机器B上各自创建虚拟网桥br0, 分别将enp0s3和enp0s8加入到br0中:
MachineA:
brctl addbr br0
brctl addif br0 enp0s3 //将物理网卡插入网桥
brctl stp br0 on //打开stp
ifconfig enp0s3 0 //将要加入br0的物理网卡ip取消
ip addr add 192.168.187.5/24 dev br0 //将网桥地址设置为原先物理网卡的地址
MachineA:
brctl addbr br0
brctl addif br0 enp0s8 //将物理网卡插入网桥
brctl stp br0 on //打开stp
ifconfig enp0s8 0 //将要加入br0的物理网卡ip取消
ip addr add 192.168.187.101/24 dev br0 //将网桥地址设置为原先物理网卡的地址
这时,MachineA和MachineB上各自的网桥br0的地址是192.168.187.5和192.168.187.101,两个br0之间是联通的。
创建虚拟网络:
MachineA:
tunctl -t tap0 //创建虚拟网卡tap0
brctl addif br0 tap0 //将虚拟网卡插入网桥
ip addr add 10.0.0.2/24 dev tap0 //设置虚拟网卡的ip
MachineB:
tunctl -t tap0 //创建虚拟网卡tap0
brctl addif br0 tap0 //将虚拟网卡插入网桥
ip addr add 10.0.0.6/24 dev tap0 //设置虚拟网卡的ip
修改路由:
MachineA:
ip route del default
ip route add default dev br0
MachineB:
ip route del default
ip route add default dev br0
这时在MachineA上可以ping通地址10.0.0.6。
注意: MachineA与MachineB之间的网络设备需要能够转发10.0.0.0/24网段的包才可以。
通过Linux的虚拟网卡(tap/tun)和虚拟桥(bridge)可以在Linux系统上创建一个虚拟的网络, 但是这个网卡被局限在一个Linux系统的内部。如果虚拟网络可以跨越多个系统, 那么将会 有更大的价值。这方面的工作也确实有人做了, 典型代表就是由Nicira开发的Open vSwitch (OVS)。
SDN网络可以分为控制器与交换机两部分, OVS扮演的是交换机的角色, 可以接入任何一个遵守OpenFlow协议的控制器。
安装:
./boot.sh
./configure --prefix=/opt/ovs-master
make
make install
第一次启动:
#加载驱动
modprobe openvswitch
#vswitch.ovsschema在ovs源码目录中
ovsdb-tool create /opt/ovs-master/etc/openvswitch/conf.db vswitchd/vswitch.ovsschema
##启动ovsdb-server
mkdir /var/run/openvswitch
ovsdb-server --remote=punix:/var/run/openvswitch/db.sock \
--remote=db:Open_vSwitch,Open_vSwitch,manager_options \
--private-key=db:Open_vSwitch,SSL,private_key \
--certificate=db:Open_vSwitch,SSL,certificate \
--bootstrap-ca-cert=db:Open_vSwitch,SSL,ca_cert \
--pidfile --detach
#初始化, 只需要在第一次设置数据库后使用
ovs-vsctl --db=unix:/var/run/openvswitch/db.sock --no-wait init
#启动ovs服务进程
ovs-vswitchd --pidfile --detach unix:/var/run/openvswitch/db.sock
使用:
#创建网桥
ovs-vsctl --db=unix:/var/run/openvswitch/db.sock add-br br0
#将网卡加入网桥
ovs-vsctl --db=unix:/var/run/openvswitch/db.sock add-port br0 eth0
#查看网桥信息
ovs-ofctl show br0
基本概念:
Bridge : 本地创建的网桥
Port : 网桥的接口
Interface : 接入Port的设备, 如果Port是bond模式, 可以接入多个设备
Controller : 控制器
Datapath : OVS内部的数据转发路径
Flow table : 流表