...

网络虚拟化技术 -- LXC TUN/TAP MACVLAN MACVTAP

 

Linux的网络虚拟化是LXC项目中的一个子项目,LXC包括文件系统虚拟化,进程空间虚拟化,用户虚拟化,网络虚拟化,等等 
LXC内核命名空间 ],这里使用LXC的网络虚拟化来模拟多个网络环境。

创建虚拟网络环境

使用命令

$ ip netns add net0

可以创建一个完全隔离的新网络环境,这个环境包括一个独立的网卡空间,路由表,ARP表,ip地址表,iptables,ebtables,等等。总之,与网络有关的组件都是独立的。

ip命令需要root权限的,但是由于本文大量使用ip命令,于是笔者给ip命令添加了capability,使普通用户也能使用ip命令

使用命令

$ ip netns list
net0

可以看到我们刚才创建的网络环境

进入虚拟网络环境

使用命令

$ ip netns exec net0 `command`

我们可以在 net0 虚拟环境中运行任何命令

$ ip netns exec net0 bash
$ ip ad
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

这样我们可以在新的网络环境中打开一个shell,可以看到,新的网络环境里面只有一个lo设备,并且这个lo设备与外面的lo设备是不同的,之间不能互相通讯。

连接两个网络环境

新的网络环境里面没有任何网络设备,并且也无法和外部通讯,就是一个孤岛,通过下面介绍的这个方法可以把两个网络环境连起来,简单的说,就是在两个网络环境之间拉一根网线

$ ip netns add net1

先创建另一个网络环境net1,我们的目标是把net0与net1连起来

$ ip link add type veth
$ ip ad
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
81: veth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether 12:39:09:81:3a:dd brd ff:ff:ff:ff:ff:ff
82: veth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether 32:4f:fd:cc:79:1b brd ff:ff:ff:ff:ff:ff

这里创建连一对veth虚拟网卡,类似pipe,发给veth0的数据包veth1那边会收到,发给veth1的数据包veth0会收到。就相当于给机器安装了两个网卡,并且之间用网线连接起来了

$ ip link set veth0 netns net0
$ ip link set veth1 netns net1

这两条命令的意思就是把veth0移动到net0环境里面,把veth1移动到net1环境里面,我们看看结果

$ ip ad
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
$ ip netns exec net0 ip ad
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
81: veth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether 12:39:09:81:3a:dd brd ff:ff:ff:ff:ff:ff
$ ip netns exec net1 ip ad
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
82: veth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether 32:4f:fd:cc:79:1b brd ff:ff:ff:ff:ff:ff

veth0 veth1已经在我们的环境里面消失了,并且分别出现在net0与net1里面。下面我们简单测试一下net0与net1的联通性

$ ip netns exec net0 ip link set veth0 up
$ ip netns exec net0 ip address add 10.0.1.1/24 dev veth0
$ ip netns exec net1 ip link set veth1 up
$ ip netns exec net1 ip address add 10.0.1.2/24 dev veth1

分别配置好两个设备,然后用ping测试一下联通性:

$ ip netns exec net0 ping -c 3 10.0.1.2
PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.
64 bytes from 10.0.1.2: icmp_req=1 ttl=64 time=0.101 ms
64 bytes from 10.0.1.2: icmp_req=2 ttl=64 time=0.057 ms
64 bytes from 10.0.1.2: icmp_req=3 ttl=64 time=0.048 ms

--- 10.0.1.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.048/0.068/0.101/0.025 ms

一个稍微复杂的网络环境

2013-02-29-linux-network-emulator__1.png

创建虚拟网络环境并且连接网线

ip netns add net0
ip netns add net1
ip netns add bridge
ip link add type veth
ip link set dev veth0 name net0-bridge netns net0
ip link set dev veth1 name bridge-net0 netns bridge
ip link add type veth
ip link set dev veth0 name net1-bridge netns net1
ip link set dev veth1 name bridge-net1 netns bridge

在bridge中创建并且设置br设备

ip netns exec bridge brctl addbr br
ip netns exec bridge ip link set dev br up
ip netns exec bridge ip link set dev bridge-net0 up
ip netns exec bridge ip link set dev bridge-net1 up
ip netns exec bridge brctl addif br bridge-net0
ip netns exec bridge brctl addif br bridge-net1

然后配置两个虚拟环境的网卡

ip netns exec net0 ip link set dev net0-bridge up
ip netns exec net0 ip address add 10.0.1.1/24 dev net0-bridge
ip netns exec net1 ip link set dev net1-bridge up
ip netns exec net1 ip address add 10.0.1.2/24 dev net1-bridge

