From b8c99ed7ccb072d075c251fa04d073ae0ba25f17 Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Tue, 9 Aug 2016 14:59:28 +0000 Subject: [PATCH 1/3] Revert "Cache generated ids to relieve pressure on the GC" It wasn't working as noticed by @rade (note the err != nil) and was complicating the code. This reverts commit 2f760f2f3337b9ee2c47646b35d39e9786639f63. --- report/id.go | 36 +----------------------------------- 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/report/id.go b/report/id.go index 7fb94a0b7..908376f48 100644 --- a/report/id.go +++ b/report/id.go @@ -1,13 +1,8 @@ package report import ( - "hash" - "hash/fnv" "net" "strings" - "sync" - - "github.com/bluele/gcache" ) // TheInternet is used as a node ID to indicate a remote IP. @@ -29,38 +24,9 @@ const ( DoesNotMakeConnections = "does_not_make_connections" ) -var ( - idCache = gcache.New(1024).LRU().Build() - hashers = sync.Pool{ - New: func() interface{} { - return fnv.New64a() - }, - } -) - -func lookupID(part1, part2, part3 string, f func() string) string { - h := hashers.Get().(hash.Hash64) - h.Write([]byte(part1)) - h.Write([]byte(part2)) - h.Write([]byte(part3)) - sum := h.Sum64() - var result string - if id, err := idCache.Get(sum); id != nil && err != nil { - result = id.(string) - } else { - result = f() - idCache.Set(sum, result) - } - h.Reset() - hashers.Put(h) - return result -} - // MakeEndpointNodeID produces an endpoint node ID from its composite parts. func MakeEndpointNodeID(hostID, address, port string) string { - return lookupID(hostID, address, port, func() string { - return MakeAddressNodeID(hostID, address) + ScopeDelim + port - }) + return MakeAddressNodeID(hostID, address) + ScopeDelim + port } // MakeAddressNodeID produces an address node ID from its composite parts. From 3892273096a4f0a87b7d1b597b0b88aea8198a19 Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Tue, 9 Aug 2016 17:00:50 +0000 Subject: [PATCH 2/3] Append namespace to endpoint scope for loopback connections --- probe/endpoint/nat.go | 4 +-- probe/endpoint/nat_internal_test.go | 8 +++--- probe/endpoint/procspy/proc_linux.go | 11 ++++---- probe/endpoint/procspy/spy.go | 5 ++-- probe/endpoint/reporter.go | 17 ++++++++----- probe/endpoint/reporter_test.go | 4 +-- probe/host/tagger_test.go | 2 +- render/short_lived_connections_test.go | 8 +++--- report/id.go | 35 ++++++++++++++++---------- report/id_test.go | 15 +++++------ test/fixture/report_fixture.go | 18 ++++++------- 11 files changed, 72 insertions(+), 55 deletions(-) diff --git a/probe/endpoint/nat.go b/probe/endpoint/nat.go index b779b21cd..b2c6ffe72 100644 --- a/probe/endpoint/nat.go +++ b/probe/endpoint/nat.go @@ -52,9 +52,9 @@ func (n natMapper) applyNAT(rpt report.Report, scope string) { n.flowWalker.walkFlows(func(f flow) { var ( mapping = toMapping(f) - realEndpointID = report.MakeEndpointNodeID(scope, mapping.originalIP, strconv.Itoa(mapping.originalPort)) + realEndpointID = report.MakeEndpointNodeID(scope, "", mapping.originalIP, strconv.Itoa(mapping.originalPort)) copyEndpointPort = strconv.Itoa(mapping.rewrittenPort) - copyEndpointID = report.MakeEndpointNodeID(scope, mapping.rewrittenIP, copyEndpointPort) + copyEndpointID = report.MakeEndpointNodeID(scope, "", mapping.rewrittenIP, copyEndpointPort) node, ok = rpt.Endpoint.Nodes[realEndpointID] ) if !ok { diff --git a/probe/endpoint/nat_internal_test.go b/probe/endpoint/nat_internal_test.go index 4f0cebe11..6884afedb 100644 --- a/probe/endpoint/nat_internal_test.go +++ b/probe/endpoint/nat_internal_test.go @@ -43,7 +43,7 @@ func TestNat(t *testing.T) { } have := report.MakeReport() - originalID := report.MakeEndpointNodeID("host1", "10.0.47.1", "80") + originalID := report.MakeEndpointNodeID("host1", "", "10.0.47.1", "80") have.Endpoint.AddNode(report.MakeNodeWith(originalID, map[string]string{ Addr: "10.0.47.1", Port: "80", @@ -52,7 +52,7 @@ func TestNat(t *testing.T) { })) want := have.Copy() - wantID := report.MakeEndpointNodeID("host1", "1.2.3.4", "80") + wantID := report.MakeEndpointNodeID("host1", "", "1.2.3.4", "80") want.Endpoint.AddNode(report.MakeNodeWith(wantID, map[string]string{ Addr: "1.2.3.4", Port: "80", @@ -78,7 +78,7 @@ func TestNat(t *testing.T) { } have := report.MakeReport() - originalID := report.MakeEndpointNodeID("host2", "10.0.47.2", "22222") + originalID := report.MakeEndpointNodeID("host2", "", "10.0.47.2", "22222") have.Endpoint.AddNode(report.MakeNodeWith(originalID, map[string]string{ Addr: "10.0.47.2", Port: "22222", @@ -87,7 +87,7 @@ func TestNat(t *testing.T) { })) want := have.Copy() - want.Endpoint.AddNode(report.MakeNodeWith(report.MakeEndpointNodeID("host2", "2.3.4.5", "22223"), map[string]string{ + want.Endpoint.AddNode(report.MakeNodeWith(report.MakeEndpointNodeID("host2", "", "2.3.4.5", "22223"), map[string]string{ Addr: "2.3.4.5", Port: "22223", "copy_of": originalID, diff --git a/probe/endpoint/procspy/proc_linux.go b/probe/endpoint/procspy/proc_linux.go index 652944f22..0a73130ad 100644 --- a/probe/endpoint/procspy/proc_linux.go +++ b/probe/endpoint/procspy/proc_linux.go @@ -126,7 +126,7 @@ func readProcessConnections(buf *bytes.Buffer, namespaceProcs []*process.Process } // walkNamespace does the work of walk for a single namespace -func (w pidWalker) walkNamespace(buf *bytes.Buffer, sockets map[uint64]*Proc, namespaceProcs []*process.Process) error { +func (w pidWalker) walkNamespace(namespaceID uint64, buf *bytes.Buffer, sockets map[uint64]*Proc, namespaceProcs []*process.Process) error { if found, err := readProcessConnections(buf, namespaceProcs); err != nil || !found { return err @@ -181,8 +181,9 @@ func (w pidWalker) walkNamespace(buf *bytes.Buffer, sockets map[uint64]*Proc, na // garbage if proc == nil { proc = &Proc{ - PID: uint(p.PID), - Name: p.Name, + PID: uint(p.PID), + Name: p.Name, + NetNamespaceID: namespaceID, } } @@ -225,10 +226,10 @@ func (w pidWalker) walk(buf *bytes.Buffer) (map[uint64]*Proc, error) { namespaces[namespaceID] = append(namespaces[namespaceID], &p) }) - for _, procs := range namespaces { + for namespaceID, procs := range namespaces { select { case <-w.tickc: - w.walkNamespace(buf, sockets, procs) + w.walkNamespace(namespaceID, buf, sockets, procs) case <-w.stopc: break // abort } diff --git a/probe/endpoint/procspy/spy.go b/probe/endpoint/procspy/spy.go index 73d4a560d..d59de62b4 100644 --- a/probe/endpoint/procspy/spy.go +++ b/probe/endpoint/procspy/spy.go @@ -24,8 +24,9 @@ type Connection struct { // Proc is a single process with PID and process name. type Proc struct { - PID uint - Name string + PID uint + Name string + NetNamespaceID uint64 } // ConnIter is returned by Connections(). diff --git a/probe/endpoint/reporter.go b/probe/endpoint/reporter.go index 1747410fc..10c656529 100644 --- a/probe/endpoint/reporter.go +++ b/probe/endpoint/reporter.go @@ -132,7 +132,7 @@ func (r *Reporter) Report() (report.Report, error) { } seenTuples[tuple.key()] = tuple - r.addConnection(&rpt, tuple, extraNodeInfo, extraNodeInfo) + r.addConnection(&rpt, tuple, "", extraNodeInfo, extraNodeInfo) }) } @@ -143,7 +143,8 @@ func (r *Reporter) Report() (report.Report, error) { } for conn := conns.Next(); conn != nil; conn = conns.Next() { var ( - tuple = fourTuple{ + namespaceID string + tuple = fourTuple{ conn.LocalAddress.String(), conn.RemoteAddress.String(), conn.LocalPort, @@ -157,6 +158,10 @@ func (r *Reporter) Report() (report.Report, error) { fromNodeInfo[report.HostNodeID] = hostNodeID } + if conn.Proc.NetNamespaceID > 0 { + namespaceID = strconv.FormatUint(conn.Proc.NetNamespaceID, 10) + } + // If we've already seen this connection, we should know the direction // (or have already figured it out), so we normalize and use the // canonical direction. Otherwise, we can use a port-heuristic to guess @@ -166,7 +171,7 @@ func (r *Reporter) Report() (report.Report, error) { tuple.reverse() toNodeInfo, fromNodeInfo = fromNodeInfo, toNodeInfo } - r.addConnection(&rpt, tuple, fromNodeInfo, toNodeInfo) + r.addConnection(&rpt, tuple, namespaceID, fromNodeInfo, toNodeInfo) } } @@ -174,10 +179,10 @@ func (r *Reporter) Report() (report.Report, error) { return rpt, nil } -func (r *Reporter) addConnection(rpt *report.Report, t fourTuple, extraFromNode, extraToNode map[string]string) { +func (r *Reporter) addConnection(rpt *report.Report, t fourTuple, namespaceID string, extraFromNode, extraToNode map[string]string) { var ( - fromEndpointNodeID = report.MakeEndpointNodeID(r.hostID, t.fromAddr, strconv.Itoa(int(t.fromPort))) - toEndpointNodeID = report.MakeEndpointNodeID(r.hostID, t.toAddr, strconv.Itoa(int(t.toPort))) + fromEndpointNodeID = report.MakeEndpointNodeID(r.hostID, namespaceID, t.fromAddr, strconv.Itoa(int(t.fromPort))) + toEndpointNodeID = report.MakeEndpointNodeID(r.hostID, namespaceID, t.toAddr, strconv.Itoa(int(t.toPort))) fromNode = report.MakeNodeWith(fromEndpointNodeID, map[string]string{ Addr: t.fromAddr, diff --git a/probe/endpoint/reporter_test.go b/probe/endpoint/reporter_test.go index efd4a33ef..cbbdcc15a 100644 --- a/probe/endpoint/reporter_test.go +++ b/probe/endpoint/reporter_test.go @@ -91,8 +91,8 @@ func TestSpyWithProcesses(t *testing.T) { // buf, _ := json.MarshalIndent(r, "", " ") ; t.Logf("\n%s\n", buf) var ( - scopedLocal = report.MakeEndpointNodeID(nodeID, fixLocalAddress.String(), strconv.Itoa(int(fixLocalPort))) - scopedRemote = report.MakeEndpointNodeID(nodeID, fixRemoteAddress.String(), strconv.Itoa(int(fixRemotePort))) + scopedLocal = report.MakeEndpointNodeID(nodeID, "", fixLocalAddress.String(), strconv.Itoa(int(fixLocalPort))) + scopedRemote = report.MakeEndpointNodeID(nodeID, "", fixRemoteAddress.String(), strconv.Itoa(int(fixRemotePort))) ) if want, have := 1, len(r.Endpoint.Nodes[scopedRemote].Adjacency); want != have { diff --git a/probe/host/tagger_test.go b/probe/host/tagger_test.go index 6001815e4..c198c5111 100644 --- a/probe/host/tagger_test.go +++ b/probe/host/tagger_test.go @@ -10,7 +10,7 @@ import ( func TestTagger(t *testing.T) { var ( hostID = "foo" - endpointNodeID = report.MakeEndpointNodeID(hostID, "1.2.3.4", "56789") // hostID ignored + endpointNodeID = report.MakeEndpointNodeID(hostID, "", "1.2.3.4", "56789") // hostID ignored node = report.MakeNodeWith(endpointNodeID, map[string]string{"foo": "bar"}) ) diff --git a/render/short_lived_connections_test.go b/render/short_lived_connections_test.go index 49d864ae9..a2f4d9c25 100644 --- a/render/short_lived_connections_test.go +++ b/render/short_lived_connections_test.go @@ -19,11 +19,11 @@ var ( randomIP = "3.4.5.6" randomPort = "56789" - randomEndpointNodeID = report.MakeEndpointNodeID(serverHostID, randomIP, randomPort) + randomEndpointNodeID = report.MakeEndpointNodeID(serverHostID, "", randomIP, randomPort) serverIP = "192.168.1.1" serverPort = "80" - serverEndpointNodeID = report.MakeEndpointNodeID(serverHostID, serverIP, serverPort) + serverEndpointNodeID = report.MakeEndpointNodeID(serverHostID, "", serverIP, serverPort) container1ID = "11b2c3d4e5" container1IP = "192.168.0.1" @@ -31,11 +31,11 @@ var ( container1NodeID = report.MakeContainerNodeID(container1ID) container1Port = "16782" - container1EndpointNodeID = report.MakeEndpointNodeID(serverHostID, container1IP, container1Port) + container1EndpointNodeID = report.MakeEndpointNodeID(serverHostID, "", container1IP, container1Port) duplicatedIP = "192.168.0.2" duplicatedPort = "80" - duplicatedEndpointNodeID = report.MakeEndpointNodeID(serverHostID, duplicatedIP, duplicatedPort) + duplicatedEndpointNodeID = report.MakeEndpointNodeID(serverHostID, "", duplicatedIP, duplicatedPort) container2ID = "21b2c3d4e5" container2IP = duplicatedIP diff --git a/report/id.go b/report/id.go index 908376f48..918377176 100644 --- a/report/id.go +++ b/report/id.go @@ -25,36 +25,45 @@ const ( ) // MakeEndpointNodeID produces an endpoint node ID from its composite parts. -func MakeEndpointNodeID(hostID, address, port string) string { - return MakeAddressNodeID(hostID, address) + ScopeDelim + port +func MakeEndpointNodeID(hostID, namespaceID, address, port string) string { + return makeAddressID(hostID, namespaceID, address) + ScopeDelim + port } // MakeAddressNodeID produces an address node ID from its composite parts. func MakeAddressNodeID(hostID, address string) string { + return makeAddressID(hostID, "", address) +} + +func makeAddressID(hostID, namespaceID, address string) string { var scope string - // Loopback addresses and addresses explicitly marked as - // local get scoped by hostID + // Loopback addresses and addresses explicitly marked as local get + // scoped by hostID + // Loopback addresses are also scoped by the networking + // namespace if available, since they can clash. addressIP := net.ParseIP(address) if addressIP != nil && LocalNetworks.Contains(addressIP) { scope = hostID } else if isLoopback(address) { scope = hostID + if namespaceID != "" { + scope += "-" + namespaceID + } } return scope + ScopeDelim + address } // MakeScopedEndpointNodeID is like MakeEndpointNodeID, but it always -// prefixes the ID witha scope. -func MakeScopedEndpointNodeID(hostID, address, port string) string { - return hostID + ScopeDelim + address + ScopeDelim + port +// prefixes the ID with a scope. +func MakeScopedEndpointNodeID(scope, address, port string) string { + return scope + ScopeDelim + address + ScopeDelim + port } // MakeScopedAddressNodeID is like MakeAddressNodeID, but it always // prefixes the ID witha scope. -func MakeScopedAddressNodeID(hostID, address string) string { - return hostID + ScopeDelim + address +func MakeScopedAddressNodeID(scope, address string) string { + return scope + ScopeDelim + address } // MakeProcessNodeID produces a process node ID from its composite parts. @@ -140,14 +149,14 @@ func ParseNodeID(nodeID string) (hostID string, remainder string, ok bool) { return fields[0], fields[1], true } -// ParseEndpointNodeID produces the host ID, address, and port and remainder -// (typically an address) from an endpoint node ID. Note that hostID may be -// blank. -func ParseEndpointNodeID(endpointNodeID string) (hostID, address, port string, ok bool) { +// ParseEndpointNodeID produces the scope, address, and port and remainder. +// Note that hostID may be blank. +func ParseEndpointNodeID(endpointNodeID string) (scope, address, port string, ok bool) { fields := strings.SplitN(endpointNodeID, ScopeDelim, 3) if len(fields) != 3 { return "", "", "", false } + return fields[0], fields[1], fields[2], true } diff --git a/report/id_test.go b/report/id_test.go index 31e8d71a2..7854ae273 100644 --- a/report/id_test.go +++ b/report/id_test.go @@ -18,12 +18,12 @@ var ( unknownHostID = "" // by definition, we don't know it unknownAddress = "172.16.93.112" // will be a pseudonode, no corresponding host - client54001EndpointNodeID = report.MakeEndpointNodeID(clientHostID, clientAddress, "54001") // i.e. curl - client54002EndpointNodeID = report.MakeEndpointNodeID(clientHostID, clientAddress, "54002") // also curl - server80EndpointNodeID = report.MakeEndpointNodeID(serverHostID, serverAddress, "80") // i.e. apache - unknown1EndpointNodeID = report.MakeEndpointNodeID(unknownHostID, unknownAddress, "10001") - unknown2EndpointNodeID = report.MakeEndpointNodeID(unknownHostID, unknownAddress, "10002") - unknown3EndpointNodeID = report.MakeEndpointNodeID(unknownHostID, unknownAddress, "10003") + client54001EndpointNodeID = report.MakeEndpointNodeID(clientHostID, "", clientAddress, "54001") // i.e. curl + client54002EndpointNodeID = report.MakeEndpointNodeID(clientHostID, "", clientAddress, "54002") // also curl + server80EndpointNodeID = report.MakeEndpointNodeID(serverHostID, "", serverAddress, "80") // i.e. apache + unknown1EndpointNodeID = report.MakeEndpointNodeID(unknownHostID, "", unknownAddress, "10001") + unknown2EndpointNodeID = report.MakeEndpointNodeID(unknownHostID, "", unknownAddress, "10002") + unknown3EndpointNodeID = report.MakeEndpointNodeID(unknownHostID, "", unknownAddress, "10003") clientAddressNodeID = report.MakeAddressNodeID(clientHostID, clientAddress) serverAddressNodeID = report.MakeAddressNodeID(serverHostID, serverAddress) @@ -50,7 +50,8 @@ func TestEndpointNodeID(t *testing.T) { } for input, want := range map[string]struct{ name, address, port string }{ - report.MakeEndpointNodeID("host.com", "1.2.3.4", "c"): {"", "1.2.3.4", "c"}, + report.MakeEndpointNodeID("host.com", "namespaceid", "127.0.0.1", "c"): {"host.com-namespaceid", "127.0.0.1", "c"}, + report.MakeEndpointNodeID("host.com", "", "1.2.3.4", "c"): {"", "1.2.3.4", "c"}, "a;b;c": {"a", "b", "c"}, } { haveName, haveAddress, havePort, ok := report.ParseEndpointNodeID(input) diff --git a/test/fixture/report_fixture.go b/test/fixture/report_fixture.go index 9296d0acc..d94a5f0ba 100644 --- a/test/fixture/report_fixture.go +++ b/test/fixture/report_fixture.go @@ -57,15 +57,15 @@ var ( ClientHostNodeID = report.MakeHostNodeID(ClientHostID) ServerHostNodeID = report.MakeHostNodeID(ServerHostID) - Client54001NodeID = report.MakeEndpointNodeID(ClientHostID, ClientIP, ClientPort54001) // curl (1) - Client54002NodeID = report.MakeEndpointNodeID(ClientHostID, ClientIP, ClientPort54002) // curl (2) - Server80NodeID = report.MakeEndpointNodeID(ServerHostID, ServerIP, ServerPort) // apache - UnknownClient1NodeID = report.MakeEndpointNodeID(ServerHostID, UnknownClient1IP, UnknownClient1Port) // we want to ensure two unknown clients, connnected - UnknownClient2NodeID = report.MakeEndpointNodeID(ServerHostID, UnknownClient2IP, UnknownClient2Port) // to the same server, are deduped. - UnknownClient3NodeID = report.MakeEndpointNodeID(ServerHostID, UnknownClient3IP, UnknownClient3Port) // Check this one isn't deduped - RandomClientNodeID = report.MakeEndpointNodeID(ServerHostID, RandomClientIP, RandomClientPort) // this should become an internet node - NonContainerNodeID = report.MakeEndpointNodeID(ServerHostID, ServerIP, NonContainerClientPort) - GoogleEndpointNodeID = report.MakeEndpointNodeID(ServerHostID, GoogleIP, GooglePort) + Client54001NodeID = report.MakeEndpointNodeID(ClientHostID, "", ClientIP, ClientPort54001) // curl (1) + Client54002NodeID = report.MakeEndpointNodeID(ClientHostID, "", ClientIP, ClientPort54002) // curl (2) + Server80NodeID = report.MakeEndpointNodeID(ServerHostID, "", ServerIP, ServerPort) // apache + UnknownClient1NodeID = report.MakeEndpointNodeID(ServerHostID, "", UnknownClient1IP, UnknownClient1Port) // we want to ensure two unknown clients, connnected + UnknownClient2NodeID = report.MakeEndpointNodeID(ServerHostID, "", UnknownClient2IP, UnknownClient2Port) // to the same server, are deduped. + UnknownClient3NodeID = report.MakeEndpointNodeID(ServerHostID, "", UnknownClient3IP, UnknownClient3Port) // Check this one isn't deduped + RandomClientNodeID = report.MakeEndpointNodeID(ServerHostID, "", RandomClientIP, RandomClientPort) // this should become an internet node + NonContainerNodeID = report.MakeEndpointNodeID(ServerHostID, "", ServerIP, NonContainerClientPort) + GoogleEndpointNodeID = report.MakeEndpointNodeID(ServerHostID, "", GoogleIP, GooglePort) ClientProcess1NodeID = report.MakeProcessNodeID(ClientHostID, Client1PID) ClientProcess2NodeID = report.MakeProcessNodeID(ClientHostID, Client2PID) From 94eb7454ff95b0dbf01dc5a233c6284747535a10 Mon Sep 17 00:00:00 2001 From: Alfonso Acosta Date: Wed, 10 Aug 2016 10:56:37 +0000 Subject: [PATCH 3/3] Do not attribute connections to containers based on loopback addresses --- render/container.go | 5 +++++ report/id.go | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/render/container.go b/render/container.go index 6b48a758e..412957559 100644 --- a/render/container.go +++ b/render/container.go @@ -264,6 +264,11 @@ func MapContainer2IP(m report.Node) []string { if !ok { continue } + // loopback addresses are shared among all namespaces + // so we can't use them to attribute connections to a container + if report.IsLoopback(addr) { + continue + } id := report.MakeScopedEndpointNodeID(scope, addr, "") result = append(result, id) } diff --git a/report/id.go b/report/id.go index 918377176..dd1c3720f 100644 --- a/report/id.go +++ b/report/id.go @@ -44,7 +44,7 @@ func makeAddressID(hostID, namespaceID, address string) string { addressIP := net.ParseIP(address) if addressIP != nil && LocalNetworks.Contains(addressIP) { scope = hostID - } else if isLoopback(address) { + } else if IsLoopback(address) { scope = hostID if namespaceID != "" { scope += "-" + namespaceID @@ -176,7 +176,8 @@ func ExtractHostID(m Node) string { return hostID } -func isLoopback(address string) bool { +// IsLoopback ascertains if an address comes from a loopback interface. +func IsLoopback(address string) bool { ip := net.ParseIP(address) return ip != nil && ip.IsLoopback() }