网络虚拟化

作者: 李佶澳   转载请保留:原文地址   发布时间:2014/11/20 13:30:05

Linux中的虚拟网桥与虚拟网卡

Linux支持虚拟网桥(bridge)与虚拟网卡(tun/tap)。利用虚拟网桥和虚拟网卡,结合iptables可以做很多事情。

首先,写一个小程序, 这个程序会创建一个tap(或tun)设备, 然后不停从读取数据, 并保存成pcap格式。

程序地址

测试1 – 从tap/tun读取报文

假设创建的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网卡中转一下。

测试2 – 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是的情况。

测试3 – 通过iptables将tap的报文转发出去:

测试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中传播。

测试4 – 直接操作tap设备进行读写

在前面的测试,一直试图在本地通过路由等方式将报文发送到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)。

基于 Open vSwitch 的 OpenFlow 实践

SDN网络可以分为控制器与交换机两部分, OVS扮演的是交换机的角色, 可以接入任何一个遵守OpenFlow协议的控制器。

OVS

安装:

./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 :  流表

控制器

限时活动,每邀请一人即返回25元!

Copyright @2011-2018 All rights reserved. 转载请添加原文连接,合作请加微信lijiaocn或者发送邮件: lijiaocn@foxmail.com,备注网站合作 友情链接: lijiaocn github.com