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)