对于每个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),设置result.Interfaces = []*current.Interface{brInterface, hostInterface, containerInterface}

7、调用gwsV4, gwsV6, err := calcGateways(result, n) ---> 获取IPv4,IPv6的网关信息

8、调用netns.Do(),首先调用disableIPV6DAD(args.IfName),防止bridge的hairpin mode启动,导致DAD失败

再调用ipam.ConfigureIface(args.IfName, result)

如果result.IPs[0].Address.IP.To4()不为nil,则调用ip.SetHWAddrByIP(args.IfName, result.IPs[0].Address.IP, nil) ---> 重新设置MAC地址,使其与IP地址关联

最后,因为veth设备的MAC地址可能改变,因此调用link, err := netlink.LinkByName(args.IfName)以及containerInterface.Mac = link.Attrs().HardwareAddr.String()更新MAC地址。

9、n.IsGW为真,则进行一系列设置,其实就是对网桥进行配置,使其作为默认网关

  1. 先遍历gws= gwsV4和gwsV6,再遍历gw in range gws,
  2. 如果gw.IP.To4()不为nil,且firstV4Addr为nil,则firstV4Addr = gw.IP,之后再调用err  = ensureBridgeAddr(br, gws.family, &gw, n.ForceAddress)设置网桥地址
  3. 最后,如果gws.gws不为nil,则调用enableIPForward(gws.family)

11、若n.IPMasq为真,首先调用chain := utils.FormatChainName(n.Name, args.ContainerID)   ---> 该函数只是生成一个用于iptables的chain名

comment := utils.FormatComment(n.Name, args.ContainerID) ---> 该函数只是生成一个注释字符串,用于标识相应的rule

最后,遍历for ips in range result.IPs,并调用ip.SetupIPMasq(ip.Network(&ipc.Address), chain, comment)

12、再调用br, err = bridgeByName(n.BrName)再对它的MAC地址进行设置,因为在第一个veth设备加入或者它被设置了IP地址之后,它的MAC地址都可能发生变化

13、设置result.DNS = n.DNS

14、最后return PrintResult(result, cniVersion)

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"`
  PromiscMode  bool   `json:"promiscMode"`
}

  

// 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, n.PromiscMode)  // create bridge if necessary

2、返回return br, &current.Interface{Name: br.Attrs().Name, Mac: br.Attrs().HardwareAddr.String()}

current.Interface结构如下所示:

type Interface struct {
  Name    string  `json:"name"`
  Mac     string  `json:"mac,omitempty"`
  Sandbox   string  `json:"sandbox, omitempty"`
}

  

// cni/plugins/main/bridge/bridge.go

4、func ensureBridge(brName string, mtu int, promiscMode bool) (*netlink.Bridge, error)

1、构造br := &netlink.Bridge{......}

2、调用err := netlink.LinkAdd(br),如果err不为nil且err不为syscall.EEXIST则报错

3、如果promiscMode为true,则调用netlink.SetPromiscOn(br)设置网桥为混杂模式

// 获取网桥的配置,如果网桥已经存在了,那么只要确定该网桥的配置和所需的配置相同即可

3、调动br, err := bridgeByName(brName)  -->l, err := netlink.LinkByName(name)找到设备,再反向断言br, ok := l.(*netlink.Bridge)

4、调用netlink.LinkSetUp(br)启动网桥

5、最后return br, nil

// 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、创建变量contIface和hostIface,两者类型都为&current.Interface{}

2、首先在container中,即netns中创建veth pair,并且将host端移动到host netns

调用netns.Do(),在Do中调用hostVeth, containerVeth, err := ip.SetupVeth(ifName, mtu, hostNS),之后用containerVeth和netns填充contIface,再用hostVeth.Name填充hostIface.Name

3、随着网卡再ns间的移动,它的index也相应改变了,因此先调用hostVeth, err := netlink.LinkByName(hostIface.Name)在host netns中找到veth end

4、再通过hostIface.Mac = hostVeth.Attrs().HardwareAddr.String()获取host端veth的MAC地址

5、接着调用netlink.LinkSetMaster(hostVeth, br)将veth连接至网桥

6、最后,调用netlink.LinkSetHairpin(hostVeth, hairpinMode)设置hairpinmode

// plugins/main/bridge/bridge.go

6、func calcGateways(result *current.Result, n *NetConf)  (*gwInfo, *gwInfo, error)

// 该函数处理IPAM plugin的结果,对于各个IP family,计算一系列的网关地址,并且如果需要的话,添加默认路由

1、首先创建gwsV4和gwsV6两个变量,两者的类型都为&gwInfo{}

2、遍历ipc in range result.IPs,创建变量defaultNet := &net.IPNet{},var gws *gwInfo

