From 86bcb1153ba6c782d0745e8698effc45b8352982 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 26 Nov 2018 15:06:58 +0000 Subject: [PATCH 1/7] Add function to subtract an entry from an IDList --- report/id_list.go | 5 +++++ report/string_set.go | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/report/id_list.go b/report/id_list.go index e74901e7a4..5fa9590f75 100644 --- a/report/id_list.go +++ b/report/id_list.go @@ -36,3 +36,8 @@ func (a IDList) Contains(id string) bool { func (a IDList) Intersection(b IDList) IDList { return IDList(StringSet(a).Intersection(StringSet(b))) } + +// Minus returns the set with id removed +func (a IDList) Minus(id string) IDList { + return IDList(StringSet(a).Minus(id)) +} diff --git a/report/string_set.go b/report/string_set.go index 7580d140ca..e4a6498c13 100644 --- a/report/string_set.go +++ b/report/string_set.go @@ -50,6 +50,15 @@ func (s StringSet) Intersection(b StringSet) StringSet { return result } +// Minus returns the set with str removed +func (s StringSet) Minus(str string) StringSet { + i := sort.Search(len(s), func(i int) bool { return s[i] >= str }) + if i < len(s) && s[i] == str { + return append(s[:i], s[i+1:]...) + } + return s +} + // Equal returns true if a and b have the same contents func (s StringSet) Equal(b StringSet) bool { if len(s) != len(b) { From 85df164dc72607420e0f9b70a18d1ba28f52dd37 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 27 Nov 2018 17:25:40 +0000 Subject: [PATCH 2/7] Stop special-casing the Docker bridge in Kubernetes mode In the case targeted, Kubernetes uses a bridge named 'cbr0'. The 'docker0' bridge should be locally-scoped in most cases, and exceptionally users can turn that off with --probe.docker.bridge="" --- prog/probe.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/prog/probe.go b/prog/probe.go index 3e14d2f376..f495bb145c 100644 --- a/prog/probe.go +++ b/prog/probe.go @@ -266,9 +266,7 @@ func probeMain(flags probeFlags, targets []appclient.Target) { } if flags.dockerEnabled { - // Don't add the bridge in Kubernetes since container IPs are global and - // shouldn't be scoped - if flags.dockerBridge != "" && !flags.kubernetesEnabled { + if flags.dockerBridge != "" { if err := report.AddLocalBridge(flags.dockerBridge); err != nil { log.Errorf("Docker: problem with bridge %s: %v", flags.dockerBridge, err) } From ceafa6c1d08de9f2585945de47e324bdd4c4b4bf Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 26 Nov 2018 15:07:35 +0000 Subject: [PATCH 3/7] Use flags to check for NAT in conntrack data --- probe/endpoint/connection_tracker.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/probe/endpoint/connection_tracker.go b/probe/endpoint/connection_tracker.go index e65c4d3c50..8d3e3cbbf7 100644 --- a/probe/endpoint/connection_tracker.go +++ b/probe/endpoint/connection_tracker.go @@ -43,18 +43,19 @@ func newConnectionTracker(conf ReporterConfig) connectionTracker { } func flowToTuple(f conntrack.Conn) (ft fourTuple) { - ft = fourTuple{ - f.Orig.Src.String(), - f.Orig.Dst.String(), - uint16(f.Orig.SrcPort), - uint16(f.Orig.DstPort), - } - // Handle DNAT-ed connections in the initial state - if !f.Orig.Dst.Equal(f.Reply.Src) { + if (f.Status & conntrack.IPS_DST_NAT) == 0 { + ft = fourTuple{ + f.Orig.Src.String(), + f.Orig.Dst.String(), + uint16(f.Orig.SrcPort), + uint16(f.Orig.DstPort), + } + } else { + // Handle DNAT-ed connections in the initial state ft = fourTuple{ - f.Reply.Dst.String(), + f.Orig.Src.String(), f.Reply.Src.String(), - uint16(f.Reply.DstPort), + uint16(f.Orig.SrcPort), uint16(f.Reply.SrcPort), } } From d22b65ea5cc69eb277d88c4cb24235c24a348f26 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 26 Nov 2018 15:08:05 +0000 Subject: [PATCH 4/7] Better NAT mapping Deal separately with SRC vs DST mapping. Includes detailed rationale. --- probe/endpoint/nat.go | 121 ++++++++++++++++++++-------- probe/endpoint/nat_internal_test.go | 32 +++++--- 2 files changed, 109 insertions(+), 44 deletions(-) diff --git a/probe/endpoint/nat.go b/probe/endpoint/nat.go index 797a0d1a84..be76d1b56a 100644 --- a/probe/endpoint/nat.go +++ b/probe/endpoint/nat.go @@ -30,45 +30,102 @@ func makeNATMapper(fw flowWalker) natMapper { return natMapper{fw} } -func toMapping(f conntrack.Conn) *endpointMapping { - var mapping endpointMapping - if f.Orig.Src.Equal(f.Reply.Dst) { - mapping = endpointMapping{ - originalIP: f.Reply.Src, - originalPort: f.Reply.SrcPort, - rewrittenIP: f.Orig.Dst, - rewrittenPort: f.Orig.DstPort, - } - } else { - mapping = endpointMapping{ - originalIP: f.Orig.Src, - originalPort: f.Orig.SrcPort, - rewrittenIP: f.Reply.Dst, - rewrittenPort: f.Reply.DstPort, - } - } - - return &mapping +func endpointNodeID(scope string, ip net.IP, port uint16) string { + return report.MakeEndpointNodeID(scope, "", ip.String(), strconv.Itoa(int(port))) } -// applyNAT duplicates Nodes in the endpoint topology of a report, based on +/* + +Some examples of connections with NAT: + +Pod to pod via Kubernetes service + picked up by ebpf as 10.32.0.16:47600->10.105.173.176:5432 and 10.32.0.6:5432 (??) + 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 + We want: 10.32.0.16:47600->10.32.0.6:5432 + - replace the destination (== NAT orig dst) with the NAT reply source + +Incoming from outside the cluster to a NodePort: + picked up by ebpf as 10.32.0.1:13488->10.32.0.7:80 + 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 + We want: 37.157.33.76:13488->10.32.0.7:80 + - replace the source (== NAT reply dst) with the NAT original source + +Outgoing from a pod: + picked up by ebpf as 10.32.0.7:36078->18.221.99.178:443 + 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 + We want: 10.32.0.7:36078->18.221.99.178:443 + - leave it alone. + +Docker container exposing port to similar on different host +host1: + picked up by ebpf as ip-172-31-5-80;172.17.0.2:43042->172.31.2.17:8080 + 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 + applying standard rule: ip-172-31-5-80;172.17.0.2:43042->172.31.2.17:8080 (i.e. no change) + 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) +host2: + picked up by ebpf as 172.31.5.80:43042->ip-172-31-2-17;172.17.0.2:80 + 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 + Ideally we might want: ip-172-31-5-80;172.17.0.2:43042->ip-172-31-2-17;172.17.0.2:80 + applying standard rule: 172.31.5.80:43042->ip-172-31-2-17;172.17.0.2:80 (i.e. no change) + 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) + +All of the above can be satisfied by these rules: + For SRC_NAT either add NAT orig source as a copy of NAT reply destination + or add NAT reply destination as a copy of NAT original source + For DST_NAT replace the destination in adjacencies with the NAT reply source + and add nat original destination as a copy of nat reply source +*/ + +// applyNAT modifies Nodes in the endpoint topology of a report, based on // the NAT table. func (n natMapper) applyNAT(rpt report.Report, scope string) { n.flowWalker.walkFlows(func(f conntrack.Conn, _ bool) { - mapping := toMapping(f) + replyDstID := endpointNodeID(scope, f.Reply.Dst, f.Reply.DstPort) + origSrcID := endpointNodeID(scope, f.Orig.Src, f.Orig.SrcPort) - realEndpointPort := strconv.Itoa(int(mapping.originalPort)) - copyEndpointPort := strconv.Itoa(int(mapping.rewrittenPort)) - realEndpointID := report.MakeEndpointNodeID(scope, "", mapping.originalIP.String(), realEndpointPort) - copyEndpointID := report.MakeEndpointNodeID(scope, "", mapping.rewrittenIP.String(), copyEndpointPort) - - node, ok := rpt.Endpoint.Nodes[realEndpointID] - if !ok { - return + if (f.Status & conntrack.IPS_SRC_NAT) != 0 { + if replyDstID != origSrcID { + // either add NAT orig source as a copy of NAT reply destination + if replyDstNode, ok := rpt.Endpoint.Nodes[replyDstID]; ok { + newNode := replyDstNode.WithID(origSrcID).WithLatests(map[string]string{ + CopyOf: replyDstID, + }) + rpt.Endpoint.AddNode(newNode) + } else if origSrcNode, ok := rpt.Endpoint.Nodes[origSrcID]; ok { + // or add NAT reply destination as a copy of NAT original source + newNode := origSrcNode.WithID(replyDstID).WithLatests(map[string]string{ + CopyOf: origSrcID, + }) + rpt.Endpoint.AddNode(newNode) + } + } } - rpt.Endpoint.AddNode(node.WithID(copyEndpointID).WithLatests(map[string]string{ - CopyOf: realEndpointID, - })) + if (f.Status & conntrack.IPS_DST_NAT) != 0 { + fromID := endpointNodeID(scope, f.Reply.Dst, f.Reply.DstPort) + fromNode, ok := rpt.Endpoint.Nodes[fromID] + if !ok { + return + } + toID := endpointNodeID(scope, f.Orig.Dst, f.Orig.DstPort) + + // replace destination with reply source + replySrcID := endpointNodeID(scope, f.Reply.Src, f.Reply.SrcPort) + if replySrcID != toID { + fromNode.Adjacency = fromNode.Adjacency.Minus(toID) + fromNode = fromNode.WithAdjacent(replySrcID) + + // add nat original destination as a copy of nat reply source + replySrcNode, ok := rpt.Endpoint.Nodes[replySrcID] + if !ok { + replySrcNode = report.MakeNode(replySrcID) + } + newNode := replySrcNode.WithID(toID).WithLatests(map[string]string{ + CopyOf: replySrcID, + }) + rpt.Endpoint.AddNode(newNode) + } + + } }) } diff --git a/probe/endpoint/nat_internal_test.go b/probe/endpoint/nat_internal_test.go index d4f3c4354b..6884614e4a 100644 --- a/probe/endpoint/nat_internal_test.go +++ b/probe/endpoint/nat_internal_test.go @@ -47,10 +47,11 @@ func TestNat(t *testing.T) { { f := conntrack.Conn{ MsgType: conntrack.NfctMsgUpdate, + Status: conntrack.IPS_DST_NAT, Orig: conntrack.Tuple{ Src: host2, Dst: host1, - SrcPort: 22222, + SrcPort: 22223, DstPort: 80, Proto: syscall.IPPROTO_TCP, }, @@ -58,7 +59,7 @@ func TestNat(t *testing.T) { Src: c1, Dst: host2, SrcPort: 80, - DstPort: 22222, + DstPort: 22223, Proto: syscall.IPPROTO_TCP, }, CtId: 1, @@ -70,15 +71,18 @@ func TestNat(t *testing.T) { have := report.MakeReport() originalID := report.MakeEndpointNodeID("host1", "", "10.0.47.1", "80") - have.Endpoint.AddNode(report.MakeNodeWith(originalID, map[string]string{ + originalNode := report.MakeNodeWith(originalID, map[string]string{ "foo": "bar", - })) + }) + have.Endpoint.AddNode(originalNode) + fromID := report.MakeEndpointNodeID("host2", "", "2.3.4.5", "22223") + have.Endpoint.AddNode(report.MakeNodeWith(fromID, nil).WithAdjacent(originalID)) want := have.Copy() - wantID := report.MakeEndpointNodeID("host1", "", "1.2.3.4", "80") - want.Endpoint.AddNode(report.MakeNodeWith(wantID, map[string]string{ + // add nat original destination as a copy of nat reply source + origDstID := report.MakeEndpointNodeID("host1", "", "1.2.3.4", "80") + want.Endpoint.AddNode(originalNode.WithID(origDstID).WithLatests(map[string]string{ CopyOf: originalID, - "foo": "bar", })) makeNATMapper(ct).applyNAT(have, "host1") @@ -91,6 +95,7 @@ func TestNat(t *testing.T) { { f := conntrack.Conn{ MsgType: conntrack.NfctMsgUpdate, + Status: conntrack.IPS_SRC_NAT, Orig: conntrack.Tuple{ Src: c2, Dst: host1, @@ -112,16 +117,19 @@ func TestNat(t *testing.T) { } have := report.MakeReport() - originalID := report.MakeEndpointNodeID("host2", "", "10.0.47.2", "22222") - have.Endpoint.AddNode(report.MakeNodeWith(originalID, map[string]string{ + fromID := report.MakeEndpointNodeID("host2", "", "10.0.47.2", "22222") + toID := report.MakeEndpointNodeID("host1", "", "1.2.3.4", "80") + have.Endpoint.AddNode(report.MakeNodeWith(toID, nil)) + have.Endpoint.AddNode(report.MakeNodeWith(fromID, map[string]string{ "foo": "baz", - })) + }).WithAdjacent(toID)) + // add NAT reply destination as a copy of NAT original source want := have.Copy() want.Endpoint.AddNode(report.MakeNodeWith(report.MakeEndpointNodeID("host2", "", "2.3.4.5", "22223"), map[string]string{ - CopyOf: originalID, + CopyOf: report.MakeEndpointNodeID("host1", "", "10.0.47.2", "22222"), "foo": "baz", - })) + }).WithAdjacent(toID)) makeNATMapper(ct).applyNAT(have, "host1") if !reflect.DeepEqual(want, have) { From 39721d86198d95bee0cf5a9715bfdb9b02ad5900 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Wed, 28 Nov 2018 12:33:06 +0000 Subject: [PATCH 5/7] Fix up NAT mapper test - Add SRC and DST NAT flag in status. - The port number was wrong from host1's perspective - it should see the mapped port on host2 not the real port inside the container. - Add nodes at the other end of the connection, like the real probe does. - Pass the local host name to MakeEndpointNodeID as it would be on the real probe. --- probe/endpoint/nat_internal_test.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/probe/endpoint/nat_internal_test.go b/probe/endpoint/nat_internal_test.go index 6884614e4a..a94d55218f 100644 --- a/probe/endpoint/nat_internal_test.go +++ b/probe/endpoint/nat_internal_test.go @@ -71,18 +71,18 @@ func TestNat(t *testing.T) { have := report.MakeReport() originalID := report.MakeEndpointNodeID("host1", "", "10.0.47.1", "80") - originalNode := report.MakeNodeWith(originalID, map[string]string{ + have.Endpoint.AddNode(report.MakeNodeWith(originalID, map[string]string{ "foo": "bar", - }) - have.Endpoint.AddNode(originalNode) - fromID := report.MakeEndpointNodeID("host2", "", "2.3.4.5", "22223") + })) + fromID := report.MakeEndpointNodeID("host1", "", "2.3.4.5", "22223") have.Endpoint.AddNode(report.MakeNodeWith(fromID, nil).WithAdjacent(originalID)) want := have.Copy() // add nat original destination as a copy of nat reply source - origDstID := report.MakeEndpointNodeID("host1", "", "1.2.3.4", "80") - want.Endpoint.AddNode(originalNode.WithID(origDstID).WithLatests(map[string]string{ + wantID := report.MakeEndpointNodeID("host1", "", "1.2.3.4", "80") + want.Endpoint.AddNode(report.MakeNodeWith(wantID, map[string]string{ CopyOf: originalID, + "foo": "bar", })) makeNATMapper(ct).applyNAT(have, "host1") @@ -91,7 +91,7 @@ func TestNat(t *testing.T) { } } - // form the PoV of host2 + // from the PoV of host2 { f := conntrack.Conn{ MsgType: conntrack.NfctMsgUpdate, @@ -117,21 +117,21 @@ func TestNat(t *testing.T) { } have := report.MakeReport() - fromID := report.MakeEndpointNodeID("host2", "", "10.0.47.2", "22222") - toID := report.MakeEndpointNodeID("host1", "", "1.2.3.4", "80") + originalID := report.MakeEndpointNodeID("host2", "", "10.0.47.2", "22222") + toID := report.MakeEndpointNodeID("host2", "", "1.2.3.4", "80") have.Endpoint.AddNode(report.MakeNodeWith(toID, nil)) - have.Endpoint.AddNode(report.MakeNodeWith(fromID, map[string]string{ + have.Endpoint.AddNode(report.MakeNodeWith(originalID, map[string]string{ "foo": "baz", }).WithAdjacent(toID)) // add NAT reply destination as a copy of NAT original source want := have.Copy() want.Endpoint.AddNode(report.MakeNodeWith(report.MakeEndpointNodeID("host2", "", "2.3.4.5", "22223"), map[string]string{ - CopyOf: report.MakeEndpointNodeID("host1", "", "10.0.47.2", "22222"), + CopyOf: originalID, "foo": "baz", }).WithAdjacent(toID)) - makeNATMapper(ct).applyNAT(have, "host1") + makeNATMapper(ct).applyNAT(have, "host2") if !reflect.DeepEqual(want, have) { t.Fatal(test.Diff(want, have)) } From 49fe4a687eebea7f2104448b25ceb7920ccd4195 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 24 Dec 2018 12:52:04 +0000 Subject: [PATCH 6/7] Store modified 'from' node --- probe/endpoint/nat.go | 1 + 1 file changed, 1 insertion(+) diff --git a/probe/endpoint/nat.go b/probe/endpoint/nat.go index be76d1b56a..8ecd0286b9 100644 --- a/probe/endpoint/nat.go +++ b/probe/endpoint/nat.go @@ -114,6 +114,7 @@ func (n natMapper) applyNAT(rpt report.Report, scope string) { if replySrcID != toID { fromNode.Adjacency = fromNode.Adjacency.Minus(toID) fromNode = fromNode.WithAdjacent(replySrcID) + rpt.Endpoint.Nodes[fromID] = fromNode // add nat original destination as a copy of nat reply source replySrcNode, ok := rpt.Endpoint.Nodes[replySrcID] From 31e8d8f79e3044e1c2a5f7059726de1d2b50dd92 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 11 Jan 2019 16:15:10 +0000 Subject: [PATCH 7/7] 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. --- probe/endpoint/nat.go | 83 +++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/probe/endpoint/nat.go b/probe/endpoint/nat.go index 8ecd0286b9..ea66cf775e 100644 --- a/probe/endpoint/nat.go +++ b/probe/endpoint/nat.go @@ -38,61 +38,66 @@ func endpointNodeID(scope string, ip net.IP, port uint16) string { Some examples of connections with NAT: +Here 10.32.0.X are pod addresses; 172.31.X.X are node addresses; 10.10X.X.X are service virtual addresses. + Pod to pod via Kubernetes service picked up by ebpf as 10.32.0.16:47600->10.105.173.176:5432 and 10.32.0.6:5432 (??) 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 We want: 10.32.0.16:47600->10.32.0.6:5432 - - replace the destination (== NAT orig dst) with the NAT reply source + - replace the destination (== NAT orig dst) with the NAT reply source (A) Incoming from outside the cluster to a NodePort: picked up by ebpf as 10.32.0.1:13488->10.32.0.7:80 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 We want: 37.157.33.76:13488->10.32.0.7:80 - - replace the source (== NAT reply dst) with the NAT original source + - replace the source (== NAT reply dst) with the NAT original source (B) + To match another probe with the other side of this connection, also want 37.157.33.76:13488->172.31.2.17:30081 + - add NAT original dst as a copy of nat reply source (C) Outgoing from a pod: picked up by ebpf as 10.32.0.7:36078->18.221.99.178:443 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 We want: 10.32.0.7:36078->18.221.99.178:443 - - leave it alone. + - leave it alone. (D) Docker container exposing port to similar on different host host1: picked up by ebpf as ip-172-31-5-80;172.17.0.2:43042->172.31.2.17:8080 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 - applying standard rule: ip-172-31-5-80;172.17.0.2:43042->172.31.2.17:8080 (i.e. no change) - 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) + We want: 172.31.5.80:43042->172.31.2.17:8080 + - can't have a blanket rule to replace NAT original source with NAT reply destination, because that breaks case D. + 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) host2: picked up by ebpf as 172.31.5.80:43042->ip-172-31-2-17;172.17.0.2:80 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 - Ideally we might want: ip-172-31-5-80;172.17.0.2:43042->ip-172-31-2-17;172.17.0.2:80 - applying standard rule: 172.31.5.80:43042->ip-172-31-2-17;172.17.0.2:80 (i.e. no change) - 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) + Rule A doesn't match and rule B is a no-op because the addresses are the same. + To match another probe with the other side of this connection, also want 172.31.5.80:43042->172.31.2.17:8080 + - add NAT original dst as a copy of nat reply source (C) All of the above can be satisfied by these rules: - For SRC_NAT either add NAT orig source as a copy of NAT reply destination - or add NAT reply destination as a copy of NAT original source - For DST_NAT replace the destination in adjacencies with the NAT reply source - and add nat original destination as a copy of nat reply source + For SRC_NAT + replace the source (== NAT reply dst) with the NAT original source (B) + or add NAT reply destination as a copy of NAT original source (E) + For DST_NAT + replace NAT original destination in adjacencies with the NAT reply source (A) + or add NAT original destination as a copy of NAT reply source (C) */ // applyNAT modifies Nodes in the endpoint topology of a report, based on // the NAT table. func (n natMapper) applyNAT(rpt report.Report, scope string) { n.flowWalker.walkFlows(func(f conntrack.Conn, _ bool) { - replyDstID := endpointNodeID(scope, f.Reply.Dst, f.Reply.DstPort) - origSrcID := endpointNodeID(scope, f.Orig.Src, f.Orig.SrcPort) if (f.Status & conntrack.IPS_SRC_NAT) != 0 { + origSrcID := endpointNodeID(scope, f.Orig.Src, f.Orig.SrcPort) + replyDstID := endpointNodeID(scope, f.Reply.Dst, f.Reply.DstPort) if replyDstID != origSrcID { - // either add NAT orig source as a copy of NAT reply destination - if replyDstNode, ok := rpt.Endpoint.Nodes[replyDstID]; ok { - newNode := replyDstNode.WithID(origSrcID).WithLatests(map[string]string{ - CopyOf: replyDstID, - }) - rpt.Endpoint.AddNode(newNode) + if fromNode, ok := rpt.Endpoint.Nodes[replyDstID]; ok { + // replace the source (== NAT reply dst) with the NAT original source (B) + delete(rpt.Endpoint.Nodes, replyDstID) + rpt.Endpoint.AddNode(fromNode.WithID(origSrcID)) } else if origSrcNode, ok := rpt.Endpoint.Nodes[origSrcID]; ok { - // or add NAT reply destination as a copy of NAT original source + // add NAT reply destination as a copy of NAT original source (E) newNode := origSrcNode.WithID(replyDstID).WithLatests(map[string]string{ CopyOf: origSrcID, }) @@ -102,29 +107,23 @@ func (n natMapper) applyNAT(rpt report.Report, scope string) { } if (f.Status & conntrack.IPS_DST_NAT) != 0 { - fromID := endpointNodeID(scope, f.Reply.Dst, f.Reply.DstPort) - fromNode, ok := rpt.Endpoint.Nodes[fromID] - if !ok { - return - } - toID := endpointNodeID(scope, f.Orig.Dst, f.Orig.DstPort) - - // replace destination with reply source replySrcID := endpointNodeID(scope, f.Reply.Src, f.Reply.SrcPort) - if replySrcID != toID { - fromNode.Adjacency = fromNode.Adjacency.Minus(toID) - fromNode = fromNode.WithAdjacent(replySrcID) - rpt.Endpoint.Nodes[fromID] = fromNode - - // add nat original destination as a copy of nat reply source - replySrcNode, ok := rpt.Endpoint.Nodes[replySrcID] - if !ok { - replySrcNode = report.MakeNode(replySrcID) + origDstID := endpointNodeID(scope, f.Orig.Dst, f.Orig.DstPort) + if replySrcID != origDstID { + fromID := endpointNodeID(scope, f.Reply.Dst, f.Reply.DstPort) + fromNode, ok := rpt.Endpoint.Nodes[fromID] + if ok && fromNode.Adjacency.Contains(origDstID) { + // replace NAT original destination in adjacencies with the NAT reply source (A) + fromNode.Adjacency = fromNode.Adjacency.Minus(origDstID) + fromNode = fromNode.WithAdjacent(replySrcID) + rpt.Endpoint.Nodes[fromID] = fromNode + } else if replySrcNode, ok := rpt.Endpoint.Nodes[replySrcID]; ok { + // add NAT original destination as a copy of NAT reply source (C) + newNode := replySrcNode.WithID(origDstID).WithLatests(map[string]string{ + CopyOf: replySrcID, + }) + rpt.Endpoint.AddNode(newNode) } - newNode := replySrcNode.WithID(toID).WithLatests(map[string]string{ - CopyOf: replySrcID, - }) - rpt.Endpoint.AddNode(newNode) } }