Skip to content

Commit 31e8d8f

Browse files
committed
Clarified rules and made SRC and DST consistent
Matching up examples to rules to code was hard, so I added letters (A), (B) etc. This helped to track down various inconsistencies. Now SRC and DST NAT are symmetrical, and the code is quite similar, apart from replacing the source vs destination of an adjacency.
1 parent 49fe4a6 commit 31e8d8f

File tree

1 file changed

+41
-42
lines changed

1 file changed

+41
-42
lines changed

probe/endpoint/nat.go

Lines changed: 41 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -38,61 +38,66 @@ func endpointNodeID(scope string, ip net.IP, port uint16) string {
3838
3939
Some examples of connections with NAT:
4040
41+
Here 10.32.0.X are pod addresses; 172.31.X.X are node addresses; 10.10X.X.X are service virtual addresses.
42+
4143
Pod to pod via Kubernetes service
4244
picked up by ebpf as 10.32.0.16:47600->10.105.173.176:5432 and 10.32.0.6:5432 (??)
4345
NAT IPS_DST_NAT orig: 10.32.0.16:47600->10.105.173.176:5432, reply: 10.32.0.6:5432->10.32.0.16:47600
4446
We want: 10.32.0.16:47600->10.32.0.6:5432
45-
- replace the destination (== NAT orig dst) with the NAT reply source
47+
- replace the destination (== NAT orig dst) with the NAT reply source (A)
4648
4749
Incoming from outside the cluster to a NodePort:
4850
picked up by ebpf as 10.32.0.1:13488->10.32.0.7:80
4951
NAT: IPS_SRC_NAT IPS_DST_NAT orig: 37.157.33.76:13488->172.31.2.17:30081, reply: 10.32.0.7:80->10.32.0.1:13488
5052
We want: 37.157.33.76:13488->10.32.0.7:80
51-
- replace the source (== NAT reply dst) with the NAT original source
53+
- replace the source (== NAT reply dst) with the NAT original source (B)
54+
To match another probe with the other side of this connection, also want 37.157.33.76:13488->172.31.2.17:30081
55+
- add NAT original dst as a copy of nat reply source (C)
5256
5357
Outgoing from a pod:
5458
picked up by ebpf as 10.32.0.7:36078->18.221.99.178:443
5559
NAT: IPS_SRC_NAT orig: 10.32.0.7:36078->18.221.99.178:443, reply: 18.221.99.178:443->172.31.2.17:36078
5660
We want: 10.32.0.7:36078->18.221.99.178:443
57-
- leave it alone.
61+
- leave it alone. (D)
5862
5963
Docker container exposing port to similar on different host
6064
host1:
6165
picked up by ebpf as ip-172-31-5-80;172.17.0.2:43042->172.31.2.17:8080
6266
NAT: IPS_SRC_NAT orig: 172.17.0.2:43042->172.31.2.17:8080, reply: 172.31.2.17:8080-> 172.31.5.80:43042
63-
applying standard rule: ip-172-31-5-80;172.17.0.2:43042->172.31.2.17:8080 (i.e. no change)
64-
we could add 172.31.5.80:43042 (nat reply destination) as a copy of ip-172-31-5-80;172.17.0.2:43042 (nat orig source)
67+
We want: 172.31.5.80:43042->172.31.2.17:8080
68+
- can't have a blanket rule to replace NAT original source with NAT reply destination, because that breaks case D.
69+
we could add 172.31.5.80:43042 (nat reply destination) as a copy of ip-172-31-5-80;172.17.0.2:43042 (nat orig source) (E)
6570
host2:
6671
picked up by ebpf as 172.31.5.80:43042->ip-172-31-2-17;172.17.0.2:80
6772
NAT: IPS_DST_NAT orig: 172.31.5.80:43042->172.31.2.17:8080, reply: 172.17.0.2:80->172.31.5.80:43042
68-
Ideally we might want: ip-172-31-5-80;172.17.0.2:43042->ip-172-31-2-17;172.17.0.2:80
69-
applying standard rule: 172.31.5.80:43042->ip-172-31-2-17;172.17.0.2:80 (i.e. no change)
70-
we could add 172.31.2.17:8080 (nat original destination) as a copy of ip-172-31-2-17;172.17.0.2:80 (nat reply source)
73+
Rule A doesn't match and rule B is a no-op because the addresses are the same.
74+
To match another probe with the other side of this connection, also want 172.31.5.80:43042->172.31.2.17:8080
75+
- add NAT original dst as a copy of nat reply source (C)
7176
7277
All of the above can be satisfied by these rules:
73-
For SRC_NAT either add NAT orig source as a copy of NAT reply destination
74-
or add NAT reply destination as a copy of NAT original source
75-
For DST_NAT replace the destination in adjacencies with the NAT reply source
76-
and add nat original destination as a copy of nat reply source
78+
For SRC_NAT
79+
replace the source (== NAT reply dst) with the NAT original source (B)
80+
or add NAT reply destination as a copy of NAT original source (E)
81+
For DST_NAT
82+
replace NAT original destination in adjacencies with the NAT reply source (A)
83+
or add NAT original destination as a copy of NAT reply source (C)
7784
*/
7885

7986
// applyNAT modifies Nodes in the endpoint topology of a report, based on
8087
// the NAT table.
8188
func (n natMapper) applyNAT(rpt report.Report, scope string) {
8289
n.flowWalker.walkFlows(func(f conntrack.Conn, _ bool) {
83-
replyDstID := endpointNodeID(scope, f.Reply.Dst, f.Reply.DstPort)
84-
origSrcID := endpointNodeID(scope, f.Orig.Src, f.Orig.SrcPort)
8590

8691
if (f.Status & conntrack.IPS_SRC_NAT) != 0 {
92+
origSrcID := endpointNodeID(scope, f.Orig.Src, f.Orig.SrcPort)
93+
replyDstID := endpointNodeID(scope, f.Reply.Dst, f.Reply.DstPort)
8794
if replyDstID != origSrcID {
88-
// either add NAT orig source as a copy of NAT reply destination
89-
if replyDstNode, ok := rpt.Endpoint.Nodes[replyDstID]; ok {
90-
newNode := replyDstNode.WithID(origSrcID).WithLatests(map[string]string{
91-
CopyOf: replyDstID,
92-
})
93-
rpt.Endpoint.AddNode(newNode)
95+
if fromNode, ok := rpt.Endpoint.Nodes[replyDstID]; ok {
96+
// replace the source (== NAT reply dst) with the NAT original source (B)
97+
delete(rpt.Endpoint.Nodes, replyDstID)
98+
rpt.Endpoint.AddNode(fromNode.WithID(origSrcID))
9499
} else if origSrcNode, ok := rpt.Endpoint.Nodes[origSrcID]; ok {
95-
// or add NAT reply destination as a copy of NAT original source
100+
// add NAT reply destination as a copy of NAT original source (E)
96101
newNode := origSrcNode.WithID(replyDstID).WithLatests(map[string]string{
97102
CopyOf: origSrcID,
98103
})
@@ -102,29 +107,23 @@ func (n natMapper) applyNAT(rpt report.Report, scope string) {
102107
}
103108

104109
if (f.Status & conntrack.IPS_DST_NAT) != 0 {
105-
fromID := endpointNodeID(scope, f.Reply.Dst, f.Reply.DstPort)
106-
fromNode, ok := rpt.Endpoint.Nodes[fromID]
107-
if !ok {
108-
return
109-
}
110-
toID := endpointNodeID(scope, f.Orig.Dst, f.Orig.DstPort)
111-
112-
// replace destination with reply source
113110
replySrcID := endpointNodeID(scope, f.Reply.Src, f.Reply.SrcPort)
114-
if replySrcID != toID {
115-
fromNode.Adjacency = fromNode.Adjacency.Minus(toID)
116-
fromNode = fromNode.WithAdjacent(replySrcID)
117-
rpt.Endpoint.Nodes[fromID] = fromNode
118-
119-
// add nat original destination as a copy of nat reply source
120-
replySrcNode, ok := rpt.Endpoint.Nodes[replySrcID]
121-
if !ok {
122-
replySrcNode = report.MakeNode(replySrcID)
111+
origDstID := endpointNodeID(scope, f.Orig.Dst, f.Orig.DstPort)
112+
if replySrcID != origDstID {
113+
fromID := endpointNodeID(scope, f.Reply.Dst, f.Reply.DstPort)
114+
fromNode, ok := rpt.Endpoint.Nodes[fromID]
115+
if ok && fromNode.Adjacency.Contains(origDstID) {
116+
// replace NAT original destination in adjacencies with the NAT reply source (A)
117+
fromNode.Adjacency = fromNode.Adjacency.Minus(origDstID)
118+
fromNode = fromNode.WithAdjacent(replySrcID)
119+
rpt.Endpoint.Nodes[fromID] = fromNode
120+
} else if replySrcNode, ok := rpt.Endpoint.Nodes[replySrcID]; ok {
121+
// add NAT original destination as a copy of NAT reply source (C)
122+
newNode := replySrcNode.WithID(origDstID).WithLatests(map[string]string{
123+
CopyOf: replySrcID,
124+
})
125+
rpt.Endpoint.AddNode(newNode)
123126
}
124-
newNode := replySrcNode.WithID(toID).WithLatests(map[string]string{
125-
CopyOf: replySrcID,
126-
})
127-
rpt.Endpoint.AddNode(newNode)
128127
}
129128

130129
}

0 commit comments

Comments
 (0)