mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-16 03:49:52 +00:00
Lost events were previously unnoticed. This patch adds an error in the log and stops the ebpf tracker if an event is lost.
223 lines
5.9 KiB
Go
223 lines
5.9 KiB
Go
package endpoint
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"sync"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
"github.com/weaveworks/scope/probe/endpoint/procspy"
|
|
"github.com/weaveworks/scope/probe/host"
|
|
"github.com/weaveworks/tcptracer-bpf/pkg/tracer"
|
|
)
|
|
|
|
// An ebpfConnection represents a TCP connection
|
|
type ebpfConnection struct {
|
|
tuple fourTuple
|
|
networkNamespace string
|
|
incoming bool
|
|
pid int
|
|
}
|
|
|
|
type eventTracker interface {
|
|
handleConnection(ev tracer.EventType, tuple fourTuple, pid int, networkNamespace string)
|
|
walkConnections(f func(ebpfConnection))
|
|
feedInitialConnections(ci procspy.ConnIter, seenTuples map[string]fourTuple, hostNodeID string)
|
|
isReadyToHandleConnections() bool
|
|
isDead() bool
|
|
stop()
|
|
}
|
|
|
|
var ebpfTracker *EbpfTracker
|
|
|
|
// EbpfTracker contains the sets of open and closed TCP connections.
|
|
// Closed connections are kept in the `closedConnections` slice for one iteration of `walkConnections`.
|
|
type EbpfTracker struct {
|
|
sync.Mutex
|
|
tracer *tracer.Tracer
|
|
readyToHandleConnections bool
|
|
dead bool
|
|
|
|
openConnections map[string]ebpfConnection
|
|
closedConnections []ebpfConnection
|
|
}
|
|
|
|
var releaseRegex = regexp.MustCompile(`^(\d+)\.(\d+).*$`)
|
|
|
|
func isKernelSupported() error {
|
|
release, _, err := host.GetKernelReleaseAndVersion()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
releaseParts := releaseRegex.FindStringSubmatch(release)
|
|
if len(releaseParts) != 3 {
|
|
return fmt.Errorf("got invalid release version %q (expected format '4.4[.2-1]')", release)
|
|
}
|
|
|
|
major, err := strconv.Atoi(releaseParts[1])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
minor, err := strconv.Atoi(releaseParts[2])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if major > 4 {
|
|
return nil
|
|
}
|
|
|
|
if major < 4 || minor < 4 {
|
|
return fmt.Errorf("got kernel %s but need kernel >=4.4", release)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func newEbpfTracker() (eventTracker, error) {
|
|
if err := isKernelSupported(); err != nil {
|
|
return nil, fmt.Errorf("kernel not supported: %v", err)
|
|
}
|
|
|
|
t, err := tracer.NewTracer(tcpEventCbV4, tcpEventCbV6, lostCb)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tracker := &EbpfTracker{
|
|
openConnections: map[string]ebpfConnection{},
|
|
tracer: t,
|
|
}
|
|
|
|
ebpfTracker = tracker
|
|
return tracker, nil
|
|
}
|
|
|
|
var lastTimestampV4 uint64
|
|
|
|
func tcpEventCbV4(e tracer.TcpV4) {
|
|
if lastTimestampV4 > e.Timestamp {
|
|
// A kernel bug can cause the timestamps to be wrong (e.g. on Ubuntu with Linux 4.4.0-47.68)
|
|
// Upgrading the kernel will fix the problem. For further info see:
|
|
// https://github.com/iovisor/bcc/issues/790#issuecomment-263704235
|
|
// https://github.com/weaveworks/scope/issues/2334
|
|
log.Errorf("tcp tracer received event with timestamp %v even though the last timestamp was %v. Stopping the eBPF tracker.", e.Timestamp, lastTimestampV4)
|
|
ebpfTracker.dead = true
|
|
ebpfTracker.stop()
|
|
}
|
|
|
|
lastTimestampV4 = e.Timestamp
|
|
|
|
tuple := fourTuple{e.SAddr.String(), e.DAddr.String(), e.SPort, e.DPort}
|
|
ebpfTracker.handleConnection(e.Type, tuple, int(e.Pid), strconv.Itoa(int(e.NetNS)))
|
|
}
|
|
|
|
func tcpEventCbV6(e tracer.TcpV6) {
|
|
// TODO: IPv6 not supported in Scope
|
|
}
|
|
|
|
func lostCb(count uint64) {
|
|
log.Errorf("tcp tracer lost %d events. Stopping the eBPF tracker", count)
|
|
ebpfTracker.dead = true
|
|
ebpfTracker.stop()
|
|
}
|
|
|
|
func (t *EbpfTracker) handleConnection(ev tracer.EventType, tuple fourTuple, pid int, networkNamespace string) {
|
|
t.Lock()
|
|
defer t.Unlock()
|
|
|
|
if !t.isReadyToHandleConnections() {
|
|
return
|
|
}
|
|
|
|
log.Debugf("handleConnection(%v, [%v:%v --> %v:%v], pid=%v, netNS=%v)",
|
|
ev, tuple.fromAddr, tuple.fromPort, tuple.toAddr, tuple.toPort, pid, networkNamespace)
|
|
|
|
switch ev {
|
|
case tracer.EventConnect:
|
|
conn := ebpfConnection{
|
|
incoming: false,
|
|
tuple: tuple,
|
|
pid: pid,
|
|
networkNamespace: networkNamespace,
|
|
}
|
|
t.openConnections[tuple.String()] = conn
|
|
case tracer.EventAccept:
|
|
conn := ebpfConnection{
|
|
incoming: true,
|
|
tuple: tuple,
|
|
pid: pid,
|
|
networkNamespace: networkNamespace,
|
|
}
|
|
t.openConnections[tuple.String()] = conn
|
|
case tracer.EventClose:
|
|
if deadConn, ok := t.openConnections[tuple.String()]; ok {
|
|
delete(t.openConnections, tuple.String())
|
|
t.closedConnections = append(t.closedConnections, deadConn)
|
|
} else {
|
|
log.Debugf("EbpfTracker: unmatched close event: %s pid=%d netns=%s", tuple.String(), pid, networkNamespace)
|
|
}
|
|
}
|
|
}
|
|
|
|
// walkConnections calls f with all open connections and connections that have come and gone
|
|
// since the last call to walkConnections
|
|
func (t *EbpfTracker) walkConnections(f func(ebpfConnection)) {
|
|
t.Lock()
|
|
defer t.Unlock()
|
|
|
|
for _, connection := range t.openConnections {
|
|
f(connection)
|
|
}
|
|
for _, connection := range t.closedConnections {
|
|
f(connection)
|
|
}
|
|
t.closedConnections = t.closedConnections[:0]
|
|
}
|
|
|
|
func (t *EbpfTracker) feedInitialConnections(conns procspy.ConnIter, seenTuples map[string]fourTuple, hostNodeID string) {
|
|
t.readyToHandleConnections = true
|
|
for conn := conns.Next(); conn != nil; conn = conns.Next() {
|
|
var (
|
|
namespaceID string
|
|
tuple = fourTuple{
|
|
conn.LocalAddress.String(),
|
|
conn.RemoteAddress.String(),
|
|
conn.LocalPort,
|
|
conn.RemotePort,
|
|
}
|
|
)
|
|
|
|
if conn.Proc.NetNamespaceID > 0 {
|
|
namespaceID = strconv.FormatUint(conn.Proc.NetNamespaceID, 10)
|
|
}
|
|
|
|
// We can use a port-heuristic to guess the direction.
|
|
// We assume that tuple.fromPort < tuple.toPort is a connect event (outgoing)
|
|
canonical, ok := seenTuples[tuple.key()]
|
|
if (ok && canonical != tuple) || (!ok && tuple.fromPort < tuple.toPort) {
|
|
t.handleConnection(tracer.EventConnect, tuple, int(conn.Proc.PID), namespaceID)
|
|
} else {
|
|
t.handleConnection(tracer.EventAccept, tuple, int(conn.Proc.PID), namespaceID)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *EbpfTracker) isReadyToHandleConnections() bool {
|
|
return t.readyToHandleConnections
|
|
}
|
|
|
|
func (t *EbpfTracker) isDead() bool {
|
|
return t.dead
|
|
}
|
|
|
|
func (t *EbpfTracker) stop() {
|
|
if t.tracer != nil {
|
|
t.tracer.Stop()
|
|
}
|
|
t.dead = true
|
|
}
|