• CNI portmap插件实现源码分析


    DNAT创建的iptables规则如下:(重写目的IP和端口)

    PREROUTING, OUTPUT: --dst-type local -j CNI-HOSTPORT_DNAT    // PREROUTING和OUTPUT链中目的地址类型为local的跳转至CNI-HOSTPORT-DNAT进行处理

    CNI-HOSTPORT-DNAT: -j CNI-DN-abcd123    // 进入相应容器的chain进行处理

    CNI-DN-abcd123: -p tcp --dport 8080 -j DNAT --to-destination 192.0.2.33:80

    CNI-DN-abcd123: -p tcp --dport 8081 -j DNAT ....

    SNAT创建的iptables规则如下:(在dnat之后,重写来自于localhost的源地址)

    POSTROUTING:-s 127.0.0.1 ! -d 127.0.0.1 -j CNI-HOSTPORT-SNAT

    CNI-HOSTPORT-SNAT:-j CNI-SN-abcd123

    CNI-SN-abcd123:-p tcp -s 127.0.0.1 -d 192.0.2.33 --dport 80 -j MASQUERADE

    CNI-SN-abcd123:-p tcp -s 127.0.0.1 -d 192.0.2.33 --dport 90 -j MASQUERADE

    // plugins/plugins/meta/portmap/main.go

    1、func cmdAdd(args *skel.CmdArgs) error

    1、首先调用netConf, err := parseConfig(args.StdinData, args.IfName)对配置进行解析

    2、因为portmap只能作为chained plugin存在,因此当netConf.PrevResult为nil时,报错

    3、当len(netConf.RuntimeConfig.PortMaps)为0时,说明没有配置portmap,直接返回PrevResult

    4、当netConf.ConfIPv4不为nil时,调用forwardPorts(netConf, netConf.ContIPv4)

    5、当netConf.ConfIPv6不为nil时,调用forwardPorts(netConf, netConf.ConfIPv6)

    PortMapConf的结构如下所示:

    type PortMapConf struct {
      types.NetConf
      SNAT  *bool
      ConditionsV4 *[]string
      Conditions V6 *[]string
      RuntimeConfig struct {
        PortMaps  [ ]PortMapEntry
      }
    
      RawPrevResult map[string]interface{}
      PrevResult  *current.Result
      
    
      // These are fields parsed out of the config or the environment;
      // included here for convenience
      ContainerID  string
      ContIPv4    net.IP
      ContIPv6     net.IP
    }
    

    PortMapEntry的结构如下所示:

    type PortMapEntry struct {
      HostPort     int
      ContainerPort  int
      Protocol      string
      HostIP      string
    }
    

      

    // plugins/plugins/meta/portmap/main.go

    2、func parseConfig(stdin []byte, ifName string) (*PortMapConf, error)

    1、首先创建conf := PortMapConf{},并调用json.Unmarshal(stdin, &conf)进行解析

    2、如果conf.RawPrevResult不为nil,则先将其转换为conf.PrevResult

    3、如果解析之后conf.SNAT为nil,则设置conf.SNAT赋值为true

    4、遍历conf.RuntimeConfig.PortMaps,对每个pm进行检测,如果pm.ContainerPort <= 0或者pm.HostPort<=0,则报错

    5、如果conf.PrevResult不为nil,则对conf.PrevResult.IPs进行遍历,将conf.ContIPv4和conf.ContIPv6分别设置为第一个遇到的container interface的地址

    // plugins/plugins/meta/portmap/portmap.go

    3、func forwardPorts(config *PortMapConf, containerIP net.IP) error

    1、当containerIP为IPv4时,调用ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)和conditions = config.ConditionsV4获取iptable

    2、调用toplevelDnatChain := genToplevelDnatChain()和toplevelDnatChain.setup(ipt, nil)创建top level chain,

    3、调用dnatChain := genDnatChain(config.Name, config.ContainerID, conditions)创建dnat chain,并调用_ = dnatChain.teardown(ipt)进行清理,如果有冲突的话

    4、调用dnatRules := dnatRules(config.RuntimeConfig.PortMaps, containerIP)和dnatChain.setup(ipt, dnatRules)在dnat chain中创建规则

    5、如果config.SNAT为真,且不是地址不是IPv6,则首先调用toplevelSnatChain := genToplevelSnatChain(isV6)和toplevelSnatChain.setup(ipt, nil)创建top level snat chain

    6、调用snatChain := genSnatChain(config.Name, config.ContainerID)和_ = snatChain.teardown(ipt)获取相应的snat chain,如果chain已存在,则进行清理

    7、调用snatRules := snatRules(config.RuntimeConfig.PortMaps, containerIP)和snatChain.setup(ipt, snatRules)创建规则

    8、如果isV6为false,则设置host interface的route_localnet bit,从而让127/8能cross a routing boundary,先调用hostIfName := getRoutableHostIF(containerIP)

    如果hostIfName不为"",则调用enableLocalnetRouting(hostIfName)

    // plugins/plugins/meta/portmap/portmap.go

    4、func genToplevelDnatChain() chain

    该函数仅仅返回一个chain{}结构,该chain为top-levle summary chain,其他所有的dnat chain都通过它进行调用,具体代码如下:

    return chain{
      table:  "nat",
      name:  TopLevelDNATChainName,
      entryRule:  [ ]string{
        "-m", "addrtype",
        "--dst-type", "LOCAL",
      },
      entryChains: []string{"PREROUTING", "OUTPUT"},
    
    }
    

      

    // plugins/plugins/meta/portmap/chain.go

    5、func (c *chain) setup(ipt *iptables.IPTables, rules [][]string) error

    1、调用exists, err := chainExists(ipt, c.table, c.name)判断chain是否存在,不在则调用ipt.NewChain(c.table, c.name)进行创建

    2、倒序遍历rules,调用prependUnique(ipt, c.table, c.name, rules[i])将rule添加至chain中

    3、调用entryRule := append(c.entryRule, "-j", c.name),并遍历c.entryChains,调用prependUnique(ipt, c.table, entryChain, entryRule)将rule加入其他chain中

    // plugins/plugins/meta/portmap/chain.go

    6、func genDnatChain(netName, containerID string, conditions *[]string) chain

    1、首先调用name := formatChainName("DN-", netName, containerID)创建新的dnat chain名字

    2、调用comment := fmt.Sprintf(...)创建comment

    3、创建的chain如下所示:

    ch := chain{
      table:  "nat",
      name:  name,
      entryRule:  []string{
        "-m", "comment",
        "--comment", comment
      },
      entryChains:  []string{TopLevelDNATChainName},
    }
    

    4、如果conditions不为nil,且len(*conditions)不为0,则调用ch.entryRule = append(ch.entryRule, *conditions...)

    // plugins/plugins/meta/portmap/chain.go

    7、func (c *chain) teardown(ipt *iptables.IPTables) error

    teardown用于删除一个chain,如果chain不存在的话并不会报错,并且它先删除所有entryChains对该chain的引用

    1、首先调用ipt.ClearChain(c.table, c.name),如果该chain不存在的话,创建该chain,如果存在,则先对该chain进行flush

    2、遍历c.entryChains,调用entryChainRules, err := ipt.List(c.table, entryChain)获取该chain上的所有rule

    3、遍历entryChainRules[1:],当entryChainRule中存在"-j"+c.name的后缀时,调用chainParts, err := shellwords.Parse(entryChainRule)和chainParts = chainParts[2:]获取rule的内容

    再调用ipt.Delete(c.table, entryChain, chainParts...)删除对应的reference

    4、最后调用ipt.DeleteChain(c.table, c.name)删除该chain

    // plugins/plugins/meta/portmap/portmap.go

    8、func dnatRules(entries []PortMapEntry, containerIP net.IP) [ ][ ]string

    1、遍历entries,添加的iptables规则为"-p entry.Protocol --dport strconv.Itoa(entry.HostPort) -j DNAT --to-destination fmtIpPort(containerIP, entry.ContainerPort)"

    如果entry.HostIP不为"",则进一步扩展"-d entry.HostIP"

    // plugins/plugins/meta/portmap/portmap.go

    9、func genToplevelSnatChain(isV6 bool) chain

    1、创建的top level chain如下所示:

    return chain {
      table: "nat",
      name: TopLevelSNATChainName,
      entryRule: [ ]string {
        "-s", localhostIP(isV6),      // localhostIP函数,如果isV6为true的话,则返回"::1",否则返回"127.0.0.1"
        "!", "-d", localhostIP(isV6),
      },
      entryChains: [ ]string{"POSTROUTING"},
    
    }
    

      

    // plugins/plugins/meta/portmap/portmap.go

    10、func genSnatChain(netName, containerID string) chain

    1、首先调用name := formatChainName("SN-", netName, containerID)创建chain的名字

    2、调用comment := fmt.Sprintf(...)创建comment

    3、最后返回的chain如下:

    return chain {
      table: "nat"
      name: name,
      entryChains: [ ]string {
        "-m", "comment",
        "--comment", comment
      },
      entryChains: [ ]string{TopLevelSNATChainName}
    
    }
    

      

    // plugins/plugins/meta/portmap/portmap.go

    11、func snatRules(entries [ ]PortMapEntry, containerIP net.IP) [ ][ ]string

    1、遍历entries,新建的iptable的rule为"-p entry.Protocol -s localhostIP(isV6) -d containerIP.String() --dport strconv.Itoa(entry.ContainerPort) -j MASQUERADE"


    // plugins/plugins/meta/portmap/utils.go

    12、func getRoutableHostIF(containerIP net.IP) string

    获取路由容器流量的interface,这是关闭martian filtering的第一步

    1、调用routes, err := netlink.RouteGet(containerIP)获取与containerIP有关的路由

    2、遍历routes,根据route.LinkIndex找到相应的link,再返回link.Attrs().Name即可

    // plugins/plugins/meta/portmap/portmap.go

    13、func enableLocalnetRouting(ifName string) error

    1、创建routeLocalnetPath := "net.ipv4.conf." + ifName + ".route_localnet"

    2、调用sysctl.Sysctl(routeLocalnetPath, "1")即可

    // plugins/plugins/meta/portmap/main.go

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

    1、首先调用netConf, err := parseConfig(args.StdinData, args.IfName)获取配置

    2、调用netConf.ContainerID = args.ContainerID

    3、调用err := unforwardPorts(netConf)

    // plugins/plugins/meta/portmap/portmap.go

    15、func unforwardPorts(config *PortMapConf) error

    unforwardPorts删除所有由该plugin创建的iptables rules。并且因为在DELETE的时候我们并不知道使用的是哪种协议,因此我们首先检查对应的iptables是否存在

    如果不存在也不报错,除非IPv4和IPv6都不存在

    1、首先调用dnatChain := genDnatChain(config.Name, config.ContainerID, nil)和snatChain := genSnatChain(config.Name, config.ContainerID)获取container对应的chain

    2、调用ip4t := maybeGetIptables(false)和ip6t := maybeGetIptables(true)

    3、如果ip4t不为nil,则调用dnatChain.teardown(ip4t)和snatChain.teardown(ip4t)

    4、如果ip6t不为nil,则调用dnatChain.teardown(ip6t)

    // plugins/plugins/meta/portmap/portmap.go

    16、func maybeGetIptables(isV6 bool) *iptables.IPTables

    1、首先调用proto := iptables.ProtocolIPv4,如果isV6为true,则proto = iptables.ProtocolIPv6

    2、调用ipt, err := iptables.NewWithProtocol(proto)获取iptables,并调用_, err = ipt.List("nat", "OUTPUT")查看是否存在nat的OUTPUT链

    3、若存在错误,则返回nil,否则return ipt

  • 相关阅读:
    WeGame 上线,打造一个wegame游戏的良性游戏圈子
    H5 视频作为背景 source src改变后 循环播放的问题笔记
    windows无法安装到这个磁盘具有mbr分区表
    jquery的扩展:编写好代码封装起来供他人使用
    获取 HTML data-*属性的值( 文章列表页面,存储文章id 为读取详细页面
    max(min)-device-width和max(min)-width的区别
    git 红色标出没明白
    git-flow 的工作流程
    会计服务平台 使用条款
    若依:设置跨域配置位置和图片中框出代码
  • 原文地址:https://www.cnblogs.com/YaoDD/p/7308045.html
Copyright © 2020-2023  润新知