func TestEndpointNodeID(t *testing.T) { for _, bad := range []string{ clientAddressNodeID, serverAddressNodeID, unknownAddressNodeID, clientHostNodeID, serverHostNodeID, "host.com;1.2.3.4", "a;b", "a;", ";b", ";", "", } { if haveName, haveAddress, havePort, ok := report.ParseEndpointNodeID(bad); ok { t.Errorf("%q: expected failure, but got {%q, %q, %q}", bad, haveName, haveAddress, havePort) } } 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"}, "a;b;c": {"a", "b", "c"}, } { haveName, haveAddress, havePort, ok := report.ParseEndpointNodeID(input) if !ok { t.Errorf("%q: not OK", input) continue } if want.name != haveName || want.address != haveAddress || want.port != havePort { t.Errorf("%q: want %q, have {%q, %q, %q}", input, want, haveName, haveAddress, havePort) } } }
// MapEndpoint2IP maps endpoint nodes to their IP address, for joining // with container nodes. We drop endpoint nodes with pids, as they // will be joined to containers through the process topology, and we // don't want to double count edges. func MapEndpoint2IP(m RenderableNode, local report.Networks) RenderableNodes { // Don't include procspied connections, to prevent double counting _, ok := m.Latest.Lookup(endpoint.Procspied) if ok { return RenderableNodes{} } scope, addr, port, ok := report.ParseEndpointNodeID(m.ID) if !ok { return RenderableNodes{} } if ip := net.ParseIP(addr); ip != nil && !local.Contains(ip) { return RenderableNodes{TheInternetID: newDerivedPseudoNode(TheInternetID, TheInternetMajor, m)} } // We don't always know what port a container is listening on, and // container-to-container communications can be unambiguously identified // without ports. OTOH, connections to the host IPs which have been port // mapped to a container can only be unambiguously identified with the port. // So we need to emit two nodes, for two different cases. id := report.MakeScopedEndpointNodeID(scope, addr, "") idWithPort := report.MakeScopedEndpointNodeID(scope, addr, port) m = m.WithParents(report.EmptySets) return RenderableNodes{ id: NewRenderableNodeWith(id, "", "", "", m), idWithPort: NewRenderableNodeWith(idWithPort, "", "", "", m), } }
func connectionDetailsRows(topology report.Topology, originID string) []Row { rows := []Row{} labeler := func(nodeID string, meta map[string]string) (string, bool) { if _, addr, port, ok := report.ParseEndpointNodeID(nodeID); ok { if name, ok := meta["name"]; ok { return fmt.Sprintf("%s:%s", name, port), true } return fmt.Sprintf("%s:%s", addr, port), true } if _, addr, ok := report.ParseAddressNodeID(nodeID); ok { return addr, true } return "", false } local, ok := labeler(originID, topology.Nodes[originID].Metadata) if !ok { return rows } // Firstly, collection outgoing connections from this node. for _, serverNodeID := range topology.Nodes[originID].Adjacency { remote, ok := labeler(serverNodeID, topology.Nodes[serverNodeID].Metadata) if !ok { continue } rows = append(rows, Row{ Key: local, ValueMajor: remote, Expandable: true, }) } // Next, scan the topology for incoming connections to this node. for clientNodeID, clientNode := range topology.Nodes { if clientNodeID == originID { continue } serverNodeIDs := clientNode.Adjacency if !serverNodeIDs.Contains(originID) { continue } remote, ok := labeler(clientNodeID, clientNode.Metadata) if !ok { continue } rows = append(rows, Row{ Key: remote, ValueMajor: local, ValueMinor: "", Expandable: true, }) } return rows }
func connectionDetailsRows(topology report.Topology, originID string) []Row { rows := []Row{} labeler := func(nodeID string) (string, bool) { if _, addr, port, ok := report.ParseEndpointNodeID(nodeID); ok { return fmt.Sprintf("%s:%s", addr, port), true } if _, addr, ok := report.ParseAddressNodeID(nodeID); ok { return addr, true } return "", false } local, ok := labeler(originID) if !ok { return rows } // Firstly, collection outgoing connections from this node. originAdjID := report.MakeAdjacencyID(originID) for _, serverNodeID := range topology.Adjacency[originAdjID] { remote, ok := labeler(serverNodeID) if !ok { continue } rows = append(rows, Row{ Key: local, ValueMajor: remote, Expandable: true, }) } // Next, scan the topology for incoming connections to this node. for clientAdjID, serverNodeIDs := range topology.Adjacency { if clientAdjID == originAdjID { continue } if !serverNodeIDs.Contains(originID) { continue } clientNodeID, ok := report.ParseAdjacencyID(clientAdjID) if !ok { continue } remote, ok := labeler(clientNodeID) if !ok { continue } rows = append(rows, Row{ Key: remote, ValueMajor: local, Expandable: true, }) } return rows }
func connectionDetailsRows(endpointTopology report.Topology, originID string, nmd report.NodeMetadata) []Row { rows := []Row{} local := fmt.Sprintf("%s:%s", nmd.Metadata[endpoint.Addr], nmd.Metadata[endpoint.Port]) adjacencies := endpointTopology.Adjacency[report.MakeAdjacencyID(originID)] sort.Strings(adjacencies) for _, adj := range adjacencies { if _, address, port, ok := report.ParseEndpointNodeID(adj); ok { rows = append(rows, Row{ Key: local, ValueMajor: fmt.Sprintf("%s:%s", address, port), }) } } return rows }
// ShortLivedConnectionJoin joins the given renderer with short lived connections // from the endpoints topology, using the toIPs function to extract IPs from // the nodes. func ShortLivedConnectionJoin(r Renderer, toIPs func(report.Node) []string) Renderer { nodeToIP := func(n report.Node, _ report.Networks) report.Nodes { result := report.Nodes{} for _, ip := range toIPs(n) { result[ip] = NewDerivedNode(ip, n). WithTopology(IP). WithLatests(map[string]string{ originalNodeID: n.ID, originalNodeTopology: n.Topology, }). WithCounters(map[string]int{IP: 1}) } return result } ipToNode := func(n report.Node, _ report.Networks) report.Nodes { // If an IP is shared between multiple nodes, we can't // reliably attribute an connection based on its IP if count, _ := n.Counters.Lookup(IP); count > 1 { return report.Nodes{} } // Propagate the internet pseudo node if strings.HasSuffix(n.ID, TheInternetID) { return report.Nodes{n.ID: n} } // If this node is not of the original type, exclude it. // This excludes all the nodes we've dragged in from endpoint // that we failed to join to a node. id, ok := n.Latest.Lookup(originalNodeID) if !ok { return report.Nodes{} } topology, ok := n.Latest.Lookup(originalNodeTopology) if !ok { return report.Nodes{} } return report.Nodes{ id: NewDerivedNode(id, n). WithTopology(topology), } } // MapEndpoint2IP maps endpoint nodes to their IP address, for joining // with container nodes. We drop endpoint nodes with pids, as they // will be joined to containers through the process topology, and we // don't want to double count edges. endpoint2IP := func(m report.Node, local report.Networks) report.Nodes { // Don't include procspied connections, to prevent double counting _, ok := m.Latest.Lookup(endpoint.Procspied) if ok { return report.Nodes{} } scope, addr, port, ok := report.ParseEndpointNodeID(m.ID) if !ok { return report.Nodes{} } if ip := net.ParseIP(addr); ip != nil && !local.Contains(ip) { node := theInternetNode(m) return report.Nodes{node.ID: node} } // We also allow for joining on ip:port pairs. This is useful // for connections to the host IPs which have been port // mapped to a container can only be unambiguously identified with the port. // So we need to emit two nodes, for two different cases. id := report.MakeScopedEndpointNodeID(scope, addr, "") idWithPort := report.MakeScopedEndpointNodeID(scope, addr, port) return report.Nodes{ id: NewDerivedNode(id, m).WithTopology(IP), idWithPort: NewDerivedNode(idWithPort, m).WithTopology(IP), } } return FilterUnconnected(MakeMap( ipToNode, MakeReduce( MakeMap( nodeToIP, r, ), MakeMap( endpoint2IP, SelectEndpoint, ), ), )) }