测试

$ ip netns exec net0 ping -c 3 10.0.1.2
PING 10.0.1.2 (10.0.1.2) 56(84) bytes of data.
64 bytes from 10.0.1.2: icmp_req=1 ttl=64 time=0.121 ms
64 bytes from 10.0.1.2: icmp_req=2 ttl=64 time=0.072 ms
64 bytes from 10.0.1.2: icmp_req=3 ttl=64 time=0.069 ms

--- 10.0.1.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.069/0.087/0.121/0.025 ms

配置lldpd检查线路链接情况

随着虚拟网络环境增加,环境中网卡数量也在不断增加,经常会忘记环境中哪些网卡连接到哪里,通过 lldp 
Link Layer Discovery Protocol ]
协议,我们可以清楚看到每个网卡连接到了哪些环境中的哪个网卡。

github 上有一个 lldp 在 linux 下的开源实现 
implementation of IEEE 802.1ab (LLDP) ]
,通过在每个环境中起一个 lldp daemon,我们就可以实时查看每个网卡的连接情况

Bridge 上 lldp 的数据

$ lldpcli show neighbors

LLDP neighbors:

Interface:    bridge-net0, via: LLDP, RID: 2, Time: 0 day, 00:06:53
  Chassis:
    ChassisID:    mac 82:be:2a:ec:70:69
    SysName:      localhost
    SysDescr:     net0
    Capability:   Bridge, off
    Capability:   Router, off
    Capability:   Wlan, off
  Port:
    PortID:       mac 82:be:2a:ec:70:69
    PortDescr:    net0-bridge

Interface:    bridge-net1, via: LLDP, RID: 1, Time: 0 day, 00:06:53
  Chassis:
    ChassisID:    mac b2:34:28:b1:be:49
    SysName:      localhost
    SysDescr:     net1
    Capability:   Bridge, off
    Capability:   Router, off
    Capability:   Wlan, off
  Port:
    PortID:       mac b2:34:28:b1:be:49
    PortDescr:    net1-bridge
 

 

TUN 设备

TUN 设备是一种虚拟网络设备,通过此设备,程序可以方便得模拟网络行为。先来看看物理设备是如何工作的:

2013-03-27-linux-network-tun__1.png

所有物理网卡收到的包会交给内核的 Network Stack 处理,然后通过 Socket API 通知给用户程序。下面看看 TUN 的工作方式:

2013-03-27-linux-network-tun__2.png

普通的网卡通过网线收发数据包,但是 TUN 设备通过一个文件收发数据包。所有对这个文件的写操作会通过 TUN 设备转换成一个数据包送给内核;当内核发送一个包给 TUN 设备时,通过读这个文件可以拿到包的内容。

如果我们使用 TUN 设备搭建一个基于 UDP VPN,那么整个处理过程就是这样:

2013-03-27-linux-network-tun__3.png

数据包会通过内核网络栈两次。但是经过 App 的处理后,数据包可能已经加密,并且原有的 ip 头被封装在 udp 内部,所以第二次通过网络栈内核看到的是截然不同的网络包。

TAP 设备

TAP 设备与 TUN 设备工作方式完全相同,区别在于:

  • TUN 设备的 /dev/tunX 文件收发的是 IP 层数据包,只能工作在 IP 层,无法与物理网卡做 bridge,但是可以通过三层交换(如 ip_forward)与物理网卡连通。

  • TAP 设备的 /dev/tapX 文件收发的是 MAC 层数据包,拥有 MAC 层功能,可以与物理网卡做 bridge,支持 MAC 层广播

MACVLAN

有时我们可能需要一块物理网卡绑定多个 IP 以及多个 MAC 地址,虽然绑定多个 IP 很容易,但是这些 IP 会共享物理网卡的 MAC 地址,可能无法满足我们的设计需求,所以有了 MACVLAN 设备,其工作方式如下:

2013-03-27-linux-network-tun__4.png

MACVLAN 会根据收到包的目的 MAC 地址判断这个包需要交给哪个虚拟网卡。单独使用 MACVLAN 好像毫无意义,但是配合之前介绍的 network namespace 使用,我们可以构建这样的网络:

2013-03-27-linux-network-tun__5.png

由于 macvlan 与 eth0 处于不同的 namespace,拥有不同的 network stack,这样使用可以不需要建立 bridge 在 virtual namespace 里面使用网络。

MACVTAP

MACVTAP 是对 MACVLAN的改进,把 MACVLAN 与 TAP 设备的特点综合一下,使用 MACVLAN 的方式收发数据包,但是收到的包不交给 network stack 处理,而是生成一个 /dev/tapX 文件,交给这个文件:

2013-03-27-linux-network-tun__6.png

由于 MACVLAN 是工作在 MAC 层的,所以 MACVTAP 也只能工作在 MAC 层,不会有 MACVTUN 这样的设备。

 

 

 

 

veth pair

veth pair是用于不同network namespace间进行通信的方式,veth pair将一个network namespace数据发往另一个network namespace的veth。如下:

NewImage

 

# add the namespaces

ip netns add ns1

ip netns add ns2

# create the veth pair

ip link add tap1 type veth peer name tap2

# move the interfaces to the namespaces

ip link set tap1 netns ns1

ip link set tap2 netns ns2

# bring up the links

ip netns exec ns1 ip link set dev tap1 up

ip netns exec ns2 ip link set dev tap2 up

 

如果多个network namespace需要进行通信,则需要借助bridge:

NewImage

# add the namespaces

ip netns add ns1

ip netns add ns2

# create the switch

BRIDGE=br-test

brctl addbr $BRIDGE

brctl stp   $BRIDGE off

ip link set dev $BRIDGE up

#

#### PORT 1

# create a port pair

ip link add tap1 type veth peer name br-tap1

# attach one side to linuxbridge

brctl addif br-test br-tap1

# attach the other side to namespace

ip link set tap1 netns ns1

# set the ports to up

ip netns exec ns1 ip link set dev tap1 up

ip link set dev br-tap1 up

#

#### PORT 2

# create a port pair

ip link add tap2 type veth peer name br-tap2

# attach one side to linuxbridge

brctl addif br-test br-tap2

# attach the other side to namespace

ip link set tap2 netns ns2

# set the ports to up

ip netns exec ns2 ip link set dev tap2 up

ip link set dev br-tap2 up

#

 

内核实现

veth的实现与loopback interface类似,比较简单:

//drivers/net/veth.c

static netdev_tx_t veth_xmit(struct sk_buff *skb, struct net_device *dev)