当ipc是IPv4时,设置gws = gwsV4,gws.family = netlink.FAMILY_V4,defaultNet.IP = net.IPv4Zero,对于IPv6的操作类似

3、设置defaultNet.Mask = net.IPMask(defaultNet.IP)

4、设置ipc.Interface = current.Int(2) ---> 所有的IP地址都指向容器的网卡

5、如果ipc.Gateway为nil并且n.IsGW为true,则调用ipc.Gateway = calcGatewayIP(&ipc.Address) ---> 先对IP进行掩码操作,再加一,得到网关地址

6、如果n.IsDefaultGW为true且gws.defaultRouteFound为false,则使用当前的网关地址添加默认路由 ---> 首先遍历路由,查看是否有默认路由,否则添加

7、若n.IsGW为true,则创建gw := net.IPNet{IP: ipc.Gateway, Mask: ipc.Address.Mask},再调用gws.gws  = append(gws.gws, gw)进行网关的添加

8、最后,return gwsV4,gwsV6, nil

IPConfig结构如下所示:

type IPConfig struct {
  Version     string
  // Index into Result structs Interfaces list
  Interface    *index
  Address     net.IPNet
  Gateway     net.IP }

  

gwInfo的结构如下所示:

type gwInfo struct {
  gws           []net.IPNet
  family          int
  defaultRouteFound   bool
}

  

// plugins/pkg/ipam/ipam.go

7、func ConfigureIface(ifName string, res *current.Result) error

1、首先调用link, err := netlink.LinkByName(ifName)和netlink.LinkSetUp(link)查找并启动网卡

2、遍历ipc in range res.IPs,进行一系列的检测,包括ipc.Interface不能为nil等等

3、构建addr := &netlink.Addr{...}并通过netlink.AddrAdd(link, addr)给网卡添加地址

4、如果ipc.Gateway().To4()不为nil,则设置v4gw = ipc.Gateway,否则,v6gw = ipc.Gateway

5、调用ip.SettleAddresses(ifName, 10)

6、遍历for r in range res.Routes,设置gw := r.GW,如果gw为nil,且为IPV4的路由,则gw = v4gw,否则gw = v6gw

7、最后,调用ip.AddRoute(&r.Dst, gw, link)添加路由,并且重复的路由不再添加

// plugins/main/bridge/bridge.go

8、func ensureBridgeAddr(br *netlink.Bridge, family int, ipn *net.IPNet, forceAddress bool) error

1、调用addrs, err := netlink.AddrList(br, family)获取网桥上的IP地址链

2、调用ipnStr := ipn.String()

3、遍历for a in range addrs,如果a.IPNet.String() == ipnStr表示地址已存在,则直接返回

4、在网桥上允许存在多个IPV6地址,如果它们的子网不重合的话。而对于IPv4地址或者子网有重合的IPv6地址,只有forceAddress为true的时候,才能进行重新配置

5、设置addr := &netlink.Addr{...},调用netlink.AddrAdd(br, addr)添加IP地址到网卡

// plugins/pkg/ip/ipmasq.go

9、func SetupIPMasq(ipn *net.IPNet, chain string, comment string)  error

1、首先根据ipn的IP类型设置multicastNet,若为IPv4则设置为multicast = "244.0.0.0/24"

2、如果对应的nat用户chain不存在,则创建之

3、接受所有发往该network的网络包,ipt.AppendUnique("nat", chain, "-d", ipn.String(), "-j", "ACCEPT", ....)

4、不要对多播包进行masquerade,ipt.AppendUnique("nat", chain, "!", "-d", multicastNet, "-j", "MASQUERADE", ....)

5、最后调用return ipt.AppendUnique("nat", "POSTROUTING", "-s", ipn.String(), "-j", chain, ...)将流量导入chain中

// plugins/main/bridge/bridge.go

10、func cmdDel(args *skel.CmdArgs) error

1、首先调用n, _, err := loadNetConf(args.StdinData)加载配置

2、调用ipam.ExecDel(n.IPAM.Type, args.StdinData)删除从ipam中删除ip

3、清除netns,因为Delete可以被调用多次,因此,如果设备已经被移除,不要报错,如果设备已经不存在了,就不要再清理IP masq了
调用ns.WithNetNSPath(...){},在其中调用ipn, err = ip.DelLinkByNameAddr(args.IfName, netlink.FAMILY_ALL)

4、如果ipn不为nil,且n.IPMasq为true,则依次调用chain := utils.FormatChainName(n.Name, args.ContainerID)和comment := utils.FormatComment(n.Name, args.ContainerID)

最后调用ip.TeardownIPMasq(ipn, chain, comment)删除对应的ip masq

