最近研究OpenStack,发现Neutron很有趣,在宿主机上执行ifconfig可以看到很多tap/br等网络设备关键字,于是开始研究Linux虚拟网络基础。
tap
tap虚拟网络设备,tap设备位于ISO的2层,数据链路层。
数据链路层的主要协议有:
- 点对点协议
- 以太网协议
- 高级数据链路协议
- 帧中继
- 异步传输模式
但是tap只与其中的以太网协议对应。所以,tap也称为虚拟以太设备。
Linux使用tun模块实现了tun/tap。
使用linux命令来操作tap:
检查是否有tun模块
# modinfo tun
filename: /lib/modules/3.10.0-229.el7.x86_64/kernel/drivers/net/tun.ko
alias: devname:net/tun
alias: char-major-10-200
......
查看tun模块是否加载
# lsmod | grep tun
tun 27183 0
如果以上没有输出,即没有加载tun,则使用以下命令加载tun模块
# modprobe tun
tun模块有了,还需要tunctl工具来操作tap/tun设备
# yum install tunctl -y
创建一个tap设备
# tunctl -t tap_test
Set 'tap_test' persistent and owned by uid 0
查看刚刚创建的tap设备
# ip link list
...
17: tap_test: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 500
link/ether 72:3f:cd:fd:0b:ee brd ff:ff:ff:ff:ff:ff
给tap设备绑定IP
# ip addr add local 192.168.12.0/24 dev tap_test
或者:
# ifconfig tap_test 192.168.12.0/24
查看是否成功绑定IP
# ifconfig -a
...
tap_test: flags=4098<BROADCAST,MULTICAST> mtu 1500
inet 192.168.12.0 netmask 255.255.255.0 broadcast 0.0.0.0
ether 72:3f:cd:fd:0b:ee txqueuelen 500 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
namespace
Namespace,即命名空间,主要是实现linux的资源隔离,我们这里主要研究对网络资源的隔离。
Linux操作网络namespace的命令是ip netns。
查看命令帮助:
# ip netns help
Usage: ip netns list
ip netns add NAME
ip netns delete NAME
ip netns identify PID
ip netns pids NAME
ip netns exec NAME cmd ...
ip netns monitor
查看ns
# ip netns list
创建一个ns:名字是ns_test
# ip netns add ns_test
再查看
# ip netns list
ns_test
接下来,我们帮上面创建的tap设备tap_test迁移到这个ns里边去:
# ip link set tap_test netns ns_test
迁移成功后,原来的主机里面,执行ip link list命令,就会发现这个tap_test已经消失了
操作namespace里边的设备
ip [all] netns exec [NAME] cmd ... // cmd为想要操作的命令行
在ns里边执行ip link list
# ip netns exec ns_test ip link list
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
17: tap_test: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 500
link/ether 72:3f:cd:fd:0b:ee brd ff:ff:ff:ff:ff:ff
在ns里绑定IP地址
# ip netns exec ns_test ifconfig tap_test 192.168.12.1/24 up
查看IP地址
# ip netns exec ns_test ifconfig -a
...
tap_test: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 192.168.12.1 netmask 255.255.255.0 broadcast 192.168.12.255
ether 72:3f:cd:fd:0b:ee txqueuelen 500 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
veth pair
veth pair不是一个设备,而是一对设备,用以连接两个虚拟以太端口。
操作veth pair,需要跟namespace一起配合,否则就没有意义。
设计一个测试用例,如下图:
两个namespace ns1/ns2中各有一个tap组成veth pair,两者的IP地址如图所示,两个IP进行互ping测试。
具体实现:
创建veth pair
# ip link add tap1 type veth peer name tap2
创建namespace:ns1,ns2
# ip netns add ns1
# ip netns add ns2
把两个tap分别迁移到对应的namespace中
# ip link set tap1 netns ns1
# ip link set tap2 netns ns2
分别给两个tap绑定IP地址
# ip netns exec ns1 ip addr add local 192.168.12.1/24 dev tap1
# ip netns exec ns2 ip addr add local 192.168.12.2/24 dev tap2
将两个tap设置为UP
# ip netns exec ns1 ifconfig tap1 up
# ip netns exec ns2 ifconfig tap2 up
Ping测试
# ip netns exec ns2 ping 192.168.12.1
PING 192.168.12.1 (192.168.12.1) 56(84) bytes of data.
64 bytes from 192.168.12.1: icmp_seq=1 ttl=64 time=0.052 ms
...
# ip netns exec ns1 ping 192.168.12.2
PING 192.168.12.2 (192.168.12.2) 56(84) bytes of data.
64 bytes from 192.168.12.2: icmp_seq=1 ttl=64 time=0.206 ms
...
通过上面的测试用例,我们了解到通过veth pair连接两个namespace的方法。
但是,如果是3个namespace之间需要互通呢?或者多个namespace之间需要互通呢?
veth pair只有一对tap,无法胜任,这时就需要用到Bridge/Switch。
Bridge
在Linux里边,bridge(网桥)和switch(交换机)都是实现2层的功能,概念相近,所以这里也不做区分。
Linux实现Bridge功能的是brctl模块。
安装brctl
# yum install -y bridge-utils
先查看帮助
# brctl help
...
测试用例图:
图中,有4个namespace,每个ns都有一个tap与虚拟网桥vb上一个tap口组成veth pair。
这样4个namespace就通过veth pair及bridge互联起来。
具体实现:
创建veth pair
# ip link add tap1 type veth peer name tap1_peer
# ip link add tap2 type veth peer name tap2_peer
# ip link add tap3 type veth peer name tap3_peer
# ip link add tap4 type veth peer name tap4_peer
创建namespace
# ip netns add ns1
# ip netns add ns2
# ip netns add ns3
# ip netns add ns4
把tap设备迁移到对应的namespace中
# ip link set tap1 netns ns1
# ip link set tap2 netns ns2
# ip link set tap3 netns ns3
# ip link set tap4 netns ns4
创建bridge
# brctl addbr br1
把相应tap添加到bridge中
# brctl addif br1 tap1_peer
# brctl addif br1 tap2_peer
# brctl addif br1 tap3_peer
# brctl addif br1 tap4_peer
查看网桥
# brctl show
bridge name bridge id STP enabled interfaces
br1 8000.3601df1a8177 no tap1_peer
tap2_peer
tap3_peer
tap4_peer
配置相应tap的IP地址
# ip netns exec ns1 ip addr add local 192.168.12.1/24 dev tap1
# ip netns exec ns2 ip addr add local 192.168.12.2/24 dev tap2
# ip netns exec ns3 ip addr add local 192.168.12.3/24 dev tap3
# ip netns exec ns4 ip addr add local 192.168.12.4/24 dev tap4
将bridge及所有tap状态设置为up
# ip link set br1 up
# ip link set tap1_peer up
# ip link set tap2_peer up
# ip link set tap3_peer up
# ip link set tap4_peer up
# ip netns exec ns1 ip link set tap1 up
# ip netns exec ns2 ip link set tap2 up
# ip netns exec ns3 ip link set tap3 up
# ip netns exec ns4 ip link set tap4 up
Ping测试
# ip netns exec ns1 ping 192.168.12.2
PING 192.168.12.2 (192.168.12.2) 56(84) bytes of data.
64 bytes from 192.168.12.2: icmp_seq=1 ttl=64 time=0.143 ms
...
# ip netns exec ns3 ping 192.168.12.1
PING 192.168.12.1 (192.168.12.1) 56(84) bytes of data.
64 bytes from 192.168.12.1: icmp_seq=1 ttl=64 time=0.134 ms
...
Router
Linux服务器本身就可以作为路由器,只需要开启转发功能。
查看Linux转发功能
# cat /proc/sys/net/ipv4/ip_forward
0
临时开启转发
# echo "1" > /proc/sys/net/ipv4/ip_forward
再次查看
# cat /proc/sys/net/ipv4/ip_forward
1
测试用例图:
图中,ns1/tap1与ns2/tap2不在同一个网段中,中间需要经过一个路由器进行转发才能互通。
图中的router是一个示意,其实就是Linux开启了路由转发功能。
具体实现:
创建veth pair
# ip link add tap1 type veth peer name tap1_pair
# ip link add tap2 type veth peer name tap2_pair
创建namespace
# ip netns add ns1
# ip netns add ns2
将tap迁移到namespace
# ip link set tap1 netns ns1
# ip link set tap2 netns ns2
配置tap 的IP地址
# ip addr add local 192.168.12.1/24 dev tap1_pair
# ip addr add local 192.168.22.1/24 dev tap2_pair
# ip netns exec ns1 ip addr add local 192.168.12.2/24 dev tap1
# ip netns exec ns2 ip addr add local 192.168.22.2/24 dev tap2
将tap设置为UP
# ip link set tap1_pair up
# ip link set tap2_pair up
# ip netns exec ns1 ip link set tap1 up
# ip netns exec ns2 ip link set tap2 up
查看路由表
# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.1.1 0.0.0.0 UG 100 0 0 eno50332208
192.168.12.0 0.0.0.0 255.255.255.0 U 0 0 0 tap1_pair
192.168.22.0 0.0.0.0 255.255.255.0 U 0 0 0 tap2_pair
可以看到,当我们添加了tap设备并为其绑定了IP,Linux会自动生成直连路由。
Ping测试
# ip netns exec ns1 ping 192.168.22.2
connect: 网络不可达
ping不通,我们再看一下ns1的路由表
# ip netns exec ns1 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.12.0 0.0.0.0 255.255.255.0 U 0 0 0 tap1
ns1中并没有到达192.168.22.0/24的路由表项,需要我们手动添加。
ns1,ns2都添加静态路由,分别到达对方的网段
# ip netns exec ns1 route add -net 192.168.22.0 netmask 255.255.255.0 gw 192.168.12.1
# ip netns exec ns2 route add -net 192.168.12.0 netmask 255.255.255.0 gw 192.168.22.1
再查看ns1的路由表
# ip netns exec ns1 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.12.0 0.0.0.0 255.255.255.0 U 0 0 0 tap1
192.168.22.0 192.168.12.1 255.255.255.0 UG 0 0 0 tap1
再次ping测试
# ip netns exec ns1 ping 192.168.22.2
PING 192.168.22.2 (192.168.22.2) 56(84) bytes of data.
64 bytes from 192.168.22.2: icmp_seq=1 ttl=63 time=0.364 ms
...
# ip netns exec ns2 ping 192.168.12.2
PING 192.168.12.2 (192.168.12.2) 56(84) bytes of data.
64 bytes from 192.168.12.2: icmp_seq=1 ttl=63 time=0.204 ms
...
tun
tun位于ISO的第3层,是一个点对点设备,它启用了IP层隧道功能。
Linux原生支持的三层隧道,可以通过命令行ip tunnel help来查。
可以发现,Linux一共原生支持5种三层隧道,如下
隧道 | 简述 |
ipip | IP in IP,在IPv4报文的基础上再封装一个IPv4报文头,属于IPv4 IN IPv4。 |
gre | 通用路由封装(Generic Routing Encapsulation),定义了在任意一种网络层协议上封装任意一个其他网络层协议的协议,属于IPv4/IPv6 over IPv4。 |
sit | 这个跟ipip类似,只不过是用一个IPv4的报文头封装IPv6的报文,属于IPv6 over IPv4。 |
isatap | 站内自动隧道寻址协议,一般用于IPv4网络中的IPv6/IPv4节点间的通信。 |
vti | 全称是Virtual Tunnel Interface,为IPsec隧道提供一个可路由的接口类型。 |
测试用例图:
假设tap1与tap2能通,我们之前已经实现。
怎样才能让tun1与tun2互通呢?
具体实现:
这里以ipip隧道为例进行配置。
查看Linux系统是否已经加载ipip模块
# lsmod | grep ipip
加载ipip模块
# modprobe ipip
# lsmod | grep ipip
ipip 13472 0
tunnel4 13252 1 ipip
ip_tunnel 23760 1 ipip
在ns1上创建tun1和ipip tunnel
# ip netns exec ns1 ip tunnel add tun1 mode ipip remote 192.168.22.2 local 192.168.12.2 ttl 255
# ip netns exec ns1 ip link set tun1 up
# ip netns exec ns1 ip addr add 192.168.88.8 peer 192.168.99.9 dev tun1
在ns2上创建tun2和ipip tunnel
# ip netns exec ns2 ip tunnel add tun2 mode ipip remote 192.168.12.2 local 192.168.22.2 ttl 255
# ip netns exec ns2 ip link set tun2 up
# ip netns exec ns2 ip addr add 192.168.99.9 peer 192.168.88.8 dev tun2
Ping测试(都不通,没找到原因,有解决的朋友留言告知一下)
# ip netns exec ns1 ping 192.168.99.9
PING 192.168.99.9 (192.168.99.9) 56(84) bytes of data.
^C
--- 192.168.99.9 ping statistics ---
6 packets transmitted, 0 received, 100% packet loss, time 5030ms
# ip netns exec ns2 ping 192.168.88.8
PING 192.168.88.8 (192.168.88.8) 56(84) bytes of data.
^C
--- 192.168.88.8 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3012ms
查看这个tun设备的信息
# ip netns exec ns1 ifconfig -a
...
tun1: flags=209<UP,POINTOPOINT,RUNNING,NOARP> mtu 1480
inet 192.168.88.8 netmask 255.255.255.255 destination 192.168.99.9
tunnel txqueuelen 0 (IPIP Tunnel)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 347 bytes 29148 (28.4 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
可以看到,tun1是一个ipip隧道的一个端点,IP是192.168.88.8,其对端IP是192.168.99.9。
再看看路由表
# ip netns exec ns1 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
192.168.12.0 0.0.0.0 255.255.255.0 U 0 0 0 tap1
192.168.22.0 192.168.12.1 255.255.255.0 UG 0 0 0 tap1
192.168.99.9 0.0.0.0 255.255.255.255 UH 0 0 0 tun1
路由表结果告诉我们,到达目的地192.168.99.9的路由的一个直连路由直接从tun1出去即可。