首页 > 代码库 > 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

  1. 调用n, cniVersion, err := loadNetConf(args.StdinData)中加载网络配置
  2. 调用br, brInterface, err := setupBridge(n),创建网桥,如果需要的话
  3. 调用netns, err := ns.GetNS(args.Netns)解析出net ns
  4. 调用hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode)创建veth pair
  5. 调用r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)运行IPAM插件,并获取结果
  6. 调用result, err := current.NewResultFromResult(r)
  7. 遍历result.IPs,将其中的Interface都设置为2,即默认作为result.Interfaces[2]的IP,若Gateway为nil并且n.IsGW为真,则调用calcGatewayIP(&ipc.Address),由IP地址计算出网关地址(??)
  8. 调用netns.Do(),如果n.IsDefaultGW为真的话,设置每个IP所在子网的默认路由,再调用ipam.ConfigureIface(args.IfName, result)以及ip.SetHWAddrBrIP(args.IfName, result.IPs[0].Address.IP, nil),最后,重新获取veth的MAC地址,因为它可能改变了(??)。
  9. 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()
  10. 若n.IPMasq为真,则进行一系列设置
  11. 最后,再调用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)

  1. 先调用br, err := ensureBridge(n.BrName, n.MTU)  // create bridge if necessary
  2. 返回return br, &current.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 插件实现代码分析