cni 添加網絡 流程分析

From http://www.cnblogs.com/YaoDD/p/6024535.html?utm_source=itdadao&utm_medium=referral

1
2
3
4
cnitool: Add or remove network interfaces from a network namespace
 
  cnitool  add  <net>  <netns>
  cnitool  del  <net>  <netns>

cnitool的使用方式如下:其中<net>是配置文件所在目錄,一般爲/etc/cni/net.d/*.conf文件,<netns>爲network namespace的目錄文件,一般爲/var/run/netns/NS-ID

 

1、cni/cnitool/cni.go

main函數:

(1)、首先從環境變量NETCONFPATH中獲取netdir,若不存在則設置爲默認值"/etc/cni/net.d",之後調用netconf, err := libcni.LoadConf(netdir, os.Args[2])加載配置變量,netns賦值爲os.Args[3]

(2)、調用獲得CNIConfig和RuntimeConf

1
2
3
4
5
6
7
8
9
cninet := &libcni.CNIConfig{
  Path: strings.Split(os.Getenv(EnvCNIPath), ":")
}
 
rt := &libcni.RuntimeConf{
  ContainerID:    "cni"
  NetNS:       netns,
  IfName:       "eth0",
}

(3)、os.Args[1]爲add時,調用_, err := cninet.AddNetwork(netconf, rt)添加網絡

 

NetworkConfig的數據結構如下所示:

1
2
3
4
type NetworkConfig struct {
  Network  *types.NetConf
  Bytes   []byte
}

  

1
2
3
4
5
6
7
8
9
type NetConf struct {
  CNIVersion    string
  Name       string
  Type       string
  IPAM struct {
      Type   string
  }
  DNS    DNS
}

  

Runtime的數據結構如下所示:

1
2
3
4
5
6
type RuntimeConf struct {
  ContainerID   string
  NetNS       string
  IfName      string
  Args       [][2]string
}

 

CNIConfig的數據結構如下所示:CNIconfig包含的是bridge,dhcp等等二進制文件的目錄集

1
2
3
type CNIConfig struct {
  Path    []string
}

  

2、cni/libcni/api.go

// AddNetwork executes the plugin with ADD command

func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (*types.Result, error)

(1)、首先調用pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path),該函數用於在c.Path中尋找net.Network.Type,然後返回全路徑

(2)、調用 return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt)),net.Bytes是配置文件的序列化二進制碼,其中c.args函數主要的作用是填充並返回一個*invoke.Args類型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
return &invoke.Args {
 
  Command:       action,
 
  ContainerID:     rt.ContainerID,
 
  NetNS:         rt.NetNS,
 
  PluginArgs:      rt.Args,
 
  IfName:        rt.IfName,
 
  Path:         strings.Join(c.Path, ":"),
 
}

  

3、cni/pkg/invoke/exec.go

func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (*types.Result, error)

該函數只是簡單地返回 return defaultPluginExec.WithResult(pluginPath, netconf, args)

其中defaultPluginExec是一個*PluginExec的類型變量,賦值過程如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var defaultPluginExec = &PluginExec{
 
  RawExec:      &RawExec{Stderr: os.Stderr},    --->RawExec又是在raw_exec.go中定義的一個結構類型,其中只有一個Stderr的io.Writer類型
  VersionDecoder:   &version.PluginDecoder{},
}
 
// 其中PluginExec的定義如下所示:
 
type PluginExec struct {
 
  RawExec interface {
    ExecPlugin(pluginPath string, stdinData []byte, environ []string)
  }
 
  VersionDecoder interface {
    Decode(jsonBytes []byte) (version.PluginInfo, error)
  }
 
}

  

4、cni/pkg/invoke/exec.go

func (e *PluginExec) WithResult(pluginPath string, netconf []byte, args CNIArgs) (*types.Result, error)

(1)、調用stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv()),args.AsEnv()將args裏的內容轉變爲環境變量返回,例如CNI_COMMAND=ADD等等

(2)、res := &types.Result{},再調用json.Unmarshal(stdoutBytes, res)解析出Result並返回

 

5、cni/pkg/invoke/raw_exec.go

func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error)

(1)、首先獲得stdout := bytes.Buffer{}作爲輸出緩衝器

(2)、創建執行命令並調用c.Run()運行

1
2
3
4
5
6
7
8
c := exec.Cmd {
  Env:      environ,
  Path:     pluginPath,
  Args:     []string{pluginPath},
  Stdin:     bytes.NewBuffer(stdinData),
  Stdout:    stdout,
  Stderr:    e.Stderr,
}

(3)、最後返回stdout.Bytes()

 

----------------------------------------------------------------------------------- plugin的執行框架 -----------------------------------------------------------------------

每個插件的main函數都調用了skel.PluginMain(cmdAdd, cmdDel, version.Legacy),其中skel包是一個框架庫,所有新添加的插件只要調用skel.PluginMain傳入差價的cmdAdd和cmdDel方法就可以了。

1、cni/pkg/skel/skel.go

// PluginMain is the "main" for a plugin. It accepts two callbacks functions for add and del commands

func PluginMain(cmdAdd, cmdDel)

(1)、首先構造一個一個dispatcher類型的caller:

1
2
3
4
5
6
7
8
9
10
11
caller := dispatcher {
 
  Getenv:    os.Getenv,
 
  Stdin:     os.Stdin,
 
  Stdout:      os.Stdout,
 
  Stderr:       os.Stderrr,
 
}

  

之後再簡單地調用 err := caller.pluginMain(cmdAdd, cmdDel, versionInfo)

 

2、cni/pkg/skel/skel.go

func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) *types.Error

(1)、首先調用cmd, cmdArgs, err := t.getCmdArgsFromEnv(),該函數從之前傳入給本進程的環境變量中解析出很多例如CNI_COMMAND,CNI_CONTAINERID之類的信息,填充獲得cmdArgs,如下所示:

1
2
3
4
5
6
7
8
9
cmdArgs := &CmdArgs {
 
  ContainerID:      contID,
  Netns:         netns,
  IfName:         ifName,
  Args:           args,
  Path:           path,
  StdinData:        stdinData,
}

  

(2)、根據cmd調用相應的處理函數進行處理,例如我們對add進行分析,調用err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdAdd)

 

3、cni/pkg/skel/skel.go

func (t *dispatcher) checkVersionAndCall(cmdArgs *CmdArgs, pluginVersionInfo version.PluginInfo, toCall func (*CmdArgs) error)

(1)、首先調用configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)和verErr := t.VersionReconciler.Check(configVersion, pluginVersionInfo)對version進行檢查

(2)、最後調用return toCall(cmdArgs)函數,進入具體的插件執行網絡的add或者delete操作

 

-------------------------------------------------------------- 當type爲bridge時 ------------------------------------------------------------------------

NetConf的數據結構如下所示:

1
2
3
4
5
6
7
8
9
10
11
type NetConf struct {
 
  types.NetConf      // 各個plugin的公共部分
  BrName    string
  IsGW      bool
  IsDefaultGW  bool
  ForceAddress   bool
  IPMasq     bool
  MTU       int
  HairpinMode  bool
}

  

1、cni/plugins/main/bridge/bridge.go

(1)、首先調用n, err := loadNetConf(args.StdinData),加載獲得配置文件

(2)、調用br, err := setupBridge(n)建立網橋

(3)、調用netns, err := ns.GetNS(args.Netns), 返回一個接口NetNS,其中最核心的內容就是打開的net ns的*os.File

(4)、調用setupVeth(netns, br, args.IfName, n.MUT, n.HairpinMode)

(5)、調用result, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData),運行IPAM插件,返回配置

(6)、當result.IP4.Gateway == nil 並且n.IsGW爲true時,調用result.IP4.Gateway = calcGatewayIP(&result.IP4.IP)

(7)、調用netns.Do(),其中填充函數,該函數的執行流程如下:

    一、當n.IsDefaultGW爲true時,首先調用_, defaultNet, err := net.parseCIDR("0.0.0.0/0"),之後在遍歷result.IP4.Routes,判斷是否同時設置了isDefaultGateway爲true以及IPAM設置了默認的路由,最後擴展result.IP4.Routes

    二、調用ipam.ConfigureIface(args.IfName, result)和ip.SetHWAddrByIP(args.IfName, result.IP4.IP.IP, nil)

(8)、當n.IsGW爲true,設置變量gwn := &net.IPNet{ IP: result.IP4.Gateway, Mask: result.IP4.IP.Mask},接着再依次調用ensureBridgeAddr(br, gwn, n.ForceAddress),ip.SetHWAddrByIP(n.BrName, gwn.IP, nil)和ip.EnableIP4Forward()

(9)、當n.IPMasq爲true時,調用chain := utils.FormatChainName(n.Name, args.ContainerID),comment := utils.FormatComment(n.Name, args.ContainerID),最後再調用ip.SetupIPMasq(ip.Network(&result.IP4.IP), chain, comment)

(10)、最後設置result.DNS = n.DNS並返回return result.Print()

 

2、cni/plugins/main/bridge/bridge.go

func setupBridge(n *NetConf) (*netlink.Bridge, error)

該函數的作用僅僅是調用br, err := ensureBridge(n.BrName, n.MTU),並返回return br, nil

 

3、cni/plugins/main/bridge/bridge.go

func ensureBridge(brName, mtu int) (*netlink.Bridge, error)

(1)、首先創建網橋的數據結構:

1
2
3
4
5
6
7
8
9
10
br := &netlink.Bridge{
 
  LinkAttrs : netlink.LinkAttrs{
 
    Name:  brName,
    MTU:    mtu,
    TxQlen:   -1, // means use TX queue length as the default packet limit
  },
 
}

  

(2)、調用netlink.LinkAdd(br),添加網橋,如果報錯,且錯誤不是設備已經存在了,那麼返回錯誤。如果問題是設備已經存在,並且配置是相同的,那麼沒有問題。

(3)、調用netlink.LinkSetUp(br),啓動網橋,並返回return br, nil

 

------------------------------------------------------------------------ veth 的配置 ------------------------------------------------------------------------------ 

 

4、cni/plugins/main/bridge/bridge.go

func setupVeth(netns ns.NetNS, br *netlink.Bridge, ifName string, mtu int, hairpinMode bool) error

(1)、首先調用函數netns.Do(),其中封裝的函數的作用爲創建一個veth對,並且將host端放入host的namespace,其中主要調用了hostVeth, _, err := ip.SetupVeth(ifName, mtu, hostNS) 和 hostVethName = hostVeth.Attrs().Name

(2)、隨着namespace的移動,hostVeth的索引已經移動了,因此需要調用hostVeth, err := netlink.LinkByName(hostVethName)找回hostVeth

(3)、調用netlink.LinkSetMaster(hostVeth, br)將host端的veth和bridge相連

(4)、調用netlink.LinkSetHairpin(hostVeth, hairpinMode)來設置hairpin mode

 

5、cni/pkg/link.go

// SetupVeth sets up a virtual ethernet link.

// Should be in container netns, and will switch back to hostNS to set the host veth end

func SetupVeth(convVethName string, mtu int, hostNS net.NetNS) (hostVeth, contVeth netlink.Link, err error)

(1) 、首先調用hostVethName, contVeth, err = makeVeth(contVethName, mtu)創建veth對

(2)、調用netlink.LinkSetUp(contVeth)

(3)、調用hostVeth, err = netlink.LinkByName(hostVethName)

(4)、調用netlink.LinkSetNsFd(hostVeth, int(hostNS.Fd())) --> move veth to host netns

(5)、最後調用hostNS.Do(func() error{...}),函數中調用hostVeth, err := netlink.LinkByName(hostVethName)和netlink.LinkSetUp(hostVeth),激活host端的veth

 

5、cni/pkg/ipam/ipam.go

func ExecAdd(plugin, netconf []byte) (*types.Result, error)

該函數僅僅調用 return invoke.DelegateAdd(plugin, netconf)

Result 結構如下所示:

1
2
3
4
5
6
type Result struct {
 
  IP4  *IPConfig
  IP6  *IPConfig
  DNS   DNS
}

  

IPConfig 的結構如下

1
2
3
4
5
6
type IPConfig struct {
 
  IP    net.IPNet
  Gateway net.IP
  Routes  []Route
}

  


發佈了41 篇原創文章 · 獲贊 9 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章