Files
weave-scope/probe/endpoint/ebpf_test.go
Bryan Boreham fc46ea17ee refactor(probe/ebpf): track connections by four-tuple+namespace
The previous code tracked only by four-tuple, which meant that two
connections with same address/port combinations in different namespace
would clash and one would get dropped.

Also previously the tuple was duplicated between the map key and
value, so we remove it from the value.

We only add the namespace in the case that the local address is
loopback, which matches how the rest of Scope treats addresses.
2020-01-13 08:53:47 +00:00

292 lines
7.1 KiB
Go

// +build linux
package endpoint
import (
"net"
"reflect"
"testing"
"time"
"github.com/weaveworks/tcptracer-bpf/pkg/tracer"
"github.com/weaveworks/scope/probe/host"
)
func newMockEbpfTracker() *EbpfTracker {
return &EbpfTracker{
ready: true,
dead: false,
openConnections: map[ebpfKey]ebpfDetail{},
}
}
func TestHandleConnection(t *testing.T) {
var (
ServerPid uint32 = 42
ClientPid uint32 = 43
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
IPv4ConnectEvent = tracer.TcpV4{
CPU: 0,
Type: tracer.EventConnect,
Pid: ClientPid,
Comm: "cmd",
SAddr: ClientIP,
DAddr: ServerIP,
SPort: ClientPort,
DPort: ServerPort,
NetNS: NetNS,
}
IPv4ConnectEbpfConnection = ebpfDetail{
incoming: false,
pid: ClientPid,
}
IPv4ConnectCloseEvent = tracer.TcpV4{
CPU: 0,
Type: tracer.EventClose,
Pid: ClientPid,
Comm: "cmd",
SAddr: ClientIP,
DAddr: ServerIP,
SPort: ClientPort,
DPort: ServerPort,
NetNS: NetNS,
}
IPv4AcceptEvent = tracer.TcpV4{
CPU: 0,
Type: tracer.EventAccept,
Pid: ServerPid,
Comm: "cmd",
SAddr: ServerIP,
DAddr: ClientIP,
SPort: ServerPort,
DPort: ClientPort,
NetNS: NetNS,
}
IPv4AcceptEbpfConnection = ebpfDetail{
incoming: true,
pid: ServerPid,
}
IPv4AcceptCloseEvent = tracer.TcpV4{
CPU: 0,
Type: tracer.EventClose,
Pid: ClientPid,
Comm: "cmd",
SAddr: ServerIP,
DAddr: ClientIP,
SPort: ServerPort,
DPort: ClientPort,
NetNS: NetNS,
}
)
mockEbpfTracker := newMockEbpfTracker()
tuple := fourTuple{ClientAddr, ServerAddr, IPv4ConnectEvent.SPort, IPv4ConnectEvent.DPort}
mockEbpfTracker.handleConnection(IPv4ConnectEvent.Type, tuple, int(IPv4ConnectEvent.Pid), NetNS)
key := makeKey(tuple, NetNS)
if !reflect.DeepEqual(mockEbpfTracker.openConnections[key], IPv4ConnectEbpfConnection) {
t.Errorf("Connection mismatch connect event\nTarget connection:%v\nParsed connection:%v",
IPv4ConnectEbpfConnection, mockEbpfTracker.openConnections[key])
}
tuple = fourTuple{ClientAddr, ServerAddr, uint16(IPv4ConnectCloseEvent.SPort), uint16(IPv4ConnectCloseEvent.DPort)}
mockEbpfTracker.handleConnection(IPv4ConnectCloseEvent.Type, tuple, int(IPv4ConnectCloseEvent.Pid), NetNS)
key = makeKey(tuple, NetNS)
if len(mockEbpfTracker.openConnections) != 0 {
t.Errorf("Connection mismatch close event\nConnection to close:%v",
mockEbpfTracker.openConnections[key])
}
mockEbpfTracker = newMockEbpfTracker()
tuple = fourTuple{ServerAddr, ClientAddr, uint16(IPv4AcceptEvent.SPort), uint16(IPv4AcceptEvent.DPort)}
mockEbpfTracker.handleConnection(IPv4AcceptEvent.Type, tuple, int(IPv4AcceptEvent.Pid), NetNS)
key = makeKey(tuple, NetNS)
if !reflect.DeepEqual(mockEbpfTracker.openConnections[key], IPv4AcceptEbpfConnection) {
t.Errorf("Connection mismatch connect event\nTarget connection:%v\nParsed connection:%v",
IPv4AcceptEbpfConnection, mockEbpfTracker.openConnections[key])
}
tuple = fourTuple{ServerAddr, ClientAddr, uint16(IPv4AcceptCloseEvent.SPort), uint16(IPv4AcceptCloseEvent.DPort)}
mockEbpfTracker.handleConnection(IPv4AcceptCloseEvent.Type, tuple, int(IPv4AcceptCloseEvent.Pid), NetNS)
if len(mockEbpfTracker.openConnections) != 0 {
t.Errorf("Connection mismatch close event\nConnection to close:%v",
mockEbpfTracker.openConnections)
}
}
func TestWalkConnections(t *testing.T) {
var (
cnt int
activeKey = ebpfKey{networkNamespace: 12345}
inactiveKey = ebpfKey{networkNamespace: 12345}
)
mockEbpfTracker := newMockEbpfTracker()
mockEbpfTracker.openConnections[activeKey] = ebpfDetail{
pid: 0,
}
mockEbpfTracker.closedConnections = append(mockEbpfTracker.closedConnections,
ebpfClosedConnection{
key: inactiveKey,
ebpfDetail: ebpfDetail{
pid: 0,
},
})
mockEbpfTracker.walkConnections(func(k ebpfKey, e ebpfDetail) {
cnt++
})
if cnt != 2 {
t.Errorf("walkConnections found %v instead of 2 connections", cnt)
}
}
func TestInvalidTimeStampDead(t *testing.T) {
var (
cnt int
ClientPid uint32 = 43
ServerIP = net.ParseIP("127.0.0.1")
ClientIP = net.ParseIP("127.0.0.2")
ServerPort uint16 = 12345
ClientPort uint16 = 6789
NetNS uint32 = 123456789
event = tracer.TcpV4{
CPU: 0,
Type: tracer.EventConnect,
Pid: ClientPid,
Comm: "cmd",
SAddr: ClientIP,
DAddr: ServerIP,
SPort: ClientPort,
DPort: ServerPort,
NetNS: NetNS,
}
)
mockEbpfTracker := newMockEbpfTracker()
event.Timestamp = 0
mockEbpfTracker.TCPEventV4(event)
event2 := event
event2.SPort = 1
event2.Timestamp = 2
mockEbpfTracker.TCPEventV4(event2)
mockEbpfTracker.walkConnections(func(ebpfKey, ebpfDetail) {
cnt++
})
if cnt != 2 {
t.Errorf("walkConnections found %v instead of 2 connections", cnt)
}
if mockEbpfTracker.isDead() {
t.Errorf("expected ebpfTracker to be alive after events with valid order")
}
cnt = 0
event.Timestamp = 1
mockEbpfTracker.TCPEventV4(event)
mockEbpfTracker.walkConnections(func(ebpfKey, ebpfDetail) {
cnt++
})
if cnt != 2 {
t.Errorf("walkConnections found %v instead of 2 connections", cnt)
}
// EbpfTracker is marked as dead asynchronously.
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
if mockEbpfTracker.isDead() {
break
}
time.Sleep(100 * time.Millisecond)
}
if !mockEbpfTracker.isDead() {
t.Errorf("expected ebpfTracker to be set to dead after events with wrong order")
}
}
func TestIsKernelSupported(t *testing.T) {
var release, version string
oldGetKernelReleaseAndVersion := host.GetKernelReleaseAndVersion
defer func() {
host.GetKernelReleaseAndVersion = oldGetKernelReleaseAndVersion
}()
host.GetKernelReleaseAndVersion = func() (string, string, error) { return release, version, nil }
testVersions := []struct {
release string
version string
supported bool
}{
{
"4.1",
"",
false,
},
{
"4.4",
"",
true,
},
{
"4.4-custom",
"",
true,
},
{
"4.4.127",
"",
true,
},
{
"4.4.0-119-generic",
"#143-Ubuntu SMP Mon Apr 2 16:08:24 UTC 2018",
false,
},
{
"4.4.0-127-generic",
"#153-Ubuntu SMP Sat May 19 10:58:46 UTC 2018",
true,
},
{
"4.4.0-116-generic",
"#140-Ubuntu SMP Mon Feb 12 21:23:04 UTC 2018",
true,
},
{
"4.13.0-38-generic",
"#43-Ubuntu SMP Wed Mar 14 15:20:44 UTC 2018",
true,
},
{
"4.13.0-119-generic",
"#43-Ubuntu SMP Wed Apr 1 00:00:00 UTC 2018",
true,
},
{
"4.9.0-6-amd64",
"#1 SMP Debian 4.9.82-1+deb9u3 (2018-03-02)",
true,
},
}
for _, tv := range testVersions {
release = tv.release
version = tv.version
err := isKernelSupported()
if tv.supported && err != nil {
t.Errorf("expected kernel release %q version %q to be supported but got error: %v", release, version, err)
}
if !tv.supported && err == nil {
t.Errorf("expected kernel release %q version %q to not be supported", release, version)
}
}
}