NAT和MASQ
# 1.hostIPMgr
# 1.1.简介
iptables模式需要区分dst=local/remote以决定流量处理,hostIPMgr会监听主机网卡变化写入ipset,基于chain接入处理链。注意
HostIP不会硬编码,基于ipset维护及被多条静态规则引用用于转发及Mask检查
# 1.2.初始化
iptables模式会注册hostIPMgr,基于dataplane监听本机网卡IP变化及同步到hostIPMgr,最终下发到ipset供其它chain检查流量。func NewIntDataplaneDriver(config Config) *InternalDataplane { ... // iptables模式启用 if !config.BPFEnabled { ... // 注册hostIPMgr dp.RegisterManager(newHostIPManager(config.RulesConfig.WorkloadIfacePrefixes, rules.IPSetIDThisHostIPs, ipSetsV4, config.MaxIPSetSize)) ... // eBPF CTLB卸载及Pin Map清理 bpfnat.RemoveConnectTimeLoadBalancer("") ... tc.CleanUpProgramsAndPins() } ... return dp } func newHostIPManager(wlIfacesPrefixes []string, ipSetID string, ipsets common.IPSetsDataplane, maxIPSetSize int) *hostIPManager { // 薄封装 return newHostIPManagerWithShims( wlIfacesPrefixes, ipSetID, ipsets, maxIPSetSize, ) } func newHostIPManagerWithShims(wlIfacesPrefixes []string, ipSetID string, ipsets IPSetsDataplane, maxIPSetSize int) *hostIPManager { // Pod接口匹配正则 wlIfacesPattern := "^(" + strings.Join(wlIfacesPrefixes, "|") + ").*" wlIfacesRegexp := regexp.MustCompile(wlIfacesPattern) return &hostIPManager{ nonHostIfacesRegexp: wlIfacesRegexp, hostIfaceToAddrs: map[string]set.Set[string]{}, // hostIface-->addr hostIPSetID: ipSetID, // hostIP对应的ipset(this-host) ipsetsDataplane: ipsets, // ipset工具 maxSize: maxIPSetSize, } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46注意
dataplane监听的网卡地址变化基于hostIPMgr同步到ipset member,其它chain会引用ipset检查流量
# 1.3.同步
m.OnUpdate()会同步主机网卡地址变化,过滤掉Pod/L3网卡,将主机地址汇总后推送到ipsetMgr,批量刷新到this-host ipset。// ipset list cali40this-host // Name: cali40this-host // Type: hash:ip // Revision: 4 // Header: family inet hashsize 1024 maxelem 1048576 // Members: // 192.168.1.10 (eth0 主IP) // 10.0.0.1 (docker0 bridge) // 172.17.0.1 (docker0,某些发行版) // ... func (m *hostIPManager) OnUpdate(msg interface{}) { switch msg := msg.(type) { case *ifaceAddrsUpdate: // Pod网卡忽略 if m.nonHostIfacesRegexp.MatchString(msg.Name) { return } // 更新hostIface--->addr if msg.Addrs != nil { m.hostIfaceToAddrs[msg.Name] = msg.Addrs } else { delete(m.hostIfaceToAddrs, msg.Name) } // 生成ipset meta元数据 metadata := ipsets.IPSetMetadata{Type: IPSetTypeHashIP, SetID: m.hostIPSetID, MaxSize: m.maxSize} // 推到ipsetMgr pending区,后续Apply m.ipsetsDataplane.AddOrReplaceIPSet(metadata, m.getCurrentMembers()) } } func (m *hostIPManager) getCurrentMembers() []string { ... for _, addrs := range m.hostIfaceToAddrs { addrs.Iter(func(ip string) error { members = append(members, ip) return nil }) } return members }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44注意
这里是全量
replace不是增量add/update,主机地址变化低频,全量刷简单无计算开销
# 1.4.使用
cali40this-host由cali-forward-check chain消费,其劫持cali-INPUT流量及跳转到本链,用于检查及修正进入主机的转发流量。// cali40this-host/cali60this-host hostIPSet := nameForIPSet(IPSetIDThisHostIPs) // 已建立连接直接RETURN。 // 场景: 本机进程访问Service的回程包 Rule{ Match: ConntrackState("RELATED,ESTABLISHED"), Action: ReturnAction{}, } // 目的IP是本机但语义上是访问NodePort Service,不能当普通to-host流量处理 // 跳到cali-set-endpoint-mark根据入接口设置endpoint mark for _, portSplit := range SplitPortList(portRanges) { Rule{ Match: Protocol("tcp"). DestPortRanges(portSplit). DestIPSet(hostIPSet), Action: GotoAction{Target: ChainDispatchSetEndPointMark}, Comment: []string{"To kubernetes NodePort service"}, } Rule{ Match: Protocol("udp"). DestPortRanges(portSplit). DestIPSet(hostIPSet), Action: GotoAction{Target: ChainDispatchSetEndPointMark}, Comment: []string{"To kubernetes NodePort service"}, } } // 目的IP非本机,这类包通常是service/forwarded流量,先按入接口设置endpoint mark // 这里不做DNAT/转发,只是打mark,真正转发由IPVS/Linux网络栈完成 Rule{ Match: NotDestIPSet(hostIPSet), Action: JumpAction{Target: ChainDispatchSetEndPointMark}, Comment: []string{"To kubernetes service"}, }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37注意
1.这里转入
endpoint mark是为了标记流量,避免将这类service/forward流量误判为普通的to-host流量2.上一步提到的误判,其实就是因为
kube-proxy处于ipvs模式会创建kube-ipvs0网卡挂载Service VIP
# 2.serviceMgr
# 2.1.简介
calico基于BGP向默认网关宣告Service CIDR时,可能会出现路由环路风险,造成gateway-->node-->gateway的流量循环黑洞。--- 问题 1.外部有包发向一个不存在的Service VIP 2.网关查路由,发现VIP经Calico宣告到本节点,转发过来 3.kube-proxy对这个不存在的Service无法命中DNAT规则 4.包到达FORWARD链,本节点不是目标,查路由又送回默认网关 5.网关又转发回本节点,造成环路,最终TTL耗尽1
2
3
4
5
6注意
1.
kube-proxy进行DNAT发生在NAT表,早于filter,因此Service流量走到filter forward时目标地址已经是PodIP2.基于上一步特点,
serviceLoopMgr会在filter forward chain补充一条规则,未被DNAT的Service流量直接DROP
# 2.2.初始化
dataplane初始化会注册serviceLoopManager,用于后续service cidr调整就行调度,渲染filter chain及更新拦截规则实现流量处理。func NewIntDataplaneDriver(config Config) *InternalDataplane { ... dp.RegisterManager(newServiceLoopManager(filterTableV4, ruleRenderer, 4)) ... return dp } func newServiceLoopManager(filter iptablesTable, render RuleRenderer, ipVersion uint8) *svcLoopMgr { return &serviceLoopManager{ ipVersion: ipVersion, // ipv4或ipv6 filterTable: filter, // iptables filter表对象 ruleRenderer: render, // 规则渲染 activeFilterChains: []*iptables.Chain{}, // 已下发到filterTable的chain pendingGlobalBGPConfig: &proto.GlobalBGPConfigUpdate{}, // 待处理的BGP全局配置 } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19注意
iptables/BPF模式都有可能出现service环形路由问题,所以这里未限制启用条件
# 2.3.同步
BPG全局配置有完整的service cidr,出现变化时serviceLoopMgr会重新渲染chain&rules,相关的规则会更新到cali-cidr-block。func (m *serviceLoopManager) OnUpdate(protoBufMsg interface{}) { switch msg := protoBufMsg.(type) { case *proto.GlobalBGPConfigUpdate: m.pendingGlobalBGPConfig = msg // 全局BGP配置,包含service cidr } } func (m *serviceLoopManager) CompleteDeferredWork() error { if m.pendingGlobalBGPConfig != nil { ... // ClusterIP Service CIDR blockedCIDRs = append(blockedCIDRs, m.pendingGlobalBGPConfig.GetSvcClusterCidrs()...) // ExternalIP Service CIDR blockedCIDRs = append(blockedCIDRs, m.pendingGlobalBGPConfig.GetSvcExternalCidrs()...) // LoadBalancer Service CIDR blockedCIDRs = append(blockedCIDRs, m.pendingGlobalBGPConfig.GetSvcLoadbalancerCidrs()...) // 基于service cidr渲染cali-cidr-block对应rules newFilterChains := m.ruleRenderer.BlockedCIDRsToIptablesChains(blockedCIDRs, m.ipVersion) // chain出现差异 if !reflect.DeepEqual(m.activeFilterChains, newFilterChains) { m.filterTable.RemoveChains(m.activeFilterChains) // 清理旧的chain m.filterTable.UpdateChains(newFilterChains) // 注册新的chain m.activeFilterChains = newFilterChains // 更新缓存 } m.pendingGlobalBGPConfig = nil } return nil } func (r *DefaultRuleRenderer) BlockedCIDRsToIptablesChains(cidrs []string, ipVersion uint8) ... { ... if r.blockCIDRAction != nil { // cidr排序,确保规则生成顺序稳定 sort.Strings(cidrs) for _, cidr := range cidrs { if strings.Contains(cidr, ":") == (ipVersion == 6) { // 命中cidr的流量DROP/REJECT rules = append(rules, iptables.Rule{ Match: iptables.Match().DestNet(cidr), Action: r.blockCIDRAction, // DROP或Reject,取决于ServiceLoopPrevention配置 }) } } } return []*iptables.Chain{{ Name: ChainCIDRBlock, Rules: rules, }} }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52注意
当
ServiceLoopPrevention=Disabled时,cali-cidr-block chain存在但是没有DROP/REJECT规则
# 3.floatIPMgr
# 3.1.简介
floatingIP(FIP)是openstack的概念,将外部可达的IP通过DNAT映射到PodIP,实现主备切换的FIP漂移及外部直访不可路由的Pod。--- hairpin回环 这是一种特殊情况,pod基于FIP访问自己,此时需要进行SNAT,否则src=dst的包会被内核丢起1
2注意
floatingIPMgr维护cali-fip-dnat和cali-fip-snat链,节点外基于FIP访问Pod用dnat chain,节点内用snat chain
# 3.2.初始化
floatingIPManager负责接收上游的WEP NAT映射,将ExtIp--IntIp的FIP关系缓存及标记,统一渲染和下发iptables DNAT/SNAT规则。func NewIntDataplaneDriver(config Config) *InternalDataplane { ... dp.RegisterManager(newFloatingIPManager(natTableV4, ruleRenderer, 4, FloatingIPsEnabled)) ... return dp } func newFloatingIPManager(natTable iptablesTable, ruleRenderer RuleRenderer, ipVersion uint8, enabled bool) *floatingIPManager { return &floatingIPManager{ natTable: natTable, // iptables nat ruleRenderer: ruleRenderer, // 规则渲染 ipVersion: ipVersion, // ipv4/ipv6 activeDNATChains: []*iptables.Chain{}, // 生效的dnat chain activeSNATChains: []*iptables.Chain{}, // 生效的snat chain natInfo: map[proto.WorkloadEndpointID][]*proto.NatInfo{}, // wepID-->nat rule dirtyNATInfo: true, enabled: enabled, } } func (m *floatingIPManager) OnUpdate(protoBufMsg interface{}) { switch msg := protoBufMsg.(type) { case *proto.WorkloadEndpointUpdate: // 启用floatingIP或openstack环境,为endpoint生成nat规则 if m.enabled || msg.Id.OrchestratorId == apiv3.OrchestratorOpenStack { //{ // { // ExtIp: "172.16.1.3", // IntIp: "10.0.240.2", // }, //} if m.ipVersion == 4 { m.natInfo[*msg.Id] = msg.Endpoint.Ipv4Nat } else { m.natInfo[*msg.Id] = msg.Endpoint.Ipv6Nat } // 否则清理nat规则 } else { delete(m.natInfo, *msg.Id) } m.dirtyNATInfo = true case *proto.WorkloadEndpointRemove: delete(m.natInfo, *msg.Id) m.dirtyNATInfo = true } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50注意
这里注意一下,
floatingIPMgr不同于serviceLoopMgr,虽然注册未限制,但功能生效由启用开关决定,3.3.
# 3.3.同步
m.CompleteDeferredWork()会聚合onUpdate收集的FIP--PodIP映射,渲染为dnat/snat规则提交到natInfo,后续统一同步到iptables chain。func (m *floatingIPManager) CompleteDeferredWork() error { if m.dirtyNATInfo { ... // dnat聚合,FIP对应一个PodIP for _, natInfos := range m.natInfo { for _, natInfo := range natInfos { // FIP匹配到多个PodIP existingIntIP := dnats[natInfo.ExtIp] // 取PodIP字典序最小的 if existingIntIP == "" || natInfo.IntIp < existingIntIP { dnats[natInfo.ExtIp] = natInfo.IntIp } } } ... // snat聚合,PodIP可能绑定多个FIP for extIP, intIP := range dnats { // PodIP匹配到多个FIP existingExtIP := snats[intIP] // 取FIP字典序最小的 if existingExtIP == "" || extIP < existingExtIP { snats[intIP] = extIP } } // -A cali-fip-dnat -d 172.16.1.3/32 -j DNAT --to-destination 10.0.240.2 dnatChains := m.ruleRenderer.DNATsToIptablesChains(dnats) // -A cali-fip-snat -s 10.0.240.2/32 -d 10.0.240.2/32 -j SNAT --to-source 172.16.1.3 snatChains := m.ruleRenderer.SNATsToIptablesChains(snats) // dnat chain变化 if !reflect.DeepEqual(m.activeDNATChains, dnatChains) { // 清理旧规则 m.natTable.RemoveChains(m.activeDNATChains) // 写入新规则 m.natTable.UpdateChains(dnatChains) // 更新内存缓存 m.activeDNATChains = dnatChains } // snat变化 if !reflect.DeepEqual(m.activeSNATChains, snatChains) { // 清理旧规则 m.natTable.RemoveChains(m.activeSNATChains) // 写入新规则 m.natTable.UpdateChains(snatChains) // 更新内存缓存 m.activeSNATChains = snatChains } m.dirtyNATInfo = false } return nil }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53注意
这里不是立即写入内核
iptables,仅更新内存侧iptables.Table的期望状态,后续由iptablesMgr统一提交
# 3.4.使用
dnat/snat chain会被多处引用,假设有两个FIP映射:10.0.0.10→172.16.0.5和10.0.0.11→172.16.0.6,流量由主链劫持到dnat/snat chain。Chain cali-PREROUTING (1 references) pkts bytes target prot opt in out source destination 0 0 cali-fip-dnat all -- * * 0.0.0.0/0 0.0.0.0/0 0 0 DNAT tcp -- * * 0.0.0.0/0 169.254.169.254:80 to:169.254.169.254:8775 Chain cali-OUTPUT (1 references) pkts bytes target prot opt in out source destination 0 0 cali-fip-dnat all -- * * 0.0.0.0/0 0.0.0.0/0 Chain cali-POSTROUTING (1 references) pkts bytes target prot opt in out source destination 0 0 cali-fip-snat all -- * * 0.0.0.0/0 0.0.0.0/0 <-- hairpin先处理 0 0 cali-nat-outgoing all -- * * 0.0.0.0/0 0.0.0.0/0 <-- 这是pod-->集群外 Chain cali-fip-dnat (2 references) pkts bytes target prot opt in out source destination 0 0 DNAT all -- * * 0.0.0.0/0 10.0.0.10 to:172.16.0.5 0 0 DNAT all -- * * 0.0.0.0/0 10.0.0.11 to:172.16.0.6 Chain cali-fip-snat (1 references) pkts bytes target prot opt in out source destination 0 0 SNAT all -- * * 172.16.0.5 172.16.0.5 to:10.0.0.10 0 0 SNAT all -- * * 172.16.0.6 172.16.0.6 to:10.0.0.111
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23注意
这里有三种流量,分别为
外部-->FIP、Pod-->FIP和Pod-->外部,最后一种的snat由mask chain处理
# 4.masqMgr
# 4.1.简介
calico网络的Pod跨节点访问是可路由的(BGP/L3),流量无需NAT,但Pod访问非calico网络单元必须SNAT,否则回包无法响应到本节点。--- SNAT条件 1.IP Pool有一个masquerade字段,标记池内IP访问外部服务需不需要SNAT 2.由masq池出发,目标IP不在任何calico网络池会进行源地址转换1
2
3注意
这里会维护
mask-pools和all-pools,分别用于检查src和dst决定是否SNAT
# 4.2.初始化
dataplane会注册masqMgr及推送监听的IP Pool,由masqMgr保存all pool和masq pool及更新内存侧ipset缓存供chain引用。func NewIntDataplaneDriver(config Config) *InternalDataplane { ... dp.RegisterManager(newMasqManager(ipSetsV4, natTableV4, ruleRenderer, config.MaxIPSetSize, 4)) ... return dp } func newMasqManager(ipsetsDataplane IPSetsDataplane, natTable iptablesTable, ruleRenderer rules.RuleRenderer, maxIPSetSize int, ipVersion uint8) *masqManager { // all-ipam-pools chain ipsetsDataplane.AddOrReplaceIPSet(ipsets.IPSetMetadata{ MaxSize: maxIPSetSize, SetID: rules.IPSetIDNATOutgoingAllPools, Type: ipsets.IPSetTypeHashNet, }, []string{}) // masq-ipam-pools chain ipsetsDataplane.AddOrReplaceIPSet(ipsets.IPSetMetadata{ MaxSize: maxIPSetSize, SetID: rules.IPSetIDNATOutgoingMasqPools, Type: ipsets.IPSetTypeHashNet, }, []string{}) return &masqManager{ ipVersion: ipVersion, // ipv4/ipv6 ipsetsDataplane: ipsetsDataplane, // ipset工具 natTable: natTable, // iptables nat工具 activePools: map[string]*proto.IPAMPool{}, // IP Pool集合 masqPools: set.New[string](), // Masq IP Pool集合 dirty: true, ruleRenderer: ruleRenderer, // 规则渲染 } } func (d *masqManager) OnUpdate(msg interface{}) { ... switch msg := msg.(type) { case *proto.IPAMPoolUpdate: poolID = msg.Id newPool = msg.Pool case *proto.IPAMPoolRemove: poolID = msg.Id default: return } ... // 旧Pool清理 if oldPool := d.activePools[poolID]; oldPool != nil { d.ipsetsDataplane.RemoveMembers(rules.IPSetIDNATOutgoingAllPools, []string{oldPool.Cidr}) if oldPool.Masquerade { d.ipsetsDataplane.RemoveMembers(IPSetIDNATOutgoingMasqPools, []string{oldPool.Cidr}) } delete(d.activePools, poolID) d.masqPools.Discard(poolID) } // 新Pool注册 if newPool != nil { ... // 注册到all pool d.ipsetsDataplane.AddMembers(rules.IPSetIDNATOutgoingAllPools, []string{newPool.Cidr}) // 注册到masq pool if newPool.Masquerade { d.ipsetsDataplane.AddMembers(rules.IPSetIDNATOutgoingMasqPools, []string{newPool.Cidr}) d.masqPools.Add(poolID) } d.activePools[poolID] = newPool } d.dirty = true }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75注意
1.
iptables/BPF模式都涉及到pod-->集群外,此时都需要进行snat确保回包正常,所以这里也没有限制启用条件2.更新场景下,
cidr先删后写,ipset写入层会把add/remove合并为批量操作,同cidr的remove+add抵消
# 4.3.同步
m.CompleteDeferredWork()会收集masq pool/all pool提交到ipset,引用ipset渲染outgoing chain决定流量是否masq和snat。func (m *masqManager) CompleteDeferredWork() error { // 没有IP Pool变化 if !m.dirty { return nil } // 渲染cali-nat-outgoing chain chain := m.ruleRenderer.NATOutgoingChain(m.masqPools.Len() > 0, m.ipVersion) // chain更新到iptables natTable,后续统一下发 m.natTable.UpdateChain(chain) m.dirty = false return nil } func (r *DefaultRuleRenderer) NATOutgoingChain(natOutgoingActive bool, ipVersion uint8) *iptables.Chain { ... // 有Masq IPPool if natOutgoingActive { // 默认使用MASQUERADE,让内核选择出口地址作为源地址 defaultSnatRule := iptables.Action = iptables.MasqAction{} // calico显示配置NATOutgoingAddress if r.Config.NATOutgoingAddress != nil { // 用配置的固定SNAT到目标地址 defaultSnatRule = iptables.SNATAction{ToAddr: r.Config.NATOutgoingAddress.String()} } // calico限制配置NATPortRange,限制TCP/UDP NAT使用的端口范围 if r.Config.NATPortRange.MaxPort > 0 { // 默认是MASQUERADE --to-ports min-max。 toPorts := fmt.Sprintf("%d-%d", r.Config.NATPortRange.MinPort, r.Config.NATPortRange.MaxPort) portRangeSnatRule := iptables.Action = iptables.MasqAction{ToPorts: toPorts} // calico显示配置SNAT地址 if r.Config.NATOutgoingAddress != nil { // 使用SNAT --to-source addr:min-max toAddress := fmt.Sprintf("%s:%s", r.Config.NATOutgoingAddress.String(), toPorts) portRangeSnatRule = iptables.SNATAction{ToAddr: toAddress} } // TCP/UDP限制端口的NAT规则,这些rule基于masq pool和all pool检查是否masq及snat rules = []iptables.Rule{ // TCP→端口段MASQ/SNAT r.MakeNatOutgoingRule("tcp", portRangeSnatRule, ipVersion), // TCP端口段用完→RETURN r.MakeNatOutgoingRule("tcp", iptables.ReturnAction{}, ipVersion), // UDP→端口段MASQ/SNAT r.MakeNatOutgoingRule("udp", portRangeSnatRule, ipVersion), // UDP端口段用完→RETURN r.MakeNatOutgoingRule("udp", iptables.ReturnAction{}, ipVersion), // 这里用于匹配其它协议 r.MakeNatOutgoingRule("", defaultSnatRule, ipVersion), } } else { rules = []iptables.Rule{ r.MakeNatOutgoingRule("", defaultSnatRule, ipVersion), // 走默认MASQ } } } return &iptables.Chain{ Name: ChainNATOutgoing, Rules: rules, } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69注意
1.高并发场景下,主机端口可能被
Pod SNAT流量耗尽,导致主机无法建立连接,因此支持限制端口范围及源地址2.规则渲染时,
MakeNatOutgoingRule会区分iptables/eBPF模式,渲染出的snat规则不同
# 4.4.渲染
r.MakeNatOutgoingRule()会根据iptables/eBPF模式先渲染iptables rules,根据规则命中POSTROUTing的SNAT流量进行地址伪装。func (r *DefaultRuleRenderer) MakeNatOutgoingRule(protocol string, action Action, ipVersion uint8) Rule { // eBPF模式,SNAT由BPF TC检查过,识别mark就可以 if r.Config.BPFEnabled { return r.makeRuleBPF(ipVersion, protocol, action) } // iptables模式,基于ipset member检查snat return r.makeRuleIPTables(ipVersion, protocol, action) } // -A cali-nat-outgoing -m mark --mark 0x03800000/0x02f00000 -j MASQUERADE func (r *DefaultRuleRenderer) makeRuleBPF(version uint8, protocol string, action Action) Rule{ // 匹配BPF TC打的mark标记 match := iptables.Match().MarkMatchesWithMask(tcdefs.MarkSeenNATOutgoing, MarkSeenNATOutgoingMask) // TCP/UDP协议匹配 if protocol != "" { match = match.Protocol(protocol) } // calico显示配置出接口 if r.Config.IptablesNATOutgoingInterfaceFilter != "" { // 增加-o接口匹配 match = match.OutInterface(r.Config.IptablesNATOutgoingInterfaceFilter) } rule := iptables.Rule{ Action: action, Match: match, } return rule } // -A cali-nat-outgoing -m set --match-set cali40masq-ipam-pools src \ // -m set ! --match-set cali40all-ipam-pools dst -j MASQUERADE func (r *DefaultRuleRenderer) makeRuleIPTables(ipVersion uint8, protocol string, action Action) Rule { ... // all-ipam-pools allIPsSetName := ipConf.NameForMainIPSet(IPSetIDNATOutgoingAllPools) // masq-ipam-pools masqIPsSetName := ipConf.NameForMainIPSet(IPSetIDNATOutgoingMasqPools) // 匹配src ∈ masq-ipam-pools && dst ∉ all-ipam-pools的流量 // 集群内Pod互访无需SNAT,Pod访问外部网络则会进行源地址伪装 match := iptables.Match(). SourceIPSet(masqIPsSetName). NotDestIPSet(allIPsSetName) // 协议匹配 if protocol != "" { match = match.Protocol(protocol) } // calico显示配置出接口,增加-o过滤 if r.Config.IptablesNATOutgoingInterfaceFilter != "" { match = match.OutInterface(r.Config.IptablesNATOutgoingInterfaceFilter) } rule := iptables.Rule{ Action: action, Match: match, } return rule }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68注意
eBPF会处理service dnat/dsr,但Pod访问外网会打mark,由mark+iptables chain完成SNAT,避免与conntrack冲突