CNI bridge 插件实现代码分析的更多相关文章

  1. Docker Libnetwork Bridge插件实现代码分析----创建网络部分

    // drivers/bridge/bridge.go // Create a new network using bridge plugin 1.func (d *driver) CreateNet ...

  2. Docker Libnetwork Bridge插件实现代码分析----初始化部分

    Bridge driver数据结构如下所示: type driver struct { config *configuration network *bridgeNetwork natChain *i ...

  3. 使用 Gradle 插件进行代码分析(转)

    代码分析在大多数项目中通常是作为最后一个步骤(如果做了的话)完成的.其通常难以配置及与现有代码整合. 本文旨在勾勒出使用 Gradle 整合 PMD 与 FindBugs 的步骤,并将其与一个现有的 ...

  4. Eclipse插件(导出UML图,打开文件资源管理器插件,静态代码分析工具PMD,在eclipse上安装插件)

    目录 能够导出UML图的Eclipse插件 打开文件资源管理器插件 Java静态代码分析工具PMD 如何在eclipse上安装插件 JProfiler性能分析工具 从更新站点安装EclEmma 能够导 ...

  5. jQuery File Upload 插件 php代码分析

    jquery file upload php代码分析首先进入构造方法 __construct() 再进入 initialize()因为我是post方式传的数据  在进入initialize()中的po ...

  6. CNI插件编写框架分析

    概述 在<CNI, From A Developer's Perspective>一文中,我们已经对CNI有了较为深入的了解.我们知道,容器网络功能的实现最终是通过CNI插件来完成的.每个 ...

  7. 完整全面的Java资源库(包括构建、操作、代码分析、编译器、数据库、社区等等)

    构建 这里搜集了用来构建应用程序的工具. Apache Maven:Maven使用声明进行构建并进行依赖管理,偏向于使用约定而不是配置进行构建.Maven优于Apache Ant.后者采用了一种过程化 ...

  8. 常用 Java 静态代码分析工具的分析与比较

    常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基 本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBu ...

  9. SonarQube-5.6.3 代码分析平台搭建使用

    python代码分析 官网主页: http://docs.sonarqube.org/display/PLUG/Python+Plugin Windows下安装使用: 快速使用: 1.下载jdk ht ...

随机推荐

  1. CCNA2.0笔记_路由分类

    直连路由:当在路由器上配置了接口的IP地址,并且接口状态为up的时候,路由表中就出现直连路由项 静态路由:静态路由是由管理员手工配置的,是单向的. 默认路由:当路由器在路由表中找不到目标网络的路由条目 ...

  2. 如何使用Photoshop(PS)将图片的底色变为透明

    很多时候需要将一张图片的底色变得透明.本文描述了使用PS将图片的一部分变得透明的方法.本例将一段艺术字的背景去掉,将背景透明的文字单独保存成图片,这样以后将这段文字粘贴到其他素材上的时候,就不用担心它 ...

  3. 小型web服务器thttpd的学习总结(下)

    1.主函数模块分析 对于主函数而言,概括来说主要做了三点内容,也就是初始化系统,进行系统大循环,退出系统.下面主要简单阐述下在这三个部分,又做了哪些工作呢. 初始化系统 拿出程序的名字(argv[0] ...

  4. spoj7001 Visible Lattice Points 莫比乌斯反演+三维空间互质对数

    /** 题目:Visible Lattice Points 链接:https://vjudge.net/contest/178455#problem/A 题意:一个n*n*n大小的三维空间.一侧为(0 ...

  5. codeforces 825F F. String Compression dp+kmp找字符串的最小循环节

    /** 题目:F. String Compression 链接:http://codeforces.com/problemset/problem/825/F 题意:压缩字符串后求最小长度. 思路: d ...

  6. php ocket通信机制

    php 实例说明 socket通信机制 一,socket是什么 什么是socket 所谓socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄.应用程序通常通 ...

  7. .NET平台下 极光推送

    正好看到别人发了个极光的推送例子,想来前面也刚做过这个,就把我的push类共享下 public class JPush { /// <summary> /// push信息到手机应用上 J ...

  8. Linux IO操作——RIO包

    1.linux基本I/O接口介绍 ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, void *buf, siz ...

  9. 蓝桥杯 第三届C/C++预赛真题(8) 密码发生器(水题)

    在对银行账户等重要权限设置密码的时候,我们常常遇到这样的烦恼:如果为了好记用生日吧,容易被破解,不安全:如果设置不好记的密码,又担心自己也会忘记:如果写在纸上,担心纸张被别人发现或弄丢了... 这个程 ...

  10. hdu 4067(最小费用最大流)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4067 思路:很神奇的建图,参考大牛的: 如果人为添加t->s的边,那么图中所有顶点要满足的条件都 ...