diff --git a/probe/endpoint/connection_tracker.go b/probe/endpoint/connection_tracker.go index db9764b4e..24b6fc12f 100644 --- a/probe/endpoint/connection_tracker.go +++ b/probe/endpoint/connection_tracker.go @@ -3,6 +3,7 @@ package endpoint import ( + "net" "strconv" "time" @@ -43,22 +44,11 @@ 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), + if f.Orig.Dst.Equal(f.Reply.Src) { + return makeFourTuple(f.Orig.Src, f.Orig.Dst, uint16(f.Orig.SrcPort), uint16(f.Orig.DstPort)) } // Handle DNAT-ed connections in the initial state - if !f.Orig.Dst.Equal(f.Reply.Src) { - ft = fourTuple{ - f.Reply.Dst.String(), - f.Reply.Src.String(), - uint16(f.Reply.DstPort), - uint16(f.Reply.SrcPort), - } - } - return ft + return makeFourTuple(f.Orig.Dst, f.Orig.Src, uint16(f.Orig.DstPort), uint16(f.Orig.SrcPort)) } func (t *connectionTracker) useProcfs() { @@ -110,7 +100,7 @@ func (t *connectionTracker) ReportConnections(rpt *report.Report) { t.flowWalker.walkFlows(func(f conntrack.Conn, alive bool) { tuple := flowToTuple(f) seenTuples[tuple.key()] = tuple - t.addConnection(rpt, false, tuple, "", nil, nil) + t.addConnection(rpt, false, tuple, 0, nil, nil) }) if t.conf.WalkProc && t.conf.Scanner != nil { @@ -200,24 +190,25 @@ func (t *connectionTracker) performEbpfTrack(rpt *report.Report, hostNodeID stri return nil } -func (t *connectionTracker) addConnection(rpt *report.Report, incoming bool, ft fourTuple, namespaceID string, extraFromNode, extraToNode map[string]string) { +func (t *connectionTracker) addConnection(rpt *report.Report, incoming bool, ft fourTuple, namespaceID uint64, extraFromNode, extraToNode map[string]string) { if incoming { ft = reverse(ft) extraFromNode, extraToNode = extraToNode, extraFromNode } var ( - fromNode = t.makeEndpointNode(namespaceID, ft.fromAddr, ft.fromPort, extraFromNode) - toNode = t.makeEndpointNode(namespaceID, ft.toAddr, ft.toPort, extraToNode) + fromAddr = net.IP(ft.fromAddr[:]) + fromNode = t.makeEndpointNode(namespaceID, fromAddr, ft.fromPort, extraFromNode) + toAddr = net.IP(ft.toAddr[:]) + toNode = t.makeEndpointNode(namespaceID, toAddr, ft.toPort, extraToNode) ) rpt.Endpoint.AddNode(fromNode.WithAdjacent(toNode.ID)) rpt.Endpoint.AddNode(toNode) - t.addDNS(rpt, ft.fromAddr) - t.addDNS(rpt, ft.toAddr) + t.addDNS(rpt, fromAddr.String()) + t.addDNS(rpt, toAddr.String()) } -func (t *connectionTracker) makeEndpointNode(namespaceID string, addr string, port uint16, extra map[string]string) report.Node { - portStr := strconv.Itoa(int(port)) - node := report.MakeNodeWith(report.MakeEndpointNodeID(t.conf.HostID, namespaceID, addr, portStr), nil) +func (t *connectionTracker) makeEndpointNode(namespaceID uint64, addr net.IP, port uint16, extra map[string]string) report.Node { + node := report.MakeNodeWith(report.MakeEndpointNodeIDB(t.conf.HostID, namespaceID, addr, port), nil) if extra != nil { node = node.WithLatests(extra) } @@ -249,22 +240,14 @@ func (t *connectionTracker) Stop() error { return nil } -func connectionTuple(conn *procspy.Connection, seenTuples map[string]fourTuple) (fourTuple, string, bool) { - namespaceID := "" - tuple := fourTuple{ - conn.LocalAddress.String(), - conn.RemoteAddress.String(), - conn.LocalPort, - conn.RemotePort, - } - if conn.Proc.NetNamespaceID > 0 { - namespaceID = strconv.FormatUint(conn.Proc.NetNamespaceID, 10) - } +func connectionTuple(conn *procspy.Connection, seenTuples map[string]fourTuple) (fourTuple, uint64, bool) { + tuple := makeFourTuple(conn.LocalAddress, conn.RemoteAddress, conn.LocalPort, conn.RemotePort) // 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 // the direction. canonical, ok := seenTuples[tuple.key()] - return tuple, namespaceID, (ok && canonical != tuple) || (!ok && tuple.fromPort < tuple.toPort) + incoming := (ok && canonical != tuple) || (!ok && tuple.fromPort < tuple.toPort) + return tuple, conn.Proc.NetNamespaceID, incoming } diff --git a/probe/endpoint/ebpf.go b/probe/endpoint/ebpf.go index f278056d1..7afab8d69 100644 --- a/probe/endpoint/ebpf.go +++ b/probe/endpoint/ebpf.go @@ -25,7 +25,7 @@ import ( // An ebpfConnection represents a TCP connection type ebpfConnection struct { tuple fourTuple - networkNamespace string + networkNamespace uint64 incoming bool pid int } @@ -172,8 +172,8 @@ func (t *EbpfTracker) TCPEventV4(e tracer.TcpV4) { if e.Type == tracer.EventFdInstall { t.handleFdInstall(e.Type, int(e.Pid), int(e.Fd)) } else { - tuple := fourTuple{e.SAddr.String(), e.DAddr.String(), e.SPort, e.DPort} - t.handleConnection(e.Type, tuple, int(e.Pid), strconv.Itoa(int(e.NetNS))) + tuple := makeFourTuple(e.SAddr, e.DAddr, e.SPort, e.DPort) + t.handleConnection(e.Type, tuple, int(e.Pid), uint64(e.NetNS)) } } @@ -198,7 +198,7 @@ func (t *EbpfTracker) LostV6(count uint64) { // TODO: IPv6 not supported in Scope } -func tupleFromPidFd(pid int, fd int) (tuple fourTuple, netns string, ok bool) { +func tupleFromPidFd(pid int, fd int) (tuple fourTuple, netns uint64, ok bool) { // read /proc/$pid/ns/net // // probe/endpoint/procspy/proc_linux.go supports Linux < 3.8 but we @@ -206,21 +206,20 @@ func tupleFromPidFd(pid int, fd int) (tuple fourTuple, netns string, ok bool) { netnsIno, err := procspy.ReadNetnsFromPID(pid) if err != nil { log.Debugf("netns proc file for pid %d disappeared before we could read it: %v", pid, err) - return fourTuple{}, "", false + return fourTuple{}, 0, false } - netns = fmt.Sprintf("%d", netnsIno) // find /proc/$pid/fd/$fd's ino fdFilename := fmt.Sprintf("/proc/%d/fd/%d", pid, fd) var statFdFile syscall.Stat_t if err := fs.Stat(fdFilename, &statFdFile); err != nil { log.Debugf("proc file %q disappeared before we could read it", fdFilename) - return fourTuple{}, "", false + return fourTuple{}, 0, false } if statFdFile.Mode&syscall.S_IFMT != syscall.S_IFSOCK { log.Errorf("file %q is not a socket", fdFilename) - return fourTuple{}, "", false + return fourTuple{}, 0, false } ino := statFdFile.Ino @@ -228,7 +227,7 @@ func tupleFromPidFd(pid int, fd int) (tuple fourTuple, netns string, ok bool) { buf := bytes.NewBuffer(make([]byte, 0, 5000)) if _, err := procspy.ReadTCPFiles(pid, buf); err != nil { log.Debugf("TCP proc file for pid %d disappeared before we could read it: %v", pid, err) - return fourTuple{}, "", false + return fourTuple{}, 0, false } // find /proc/$pid/fd/$fd's ino in /proc/pid/net/tcp @@ -240,11 +239,12 @@ func tupleFromPidFd(pid int, fd int) (tuple fourTuple, netns string, ok bool) { break } if n.Inode == ino { - return fourTuple{n.LocalAddress.String(), n.RemoteAddress.String(), n.LocalPort, n.RemotePort}, netns, true + tuple := makeFourTuple(n.LocalAddress, n.RemoteAddress, n.LocalPort, n.RemotePort) + return tuple, netnsIno, true } } - return fourTuple{}, "", false + return fourTuple{}, 0, false } func (t *EbpfTracker) handleFdInstall(ev tracer.EventType, pid int, fd int) { @@ -252,7 +252,7 @@ func (t *EbpfTracker) handleFdInstall(ev tracer.EventType, pid int, fd int) { t.tracer.RemoveFdInstallWatcher(uint32(pid)) } tuple, netns, ok := tupleFromPidFd(pid, fd) - log.Debugf("EbpfTracker: got fd-install event: pid=%d fd=%d -> tuple=%s netns=%s ok=%v", pid, fd, tuple, netns, ok) + log.Debugf("EbpfTracker: got fd-install event: pid=%d fd=%d -> tuple=%s netns=%v ok=%v", pid, fd, tuple, netns, ok) if !ok { return } @@ -268,7 +268,7 @@ func (t *EbpfTracker) handleFdInstall(ev tracer.EventType, pid int, fd int) { } } -func (t *EbpfTracker) handleConnection(ev tracer.EventType, tuple fourTuple, pid int, networkNamespace string) { +func (t *EbpfTracker) handleConnection(ev tracer.EventType, tuple fourTuple, pid int, networkNamespace uint64) { t.Lock() defer t.Unlock() @@ -298,7 +298,7 @@ func (t *EbpfTracker) handleConnection(ev tracer.EventType, tuple fourTuple, pid delete(t.openConnections, tuple) t.closedConnections = append(t.closedConnections, deadConn) } else { - log.Debugf("EbpfTracker: unmatched close event: %s pid=%d netns=%s", tuple, pid, networkNamespace) + log.Debugf("EbpfTracker: unmatched close event: %s pid=%d netns=%v", tuple, pid, networkNamespace) } default: log.Debugf("EbpfTracker: unknown event: %s (%d)", ev, ev) diff --git a/probe/endpoint/ebpf_test.go b/probe/endpoint/ebpf_test.go index cbca96799..b7956b907 100644 --- a/probe/endpoint/ebpf_test.go +++ b/probe/endpoint/ebpf_test.go @@ -5,7 +5,6 @@ package endpoint import ( "net" "reflect" - "strconv" "testing" "time" @@ -27,8 +26,10 @@ func TestHandleConnection(t *testing.T) { var ( ServerPid uint32 = 42 ClientPid uint32 = 43 - ServerIP = net.IP("127.0.0.1") - ClientIP = net.IP("127.0.0.2") + ServerAddr = [net.IPv4len]byte{127, 0, 0, 1} + ServerIP = net.IP(ServerAddr[:]) + ClientAddr = [net.IPv4len]byte{127, 0, 0, 2} + ClientIP = net.IP(ClientAddr[:]) ServerPort uint16 = 12345 ClientPort uint16 = 6789 NetNS uint32 = 123456789 @@ -47,12 +48,12 @@ func TestHandleConnection(t *testing.T) { IPv4ConnectEbpfConnection = ebpfConnection{ tuple: fourTuple{ - fromAddr: ClientIP.String(), - toAddr: ServerIP.String(), + fromAddr: ClientAddr, + toAddr: ServerAddr, fromPort: ClientPort, toPort: ServerPort, }, - networkNamespace: strconv.Itoa(int(NetNS)), + networkNamespace: uint64(NetNS), incoming: false, pid: int(ClientPid), } @@ -83,12 +84,12 @@ func TestHandleConnection(t *testing.T) { IPv4AcceptEbpfConnection = ebpfConnection{ tuple: fourTuple{ - fromAddr: ServerIP.String(), - toAddr: ClientIP.String(), + fromAddr: ServerAddr, + toAddr: ClientAddr, fromPort: ServerPort, toPort: ClientPort, }, - networkNamespace: strconv.Itoa(int(NetNS)), + networkNamespace: uint64(NetNS), incoming: true, pid: int(ServerPid), } @@ -108,15 +109,15 @@ func TestHandleConnection(t *testing.T) { mockEbpfTracker := newMockEbpfTracker() - tuple := fourTuple{IPv4ConnectEvent.SAddr.String(), IPv4ConnectEvent.DAddr.String(), uint16(IPv4ConnectEvent.SPort), uint16(IPv4ConnectEvent.DPort)} - mockEbpfTracker.handleConnection(IPv4ConnectEvent.Type, tuple, int(IPv4ConnectEvent.Pid), strconv.FormatUint(uint64(IPv4ConnectEvent.NetNS), 10)) + tuple := fourTuple{ClientAddr, ServerAddr, uint16(IPv4ConnectEvent.SPort), uint16(IPv4ConnectEvent.DPort)} + mockEbpfTracker.handleConnection(IPv4ConnectEvent.Type, tuple, int(IPv4ConnectEvent.Pid), uint64(NetNS)) if !reflect.DeepEqual(mockEbpfTracker.openConnections[tuple], IPv4ConnectEbpfConnection) { t.Errorf("Connection mismatch connect event\nTarget connection:%v\nParsed connection:%v", IPv4ConnectEbpfConnection, mockEbpfTracker.openConnections[tuple]) } - tuple = fourTuple{IPv4ConnectCloseEvent.SAddr.String(), IPv4ConnectCloseEvent.DAddr.String(), uint16(IPv4ConnectCloseEvent.SPort), uint16(IPv4ConnectCloseEvent.DPort)} - mockEbpfTracker.handleConnection(IPv4ConnectCloseEvent.Type, tuple, int(IPv4ConnectCloseEvent.Pid), strconv.FormatUint(uint64(IPv4ConnectCloseEvent.NetNS), 10)) + tuple = fourTuple{ClientAddr, ServerAddr, uint16(IPv4ConnectCloseEvent.SPort), uint16(IPv4ConnectCloseEvent.DPort)} + mockEbpfTracker.handleConnection(IPv4ConnectCloseEvent.Type, tuple, int(IPv4ConnectCloseEvent.Pid), uint64(NetNS)) if len(mockEbpfTracker.openConnections) != 0 { t.Errorf("Connection mismatch close event\nConnection to close:%v", mockEbpfTracker.openConnections[tuple]) @@ -124,15 +125,15 @@ func TestHandleConnection(t *testing.T) { mockEbpfTracker = newMockEbpfTracker() - tuple = fourTuple{IPv4AcceptEvent.SAddr.String(), IPv4AcceptEvent.DAddr.String(), uint16(IPv4AcceptEvent.SPort), uint16(IPv4AcceptEvent.DPort)} - mockEbpfTracker.handleConnection(IPv4AcceptEvent.Type, tuple, int(IPv4AcceptEvent.Pid), strconv.FormatUint(uint64(IPv4AcceptEvent.NetNS), 10)) + tuple = fourTuple{ServerAddr, ClientAddr, uint16(IPv4AcceptEvent.SPort), uint16(IPv4AcceptEvent.DPort)} + mockEbpfTracker.handleConnection(IPv4AcceptEvent.Type, tuple, int(IPv4AcceptEvent.Pid), uint64(NetNS)) if !reflect.DeepEqual(mockEbpfTracker.openConnections[tuple], IPv4AcceptEbpfConnection) { t.Errorf("Connection mismatch connect event\nTarget connection:%v\nParsed connection:%v", IPv4AcceptEbpfConnection, mockEbpfTracker.openConnections[tuple]) } - tuple = fourTuple{IPv4AcceptCloseEvent.SAddr.String(), IPv4AcceptCloseEvent.DAddr.String(), uint16(IPv4AcceptCloseEvent.SPort), uint16(IPv4AcceptCloseEvent.DPort)} - mockEbpfTracker.handleConnection(IPv4AcceptCloseEvent.Type, tuple, int(IPv4AcceptCloseEvent.Pid), strconv.FormatUint(uint64(IPv4AcceptCloseEvent.NetNS), 10)) + tuple = fourTuple{ServerAddr, ClientAddr, uint16(IPv4AcceptCloseEvent.SPort), uint16(IPv4AcceptCloseEvent.DPort)} + mockEbpfTracker.handleConnection(IPv4AcceptCloseEvent.Type, tuple, int(IPv4AcceptCloseEvent.Pid), uint64(NetNS)) if len(mockEbpfTracker.openConnections) != 0 { t.Errorf("Connection mismatch close event\nConnection to close:%v", @@ -142,32 +143,21 @@ func TestHandleConnection(t *testing.T) { func TestWalkConnections(t *testing.T) { var ( - cnt int - activeTuple = fourTuple{ - fromAddr: "", - toAddr: "", - fromPort: 0, - toPort: 0, - } - - inactiveTuple = fourTuple{ - fromAddr: "", - toAddr: "", - fromPort: 0, - toPort: 0, - } + cnt int + activeTuple = fourTuple{} + inactiveTuple = fourTuple{} ) mockEbpfTracker := newMockEbpfTracker() mockEbpfTracker.openConnections[activeTuple] = ebpfConnection{ tuple: activeTuple, - networkNamespace: "12345", + networkNamespace: 12345, incoming: true, pid: 0, } mockEbpfTracker.closedConnections = append(mockEbpfTracker.closedConnections, ebpfConnection{ tuple: inactiveTuple, - networkNamespace: "12345", + networkNamespace: 12345, incoming: false, pid: 0, }) @@ -183,8 +173,8 @@ func TestInvalidTimeStampDead(t *testing.T) { var ( cnt int ClientPid uint32 = 43 - ServerIP = net.IP("127.0.0.1") - ClientIP = net.IP("127.0.0.2") + ServerIP = net.ParseIP("127.0.0.1") + ClientIP = net.ParseIP("127.0.0.2") ServerPort uint16 = 12345 ClientPort uint16 = 6789 NetNS uint32 = 123456789 diff --git a/probe/endpoint/four_tuple.go b/probe/endpoint/four_tuple.go index d322d1ddc..c3fb6d496 100644 --- a/probe/endpoint/four_tuple.go +++ b/probe/endpoint/four_tuple.go @@ -2,6 +2,7 @@ package endpoint import ( "fmt" + "net" "sort" "strings" ) @@ -10,10 +11,17 @@ import ( // active tells whether the connection belongs to an activeFlow (see // conntrack.go) type fourTuple struct { - fromAddr, toAddr string + fromAddr, toAddr [net.IPv4len]byte fromPort, toPort uint16 } +func makeFourTuple(fromAddr, toAddr net.IP, fromPort, toPort uint16) fourTuple { + tuple := fourTuple{fromPort: fromPort, toPort: toPort} + copy(tuple.fromAddr[:], fromAddr.To4()) + copy(tuple.toAddr[:], toAddr.To4()) + return tuple +} + func (t fourTuple) String() string { return fmt.Sprintf("%s:%d-%s:%d", t.fromAddr, t.fromPort, t.toAddr, t.toPort) } diff --git a/probe/endpoint/nat.go b/probe/endpoint/nat.go index 797a0d1a8..e3dd1f2a1 100644 --- a/probe/endpoint/nat.go +++ b/probe/endpoint/nat.go @@ -4,7 +4,6 @@ package endpoint import ( "net" - "strconv" "github.com/typetypetype/conntrack" @@ -57,10 +56,8 @@ func (n natMapper) applyNAT(rpt report.Report, scope string) { n.flowWalker.walkFlows(func(f conntrack.Conn, _ bool) { mapping := toMapping(f) - 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) + realEndpointID := report.MakeEndpointNodeIDB(scope, 0, mapping.originalIP, mapping.originalPort) + copyEndpointID := report.MakeEndpointNodeIDB(scope, 0, mapping.rewrittenIP, mapping.rewrittenPort) node, ok := rpt.Endpoint.Nodes[realEndpointID] if !ok { diff --git a/report/id.go b/report/id.go index 22224e2d4..6836d926e 100644 --- a/report/id.go +++ b/report/id.go @@ -2,6 +2,7 @@ package report import ( "net" + "strconv" "strings" ) @@ -29,25 +30,37 @@ const ( // MakeEndpointNodeID produces an endpoint node ID from its composite parts. func MakeEndpointNodeID(hostID, namespaceID, address, port string) string { - return makeAddressID(hostID, namespaceID, address) + ScopeDelim + port + addressIP := net.ParseIP(address) + return makeAddressID(hostID, namespaceID, address, addressIP) + ScopeDelim + port +} + +// MakeEndpointNodeIDB produces an endpoint node ID from its composite parts in binary, not strings. +func MakeEndpointNodeIDB(hostID string, namespaceID uint64, addressIP net.IP, port uint16) string { + namespace := strconv.FormatUint(namespaceID, 10) + return makeAddressID(hostID, namespace, addressIP.String(), addressIP) + ScopeDelim + strconv.Itoa(int(port)) } // MakeAddressNodeID produces an address node ID from its composite parts. func MakeAddressNodeID(hostID, address string) string { - return makeAddressID(hostID, "", address) + addressIP := net.ParseIP(address) + return makeAddressID(hostID, "", address, addressIP) } -func makeAddressID(hostID, namespaceID, address string) string { +// MakeAddressNodeIDB produces an address node ID from its composite parts, in binary not string. +func MakeAddressNodeIDB(hostID string, addressIP net.IP) string { + return makeAddressID(hostID, "", addressIP.String(), addressIP) +} + +func makeAddressID(hostID, namespaceID, address string, addressIP net.IP) string { var scope string // 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) { + } else if addressIP != nil && addressIP.IsLoopback() { scope = hostID if namespaceID != "" { scope += "-" + namespaceID