首页 > 代码库 > CNI bridge 插件实现代码分析
CNI bridge 插件实现代码分析
对于每个CNI 插件在执行函数cmdAdd之前的操作是完全一样的,都是从环境变量和标准输入内读取配置。这在http://www.cnblogs.com/YaoDD/p/6410725.html这篇博文里面已经有完整的叙述了。接下来就直接从CmdAdd函数开始分析CNI bridge插件的执行过程。
skel.CmdArgs数据结构如下所示
// CmdArgs captures all the arguments passed in to the plugin // via both env vars and stdin type CmdArgs struct { ContainerID string Netns string IfName string Args string Path string StdinData []byte }
// cni/plugins/main/bridge/bridge.go
1、func cmdAdd(args *skel.CmdArgs) error
- 调用n, cniVersion, err := loadNetConf(args.StdinData)中加载网络配置
- 调用br, brInterface, err := setupBridge(n),创建网桥,如果需要的话
- 调用netns, err := ns.GetNS(args.Netns)解析出net ns
- 调用hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode)创建veth pair
- 调用r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)运行IPAM插件,并获取结果
- 调用result, err := current.NewResultFromResult(r)
- 遍历result.IPs,将其中的Interface都设置为2,即默认作为result.Interfaces[2]的IP,若Gateway为nil并且n.IsGW为真,则调用calcGatewayIP(&ipc.Address),由IP地址计算出网关地址(??)
- 调用netns.Do(),如果n.IsDefaultGW为真的话,设置每个IP所在子网的默认路由,再调用ipam.ConfigureIface(args.IfName, result)以及ip.SetHWAddrBrIP(args.IfName, result.IPs[0].Address.IP, nil),最后,重新获取veth的MAC地址,因为它可能改变了(??)。
- n.IsGW为真,则进行一系列设置,其实就是对网桥进行配置,使其作为默认网关
- 遍历result.IPs,设置gwn := &net.IPNet{IP: ipc.Gateway, Mask: ipc.Address.Mask}
- 若ipc.Gateway不为空且firstV4Addr为空,则设置firstV4Addr为ipc.Gateway
- 最后,调用err = ensureBridgeAddr(br, gwn, n.ForceAddress)
- 如果firstV4Addr不为nil,则调用ip.SetHWAddrByIP(n.BrName, firstV4Addr, nil)
- 最后,调用ip.EnableIP4Forward()
- 若n.IPMasq为真,则进行一系列设置
- 最后,再调用br, err = bridgeByName(n.BrName)再对它的MAC地址进行设置,因为在第一个veth设备加入或者它被设置了IP地址之后,它的MAC地址都可能发生变化
NetConf的数据结构如下所示
type NetConf struct { types.NetConf BrName string `json:"bridge"` IsGW bool `json:"isGateway"` IsDefaultGW bool `json:"isDefaultGateway"` ForceAddress bool `json:"forceAddress"` IPMasq bool `json:"ipMasq"` MTU int `json:"mtu"` HairpinMode bool `json:"hairpinMode"` }
// cni/plugins/main/bridge/bridge.go
2、func loadNetConf(bytes []byte) (*NetConf, string, error)
该函数将NetConf的BrName设置为defaultBrName = "cni0",之后再将bytes中的内容unmarshal到NetConf中
// cni/plugins/main/bridge/bridge.go
3、func setupBridge(n *NetConf) (*netlink.Bridge, *current.Interface, error)
- 先调用br, err := ensureBridge(n.BrName, n.MTU) // create bridge if necessary
- 返回return br, ¤t.Interface{Name: br.Attrs().Name, Mac: br.Attrs().HardwareAddr.String()}
// cni/plugins/main/bridge/bridge.go
4、func ensureBridge(brName string, mtu int) (*netlink.Bridge, error)
1、构造br := &netlink.Bridge{
LinkAttrs: netlink.LinkAttrs{
Name: brName,
MTU: mtu,
TxQLen: -1, // means default txqueuelen
}
}
2、调用err := netlink.LinkAdd(br)
// Re-fetch link to read all attributes and if it already existed,
// ensure it‘s really a bridge with similar configuration -->其实只要配置相同即可
3、调动br, err := bridgeByName(brName) -->l, err := netlink.LinkByName(name)找到设备,再反向断言br, ok := l.(*netlink.Bridge)
4、最后调用netlink.LinkSetUp(br)
// cni/plugins/main/bridge/bridge.go
5、func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool) (*current.Interface, *current.Interface, error)
1、首先在container中,即netns中创建veth pair,并且将host端移动到host netns
调用netns.Do(),在Do中调用hostVeth, containerVeth, err := ip.SetupVeth(ifName, mtu, hostNS),再用hostVeth和containerVeth填充contIface和hostIface的内容
// need to lookup hostVeth again as its index has changed during ns move
2、调用hostVeth, err := netlink.LinkByName(hostIface.Name)在host netns中找到veth end,接着调用netlink.LinkSetMaster(hostVeth, br)将veth连接至网桥。最后,调用netlink.LinkSetHairpin(hostVeth, hairpinMode)设置hairpinmode
// cni/p
CNI bridge 插件实现代码分析