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
CNI portmap插件实现源码分析的更多相关文章
- Unity时钟定时器插件——Vision Timer源码分析之二
Unity时钟定时器插件——Vision Timer源码分析之二 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 前面的已经介绍了vp_T ...
- Mybatis源码学习第七天(插件源码分析)
为了不把开发和源码分析混淆,决定分开写; 接下来分析一下插件的源码,说道这里老套路先说一个设计模式,他就是责任链模式 责任链模式:就是把一件工作分别经过链上的各个节点,让这些节点依次处理这个工作,和装 ...
- 轮播插件unsilder 源码解析(一)---使用
啰嗦几句:学习的可以直接省略,一直本着写原生的插件想法,但是前天看了吕大豹的博客觉得自己都没有正经的写个jquery插件:所以在开始写之前我会先对几个比较热门的jquery的插件进行源码分析:至于为什 ...
- Android Small插件化框架源码分析
Android Small插件化框架源码分析 目录 概述 Small如何使用 插件加载流程 待改进的地方 一.概述 Small是一个写得非常简洁的插件化框架,工程源码位置:https://github ...
- 插件开发之360 DroidPlugin源码分析(五)Service预注册占坑
请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52264977 在了解系统的activity,service,broa ...
- 插件开发之360 DroidPlugin源码分析(四)Activity预注册占坑
请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52258434 在了解系统的activity,service,broa ...
- 插件开发之360 DroidPlugin源码分析(二)Hook机制
转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52124397 前言:新插件的开发,可以说是为插件开发者带来了福音,虽然还很多坑要填补, ...
- 插件开发之360 DroidPlugin源码分析(一)初识
转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52123450 DroidPlugin的是什么? 一种新的插件机制,一种免安装的运行机制 ...
- MyBatis 源码分析 - 插件机制
1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...
随机推荐
- android逆向分析之smali语法
一 .smali数据类型 1.Dalvik字节码 Davlik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Double)用2个连续的寄存器表示: Dalvik字节码有两种类型 ...
- 创建ajax的过程
step1. 创建XMLHttpRequest对象,也就是创建一个异步调用对象: step2. 创建一个新的HTTP请求,并指定改HTTP请求的方法.URL以及验证信息: step3. 设置响应HTT ...
- java -jar Incompatible argument to function
原因分析:jar包版本问题 解决方法:到工程中查看代码引用的jar包版本是多少,然后升级jar包,就可以了!
- shell script 在if 的判断条件正则表达式=~中引号问题
今天在脚本里运行if判断的时候,总是进不了对应的分支,检查正则表达式也没有错误.单独拿到shell里面执行还是显示没有匹配.比较奇怪,就搜了下,才发现是在=~ 后面的正则表达式上不能加上引号,而且以点 ...
- 1. DataBinding - offical tutorial
1. DataBinding - offical tutorial android DataBinding tutorial 构建环境 数据与布局文件的绑定 data binding 表达式 数据对象 ...
- DataBinding 笔记
DataBinding 笔记 android DataBinding notes 那些年踩过的坑 问题 那些年踩过的坑 非 public 类型的变量,getter 方法必须有,没有就会报错:Could ...
- SSH初体验系列--Hibernate--1--环境配置及demo
最近在学hibernate,常见的教程都是搭配mysql,因为公司本地电脑用的是pg,所以就尝试着做个pg的小demo. 自己也是边学边写,只当是加深印象.话不多说,直接开始; 一) 准备工作; 1) ...
- WCF系列 Restful WCF
由于项目需要,需要完成移动端与服务端以json格式的数据交互,所以研究了Restful WCF相关内容,以实现ios端,android端与浏览器端能够与后台服务交互. 那么首先我们来了解下什么是Res ...
- 从零开始开发一个vue组件打包并发布到npm (把vue组件打包成一个可以直接引用的js文件)
自己写的组件 有的也挺好的,为了方便以后用自己再用或者给别人用,把组件打包发布到npm是最好不过了,本次打包支持 支持正常的组件调用方式,也支持Vue.use, 也可以直接引用打包好的js文件, 配合 ...
- C# Expression 树转化为SQL语句(一)
sql有有四中基本语句,分别是增删改查,在建立model后如何生成这四中sql语句,降低开发时间. 我们先模拟出一张学生表: public class Student { public int id ...