{

struct net_device *rcv = NULL;

struct veth_priv *priv, *rcv_priv;

 

priv = netdev_priv(dev);

rcv = priv->peer;

rcv_priv = netdev_priv(rcv);

 

stats = this_cpu_ptr(priv->stats);

 

length = skb->len;

   //转发给peer

if (dev_forward_skb(rcv, skb) != NET_RX_SUCCESS)

 

goto rx_drop;

 

NETIF_F_NETNS_LOCAL

NETIF_F_NETNS_LOCAL是网络设备的一个特性,设置该特性的网络设备,不允许在不同network namespace间移动。这类设备也叫做本地设备(local devices)。

Loopback,VXLAN,PPP,bridge都是这类设备。可以通过ethtool -k,或者ethtool –show- features查看该值:

# ethtool -k br0

netns-local: on [fixed]

 

如果对这类设备network namespace,会报下面的错误:

# ip link set br0 netns ns1

RTNETLINK answers: Invalid argument

 

veth pair
veth pair是用于不同network namespace间进行通信的方式,veth pair将一个network namespace数据发往另一个network namespace的veth。如下:

 

 

# add the namespaces

ip netns add ns1

ip netns add ns2

# create the veth pair

ip link add tap1 type veth peer name tap2

# move the interfaces to the namespaces

ip link set tap1 netns ns1

ip link set tap2 netns ns2

# bring up the links

ip netns exec ns1 ip link set dev tap1 up

ip netns exec ns2 ip link set dev tap2 up

 

如果多个network namespace需要进行通信,则需要借助bridge:

 

# add the namespaces

ip netns add ns1

ip netns add ns2

# create the switch

BRIDGE=br-test

brctl addbr $BRIDGE

brctl stp   $BRIDGE off

ip link set dev $BRIDGE up

#

#### PORT 1

# create a port pair

ip link add tap1 type veth peer name br-tap1

# attach one side to linuxbridge

brctl addif br-test br-tap1

# attach the other side to namespace

ip link set tap1 netns ns1

# set the ports to up

ip netns exec ns1 ip link set dev tap1 up

ip link set dev br-tap1 up

#

解释:

ip link set tap1 netns ns1 --------<ip link add  tap1 type veth peer name br-tap1>   ---------brctl addif br-test br-tap1

#### PORT 2

# create a port pair

ip link add tap2 type veth peer name br-tap2

# attach one side to linuxbridge

brctl addif br-test br-tap2  ###把所有 br-tap2i连到桥br-test

# attach the other side to namespace

ip link set tap2 netns ns2###把所有 br-tap2i的对端连到namespace

# set the ports to up

ip netns exec ns2 ip link set dev tap2 up

ip link set dev br-tap2 up

#

 

 

内核实现

veth的实现与loopback interface类似,比较简单:


//drivers/net/veth.c

static netdev_tx_t veth_xmit(struct sk_buff *skb, struct net_device *dev)

{

struct net_device *rcv = NULL;

struct veth_priv *priv, *rcv_priv;

 

priv = netdev_priv(dev);

rcv = priv->peer;

rcv_priv = netdev_priv(rcv);

 

stats = this_cpu_ptr(priv->stats);

 

length = skb->len;

//转发给peer

if (dev_forward_skb(rcv, skb) != NET_RX_SUCCESS)

goto rx_drop;

 
 
1 docker(container)的虚拟网络
docker的虚拟网络结构:

 

host创建一个虚拟bridge,每个container对应一个虚拟网络设备(TAP设备),与bridge一起构成一个虚拟网络,并通过虚拟bridge相互通信。

Host的物理网络设备eth0作为内部虚拟网络的NAT网关,container通过eth0访问外部网络。

# ifconfig

docker0   Link encap:Ethernet  HWaddr 56:84:7A:FE:97:99 

          inet addr:127.0.0.2  Bcast:0.0.0.0  Mask:255.0.0.0

eth0      Link encap:Ethernet  HWaddr 00:50:56:28:39:2C 

          inet addr:172.16.213.129  Bcast:172.16.213.255  Mask:255.255.255.0

veth4d69  Link encap:Ethernet  HWaddr A2:7D:7B:61:CA:2F 

          inet6 addr: fe80::a07d:7bff:fe61:ca2f/64 Scope:Link

          UP BROADCAST RUNNING  MTU:1500  Metric:1

 

# brctl show

bridge name     bridge id               STP enabled     interfaces

docker0         8000.56847afe9799       no              veth4d69

(1)host内部container内部互访

通过虚拟网桥完成,比较简单。

 

(2)container通过NAT访问外部网络

-t nat -A POSTROUTING -s 127.0.0.0/8 ! -d 127.0.0.0/8 -j MASQUERADE

上面127.0.0.0/8是内部container的网络,如果目标地址非内部虚拟网络,则进行NAT转换。

 

(3)外部网络访问container

通常来说,与虚拟机一样,我们的容器是用来服务的,这就需要让外部网络能够访问container,这通过DNAT来实现。

-t nat -A DOCKER ! -i docker0 -p tcp -m tcp --dport 49153 -j DNAT --to-destination 127.0.0.3:22

127.0.0.3:22是内部container的ip和sshd端口,在host上映身为49153端口。

# docker port test_sshd 22

0.0.0.0:49153

 

2与虚拟机的比较
实际上,docker的网络结构就是VMWare/KVM的NAT模式。在实际运营中,虚拟机一般会与host在同一个网络,所以采用下面这种桥接模型,而对于docker,由于container内网是不暴露给外部的,所以采用NAT方式。

KVM Bridge示意图:

 

# brctl show

bridge name     bridge id               STP enabled     interfaces

br0             8000.0019d1891e2f       no              eth0

                                                                        vnet0

玩转Linux网络namespace-单机自环测试与策略路由
上周有厂商到公司测试,拿了一块据说很猛的网络处理加速PCIe板卡,拎在手里沉甸甸的很有分量,最让人意淫的是那4个万兆光口,于是我迫不及待的想要一览光口转发时那种看不见的震撼。
       可是,仅凭4个光口怎么测试?起码你要有个“对端”啊!任何人应该都不想扛着三台机器在客户们之间跑来跑去测试其转发性能,当然你也不能指望客户那里就一定有你需要的“对端”设备,比如我们公司就没有这种和万兆光口对接的设备,不过赶巧的是,那天还真有一台设备带有万兆光口,但是只是碰巧了。最佳的测试方式当然是不依赖任何外部设备了,显而易见的方法就是做自环。
       RJ45口的双绞线可以做物理层自环,1/3,2/6短接即可,这样一台机器的一块网卡自己就可以既发又收了,但是你能对比头发略粗的光纤做什么呢?真实的做法当然是用软件解决了,在Linux上可以使用netns来解决,即net namespace。
       netns是一个很好玩的东西,它可以让你在一台机器上模拟多个网络设备,这样做的意义是非同一般的:
1.使用netns可以充分利用闲置的处理器资源,特别是你的多块网卡性能压不满CPU的时候;
2.使用netns可以将不同类型的网络应用隔离,针对每一类实施不同的策略;
3.使用netns有点玩虚拟化的意思,不过比虚拟机更灵活。
一个net namespace有自己独立的路由表,iptables策略,设备管理机构,和其它的netns完全隔离,比如你将eth0加入了netns1,那么netns2中的应用程序就看不到eth0,网卡设备管理只是netns中的一个元素,还有很多,比如你在netns1中配置的iptables策略对netns2中的数据包没有任何影响。总之,如果你懂Linux内核源码,那么只要附着有net结构体字段的那些结构,比如skb,net_device,都和netns有关。
       那么我应该怎么做自环呢?我的设备有4个网卡,我希望1和4之间通信,通过2和3转发,它的逻辑拓扑如下:
PC1/eth0----PC2/eth1(forward)PC2/eth2----PC3/eth3
很简单,将eth0和eth3设置在两个不同的netns,然后用线缆连接eth0和eth1,同样连接eth2和eth3,最后将eth0和eth1的IP地址设置在一个网段,将eth2和eth3的IP地址设置在另一个不同的网段即可。光说不练假把式,具体应该怎么做呢?同样很简单:
1.添加两个netns
ip netns add t1
ip netns add t2
2.将eth0加入t1,并且设置IP地址
ip link set eth0 netns t1
此时再ifconfig就看不到eth0了,你甚至执行ls /sys/class/net也看不到eth0了,只有执行ip netns exec t1 ls /sys/class/net才能看到。
ip netns exec t1 ifconfig eth0 192.168.1.200/24
3.将eth3加入t2,并且设置IP地址
ip link set eth3 netns t2
此时ifconfig就看不到eth3了,你甚至执行ls /sys/class/net也看不到eth3了,只有执行ip netns exec t2 ls /sys/class/net才能看到。
ip netns exec t1 ifconfig eth3 172.16.1.200/24
4.设置eth1和eth2的地址
ifconfig eth1 192.168.1.1/24
ifconfig eth2 172.16.1.1/24
5.设置两个netns的默认路由
ip netns exec t1 route add default gw 192.168.1.1
ip netns exec t2 route add default gw 172.16.1.1
6.测试
在netns t1中ping netns t2中的eth3地址
ip netns exec t1 ping 172.16.1.200
上述配置之后,从eth0发出的包会通过网线到达eth1(而不是走local路由表的loopback),然后经过eth1的forward从eth2发出。经由网线到达目的地eth3杯接收。整个过程中就一台机器,展示出的效果好像三台机器的样子。有了这个机制,是不是再也不用为搭建测试环境而发愁了呢?
       除了自环测试之外,netns还可以用于设置策略路由,这种策略路由不需要ip rule。试想一种场景,你同时运行了P1和P2两个程序,本机所在的局域网有两个出口到达外网,你希望P1通过gw1和外界通信,P2通过gw2和外界通信,约束条件是你的机器只有一张网卡eth0,怎么办呢?通过iptables为P1和P2的数据包打上不同的mark,然后通过ip rule设置策略路由无疑可以解决,另外直接在P1和P2应用程序中用setsockopt也是可以设置ipmark的,这就不需要iptables了。然而这一切都过时了,2014年我需要一种不同的方式。
       我不知道怎么表达我思考的过程,但是给出一个操作序列是简单的事情,因为照着这么做确实可以满足需求,然后看到这篇文章的人照着操作步骤倒推回去,就可以得到一个思考过程。首先你要明白的是Linux内核支持一种虚拟网卡类型,即veth,一般而言veth是成对的,从一个veth发出的数据包可以直接到达它的peer veth,感兴趣的可以看Linux内核的drivers/net/veth.c,和drivers/net/tun.c没什么不同,更简单些罢了。第一步要做的就是建立一对veth:
ip link add veth1 type veth peer name veth2
此时系统中除了eth0之外又多了两块网卡,所有的网卡为lo,eth0,veth1,veth2。中间隐含着一个事实,即veth1和veth2之间有一条虚拟的链路将两块网卡连接起来,就好像一条双绞线连接的两块物理网卡一样。我现在希望P1的数据包通过veth1发出,然后自然而然地就能发到veth2,但是随后怎么通过eth0发到物理线路呢?太简单,太简单,使用bridge吧:
brctl addbr br0
brctl addif br0 eth0 veth2
同时,veth1和br0所在的局域网设置在一个IP网段中,这下子就全通了,该二层网络的逻辑拓扑为:
veth1----veth2(bridge)eth0----gw(1,2)
怎么设置netns我本来不想说了,但是由于小小暂时不跟我玩了,我还是写完吧。首先将veth1设置到netns1(具体怎么创建netns,不再赘述)并设置路由:
ip link set veth1 netns netns1
ip netns exec netns1 route add default gw $gw1
route add default gw $gw2
这就完了?是的,完事了。事实上,保留br0的默认netns即可,没有必要创建netns2了。接下来需要做的就是启动P1和P2了:
ip netns exec netns1 P1
P2
好了,一切结束。
       我始终都觉得,在Linux上一般都是不用修改源码就能解决问题,可是我还是喜欢修改代码,原因何在?很简单,源码很容易获得,并且源码很容易修改,我走火入魔般地写了大量的Netfilter扩展以及做了大量的nf_conntrack修改,甚至还添加了一些该死的socket filter...虽然这些行为都是自娱自乐型的,并没有被应用在工作中,但是这些行为说明我不是网络管理员,而是一名程序员,哈哈,自封的资深软件工程师(我还是觉得这些成果能被应用)。然而,做一名技术精湛的网络管理人员的难度却远远超过做程序员的难度。这不,又一次遇到了OpenVPN的多实例问题,我觉得,单纯的程序员搞不定它,单纯的网管也不行。
       TAP模式的多实例已经被我用Linux Bridge完美蹂躏了,但是TUN模式的多实例问题仍然没有完美的方案,虽然修改tun驱动,使用broadcast mode bonding+tun filter可以解决,但是我还是觉得那是一种走火入魔的方式,因此就算在公司我也没能将整个调试测试进行下去,结果落了个不了了之,事实上,是我太不喜欢那种方式。tun的IP filter是我改出来的方案,并非标准的,能不能使用标准的方式进行寻址呢?使用netns,答案就是肯定的。
       假设在GW上启动了2个OpenVPN实例ovpn1和ovpn2,虚拟网卡分别为tun1和tun2,在client-connect脚本中得知ovpn2负责N1,ovpn2负责N2。现在问题的关键是,GW后方出发的数据包如何知道是将数据包发送到tun1还是tun2,这个判断能不能自动进行?如果使用netns,那就是可以的,我可以将2个tun分别设置在不同的netns,然后每一个netns对应一个同处一个netns的veth虚拟网卡,这些veth的peer们处在另外一个netns中,这样就可以实现IP层TUN模式虚拟网卡到以太网的TAP模式虚拟网卡的适配。最后将这些peer们Bridge成一个br0,那么TUN模式的OpenVPN就能和TAP模式的OpenVPN采用同一种方式处理了。

       不管怎样,当你玩弄netns的时候,你要知道你并不是在玩弄冷酷无情的虚拟化操作系统,也不是真的模拟了两台物理上相互隔离的机器,因为虽然两个程序的网络是隔离的,但是文件系统却是共享的。你要时刻准备着,使用网络隔离和使用内存,文件系统共享相结合。将一台机器既可以作为多台机器使用,又可以作为一台机器共享资源!

       不管怎样,当你玩弄netns的时候,你要知道你并不是在玩弄冷酷无情的虚拟化操作系统,也不是真的模拟了两台物理上相互隔离的机器,因为虽然两个程序的网络是隔离的,但是文件系统却是共享的。你要时刻准备着,使用网络隔离和使用内存,文件系统共享相结合。将一台机器既可以作为多台机器使用,又可以作为一台机器共享资源!
       理解了上述的例子和最后的总结,那么我来发问,单网卡或者没有网卡怎么玩自环?这个需求可能就是为了测试一下协议栈而已。略去思考的过程,很简单,多加一个层次。比如你有一台机器一块网卡也没有,那么你只需要下面的命令就可以在你的机器上实现IP转发或者bridge转发了:
ip link add v1 type veth peer name vp1
ip link add v2 type veth peer name vp2
brctl addbr br0
brctl addif vp1 vp2
ifconfig vp1 up
ifconfig vp2 up
sysctl -w net.ipv4.ip_forward=1
ip netns add t1
ip netns add t2
ip link set v1 netns t1
ip link set v2 netns t2
ip netns exec t1 ifconfig v1 1.1.1.1/24
ip netns exec t2 ifconfig v2 1.1.1.2/24
ip netns exec t1 ping 1.1.1.2
...

 https://blog.scottlowe.org/2013/09/04/introducing-linux-network-namespaces/

 

 

(1)ip netns相关命令
1.增加虚拟网络命名空间

 
ip netns add ns
   该命令会在/var/run/netns目录下创建ns网络命名空间名


2.显示所有的虚拟网络命名空间

root@wade-ThinkCentre-E73:~# ip netns list
ns
  也可通过查看/var/run/netns目录下的文件来list,结果一样

root@wade-ThinkCentre-E73:~# ls /var/run/netns/
ns
 
3.进入虚拟机网络环境

 ip netns exec ns bash  #打开虚拟网络环境ns的bash窗口
 ip addr #显示所有虚拟网络环境的设备
 exit #退出该网络虚拟环境

 
4.增加一对veth虚拟网卡
 ip link add type veth
 
5.将veth0添加到ns虚拟网络环境
ip link set veth0 netns ns
 
6.将虚拟网卡veth1改名并添加到net1虚拟网络环境中
ip link set dev veth1 name net1-bridge netns net1
 
 
7.设置虚拟网络环境ns的veth0设备处于激活状态
ip netns exec ns ip link set veth0 up
 
8.为虚拟网络环境ns的veth0设备增加IP地址
ip netns exec ns ip address add 10.0.1.1/24 dev veth0

 

 https://blog.csdn.net/tycoon1988/article/details/39055149

 

 

 

 

(2)/proc/[pid]/ns文件
从3.8版本的内核开始,用户就可以在/proc/[pid]/ns文件下看到指向不同命名空间(namespace)号的文件,[4026532791]即为namespace号。

root@wade-ThinkCentre-E73:/proc/5787/ns# ll
总用量 0
dr-x--x--x 2 root root 0 8月 31 16:13 ./
dr-xr-xr-x 9 root root 0 8月 31 12:41 ../
lrwxrwxrwx 1 root root 0 8月 31 16:36 ipc -> ipc:[4026532791]
lrwxrwxrwx 1 root root 0 8月 31 16:36 mnt -> mnt:[4026532915]
lrwxrwxrwx 1 root root 0 8月 31 16:13 net -> net:[4026532845]
lrwxrwxrwx 1 root root 0 8月 31 16:36 pid -> pid:[4026532917]
lrwxrwxrwx 1 root root 0 8月 31 16:36 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 8月 31 16:36 uts -> uts:[4026532916]
如果两个进程指向的namespace编号相同,就说明他们在同一个namespace下。/proc/[pid]/ns的另外一个作用是,一旦文件被打开,只要打开的文件描述符(fd)存在,那么就算PID所属的所有进程都已经结束,创建的namespace就会一直存在。那如何打开文件描述符呢?把/proc/[pid]/ns目录挂载起来或者软链接就可以达到这个效果,命令如下。
挂载
# touch ~/net
# mount --bind /proc/$pid/ns/net ~/net

软链接

#ln -sf /proc/$pid/ns/net "/var/run/netns/${name}"


在使用docker的过程中我们可以使用如下命令达到操作容器的网络的目的

ln -sf /proc/$pid/ns/net "/var/run/netns/${container_name}" //$pid 为docker容器pid

ip netns exec "${container_name}"  `command`

使用libvirt管理lxc(virsh篇)

 (2013-07-08 09:56:09)
标签: 

虚拟化

 

libvirt

 

lxc

 

virsh

分类: 计算系统虚拟化

作者:说话的白菜  博客:http://speakingbaicai.blog.51cto.com/#

 

一、简介

1libvirt

libvirtLinux上的虚拟化库,是长期稳定的C语言API,支持KVM/QEMUXenLXC等主流虚拟化方案。链接:http://libvirt.org/

virshlibvirt对应的shell命令。

2lxc

Linux Container容器(简称“lxc”)可以提供轻量级的虚拟化,以便隔离进程和资源,而且不需要提供指令解释机制以及全虚拟化的其他复杂性。与KVMXen等典型虚拟机相比,lxc的额外资源开销更小。

3Cgroup

Cgroupscontrol groups的缩写,是Linux内核提供的一种可以限制、记录、隔离进程组(process groups)所使用的物理资源(如:CPU, Memory, IO等)的机制。最初由Google的工程师提出,后来被整合进Linux内核。Cgroups也是LXC为实现虚拟化所使用的资源管理手段,可以说没有Cgroups就没有LXC

二、环境介绍

OSUbuntu 12.04.1 LTS

内核:Linux 3.2.0-33-generic-pae #52-Ubuntu SMP Thu Oct 18 16:39:21 UTC 2012 i686 i686 i386 GNU/Linux

libvirt0.9.81.0.2

lxc0.7.5

三、准备工作

ubuntu安装,直接apt-get install

1、安装libvirt

sudo apt-get install libvirt-bin libvirt-dev

2、网桥管理工具

sudo apt-get install bridge-utils

3lxc

sudo apt-get install lxc

该命令将自动安装LXC依赖的其他软件:cgroup-lite, lvm2, and debootstrap

四、libvirt xml配置文件

libvirt(包括virsh)使用xml文件对lxc进行配置,其中包括lxc的名称、分配内存、cpu设置等多种信息。定义、创建lxc等操作都需要xml配置文件的参与,因此这里先介绍xml配置文件。我编辑了一个名为lxc1.xmlxml文件,其中定义了一个名为lxc1kvm 虚拟机。

<domain type='lxc'>       //“虚拟机”类型

   <name>lxc1</name>  //lxc名称

   <memory>190000</memory> //分配内存,单位kb

   <vcpu>2</vcpu>        //vcpu对于lxc没有实际作用,可以不进行设置

   <cputune>

     <shares>1000</shares> //设置cpu共享时间比例

   </cputune>

   <os>

   <type>exe</type>

   <init>/bin/sh</init>

   </os>

   <devices>

     <console type='pty'/>

     <interface type = 'bridge'> //设置挂接网桥

       <source bridge = 'br0'/>

     </interface>

   </devices>

</domain>

相比于kvm虚拟机,lxc不需要设置cdromhd等硬件配置,设置信息较少

五、查看虚拟机列表

libvirt支持多种虚拟机、虚拟环境,每种虚拟机的连接接配置是不同的,如libvirt默认连接是“qemu:///system”,即支持kvm/qemu。平时我们输入virsh list,其实就是virsh -c qemu:///system listLxc对应的连击是lxc:///,当使用与本机lxc相关的virsh命令时,应当加上“-c lxc:///”

查看本机lxcvirsh -c lxc:/// list 。出现如下列信息:

$ virsh -c lxc:/// list

Id 名称 状态

----------------------------------------------------

 

$ virsh -c lxc:/// list

Id 名称 状态

----------------------------------------------------

4832 lxc1 running

 

六、定义、启动、创建虚拟机

这里以上述lxc1.xml配置文件为例

1、定义虚拟机

virsh -c lxc:/// define lxc1.xml

备注:此时lxc只被定义了,并没有真正启动。因此此时执行virsh -c lxc:/// list没有任何虚拟机信息

$ virsh -c lxc:/// list

Id 名称 状态

----------------------------------------------------

2、启动虚拟机

virsh -c lxc:/// start lxc1

启动了由lxc1.xml定义的、名为lxc1的虚拟机。此时执行virsh -c lxc:/// list出现如下信息:

$ virsh -c lxc:/// list

Id 名称 状态

----------------------------------------------------

4832 lxc1 running

3、创建虚拟机

创建虚拟机的作用相当于定义虚拟机+启动虚拟机

virsh create lxc1.xml

此时执行virsh -c lxc:/// list,结果如下:

$ virsh list

Id 名称 状态

----------------------------------------------------

4832 lxc1 running

七、进入lxc内部操作lxc

首先lxc不支持vnc查看,但可以virsh console命令进入操作

1、进入lxc1

virsh -c lxc:/// console lxc1

连接到域 lxc1

Escape character is ^]

#

进入之后就可以像在物理机上使用shell命令一样操作lxc

2、退出lxc1

合理退出应该输入“Ctrl+]”,这样退出后可以再次进入。不要使用其他命令退出,如使用“Ctrl+d”,将导致lxc被停止、销毁等。

八、取消定义、销毁lxc

1、取消定义虚拟机

virsh -c lxc:/// undefine lxc1

libvirt取消lxc1的定义,系统无法在辨识lxc1。此步骤与virsh -c lxc:/// define lxc1.xml对应。

2、销毁虚拟机

virsh -c lxc:/// destroy lxc1

libvirt直接销毁lxc1,取消lxc1的定义。

 

使用libvirt API管理lxc,与使用libvirt API管理kvm类似,可参见使用libvirt管理kvmAPI篇)http://speakingbaicai.blog.51cto.com/5667326/1162005。主要不同点在于建立连接不同,即将virConnectPtr conn1 = virConnectOpen("qemu:///system"); 改为virConnectPtr conn = virConnectOpen("lxc:///"); 

 

参考:

lxc 常用命令:http://www.cnblogs.com/lisperl/archive/2012/04/13/2446179.html

libvirt_lxc : http://libvirt.org/drvlxc.html

分步骤更为详细 https://www.berrange.com/posts/2011/09/27/getting-started-with-lxc-using-libvirt/

 

 

 

 

posted on 2019-08-09 16:39  wddblog  阅读(981)  评论(0编辑  收藏  举报