root@mydocker:~/feat-network-1/mydocker/network# go test -v -run TestAllocate=== RUN TestAllocate
ipam_test.go:14: alloc ip: 192.168.0.1
--- PASS: TestAllocate (0.00s)PASS
ok mydocker/network 0.006s
root@mydocker:~/feat-network-1/mydocker/network# go test -v -run TestRelease=== RUN TestRelease
--- PASS: TestRelease (0.00s)PASS
ok mydocker/network 0.005s
# 创建网桥sudo brctl addbr br0
# 为bridge分配IP地址,激活上线sudo ip addr add 172.18.0.1/24 dev br0
sudo ip link set br0 up
# 配置 nat 规则让容器可以访问外网sudo iptables -t nat -A POSTROUTING -s 172.18.0.0/24 ! -o br0 -j MASQUERADE
我们的网络驱动要做的事情就是把上述命令用 Go 实现,需要用到以下几个库
net 库是 Go 语言内置的库,提供了跨平台支持的网络地址处理,以及各种常见协议的IO支持,比如TCP、UDP、DNS、Unix Socket等。
netlink库 是Go 语言的操作网络接口、路由表等配置的库 ,使用它的调用相当于我们通过 IP 命令去管理网络接口。
netns库 就是 Go 语言版的ip netns exec 命令实现。通过这个库可以让 netlink 库中配置网络接口的代码在某个容器的 Net amespace 中执行。
func(d*BridgeNetworkDriver)Create(subnetstring,namestring)(*Network,error){ip,ipRange,_:=net.ParseCIDR(subnet)ipRange.IP=ipn:=&Network{Name:name,IPRange:ipRange,Driver:d.Name(),}err:=d.initBridge(n)iferr!=nil{returnnil,errors.Wrapf(err,"Failed to create bridge network")}returnn,err}
func(d*BridgeNetworkDriver)initBridge(n*Network)error{bridgeName:=n.Name// 1)创建 Bridge 虚拟设备iferr:=createBridgeInterface(bridgeName);err!=nil{returnerrors.Wrapf(err,"Failed to create bridge %s",bridgeName)}// 2)设置 Bridge 设备地址和路由gatewayIP:=*n.IPRangegatewayIP.IP=n.IPRange.IPiferr:=setInterfaceIP(bridgeName,gatewayIP.String());err!=nil{returnerrors.Wrapf(err,"Error set bridge ip: %s on bridge: %s",gatewayIP.String(),bridgeName)}// 3)启动 Bridge 设备iferr:=setInterfaceUP(bridgeName);err!=nil{returnerrors.Wrapf(err,"Failed to set %s up",bridgeName)}// 4)设置 iptables SNAT 规则iferr:=setupIPTables(bridgeName,n.IPRange);err!=nil{returnerrors.Wrapf(err,"Failed to set up iptables for %s",bridgeName)}returnnil}
funcsetInterfaceIP(namestring,rawIPstring)error{retries:=2varifacenetlink.Linkvarerrerrorfori:=0;i<retries;i++{// 通过LinkByName方法找到需要设置的网络接口iface,err=netlink.LinkByName(name)iferr==nil{break}log.Debugf("error retrieving new bridge netlink link [ %s ]... retrying",name)time.Sleep(2*time.Second)}iferr!=nil{returnerrors.Wrap(err,"abandoning retrieving the new bridge link from netlink, Run [ ip link ] to troubleshoot")}// 由于 netlink.ParseIPNet 是对 net.ParseCIDR一个封装,因此可以将 net.PareCIDR中返回的IP进行整合// 返回值中的 ipNet 既包含了网段的信息,192 168.0.0/24 ,也包含了原始的IP 192.168.0.1ipNet,err:=netlink.ParseIPNet(rawIP)iferr!=nil{returnerr}// 通过 netlink.AddrAdd给网络接口配置地址,相当于ip addr add xxx命令// 同时如果配置了地址所在网段的信息,例如 192.168.0.0/24// 还会配置路由表 192.168.0.0/24 转发到这 testbridge 的网络接口上addr:=&netlink.Addr{IPNet:ipNet}returnnetlink.AddrAdd(iface,addr)}
启动 Bridge 设备
这部分主要实现下面 ip link set xxx up这个命令,启动 Bridge 设备。
1
2
3
4
5
6
7
8
9
10
11
funcsetInterfaceUP(interfaceNamestring)error{link,err:=netlink.LinkByName(interfaceName)iferr!=nil{returnerrors.Wrapf(err,"error retrieving a link named [ %s ]:",link.Attrs().Name)}// 等价于 ip link set xxx up 命令iferr=netlink.LinkSetUp(link);err!=nil{returnerrors.Wrapf(err,"nabling interface for %s",interfaceName)}returnnil}
root@mydocker:~/feat-network-1/mydocker/network# go test -v -run TestBridgeCreate=== RUN TestBridgeCreate
bridge_driver_test.go:15: create network :&{testbridge 192.168.0.1/24 bridge}--- PASS: TestBridgeCreate (1.80s)PASS
ok mydocker/network 1.804s
然后查看是否真正创建出了网桥
1
2
3
4
5
6
7
root@mydocker:~/feat-network-1/mydocker/network# ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether fa:16:3e:58:62:ef brd ff:ff:ff:ff:ff:ff
3: testbridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/ether b6:f9:fe:f3:f7:16 brd ff:ff:ff:ff:ff:ff
可以看到,第三个就是我们刚创建出的 testbridge 网桥,说明 create 是正常的。
Delete
1
2
3
4
5
6
root@mydocker:~/feat-network-1/mydocker/network# go test -v -run TestBridgeDelete=== RUN TestBridgeDelete
bridge_driver_test.go:24: delete network :testbridge
--- PASS: TestBridgeDelete (0.02s)PASS
ok mydocker/network 0.019s
检查是否真正删除了
1
2
3
4
5
root@mydocker:~/feat-network-1/mydocker/network# ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether fa:16:3e:58:62:ef brd ff:ff:ff:ff:ff:ff
testbridge 网桥已经不存在了,说明 Delete 也是正常的。
Connect
需要先创建网桥,在进行绑定测试:
1
2
3
4
5
root@mydocker:~/feat-network-1/mydocker/network# go test -v -run TestBridgeConnect=== RUN TestBridgeConnect
--- PASS: TestBridgeConnect (0.10s)PASS
ok mydocker/network 0.104s
查看是否新建了 veth 并绑定到该网桥上了
1
2
3
4
5
root@mydocker:~/feat-network-1/mydocker/network# ip link show type veth
5: cif-testc@testc: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 2a:fb:68:92:7e:59 brd ff:ff:ff:ff:ff:ff
6: testc@cif-testc: <NO-CARRIER,BROADCAST,MULTICAST,UP,M-DOWN> mtu 1500 qdisc noqueue master testbridge state LOWERLAYERDOWN mode DEFAULT group default qlen 1000 link/ether 06:d6:04:62:13:eb brd ff:ff:ff:ff:ff:ff
root@mydocker:~/feat-network-1/mydocker/network# go test -v -run TestBridgeDisconnect=== RUN TestBridgeDisconnect
--- PASS: TestBridgeDisconnect (0.01s)PASS
ok mydocker/network 0.013s
查看是否接触绑定
1
2
3
4
5
root@mydocker:~/feat-network-1/mydocker/network# ip link show type veth
5: cif-testc@testc: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 2a:fb:68:92:7e:59 brd ff:ff:ff:ff:ff:ff
6: testc@cif-testc: <NO-CARRIER,BROADCAST,MULTICAST,UP,M-DOWN> mtu 1500 qdisc noqueue state LOWERLAYERDOWN mode DEFAULT group default qlen 1000 link/ether 06:d6:04:62:13:eb brd ff:ff:ff:ff:ff:ff