diff --git a/Makefile b/Makefile index c371c333b5..684f21d5f0 100644 --- a/Makefile +++ b/Makefile @@ -24,13 +24,24 @@ RM=--rm RUN_FLAGS=-ti BUILD_IN_CONTAINER=true GO_ENV=GOGC=off -GO=env $(GO_ENV) go -NO_CROSS_COMP=unset GOOS GOARCH -GO_HOST=$(NO_CROSS_COMP); $(GO) -WITH_GO_HOST_ENV=$(NO_CROSS_COMP); $(GO_ENV) GO_BUILD_INSTALL_DEPS=-i GO_BUILD_TAGS='netgo unsafe' GO_BUILD_FLAGS=$(GO_BUILD_INSTALL_DEPS) -ldflags "-extldflags \"-static\" -X main.version=$(SCOPE_VERSION) -s -w" -tags $(GO_BUILD_TAGS) +GOOS=$(shell go tool dist env | grep GOOS | sed -e 's/GOOS="\(.*\)"/\1/') + +ifeq ($(GOOS),linux) +GO_ENV+=CGO_ENABLED=1 +endif + +ifeq ($(GOARCH),arm) +ARM_CC=CC=/usr/bin/arm-linux-gnueabihf-gcc +endif + +GO=env $(GO_ENV) $(ARM_CC) go + +NO_CROSS_COMP=unset GOOS GOARCH +GO_HOST=$(NO_CROSS_COMP); env $(GO_ENV) go +WITH_GO_HOST_ENV=$(NO_CROSS_COMP); $(GO_ENV) IMAGE_TAG=$(shell ./tools/image-tag) all: $(SCOPE_EXPORT) diff --git a/backend/Dockerfile b/backend/Dockerfile index 128366fcb6..eb8aa20fdc 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,6 +1,9 @@ -FROM golang:1.7.4 +FROM ubuntu:yakkety +ENV GOPATH /go +ENV GOVERSION 1.7 +ENV PATH /go/bin:/usr/lib/go-${GOVERSION}/bin:/usr/bin:/bin:/usr/sbin:/sbin RUN apt-get update && \ - apt-get install -y libpcap-dev python-requests time file shellcheck && \ + apt-get install -y libpcap-dev python-requests time file shellcheck golang-${GOVERSION} git gcc-arm-linux-gnueabihf && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* RUN go clean -i net && \ go install -tags netgo std && \ @@ -13,7 +16,7 @@ RUN go get -tags netgo \ github.com/fatih/hclfmt \ github.com/mjibson/esc \ github.com/client9/misspell/cmd/misspell && \ - chmod a+wr --recursive /usr/local/go/pkg && \ + chmod a+wr --recursive /usr/lib/go-${GOVERSION}/pkg && \ rm -rf /go/pkg/ /go/src/ COPY build.sh / ENTRYPOINT ["/build.sh"] diff --git a/integration/300_internet_edge_test.sh b/integration/300_internet_edge_test.sh index a2770f2bda..a0d8839d37 100755 --- a/integration/300_internet_edge_test.sh +++ b/integration/300_internet_edge_test.sh @@ -5,12 +5,6 @@ start_suite "Test short lived connections from the Internet" -if ! echo "$HOST1" | grep "us-central1-a"; then - echo "Skipping; test needs to be run against VMs on GCE." - scope_end_suite - exit -fi - weave_on "$HOST1" launch scope_on "$HOST1" launch docker_on "$HOST1" run -d -p 80:80 --name nginx nginx diff --git a/integration/301_internet_edge_with_ebpf_test.sh b/integration/301_internet_edge_with_ebpf_test.sh new file mode 100755 index 0000000000..89d69853df --- /dev/null +++ b/integration/301_internet_edge_with_ebpf_test.sh @@ -0,0 +1,28 @@ +#! /bin/bash + +# shellcheck disable=SC1091 +. ./config.sh + +start_suite "Test short lived connections from the Internet" + +weave_on "$HOST1" launch +scope_on "$HOST1" launch --probe.ebpf.connections=true +docker_on "$HOST1" run -d -p 80:80 --name nginx nginx + +do_connections() { + while true; do + curl -s "http://$HOST1:80/" >/dev/null || true + sleep 1 + done +} +do_connections & + +wait_for_containers "$HOST1" 60 nginx "The Internet" + +has_connection_by_id containers "$HOST1" "in-theinternet" "$(node_id containers "$HOST1" nginx)" + +endpoints_have_ebpf "$HOST1" + +kill %do_connections + +scope_end_suite diff --git a/integration/311_container_to_container_edge_with_ebpf_test.sh b/integration/311_container_to_container_edge_with_ebpf_test.sh new file mode 100755 index 0000000000..8d3356e2ef --- /dev/null +++ b/integration/311_container_to_container_edge_with_ebpf_test.sh @@ -0,0 +1,24 @@ +#! /bin/bash + +# shellcheck disable=SC1091 +. ./config.sh + +start_suite "Test short lived connections between containers, with ebpf connection tracking enabled" + +weave_on "$HOST1" launch +scope_on "$HOST1" launch --probe.ebpf.connections=true +weave_on "$HOST1" run -d --name nginx nginx +weave_on "$HOST1" run -d --name client alpine /bin/sh -c "while true; do \ + wget http://nginx.weave.local:80/ -O - >/dev/null || true; \ + sleep 1; \ +done" + +wait_for_containers "$HOST1" 60 nginx client + +has_container "$HOST1" nginx +has_container "$HOST1" client +has_connection containers "$HOST1" client nginx + +endpoints_have_ebpf "$HOST1" + +scope_end_suite diff --git a/integration/312_container_to_container_edge_same_netns_with_ebpf_test.sh b/integration/312_container_to_container_edge_same_netns_with_ebpf_test.sh new file mode 100755 index 0000000000..828c35dcf8 --- /dev/null +++ b/integration/312_container_to_container_edge_same_netns_with_ebpf_test.sh @@ -0,0 +1,20 @@ +#! /bin/bash + +# shellcheck disable=SC1091 +. ./config.sh + +start_suite "Test short lived connection between containers in same network namespace, with ebpf connection tracking enabled" + +scope_on "$HOST1" launch --probe.ebpf.connections=true +docker_on "$HOST1" run -d --name nginx nginx +docker_on "$HOST1" run -d --net=container:nginx --name client albanc/dialer /go/bin/dialer connectshortlived localhost:80 + +wait_for_containers "$HOST1" 60 nginx client + +has_container "$HOST1" nginx +has_container "$HOST1" client +has_connection containers "$HOST1" client nginx + +endpoints_have_ebpf "$HOST1" + +scope_end_suite diff --git a/integration/config.sh b/integration/config.sh index 75eac309d2..99f5cdf74e 100644 --- a/integration/config.sh +++ b/integration/config.sh @@ -90,6 +90,30 @@ has_connection_by_id() { assert "curl -s http://$host:4040/api/topology/${view}?system=show | jq -r '.nodes[\"$from_id\"].adjacency | contains([\"$to_id\"])'" true } +# this checks if ebpf is true on all endpoints on a given host +endpoints_have_ebpf() { + local host="$1" + local timeout="${2:-60}" + local number_of_endpoints=-1 + local have_ebpf=-1 + local report + + for i in $(seq "$timeout"); do + report=$(curl -s "http://${host}:4040/api/report") + number_of_endpoints=$(echo "${report}" | jq -r '.Endpoint.nodes | length') + have_ebpf=$(echo "${report}" | jq -r '.Endpoint.nodes[].latest.eBPF | select(.value != null) | contains({"value": "true"})' | wc -l) + if [[ "$number_of_endpoints" -gt 0 && "$have_ebpf" -gt 0 && "$number_of_endpoints" -eq "$have_ebpf" ]]; then + echo "Found ${number_of_endpoints} endpoints with ebpf enabled" + assert "echo '$have_ebpf'" "$number_of_endpoints" + return + fi + sleep 1 + done + + echo "Only ${have_ebpf} endpoints of ${number_of_endpoints} have ebpf enabled, should be equal" + assert "echo '$have_ebpf" "$number_of_endpoints" +} + has_connection() { local view="$1" local host="$2" diff --git a/probe/endpoint/connection_tracker.go b/probe/endpoint/connection_tracker.go new file mode 100644 index 0000000000..3479c318b1 --- /dev/null +++ b/probe/endpoint/connection_tracker.go @@ -0,0 +1,250 @@ +package endpoint + +import ( + "strconv" + + log "github.com/Sirupsen/logrus" + "github.com/weaveworks/scope/probe/endpoint/procspy" + "github.com/weaveworks/scope/probe/process" + "github.com/weaveworks/scope/report" +) + +// connectionTrackerConfig are the config options for the endpoint tracker. +type connectionTrackerConfig struct { + HostID string + HostName string + SpyProcs bool + UseConntrack bool + WalkProc bool + UseEbpfConn bool + ProcRoot string + BufferSize int + Scanner procspy.ConnectionScanner + DNSSnooper *DNSSnooper +} + +type connectionTracker struct { + conf connectionTrackerConfig + flowWalker flowWalker // Interface + ebpfTracker eventTracker + reverseResolver *reverseResolver + processCache *process.CachingWalker +} + +func newConnectionTracker(conf connectionTrackerConfig) connectionTracker { + if !conf.UseEbpfConn { + // ebpf OFF, use flowWalker + return connectionTracker{ + conf: conf, + flowWalker: newConntrackFlowWalker(conf.UseConntrack, conf.ProcRoot, conf.BufferSize, "--any-nat"), + ebpfTracker: nil, + reverseResolver: newReverseResolver(), + } + } + // When ebpf will be active by default, check if it starts correctly otherwise fallback to flowWalk + et, err := newEbpfTracker(conf.UseEbpfConn) + if err != nil { + // TODO: fallback to flowWalker, when ebpf is enabled by default + log.Errorf("Error setting up the ebpfTracker, connections will not be reported: %s", err) + noopConnectionTracker := connectionTracker{ + conf: conf, + flowWalker: nil, + ebpfTracker: nil, + reverseResolver: nil, + } + return noopConnectionTracker + } + + var processCache *process.CachingWalker + processCache = process.NewCachingWalker(process.NewWalker(conf.ProcRoot)) + processCache.Tick() + + ct := connectionTracker{ + conf: conf, + flowWalker: nil, + ebpfTracker: et, + reverseResolver: newReverseResolver(), + processCache: processCache, + } + go ct.getInitialState() + return ct +} + +func flowToTuple(f flow) (ft fourTuple) { + ft = fourTuple{ + f.Original.Layer3.SrcIP, + f.Original.Layer3.DstIP, + uint16(f.Original.Layer4.SrcPort), + uint16(f.Original.Layer4.DstPort), + } + // Handle DNAT-ed connections in the initial state + if f.Original.Layer3.DstIP != f.Reply.Layer3.SrcIP { + ft = fourTuple{ + f.Reply.Layer3.DstIP, + f.Reply.Layer3.SrcIP, + uint16(f.Reply.Layer4.DstPort), + uint16(f.Reply.Layer4.SrcPort), + } + } + return ft +} + +// ReportConnections calls trackers accordingly to the configuration. +// When ebpf is enabled, only performEbpfTrack() is called +func (t *connectionTracker) ReportConnections(rpt *report.Report) { + hostNodeID := report.MakeHostNodeID(t.conf.HostID) + + if t.ebpfTracker != nil { + t.performEbpfTrack(rpt, hostNodeID) + return + } + + // seenTuples contains information about connections seen by conntrack and it will be passed to the /proc parser + seenTuples := map[string]fourTuple{} + if t.flowWalker != nil { + t.performFlowWalk(rpt, &seenTuples) + } + // if eBPF was enabled but failed to initialize, Scanner will be nil. + // We can't recover from this, so don't walk proc in that case. + // TODO: implement fallback + if t.conf.WalkProc && t.conf.Scanner != nil { + t.performWalkProc(rpt, hostNodeID, &seenTuples) + } +} + +func (t *connectionTracker) performFlowWalk(rpt *report.Report, seenTuples *map[string]fourTuple) { + // Consult the flowWalker for short-lived connections + extraNodeInfo := map[string]string{ + Conntracked: "true", + } + t.flowWalker.walkFlows(func(f flow, alive bool) { + tuple := flowToTuple(f) + (*seenTuples)[tuple.key()] = tuple + t.addConnection(rpt, tuple, "", extraNodeInfo, extraNodeInfo) + }) +} + +func (t *connectionTracker) performWalkProc(rpt *report.Report, hostNodeID string, seenTuples *map[string]fourTuple) error { + conns, err := t.conf.Scanner.Connections(t.conf.SpyProcs) + if err != nil { + return err + } + for conn := conns.Next(); conn != nil; conn = conns.Next() { + var ( + namespaceID string + tuple = fourTuple{ + conn.LocalAddress.String(), + conn.RemoteAddress.String(), + conn.LocalPort, + conn.RemotePort, + } + toNodeInfo = map[string]string{Procspied: "true"} + fromNodeInfo = map[string]string{Procspied: "true"} + ) + if conn.Proc.PID > 0 { + fromNodeInfo[process.PID] = strconv.FormatUint(uint64(conn.Proc.PID), 10) + 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 + // the direction. + canonical, ok := (*seenTuples)[tuple.key()] + if (ok && canonical != tuple) || (!ok && tuple.fromPort < tuple.toPort) { + tuple.reverse() + toNodeInfo, fromNodeInfo = fromNodeInfo, toNodeInfo + } + t.addConnection(rpt, tuple, namespaceID, fromNodeInfo, toNodeInfo) + } + return nil +} + +func (t *connectionTracker) getInitialState() { + scanner := procspy.NewSyncConnectionScanner(t.processCache) + // Run conntrack and proc parsing synchronously only once to initialize ebpfTracker + seenTuples := map[string]fourTuple{} + // Consult the flowWalker to get the initial state + if err := IsConntrackSupported(t.conf.ProcRoot); t.conf.UseConntrack && err != nil { + log.Warnf("Not using conntrack: not supported by the kernel: %s", err) + } else if existingFlows, err := existingConnections([]string{"--any-nat"}); err != nil { + log.Errorf("conntrack existingConnections error: %v", err) + } else { + for _, f := range existingFlows { + tuple := flowToTuple(f) + seenTuples[tuple.key()] = tuple + } + } + + conns, err := scanner.Connections(t.conf.SpyProcs) + if err != nil { + log.Errorf("Error initializing ebpfTracker while scanning /proc, continuing without initial connections: %s", err) + } + scanner.Stop() + + t.ebpfTracker.feedInitialConnections(conns, seenTuples, report.MakeHostNodeID(t.conf.HostID)) +} + +func (t *connectionTracker) performEbpfTrack(rpt *report.Report, hostNodeID string) error { + t.ebpfTracker.walkConnections(func(e ebpfConnection) { + fromNodeInfo := map[string]string{ + EBPF: "true", + } + toNodeInfo := map[string]string{ + EBPF: "true", + } + if e.pid > 0 { + fromNodeInfo[process.PID] = strconv.Itoa(e.pid) + fromNodeInfo[report.HostNodeID] = hostNodeID + } + + if e.incoming { + t.addConnection(rpt, reverse(e.tuple), e.networkNamespace, toNodeInfo, fromNodeInfo) + } else { + t.addConnection(rpt, e.tuple, e.networkNamespace, fromNodeInfo, toNodeInfo) + } + + }) + return nil +} + +func (t *connectionTracker) addConnection(rpt *report.Report, ft fourTuple, namespaceID string, extraFromNode, extraToNode map[string]string) { + var ( + fromNode = t.makeEndpointNode(namespaceID, ft.fromAddr, ft.fromPort, extraFromNode) + toNode = t.makeEndpointNode(namespaceID, ft.toAddr, ft.toPort, extraToNode) + ) + rpt.Endpoint = rpt.Endpoint.AddNode(fromNode.WithEdge(toNode.ID, report.EdgeMetadata{})) + rpt.Endpoint = rpt.Endpoint.AddNode(toNode) +} + +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), + map[string]string{Addr: addr, Port: portStr}) + if names := t.conf.DNSSnooper.CachedNamesForIP(addr); len(names) > 0 { + node = node.WithSet(SnoopedDNSNames, report.MakeStringSet(names...)) + } + if names, err := t.reverseResolver.get(addr); err == nil && len(names) > 0 { + node = node.WithSet(ReverseDNSNames, report.MakeStringSet(names...)) + } + if extra != nil { + node = node.WithLatests(extra) + } + return node +} + +func (t *connectionTracker) Stop() error { + if t.ebpfTracker != nil { + t.ebpfTracker.stop() + } + if t.flowWalker != nil { + t.flowWalker.stop() + } + t.reverseResolver.stop() + return nil +} diff --git a/probe/endpoint/conntrack.go b/probe/endpoint/conntrack.go index 3f4711ad73..bc79661a43 100644 --- a/probe/endpoint/conntrack.go +++ b/probe/endpoint/conntrack.go @@ -65,14 +65,14 @@ type conntrack struct { // flowWalker is something that maintains flows, and provides an accessor // method to walk them. type flowWalker interface { - walkFlows(f func(flow)) + walkFlows(f func(f flow, active bool)) stop() } type nilFlowWalker struct{} -func (n nilFlowWalker) stop() {} -func (n nilFlowWalker) walkFlows(f func(flow)) {} +func (n nilFlowWalker) stop() {} +func (n nilFlowWalker) walkFlows(f func(flow, bool)) {} // conntrackWalker uses the conntrack command to track network connections and // implement flowWalker. @@ -160,7 +160,7 @@ func logPipe(prefix string, reader io.Reader) { func (c *conntrackWalker) run() { // Fork another conntrack, just to capture existing connections // for which we don't get events - existingFlows, err := c.existingConnections() + existingFlows, err := existingConnections(c.args) if err != nil { log.Errorf("conntrack existingConnections error: %v", err) return @@ -354,8 +354,8 @@ func decodeStreamedFlow(scanner *bufio.Scanner) (flow, error) { return f, nil } -func (c *conntrackWalker) existingConnections() ([]flow, error) { - args := append([]string{"-L", "-o", "id", "-p", "tcp"}, c.args...) +func existingConnections(conntrackWalkerArgs []string) ([]flow, error) { + args := append([]string{"-L", "-o", "id", "-p", "tcp"}, conntrackWalkerArgs...) cmd := exec.Command("conntrack", args...) stdout, err := cmd.StdoutPipe() if err != nil { @@ -463,14 +463,14 @@ func (c *conntrackWalker) handleFlow(f flow, forceAdd bool) { // walkFlows calls f with all active flows and flows that have come and gone // since the last call to walkFlows -func (c *conntrackWalker) walkFlows(f func(flow)) { +func (c *conntrackWalker) walkFlows(f func(flow, bool)) { c.Lock() defer c.Unlock() for _, flow := range c.activeFlows { - f(flow) + f(flow, true) } for _, flow := range c.bufferedFlows { - f(flow) + f(flow, false) } c.bufferedFlows = c.bufferedFlows[:0] } diff --git a/probe/endpoint/ebpf.go b/probe/endpoint/ebpf.go new file mode 100644 index 0000000000..bed984d425 --- /dev/null +++ b/probe/endpoint/ebpf.go @@ -0,0 +1,217 @@ +package endpoint + +import ( + "errors" + "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 + 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(useEbpfConn bool) (eventTracker, error) { + if !useEbpfConn { + return nil, errors.New("ebpf tracker not enabled") + } + + if err := isKernelSupported(); err != nil { + return nil, fmt.Errorf("kernel not supported: %v", err) + } + + t, err := tracer.NewTracer(tcpEventCbV4, tcpEventCbV6) + 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 { + log.Errorf("ERROR: late event!\n") + } + + 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 (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) stop() { + // TODO: implement proper stopping logic + // + // Even if we stop the go routine, it's not enough since we disabled the + // async proc parser. We leave this uninmplemented for now because: + // + // * Ebpf parsing is optional (need to be enabled explicitly with + // --probe.ebpf.connections=true), if a user enables it we assume they + // check on the logs whether it works or not + // + // * It's unlikely that the ebpf tracker stops working if it started + // successfully and if it does, we probaby want it to fail hard +} diff --git a/probe/endpoint/ebpf_test.go b/probe/endpoint/ebpf_test.go new file mode 100644 index 0000000000..e813ec2e8c --- /dev/null +++ b/probe/endpoint/ebpf_test.go @@ -0,0 +1,184 @@ +package endpoint + +import ( + "net" + "reflect" + "strconv" + "testing" + + "github.com/weaveworks/tcptracer-bpf/pkg/tracer" +) + +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") + 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 = ebpfConnection{ + tuple: fourTuple{ + fromAddr: ClientIP.String(), + toAddr: ServerIP.String(), + fromPort: ClientPort, + toPort: ServerPort, + }, + networkNamespace: strconv.Itoa(int(NetNS)), + incoming: false, + pid: int(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 = ebpfConnection{ + tuple: fourTuple{ + fromAddr: ServerIP.String(), + toAddr: ClientIP.String(), + fromPort: ServerPort, + toPort: ClientPort, + }, + networkNamespace: strconv.Itoa(int(NetNS)), + incoming: true, + pid: int(ServerPid), + } + + IPv4AcceptCloseEvent = tracer.TcpV4{ + CPU: 0, + Type: tracer.EventClose, + Pid: ClientPid, + Comm: "cmd", + SAddr: ServerIP, + DAddr: ClientIP, + SPort: ServerPort, + DPort: ClientPort, + NetNS: NetNS, + } + ) + + mockEbpfTracker := &EbpfTracker{ + readyToHandleConnections: true, + dead: false, + + openConnections: map[string]ebpfConnection{}, + closedConnections: []ebpfConnection{}, + } + + 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)) + if !reflect.DeepEqual(mockEbpfTracker.openConnections[tuple.String()], IPv4ConnectEbpfConnection) { + t.Errorf("Connection mismatch connect event\nTarget connection:%v\nParsed connection:%v", + IPv4ConnectEbpfConnection, mockEbpfTracker.openConnections[tuple.String()]) + } + + 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)) + if len(mockEbpfTracker.openConnections) != 0 { + t.Errorf("Connection mismatch close event\nConnection to close:%v", + mockEbpfTracker.openConnections[tuple.String()]) + } + + mockEbpfTracker = &EbpfTracker{ + readyToHandleConnections: true, + dead: false, + + openConnections: map[string]ebpfConnection{}, + closedConnections: []ebpfConnection{}, + } + + 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)) + if !reflect.DeepEqual(mockEbpfTracker.openConnections[tuple.String()], IPv4AcceptEbpfConnection) { + t.Errorf("Connection mismatch connect event\nTarget connection:%v\nParsed connection:%v", + IPv4AcceptEbpfConnection, mockEbpfTracker.openConnections[tuple.String()]) + } + + 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)) + + 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 + activeTuple = fourTuple{ + fromAddr: "", + toAddr: "", + fromPort: 0, + toPort: 0, + } + + inactiveTuple = fourTuple{ + fromAddr: "", + toAddr: "", + fromPort: 0, + toPort: 0, + } + ) + mockEbpfTracker := &EbpfTracker{ + readyToHandleConnections: true, + dead: false, + openConnections: map[string]ebpfConnection{ + activeTuple.String(): { + tuple: activeTuple, + networkNamespace: "12345", + incoming: true, + pid: 0, + }, + }, + closedConnections: []ebpfConnection{ + { + tuple: inactiveTuple, + networkNamespace: "12345", + incoming: false, + pid: 0, + }, + }, + } + mockEbpfTracker.walkConnections(func(e ebpfConnection) { + cnt++ + }) + if cnt != 2 { + t.Errorf("walkConnetions found %v instead of 2 connections", cnt) + } +} diff --git a/probe/endpoint/four_tuple.go b/probe/endpoint/four_tuple.go new file mode 100644 index 0000000000..d322d1ddc2 --- /dev/null +++ b/probe/endpoint/four_tuple.go @@ -0,0 +1,45 @@ +package endpoint + +import ( + "fmt" + "sort" + "strings" +) + +// fourTuple is an (IP, port, IP, port) tuple, representing a connection +// active tells whether the connection belongs to an activeFlow (see +// conntrack.go) +type fourTuple struct { + fromAddr, toAddr string + fromPort, toPort uint16 +} + +func (t fourTuple) String() string { + return fmt.Sprintf("%s:%d-%s:%d", t.fromAddr, t.fromPort, t.toAddr, t.toPort) +} + +// key is a sortable direction-independent key for tuples, used to look up a +// fourTuple when you are unsure of its direction. +func (t fourTuple) key() string { + key := []string{ + fmt.Sprintf("%s:%d", t.fromAddr, t.fromPort), + fmt.Sprintf("%s:%d", t.toAddr, t.toPort), + } + sort.Strings(key) + return strings.Join(key, " ") +} + +// reverse flips the direction of the tuple +func (t *fourTuple) reverse() { + t.fromAddr, t.fromPort, t.toAddr, t.toPort = t.toAddr, t.toPort, t.fromAddr, t.fromPort +} + +// reverse flips the direction of a tuple, without side effects +func reverse(tuple fourTuple) fourTuple { + return fourTuple{ + fromAddr: tuple.toAddr, + toAddr: tuple.fromAddr, + fromPort: tuple.toPort, + toPort: tuple.fromPort, + } +} diff --git a/probe/endpoint/nat.go b/probe/endpoint/nat.go index 88c9e8d696..489de36dad 100644 --- a/probe/endpoint/nat.go +++ b/probe/endpoint/nat.go @@ -49,7 +49,7 @@ func toMapping(f flow) *endpointMapping { // applyNAT duplicates Nodes in the endpoint topology of a report, based on // the NAT table. func (n natMapper) applyNAT(rpt report.Report, scope string) { - n.flowWalker.walkFlows(func(f flow) { + n.flowWalker.walkFlows(func(f flow, active bool) { mapping := toMapping(f) realEndpointPort := strconv.Itoa(mapping.originalPort) diff --git a/probe/endpoint/nat_internal_test.go b/probe/endpoint/nat_internal_test.go index 2214be5f44..efedcf1858 100644 --- a/probe/endpoint/nat_internal_test.go +++ b/probe/endpoint/nat_internal_test.go @@ -13,9 +13,9 @@ type mockFlowWalker struct { flows []flow } -func (m *mockFlowWalker) walkFlows(f func(flow)) { +func (m *mockFlowWalker) walkFlows(f func(f flow, active bool)) { for _, flow := range m.flows { - f(flow) + f(flow, true) } } diff --git a/probe/endpoint/procspy/background_reader_linux.go b/probe/endpoint/procspy/reader_linux.go similarity index 77% rename from probe/endpoint/procspy/background_reader_linux.go rename to probe/endpoint/procspy/reader_linux.go index d618998a60..359ca08b5e 100644 --- a/probe/endpoint/procspy/background_reader_linux.go +++ b/probe/endpoint/procspy/reader_linux.go @@ -20,6 +20,11 @@ const ( targetWalkTime = 10 * time.Second // Aim at walking all files in 10 seconds ) +type reader interface { + getWalkedProcPid(buf *bytes.Buffer) (map[uint64]*Proc, error) + stop() +} + type backgroundReader struct { stopc chan struct{} mtx sync.Mutex @@ -29,7 +34,7 @@ type backgroundReader struct { // starts a rate-limited background goroutine to read the expensive files from // proc. -func newBackgroundReader(walker process.Walker) *backgroundReader { +func newBackgroundReader(walker process.Walker) reader { br := &backgroundReader{ stopc: make(chan struct{}), latestSockets: map[uint64]*Proc{}, @@ -54,28 +59,6 @@ func (br *backgroundReader) getWalkedProcPid(buf *bytes.Buffer) (map[uint64]*Pro return br.latestSockets, err } -type walkResult struct { - buf *bytes.Buffer - sockets map[uint64]*Proc -} - -func performWalk(w pidWalker, c chan<- walkResult) { - var ( - err error - result = walkResult{ - buf: bytes.NewBuffer(make([]byte, 0, 5000)), - } - ) - - result.sockets, err = w.walk(result.buf) - if err != nil { - log.Errorf("background /proc reader: error walking /proc: %s", err) - result.buf.Reset() - result.sockets = nil - } - c <- result -} - func (br *backgroundReader) loop(walker process.Walker) { var ( begin time.Time // when we started the last performWalk @@ -120,6 +103,71 @@ func (br *backgroundReader) loop(walker process.Walker) { } } +type foregroundReader struct { + stopc chan struct{} + latestBuf *bytes.Buffer + latestSockets map[uint64]*Proc + ticker *time.Ticker +} + +// reads synchronously files from /proc +func newForegroundReader(walker process.Walker) reader { + fr := &foregroundReader{ + stopc: make(chan struct{}), + latestSockets: map[uint64]*Proc{}, + } + var ( + walkc = make(chan walkResult) + ticker = time.NewTicker(time.Millisecond) // fire every millisecond + pWalker = newPidWalker(walker, ticker.C, fdBlockSize) + ) + + go performWalk(pWalker, walkc) + + result := <-walkc + fr.latestBuf = result.buf + fr.latestSockets = result.sockets + fr.ticker = ticker + + return fr +} + +func (fr *foregroundReader) stop() { + fr.ticker.Stop() + close(fr.stopc) +} + +func (fr *foregroundReader) getWalkedProcPid(buf *bytes.Buffer) (map[uint64]*Proc, error) { + // Don't access latestBuf directly but create a reader. In this way, + // the buffer will not be empty in the next call of getWalkedProcPid + // and it can be copied again. + _, err := io.Copy(buf, bytes.NewReader(fr.latestBuf.Bytes())) + + return fr.latestSockets, err +} + +type walkResult struct { + buf *bytes.Buffer + sockets map[uint64]*Proc +} + +func performWalk(w pidWalker, c chan<- walkResult) { + var ( + err error + result = walkResult{ + buf: bytes.NewBuffer(make([]byte, 0, 5000)), + } + ) + + result.sockets, err = w.walk(result.buf) + if err != nil { + log.Errorf("background /proc reader: error walking /proc: %s", err) + result.buf.Reset() + result.sockets = nil + } + c <- result +} + // Adjust rate limit for next walk and calculate when it should be started func scheduleNextWalk(rateLimitPeriod time.Duration, took time.Duration) (newRateLimitPeriod time.Duration, restInterval time.Duration) { log.Debugf("background /proc reader: full pass took %s", took) diff --git a/probe/endpoint/procspy/spy_darwin.go b/probe/endpoint/procspy/spy_darwin.go index bec6f2a132..394407f39f 100644 --- a/probe/endpoint/procspy/spy_darwin.go +++ b/probe/endpoint/procspy/spy_darwin.go @@ -18,6 +18,11 @@ func NewConnectionScanner(_ process.Walker) ConnectionScanner { return &darwinScanner{} } +// NewSyncConnectionScanner creates a new synchronous Darwin ConnectionScanner +func NewSyncConnectionScanner(_ process.Walker) ConnectionScanner { + return &darwinScanner{} +} + type darwinScanner struct{} // Connections returns all established (TCP) connections. No need to be root diff --git a/probe/endpoint/procspy/spy_linux.go b/probe/endpoint/procspy/spy_linux.go index 6966f852f9..ec668a47d7 100644 --- a/probe/endpoint/procspy/spy_linux.go +++ b/probe/endpoint/procspy/spy_linux.go @@ -38,8 +38,14 @@ func NewConnectionScanner(walker process.Walker) ConnectionScanner { return &linuxScanner{br} } +// NewSyncConnectionScanner creates a new synchronous Linux ConnectionScanner +func NewSyncConnectionScanner(walker process.Walker) ConnectionScanner { + fr := newForegroundReader(walker) + return &linuxScanner{fr} +} + type linuxScanner struct { - br *backgroundReader + r reader } func (s *linuxScanner) Connections(processes bool) (ConnIter, error) { @@ -50,7 +56,7 @@ func (s *linuxScanner) Connections(processes bool) (ConnIter, error) { var procs map[uint64]*Proc if processes { var err error - if procs, err = s.br.getWalkedProcPid(buf); err != nil { + if procs, err = s.r.getWalkedProcPid(buf); err != nil { return nil, err } } @@ -68,5 +74,5 @@ func (s *linuxScanner) Connections(processes bool) (ConnIter, error) { } func (s *linuxScanner) Stop() { - s.br.stop() + s.r.stop() } diff --git a/probe/endpoint/reporter.go b/probe/endpoint/reporter.go index f58fce91a1..7f0301be37 100644 --- a/probe/endpoint/reporter.go +++ b/probe/endpoint/reporter.go @@ -1,16 +1,10 @@ package endpoint import ( - "fmt" - "sort" - "strconv" - "strings" "time" "github.com/prometheus/client_golang/prometheus" - "github.com/weaveworks/scope/probe/endpoint/procspy" - "github.com/weaveworks/scope/probe/process" "github.com/weaveworks/scope/report" ) @@ -19,6 +13,7 @@ const ( Addr = "addr" // typically IPv4 Port = "port" Conntracked = "conntracked" + EBPF = "eBPF" Procspied = "procspied" ReverseDNSNames = "reverse_dns_names" SnoopedDNSNames = "snooped_dns_names" @@ -31,6 +26,7 @@ type ReporterConfig struct { SpyProcs bool UseConntrack bool WalkProc bool + UseEbpfConn bool ProcRoot string BufferSize int Scanner procspy.ConnectionScanner @@ -39,10 +35,9 @@ type ReporterConfig struct { // Reporter generates Reports containing the Endpoint topology. type Reporter struct { - conf ReporterConfig - flowWalker flowWalker // interface - natMapper natMapper - reverseResolver *reverseResolver + conf ReporterConfig + connectionTracker connectionTracker + natMapper natMapper } // SpyDuration is an exported prometheus metric @@ -64,10 +59,20 @@ var SpyDuration = prometheus.NewSummaryVec( // with process (PID) information. func NewReporter(conf ReporterConfig) *Reporter { return &Reporter{ - conf: conf, - flowWalker: newConntrackFlowWalker(conf.UseConntrack, conf.ProcRoot, conf.BufferSize), - natMapper: makeNATMapper(newConntrackFlowWalker(conf.UseConntrack, conf.ProcRoot, conf.BufferSize, "--any-nat")), - reverseResolver: newReverseResolver(), + conf: conf, + connectionTracker: newConnectionTracker(connectionTrackerConfig{ + HostID: conf.HostID, + HostName: conf.HostName, + SpyProcs: conf.SpyProcs, + UseConntrack: conf.UseConntrack, + WalkProc: conf.WalkProc, + UseEbpfConn: conf.UseEbpfConn, + ProcRoot: conf.ProcRoot, + BufferSize: conf.BufferSize, + Scanner: conf.Scanner, + DNSSnooper: conf.DNSSnooper, + }), + natMapper: makeNATMapper(newConntrackFlowWalker(conf.UseConntrack, conf.ProcRoot, conf.BufferSize, "--any-nat")), } } @@ -76,141 +81,20 @@ func (Reporter) Name() string { return "Endpoint" } // Stop stop stop func (r *Reporter) Stop() { - r.flowWalker.stop() + r.connectionTracker.Stop() r.natMapper.stop() - r.reverseResolver.stop() r.conf.Scanner.Stop() } -type fourTuple struct { - fromAddr, toAddr string - fromPort, toPort uint16 -} - -// key is a sortable direction-independent key for tuples, used to look up a -// fourTuple, when you are unsure of it's direction. -func (t fourTuple) key() string { - key := []string{ - fmt.Sprintf("%s:%d", t.fromAddr, t.fromPort), - fmt.Sprintf("%s:%d", t.toAddr, t.toPort), - } - sort.Strings(key) - return strings.Join(key, " ") -} - -// reverse flips the direction of the tuple -func (t *fourTuple) reverse() { - t.fromAddr, t.fromPort, t.toAddr, t.toPort = t.toAddr, t.toPort, t.fromAddr, t.fromPort -} - // Report implements Reporter. func (r *Reporter) Report() (report.Report, error) { defer func(begin time.Time) { SpyDuration.WithLabelValues().Observe(time.Since(begin).Seconds()) }(time.Now()) - hostNodeID := report.MakeHostNodeID(r.conf.HostID) rpt := report.MakeReport() - seenTuples := map[string]fourTuple{} - - // Consult the flowWalker for short-lived connections - { - extraNodeInfo := map[string]string{ - Conntracked: "true", - } - r.flowWalker.walkFlows(func(f flow) { - tuple := fourTuple{ - f.Original.Layer3.SrcIP, - f.Original.Layer3.DstIP, - uint16(f.Original.Layer4.SrcPort), - uint16(f.Original.Layer4.DstPort), - } - // Handle DNAT-ed short-lived connections. - // The NAT mapper won't help since it only runs periodically, - // missing the short-lived connections. - if f.Original.Layer3.DstIP != f.Reply.Layer3.SrcIP { - tuple = fourTuple{ - f.Reply.Layer3.DstIP, - f.Reply.Layer3.SrcIP, - uint16(f.Reply.Layer4.DstPort), - uint16(f.Reply.Layer4.SrcPort), - } - } - - seenTuples[tuple.key()] = tuple - r.addConnection(&rpt, tuple, "", extraNodeInfo, extraNodeInfo) - }) - } - - if r.conf.WalkProc { - conns, err := r.conf.Scanner.Connections(r.conf.SpyProcs) - if err != nil { - return rpt, err - } - for conn := conns.Next(); conn != nil; conn = conns.Next() { - var ( - namespaceID string - tuple = fourTuple{ - conn.LocalAddress.String(), - conn.RemoteAddress.String(), - conn.LocalPort, - conn.RemotePort, - } - toNodeInfo = map[string]string{Procspied: "true"} - fromNodeInfo = map[string]string{Procspied: "true"} - ) - if conn.Proc.PID > 0 { - fromNodeInfo[process.PID] = strconv.FormatUint(uint64(conn.Proc.PID), 10) - 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 - // the direction. - canonical, ok := seenTuples[tuple.key()] - if (ok && canonical != tuple) || (!ok && tuple.fromPort < tuple.toPort) { - tuple.reverse() - toNodeInfo, fromNodeInfo = fromNodeInfo, toNodeInfo - } - r.addConnection(&rpt, tuple, namespaceID, fromNodeInfo, toNodeInfo) - } - } + r.connectionTracker.ReportConnections(&rpt) r.natMapper.applyNAT(rpt, r.conf.HostID) return rpt, nil } - -func (r *Reporter) addConnection(rpt *report.Report, t fourTuple, namespaceID string, extraFromNode, extraToNode map[string]string) { - var ( - fromNode = r.makeEndpointNode(namespaceID, t.fromAddr, t.fromPort, extraFromNode) - toNode = r.makeEndpointNode(namespaceID, t.toAddr, t.toPort, extraToNode) - ) - rpt.Endpoint = rpt.Endpoint.AddNode(fromNode.WithEdge(toNode.ID, report.EdgeMetadata{})) - rpt.Endpoint = rpt.Endpoint.AddNode(toNode) -} - -func (r *Reporter) makeEndpointNode(namespaceID string, addr string, port uint16, extra map[string]string) report.Node { - portStr := strconv.Itoa(int(port)) - node := report.MakeNodeWith( - report.MakeEndpointNodeID(r.conf.HostID, namespaceID, addr, portStr), - map[string]string{Addr: addr, Port: portStr}) - if names := r.conf.DNSSnooper.CachedNamesForIP(addr); len(names) > 0 { - node = node.WithSet(SnoopedDNSNames, report.MakeStringSet(names...)) - } - if names, err := r.reverseResolver.get(addr); err == nil && len(names) > 0 { - node = node.WithSet(ReverseDNSNames, report.MakeStringSet(names...)) - } - if extra != nil { - node = node.WithLatests(extra) - } - return node -} - -func newu64(i uint64) *uint64 { - return &i -} diff --git a/prog/main.go b/prog/main.go index 00fba1647c..1efe1cf93e 100644 --- a/prog/main.go +++ b/prog/main.go @@ -99,6 +99,7 @@ type probeFlags struct { spyProcs bool // Associate endpoints with processes (must be root) procEnabled bool // Produce process topology & process nodes in endpoint + useEbpfConn bool // Enable connection tracking with eBPF procRoot string dockerEnabled bool @@ -283,6 +284,7 @@ func main() { flag.BoolVar(&flags.probe.spyProcs, "probe.proc.spy", true, "associate endpoints with processes (needs root)") flag.StringVar(&flags.probe.procRoot, "probe.proc.root", "/proc", "location of the proc filesystem") flag.BoolVar(&flags.probe.procEnabled, "probe.processes", true, "produce process topology & include procspied connections") + flag.BoolVar(&flags.probe.useEbpfConn, "probe.ebpf.connections", false, "enable connection tracking with eBPF") // Docker flag.BoolVar(&flags.probe.dockerEnabled, "probe.docker", false, "collect Docker-related attributes for processes") diff --git a/prog/probe.go b/prog/probe.go index e9cb8d9772..cb74cf70c1 100644 --- a/prog/probe.go +++ b/prog/probe.go @@ -161,7 +161,10 @@ func probeMain(flags probeFlags, targets []appclient.Target) { var scanner procspy.ConnectionScanner if flags.procEnabled { processCache = process.NewCachingWalker(process.NewWalker(flags.procRoot)) - scanner = procspy.NewConnectionScanner(processCache) + // The eBPF tracker finds connections itself and does not need the connection scanner + if !flags.useEbpfConn { + scanner = procspy.NewConnectionScanner(processCache) + } p.AddTicker(processCache) p.AddReporter(process.NewReporter(processCache, hostID, process.GetDeltaTotalJiffies, flags.noCommandLineArguments)) } @@ -179,6 +182,7 @@ func probeMain(flags probeFlags, targets []appclient.Target) { SpyProcs: flags.spyProcs, UseConntrack: flags.useConntrack, WalkProc: flags.procEnabled, + UseEbpfConn: flags.useEbpfConn, ProcRoot: flags.procRoot, BufferSize: flags.conntrackBufferSize, Scanner: scanner, diff --git a/render/filters.go b/render/filters.go index da39e0c440..28a0fb4834 100644 --- a/render/filters.go +++ b/render/filters.go @@ -82,6 +82,18 @@ func ColorConnected(r Renderer) Renderer { // FilterFunc is the function type used by Filters type FilterFunc func(report.Node) bool +// AnyFilterFunc checks if any of the filterfuncs matches. +func AnyFilterFunc(fs ...FilterFunc) FilterFunc { + return func(n report.Node) bool { + for _, f := range fs { + if f(n) { + return true + } + } + return false + } +} + // ComposeFilterFuncs composes filterfuncs into a single FilterFunc checking all. func ComposeFilterFuncs(fs ...FilterFunc) FilterFunc { return func(n report.Node) bool { @@ -224,15 +236,30 @@ func IsRunning(n report.Node) bool { // IsStopped checks if the node is *not* a running docker container var IsStopped = Complement(IsRunning) +func nonProcspiedFilter(node report.Node) bool { + _, ok := node.Latest.Lookup(endpoint.Procspied) + return ok +} + +func nonEBPFFilter(node report.Node) bool { + _, ok := node.Latest.Lookup(endpoint.EBPF) + return ok +} + // FilterNonProcspied removes endpoints which were not found in procspy. func FilterNonProcspied(r Renderer) Renderer { - return MakeFilter( - func(node report.Node) bool { - _, ok := node.Latest.Lookup(endpoint.Procspied) - return ok - }, - r, - ) + return MakeFilter(nonProcspiedFilter, r) +} + +// FilterNonEBPF removes endpoints which were not found via eBPF. +func FilterNonEBPF(r Renderer) Renderer { + return MakeFilter(nonEBPFFilter, r) +} + +// FilterNonProcspiedNorEBPF removes endpoints which were not found in procspy +// nor via eBPF. +func FilterNonProcspiedNorEBPF(r Renderer) Renderer { + return MakeFilter(AnyFilterFunc(nonProcspiedFilter, nonEBPFFilter), r) } // IsApplication checks if the node is an "application" node diff --git a/render/process.go b/render/process.go index 82d756fb8f..13e52e830a 100644 --- a/render/process.go +++ b/render/process.go @@ -23,7 +23,7 @@ func renderProcesses(rpt report.Report) bool { } // EndpointRenderer is a Renderer which produces a renderable endpoint graph. -var EndpointRenderer = FilterNonProcspied(SelectEndpoint) +var EndpointRenderer = FilterNonProcspiedNorEBPF(SelectEndpoint) // ProcessRenderer is a Renderer which produces a renderable process // graph by merging the endpoint graph and the process topology. diff --git a/scope b/scope index 57e7bb7ccd..06c7fe44b9 100755 --- a/scope +++ b/scope @@ -170,6 +170,7 @@ launch_command() { echo docker run --privileged $USERNS_HOST -d --name="$SCOPE_CONTAINER_NAME" --net=host --pid=host \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /var/run/scope/plugins:/var/run/scope/plugins \ + -v /sys/kernel/debug:/sys/kernel/debug \ -e CHECKPOINT_DISABLE \ $WEAVESCOPE_DOCKER_ARGS "$SCOPE_IMAGE" --probe.docker=true } diff --git a/vendor/github.com/iovisor/gobpf/elf/COPYRIGHT.txt b/vendor/github.com/iovisor/gobpf/elf/COPYRIGHT.txt new file mode 100644 index 0000000000..1eae73fb26 --- /dev/null +++ b/vendor/github.com/iovisor/gobpf/elf/COPYRIGHT.txt @@ -0,0 +1,14 @@ +Copyright 2016 PLUMgrid +Copyright 2016 Kinvolk + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/github.com/iovisor/gobpf/elf/LICENSE.txt b/vendor/github.com/iovisor/gobpf/elf/LICENSE.txt new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/vendor/github.com/iovisor/gobpf/elf/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/iovisor/gobpf/elf/elf.go b/vendor/github.com/iovisor/gobpf/elf/elf.go new file mode 100644 index 0000000000..c348b7d711 --- /dev/null +++ b/vendor/github.com/iovisor/gobpf/elf/elf.go @@ -0,0 +1,559 @@ +// +build linux + +// Copyright 2016 Cilium Project +// Copyright 2016 Sylvain Afchain +// Copyright 2016 Kinvolk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package elf + +import ( + "bytes" + "debug/elf" + "encoding/binary" + "errors" + "fmt" + "io" + "os" + "strings" + "syscall" + "unsafe" +) + +/* +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// from https://github.com/safchain/goebpf +// Apache License, Version 2.0 + +typedef struct bpf_map_def { + unsigned int type; + unsigned int key_size; + unsigned int value_size; + unsigned int max_entries; +} bpf_map_def; + +typedef struct bpf_map { + int fd; + bpf_map_def def; +} bpf_map; + +__u64 ptr_to_u64(void *ptr) +{ + return (__u64) (unsigned long) ptr; +} + +static void bpf_apply_relocation(int fd, struct bpf_insn *insn) +{ + insn->src_reg = BPF_PSEUDO_MAP_FD; + insn->imm = fd; +} + +static int bpf_create_map(enum bpf_map_type map_type, int key_size, + int value_size, int max_entries) +{ + int ret; + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + + attr.map_type = map_type; + attr.key_size = key_size; + attr.value_size = value_size; + attr.max_entries = max_entries; + + ret = syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr)); + if (ret < 0 && errno == EPERM) { + // When EPERM is returned, two reasons are possible: + // 1. user has no permissions for bpf() + // 2. user has insufficent rlimit for locked memory + // Unfortunately, there is no api to inspect the current usage of locked + // mem for the user, so an accurate calculation of how much memory to lock + // for this new program is difficult to calculate. As a hack, bump the limit + // to unlimited. If program load fails again, return the error. + + struct rlimit rl = {}; + if (getrlimit(RLIMIT_MEMLOCK, &rl) == 0) { + rl.rlim_max = RLIM_INFINITY; + rl.rlim_cur = rl.rlim_max; + if (setrlimit(RLIMIT_MEMLOCK, &rl) == 0) { + ret = syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr)); + } + else { + printf("setrlimit() failed with errno=%d\n", errno); + return -1; + } + } + } + + return ret; +} + +static bpf_map *bpf_load_map(bpf_map_def *map_def) +{ + bpf_map *map; + + map = calloc(1, sizeof(bpf_map)); + if (map == NULL) + return NULL; + + memcpy(&map->def, map_def, sizeof(bpf_map_def)); + + map->fd = bpf_create_map(map_def->type, + map_def->key_size, + map_def->value_size, + map_def->max_entries + ); + + if (map->fd < 0) + return 0; + + return map; +} + +static int bpf_prog_load(enum bpf_prog_type prog_type, + const struct bpf_insn *insns, int prog_len, + const char *license, int kern_version, + char *log_buf, int log_size) +{ + int ret; + union bpf_attr attr; + memset(&attr, 0, sizeof(attr)); + + attr.prog_type = prog_type; + attr.insn_cnt = prog_len / sizeof(struct bpf_insn); + attr.insns = ptr_to_u64((void *) insns); + attr.license = ptr_to_u64((void *) license); + attr.log_buf = ptr_to_u64(log_buf); + attr.log_size = log_size; + attr.log_level = 1; + attr.kern_version = kern_version; + + ret = syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr)); + if (ret < 0 && errno == EPERM) { + // When EPERM is returned, two reasons are possible: + // 1. user has no permissions for bpf() + // 2. user has insufficent rlimit for locked memory + // Unfortunately, there is no api to inspect the current usage of locked + // mem for the user, so an accurate calculation of how much memory to lock + // for this new program is difficult to calculate. As a hack, bump the limit + // to unlimited. If program load fails again, return the error. + + struct rlimit rl = {}; + if (getrlimit(RLIMIT_MEMLOCK, &rl) == 0) { + rl.rlim_max = RLIM_INFINITY; + rl.rlim_cur = rl.rlim_max; + if (setrlimit(RLIMIT_MEMLOCK, &rl) == 0) { + ret = syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr)); + } + else { + printf("setrlimit() failed with errno=%d\n", errno); + return -1; + } + } + } + + return ret; +} + +static int bpf_update_element(int fd, void *key, void *value, unsigned long long flags) +{ + union bpf_attr attr = { + .map_fd = fd, + .key = ptr_to_u64(key), + .value = ptr_to_u64(value), + .flags = flags, + }; + + return syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); +} + + +static int perf_event_open_map(int pid, int cpu, int group_fd, unsigned long flags) +{ + struct perf_event_attr attr = {0,}; + attr.type = PERF_TYPE_SOFTWARE; + attr.sample_type = PERF_SAMPLE_RAW; + attr.wakeup_events = 1; + + attr.size = sizeof(struct perf_event_attr); + attr.config = 10; // PERF_COUNT_SW_BPF_OUTPUT + + return syscall(__NR_perf_event_open, &attr, pid, cpu, + group_fd, flags); +} +*/ +import "C" + +const useCurrentKernelVersion = 0xFFFFFFFE + +// Based on https://github.com/safchain/goebpf +// Apache License + +func elfReadLicense(file *elf.File) (string, error) { + if lsec := file.Section("license"); lsec != nil { + data, err := lsec.Data() + if err != nil { + return "", err + } + return string(data), nil + } + return "", nil +} + +func elfReadVersion(file *elf.File) (uint32, error) { + if vsec := file.Section("version"); vsec != nil { + data, err := vsec.Data() + if err != nil { + return 0, err + } + if len(data) != 4 { + return 0, errors.New("version is not a __u32") + } + version := *(*C.uint32_t)(unsafe.Pointer(&data[0])) + if err != nil { + return 0, err + } + return uint32(version), nil + } + return 0, nil +} + +func elfReadMaps(file *elf.File) (map[string]*Map, error) { + maps := make(map[string]*Map) + for sectionIdx, section := range file.Sections { + if strings.HasPrefix(section.Name, "maps/") { + data, err := section.Data() + if err != nil { + return nil, err + } + + name := strings.TrimPrefix(section.Name, "maps/") + + mapCount := len(data) / C.sizeof_struct_bpf_map_def + for i := 0; i < mapCount; i++ { + pos := i * C.sizeof_struct_bpf_map_def + cm := C.bpf_load_map((*C.bpf_map_def)(unsafe.Pointer(&data[pos]))) + if cm == nil { + return nil, fmt.Errorf("error while loading map %s", section.Name) + } + + m := &Map{ + Name: name, + SectionIdx: sectionIdx, + Idx: i, + m: cm, + } + + if oldMap, ok := maps[name]; ok { + return nil, fmt.Errorf("duplicate map: %q (section %q) and %q (section %q)", + oldMap.Name, file.Sections[oldMap.SectionIdx].Name, + name, section.Name) + } + maps[name] = m + } + } + } + return maps, nil +} + +func (b *Module) relocate(data []byte, rdata []byte) error { + var symbol elf.Symbol + var offset uint64 + + symbols, err := b.file.Symbols() + if err != nil { + return err + } + + br := bytes.NewReader(data) + + for { + switch b.file.Class { + case elf.ELFCLASS64: + var rel elf.Rel64 + err := binary.Read(br, b.file.ByteOrder, &rel) + if err != nil { + if err == io.EOF { + return nil + } + return err + } + + symNo := rel.Info >> 32 + symbol = symbols[symNo-1] + + offset = rel.Off + case elf.ELFCLASS32: + var rel elf.Rel32 + err := binary.Read(br, b.file.ByteOrder, &rel) + if err != nil { + if err == io.EOF { + return nil + } + return err + } + + symNo := rel.Info >> 8 + symbol = symbols[symNo-1] + + offset = uint64(rel.Off) + default: + return errors.New("architecture not supported") + } + + rinsn := (*C.struct_bpf_insn)(unsafe.Pointer(&rdata[offset])) + if rinsn.code != (C.BPF_LD | C.BPF_IMM | C.BPF_DW) { + return errors.New("invalid relocation") + } + + symbolSec := b.file.Sections[symbol.Section] + if !strings.HasPrefix(symbolSec.Name, "maps/") { + return fmt.Errorf("map location not supported: map %q is in section %q instead of \"maps/%s\"", + symbol.Name, symbolSec.Name, symbol.Name) + } + name := strings.TrimPrefix(symbolSec.Name, "maps/") + + m := b.Map(name) + if m == nil { + return fmt.Errorf("relocation error, symbol %q not found in section %q", + symbol.Name, symbolSec.Name) + } + + C.bpf_apply_relocation(m.m.fd, rinsn) + } +} + +func (b *Module) Load() error { + if b.fileName != "" { + fileReader, err := os.Open(b.fileName) + if err != nil { + return err + } + defer fileReader.Close() + b.fileReader = fileReader + } + + var err error + b.file, err = elf.NewFile(b.fileReader) + if err != nil { + return err + } + + license, err := elfReadLicense(b.file) + if err != nil { + return err + } + + lp := unsafe.Pointer(C.CString(license)) + defer C.free(lp) + + version, err := elfReadVersion(b.file) + if err != nil { + return err + } + if version == useCurrentKernelVersion { + version, err = currentVersion() + if err != nil { + return err + } + } + + maps, err := elfReadMaps(b.file) + if err != nil { + return err + } + b.maps = maps + + processed := make([]bool, len(b.file.Sections)) + for i, section := range b.file.Sections { + if processed[i] { + continue + } + + data, err := section.Data() + if err != nil { + return err + } + + if len(data) == 0 { + continue + } + + if section.Type == elf.SHT_REL { + rsection := b.file.Sections[section.Info] + + processed[i] = true + processed[section.Info] = true + + secName := rsection.Name + isKprobe := strings.HasPrefix(secName, "kprobe/") + isKretprobe := strings.HasPrefix(secName, "kretprobe/") + + if isKprobe || isKretprobe { + rdata, err := rsection.Data() + if err != nil { + return err + } + + if len(rdata) == 0 { + continue + } + + err = b.relocate(data, rdata) + if err != nil { + return err + } + + insns := (*C.struct_bpf_insn)(unsafe.Pointer(&rdata[0])) + + progFd := C.bpf_prog_load(C.BPF_PROG_TYPE_KPROBE, + insns, C.int(rsection.Size), + (*C.char)(lp), C.int(version), + (*C.char)(unsafe.Pointer(&b.log[0])), C.int(len(b.log))) + if progFd < 0 { + return fmt.Errorf("error while loading %q:\n%s", secName, b.log) + } + b.probes[secName] = &Kprobe{ + Name: secName, + insns: insns, + fd: int(progFd), + } + } + } + } + + for i, section := range b.file.Sections { + if processed[i] { + continue + } + + if strings.HasPrefix(section.Name, "kprobe/") || strings.HasPrefix(section.Name, "kretprobe/") { + data, err := section.Data() + if err != nil { + return err + } + + if len(data) == 0 { + continue + } + + insns := (*C.struct_bpf_insn)(unsafe.Pointer(&data[0])) + + fd := C.bpf_prog_load(C.BPF_PROG_TYPE_KPROBE, + insns, C.int(section.Size), + (*C.char)(lp), C.int(version), + (*C.char)(unsafe.Pointer(&b.log[0])), C.int(len(b.log))) + if fd < 0 { + return fmt.Errorf("error while loading %q:\n%s", section.Name, b.log) + } + b.probes[section.Name] = &Kprobe{ + Name: section.Name, + fd: int(fd), + } + } + } + + return b.initializePerfMaps() +} + +func (b *Module) initializePerfMaps() error { + for name, m := range b.maps { + var cpu C.int = 0 + + if m.m != nil && m.m.def._type != C.BPF_MAP_TYPE_PERF_EVENT_ARRAY { + continue + } + + for { + pmuFD, err := C.perf_event_open_map(-1 /* pid */, cpu /* cpu */, -1 /* group_fd */, C.PERF_FLAG_FD_CLOEXEC) + if pmuFD < 0 { + if cpu == 0 { + return fmt.Errorf("perf_event_open for map error: %v", err) + } + break + } + + // mmap + pageSize := os.Getpagesize() + pageCount := 8 + mmapSize := pageSize * (pageCount + 1) + + base, err := syscall.Mmap(int(pmuFD), 0, mmapSize, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) + if err != nil { + return fmt.Errorf("mmap error: %v", err) + } + + // enable + _, _, err2 := syscall.Syscall(syscall.SYS_IOCTL, uintptr(pmuFD), C.PERF_EVENT_IOC_ENABLE, 0) + if err2 != 0 { + return fmt.Errorf("error enabling perf event: %v", err2) + } + + // assign perf fd tp map + ret, err := C.bpf_update_element(C.int(b.maps[name].m.fd), unsafe.Pointer(&cpu), unsafe.Pointer(&pmuFD), C.BPF_ANY) + if ret != 0 { + return fmt.Errorf("cannot assign perf fd to map %q: %s (cpu %d)", name, err, cpu) + } + + b.maps[name].pmuFDs = append(b.maps[name].pmuFDs, pmuFD) + b.maps[name].headers = append(b.maps[name].headers, (*C.struct_perf_event_mmap_page)(unsafe.Pointer(&base[0]))) + + cpu++ + } + } + + return nil +} + +// Map represents a eBPF map. An eBPF map has to be declared in the +// C file. +type Map struct { + Name string + SectionIdx int + Idx int + m *C.bpf_map + + // only for perf maps + pmuFDs []C.int + headers []*C.struct_perf_event_mmap_page +} + +func (b *Module) IterMaps() <-chan *Map { + ch := make(chan *Map) + go func() { + for name := range b.maps { + ch <- b.maps[name] + } + close(ch) + }() + return ch +} + +func (b *Module) Map(name string) *Map { + return b.maps[name] +} diff --git a/vendor/github.com/iovisor/gobpf/elf/elf_unsupported.go b/vendor/github.com/iovisor/gobpf/elf/elf_unsupported.go new file mode 100644 index 0000000000..4c408e3fb8 --- /dev/null +++ b/vendor/github.com/iovisor/gobpf/elf/elf_unsupported.go @@ -0,0 +1,33 @@ +// +build !linux + +package elf + +import ( + "fmt" +) + +func (b *Module) Load() error { + return fmt.Errorf("not supported") +} + +// not supported; dummy struct +type BPFKProbePerf struct{} + +func NewBpfPerfEvent(fileName string) *BPFKProbePerf { + // not supported + return nil +} + +func (b *BPFKProbePerf) Load() error { + return fmt.Errorf("not supported") +} + +func (b *BPFKProbePerf) PollStart(mapName string, receiverChan chan []byte) { + // not supported + return +} + +func (b *BPFKProbePerf) PollStop(mapName string) { + // not supported + return +} diff --git a/vendor/github.com/iovisor/gobpf/elf/kernel_version.go b/vendor/github.com/iovisor/gobpf/elf/kernel_version.go new file mode 100644 index 0000000000..83ea52e718 --- /dev/null +++ b/vendor/github.com/iovisor/gobpf/elf/kernel_version.go @@ -0,0 +1,107 @@ +// +build linux + +// Copyright 2016-2017 Kinvolk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package elf + +import ( + "fmt" + "io/ioutil" + "regexp" + "strconv" + "strings" + "syscall" +) + +var versionRegex = regexp.MustCompile(`^(\d+)\.(\d+).(\d+).*$`) + +func kernelVersionFromReleaseString(releaseString string) (uint32, error) { + versionParts := versionRegex.FindStringSubmatch(releaseString) + if len(versionParts) != 4 { + return 0, fmt.Errorf("got invalid release version %q (expected format '4.3.2-1')", releaseString) + } + major, err := strconv.Atoi(versionParts[1]) + if err != nil { + return 0, err + } + + minor, err := strconv.Atoi(versionParts[2]) + if err != nil { + return 0, err + } + + patch, err := strconv.Atoi(versionParts[3]) + if err != nil { + return 0, err + } + out := major*256*256 + minor*256 + patch + return uint32(out), nil +} + +func currentVersionUname() (uint32, error) { + var buf syscall.Utsname + if err := syscall.Uname(&buf); err != nil { + return 0, err + } + releaseString := strings.Trim(utsnameStr(buf.Release[:]), "\x00") + return kernelVersionFromReleaseString(releaseString) +} + +func currentVersionUbuntu() (uint32, error) { + procVersion, err := ioutil.ReadFile("/proc/version_signature") + if err != nil { + return 0, err + } + var u1, u2, releaseString string + _, err = fmt.Sscanf(string(procVersion), "%s %s %s", &u1, &u2, &releaseString) + if err != nil { + return 0, err + } + return kernelVersionFromReleaseString(releaseString) +} + +var debianVersionRegex = regexp.MustCompile(`.* SMP Debian (\d+\.\d+.\d+-\d+) .*`) + +func currentVersionDebian() (uint32, error) { + procVersion, err := ioutil.ReadFile("/proc/version") + if err != nil { + return 0, err + } + match := debianVersionRegex.FindStringSubmatch(string(procVersion)) + if len(match) != 2 { + return 0, fmt.Errorf("failed to get kernel version from /proc/version: %s", procVersion) + } + return kernelVersionFromReleaseString(match[1]) +} + +func currentVersion() (uint32, error) { + // We need extra checks for Debian and Ubuntu as they modify + // the kernel version patch number for compatibilty with + // out-of-tree modules. Linux perf tools do the same for Ubuntu + // systems: https://github.com/torvalds/linux/commit/d18acd15c + // + // See also: + // https://kernel-handbook.alioth.debian.org/ch-versions.html + // https://wiki.ubuntu.com/Kernel/FAQ + version, err := currentVersionUbuntu() + if err == nil { + return version, nil + } + version, err = currentVersionDebian() + if err == nil { + return version, nil + } + return currentVersionUname() +} diff --git a/vendor/github.com/iovisor/gobpf/elf/module.go b/vendor/github.com/iovisor/gobpf/elf/module.go new file mode 100644 index 0000000000..d5cef78908 --- /dev/null +++ b/vendor/github.com/iovisor/gobpf/elf/module.go @@ -0,0 +1,167 @@ +// +build linux + +// Copyright 2016 Cilium Project +// Copyright 2016 Sylvain Afchain +// Copyright 2016 Kinvolk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package elf + +import ( + "debug/elf" + "fmt" + "io" + "io/ioutil" + "os" + "strconv" + "strings" + "syscall" +) + +/* +#include +#include +#include +#include + +static int perf_event_open_tracepoint(int tracepoint_id, int pid, int cpu, + int group_fd, unsigned long flags) +{ + struct perf_event_attr attr = {0,}; + attr.type = PERF_TYPE_TRACEPOINT; + attr.sample_type = PERF_SAMPLE_RAW; + attr.sample_period = 1; + attr.wakeup_events = 1; + attr.config = tracepoint_id; + + return syscall(__NR_perf_event_open, &attr, pid, cpu, + group_fd, flags); +} +*/ +import "C" + +type Module struct { + fileName string + fileReader io.ReaderAt + file *elf.File + + log []byte + maps map[string]*Map + probes map[string]*Kprobe +} + +// Kprobe represents a kprobe or kretprobe and has to be declared +// in the C file, +type Kprobe struct { + Name string + insns *C.struct_bpf_insn + fd int + efd int +} + +func NewModule(fileName string) *Module { + return &Module{ + fileName: fileName, + probes: make(map[string]*Kprobe), + log: make([]byte, 65536), + } +} + +func NewModuleFromReader(fileReader io.ReaderAt) *Module { + return &Module{ + fileReader: fileReader, + probes: make(map[string]*Kprobe), + log: make([]byte, 65536), + } +} + +func (b *Module) EnableKprobe(secName string) error { + var probeType, funcName string + isKretprobe := strings.HasPrefix(secName, "kretprobe/") + probe, ok := b.probes[secName] + if !ok { + return fmt.Errorf("no such kprobe %q", secName) + } + progFd := probe.fd + if isKretprobe { + probeType = "r" + funcName = strings.TrimPrefix(secName, "kretprobe/") + } else { + probeType = "p" + funcName = strings.TrimPrefix(secName, "kprobe/") + } + eventName := probeType + funcName + + kprobeEventsFileName := "/sys/kernel/debug/tracing/kprobe_events" + f, err := os.OpenFile(kprobeEventsFileName, os.O_APPEND|os.O_WRONLY, 0666) + if err != nil { + return fmt.Errorf("cannot open kprobe_events: %v\n", err) + } + defer f.Close() + + cmd := fmt.Sprintf("%s:%s %s\n", probeType, eventName, funcName) + _, err = f.WriteString(cmd) + if err != nil { + return fmt.Errorf("cannot write %q to kprobe_events: %v\n", cmd, err) + } + + kprobeIdFile := fmt.Sprintf("/sys/kernel/debug/tracing/events/kprobes/%s/id", eventName) + kprobeIdBytes, err := ioutil.ReadFile(kprobeIdFile) + if err != nil { + return fmt.Errorf("cannot read kprobe id: %v\n", err) + } + kprobeId, err := strconv.Atoi(strings.TrimSpace(string(kprobeIdBytes))) + if err != nil { + return fmt.Errorf("invalid kprobe id): %v\n", err) + } + + efd := C.perf_event_open_tracepoint(C.int(kprobeId), -1 /* pid */, 0 /* cpu */, -1 /* group_fd */, C.PERF_FLAG_FD_CLOEXEC) + if efd < 0 { + return fmt.Errorf("perf_event_open for kprobe error") + } + + _, _, err2 := syscall.Syscall(syscall.SYS_IOCTL, uintptr(efd), C.PERF_EVENT_IOC_ENABLE, 0) + if err2 != 0 { + return fmt.Errorf("error enabling perf event: %v", err2) + } + + _, _, err2 = syscall.Syscall(syscall.SYS_IOCTL, uintptr(efd), C.PERF_EVENT_IOC_SET_BPF, uintptr(progFd)) + if err2 != 0 { + return fmt.Errorf("error enabling perf event: %v", err2) + } + probe.efd = int(efd) + return nil +} + +func (b *Module) IterKprobes() <-chan *Kprobe { + ch := make(chan *Kprobe) + go func() { + for name := range b.probes { + ch <- b.probes[name] + } + close(ch) + }() + return ch +} + +func (b *Module) EnableKprobes() error { + var err error + for _, kprobe := range b.probes { + err = b.EnableKprobe(kprobe.Name) + if err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/iovisor/gobpf/elf/module_unsupported.go b/vendor/github.com/iovisor/gobpf/elf/module_unsupported.go new file mode 100644 index 0000000000..d978ee3a24 --- /dev/null +++ b/vendor/github.com/iovisor/gobpf/elf/module_unsupported.go @@ -0,0 +1,31 @@ +// +build !linux + +package elf + +import ( + "fmt" + "io" +) + +type Module struct{} +type Kprobe struct{} + +func NewModule(fileName string) *Module { + return nil +} + +func NewModuleFromReader(fileReader io.ReaderAt) *Module { + return nil +} + +func (b *Module) EnableKprobe(secName string) error { + return fmt.Errorf("not supported") +} + +func (b *Module) IterKprobes() <-chan *Kprobe { + return nil +} + +func (b *Module) EnableKprobes() error { + return fmt.Errorf("not supported") +} diff --git a/vendor/github.com/iovisor/gobpf/elf/perf.go b/vendor/github.com/iovisor/gobpf/elf/perf.go new file mode 100644 index 0000000000..2a9d467bb4 --- /dev/null +++ b/vendor/github.com/iovisor/gobpf/elf/perf.go @@ -0,0 +1,291 @@ +// +build linux + +// Copyright 2016 Cilium Project +// Copyright 2016 Sylvain Afchain +// Copyright 2016 Kinvolk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package elf + +import ( + "fmt" + "os" + "sort" + "syscall" + "unsafe" +) + +/* +#include +#include +#include +#include +#include +#include + +// from https://github.com/cilium/cilium/blob/master/pkg/bpf/perf.go + +struct event_sample { + struct perf_event_header header; + uint32_t size; + uint8_t data[]; +}; + +struct read_state { + void *buf; + int buf_len; +}; + +static int perf_event_read(int page_count, int page_size, void *_state, + void *_header, void *_sample_ptr, void *_lost_ptr) +{ + volatile struct perf_event_mmap_page *header = _header; + uint64_t data_head = *((volatile uint64_t *) &header->data_head); + uint64_t data_tail = header->data_tail; + uint64_t raw_size = (uint64_t)page_count * page_size; + void *base = ((uint8_t *)header) + page_size; + struct read_state *state = _state; + struct event_sample *e; + void *begin, *end; + void **sample_ptr = (void **) _sample_ptr; + void **lost_ptr = (void **) _lost_ptr; + + // No data to read on this ring + __sync_synchronize(); + if (data_head == data_tail) + return 0; + + begin = base + data_tail % raw_size; + e = begin; + end = base + (data_tail + e->header.size) % raw_size; + + if (state->buf_len < e->header.size || !state->buf) { + state->buf = realloc(state->buf, e->header.size); + state->buf_len = e->header.size; + } + + if (end < begin) { + uint64_t len = base + raw_size - begin; + + memcpy(state->buf, begin, len); + memcpy((char *) state->buf + len, base, e->header.size - len); + + e = state->buf; + } else { + memcpy(state->buf, begin, e->header.size); + } + + switch (e->header.type) { + case PERF_RECORD_SAMPLE: + *sample_ptr = state->buf; + break; + case PERF_RECORD_LOST: + *lost_ptr = state->buf; + break; + } + + __sync_synchronize(); + header->data_tail += e->header.size; + + return e->header.type; +} +*/ +import "C" + +type PerfMap struct { + name string + program *Module + receiverChan chan []byte + pollStop chan bool + timestamp func(*[]byte) uint64 +} + +// Matching 'struct perf_event_sample in kernel sources +type PerfEventSample struct { + PerfEventHeader + Size uint32 + data byte // Size bytes of data +} + +func InitPerfMap(b *Module, mapName string, receiverChan chan []byte) (*PerfMap, error) { + _, ok := b.maps[mapName] + if !ok { + return nil, fmt.Errorf("no map with name %s", mapName) + } + // Maps are initialized in b.Load(), nothing to do here + return &PerfMap{ + name: mapName, + program: b, + receiverChan: receiverChan, + pollStop: make(chan bool), + }, nil +} + +// SetTimestampFunc registers a timestamp callback that will be used to +// reorder the perf events chronologically. +// +// If not set, the order of events sent through receiverChan is not guaranteed. +// +// Typically, the ebpf program will use bpf_ktime_get_ns() to get a timestamp +// and store it in the perf event. The perf event struct is opaque to this +// package, hence the need for a callback. +func (pm *PerfMap) SetTimestampFunc(timestamp func(*[]byte) uint64) { + pm.timestamp = timestamp +} + +func (pm *PerfMap) PollStart() { + incoming := OrderedBytesArray{timestamp: pm.timestamp} + + m, ok := pm.program.maps[pm.name] + if !ok { + // should not happen or only when pm.program is + // suddenly changed + panic(fmt.Sprintf("cannot find map %q", pm.name)) + } + + go func() { + cpuCount := len(m.pmuFDs) + pageSize := os.Getpagesize() + pageCount := 8 + state := C.struct_read_state{} + + for { + select { + case <-pm.pollStop: + break + default: + perfEventPoll(m.pmuFDs) + } + + for { + var harvestCount C.int + beforeHarvest := nowNanoseconds() + for cpu := 0; cpu < cpuCount; cpu++ { + for { + var sample *PerfEventSample + var lost *PerfEventLost + + ok := C.perf_event_read(C.int(pageCount), C.int(pageSize), + unsafe.Pointer(&state), unsafe.Pointer(m.headers[cpu]), + unsafe.Pointer(&sample), unsafe.Pointer(&lost)) + + switch ok { + case 0: + break // nothing to read + case C.PERF_RECORD_SAMPLE: + size := sample.Size - 4 + b := C.GoBytes(unsafe.Pointer(&sample.data), C.int(size)) + incoming.bytesArray = append(incoming.bytesArray, b) + harvestCount++ + if pm.timestamp == nil { + continue + } + if incoming.timestamp(&b) > beforeHarvest { + // see comment below + break + } else { + continue + } + case C.PERF_RECORD_LOST: + default: + // TODO: handle lost/unknown events? + } + break + } + } + + if incoming.timestamp != nil { + sort.Sort(incoming) + } + for i := 0; i < incoming.Len(); i++ { + if incoming.timestamp != nil && incoming.timestamp(&incoming.bytesArray[0]) > beforeHarvest { + // This record has been sent after the beginning of the harvest. Stop + // processing here to keep the order. "incoming" is sorted, so the next + // elements also must not be processed now. + break + } + pm.receiverChan <- incoming.bytesArray[0] + // remove first element + incoming.bytesArray = incoming.bytesArray[1:] + } + if harvestCount == 0 && len(incoming.bytesArray) == 0 { + break + } + } + } + }() +} + +func (pm *PerfMap) PollStop() { + pm.pollStop <- true +} + +func perfEventPoll(fds []C.int) error { + var pfds []C.struct_pollfd + + for i, _ := range fds { + var pfd C.struct_pollfd + + pfd.fd = fds[i] + pfd.events = C.POLLIN + + pfds = append(pfds, pfd) + } + _, err := C.poll(&pfds[0], C.nfds_t(len(fds)), 500) + if err != nil { + return fmt.Errorf("error polling: %v", err.(syscall.Errno)) + } + + return nil +} + +// Assume the timestamp is at the beginning of the user struct +type OrderedBytesArray struct { + bytesArray [][]byte + timestamp func(*[]byte) uint64 +} + +func (a OrderedBytesArray) Len() int { + return len(a.bytesArray) +} + +func (a OrderedBytesArray) Swap(i, j int) { + a.bytesArray[i], a.bytesArray[j] = a.bytesArray[j], a.bytesArray[i] +} + +func (a OrderedBytesArray) Less(i, j int) bool { + return *(*C.uint64_t)(unsafe.Pointer(&a.bytesArray[i][0])) < *(*C.uint64_t)(unsafe.Pointer(&a.bytesArray[j][0])) +} + +// Matching 'struct perf_event_header in +type PerfEventHeader struct { + Type uint32 + Misc uint16 + TotalSize uint16 +} + +// Matching 'struct perf_event_lost in kernel sources +type PerfEventLost struct { + PerfEventHeader + Id uint64 + Lost uint64 +} + +// nowNanoseconds returns a time that can be compared to bpf_ktime_get_ns() +func nowNanoseconds() uint64 { + var ts syscall.Timespec + syscall.Syscall(syscall.SYS_CLOCK_GETTIME, 1 /* CLOCK_MONOTONIC */, uintptr(unsafe.Pointer(&ts)), 0) + sec, nsec := ts.Unix() + return 1000*1000*1000*uint64(sec) + uint64(nsec) +} diff --git a/vendor/github.com/iovisor/gobpf/elf/perf_unsupported.go b/vendor/github.com/iovisor/gobpf/elf/perf_unsupported.go new file mode 100644 index 0000000000..d5a5440175 --- /dev/null +++ b/vendor/github.com/iovisor/gobpf/elf/perf_unsupported.go @@ -0,0 +1,17 @@ +// +build !linux + +package elf + +import "fmt" + +type PerfMap struct{} + +func InitPerfMap(b *Module, mapName string, receiverChan chan []byte) (*PerfMap, error) { + return nil, fmt.Errorf("not supported") +} + +func (pm *PerfMap) SetTimestampFunc(timestamp func(*[]byte) uint64) {} + +func (pm *PerfMap) PollStart() {} + +func (pm *PerfMap) PollStop() {} diff --git a/vendor/github.com/iovisor/gobpf/elf/table.go b/vendor/github.com/iovisor/gobpf/elf/table.go new file mode 100644 index 0000000000..e4a2e5ee17 --- /dev/null +++ b/vendor/github.com/iovisor/gobpf/elf/table.go @@ -0,0 +1,108 @@ +// +build linux + +// Copyright 2016 Cilium Project +// Copyright 2016 Sylvain Afchain +// Copyright 2016 Kinvolk +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package elf + +import ( + "fmt" + "syscall" + "unsafe" +) + +/* +#include +#include + +extern __u64 ptr_to_u64(void *); + +// from https://github.com/cilium/cilium/blob/master/pkg/bpf/bpf.go +// Apache License, Version 2.0 + +static void create_bpf_update_elem(int fd, void *key, void *value, + unsigned long long flags, void *attr) +{ + union bpf_attr* ptr_bpf_attr; + ptr_bpf_attr = (union bpf_attr*)attr; + ptr_bpf_attr->map_fd = fd; + ptr_bpf_attr->key = ptr_to_u64(key); + ptr_bpf_attr->value = ptr_to_u64(value); + ptr_bpf_attr->flags = flags; +} + +static void create_bpf_lookup_elem(int fd, void *key, void *value, void *attr) +{ + union bpf_attr* ptr_bpf_attr; + ptr_bpf_attr = (union bpf_attr*)attr; + ptr_bpf_attr->map_fd = fd; + ptr_bpf_attr->key = ptr_to_u64(key); + ptr_bpf_attr->value = ptr_to_u64(value); +} +*/ +import "C" + +// UpdateElement stores value in key in the map stored in mp. +// The flags can have the following values (if you include "uapi/linux/bpf.h"): +// C.BPF_ANY to create new element or update existing; +// C.BPF_NOEXIST to create new element if it didn't exist; +// C.BPF_EXIST to update existing element. +func (b *Module) UpdateElement(mp *Map, key, value unsafe.Pointer, flags uint64) error { + uba := C.union_bpf_attr{} + C.create_bpf_update_elem( + C.int(mp.m.fd), + key, + value, + C.ulonglong(flags), + unsafe.Pointer(&uba), + ) + ret, _, err := syscall.Syscall( + C.__NR_bpf, + C.BPF_MAP_UPDATE_ELEM, + uintptr(unsafe.Pointer(&uba)), + unsafe.Sizeof(uba), + ) + + if ret != 0 || err != 0 { + return fmt.Errorf("unable to update element: %s", err) + } + + return nil +} + +// LookupElement looks up the given key in the the map stored in mp. +// The value is stored in the value unsafe.Pointer. +func (b *Module) LookupElement(mp *Map, key, value unsafe.Pointer) error { + uba := C.union_bpf_attr{} + C.create_bpf_lookup_elem( + C.int(mp.m.fd), + key, + value, + unsafe.Pointer(&uba), + ) + ret, _, err := syscall.Syscall( + C.__NR_bpf, + C.BPF_MAP_LOOKUP_ELEM, + uintptr(unsafe.Pointer(&uba)), + unsafe.Sizeof(uba), + ) + + if ret != 0 || err != 0 { + return fmt.Errorf("unable to lookup element: %s", err) + } + + return nil +} diff --git a/vendor/github.com/iovisor/gobpf/elf/utsname_int8.go b/vendor/github.com/iovisor/gobpf/elf/utsname_int8.go new file mode 100644 index 0000000000..9e53508002 --- /dev/null +++ b/vendor/github.com/iovisor/gobpf/elf/utsname_int8.go @@ -0,0 +1,14 @@ +// +build linux,amd64 linux,arm64 + +package elf + +func utsnameStr(in []int8) string { + out := make([]byte, len(in)) + for i := 0; i < len(in); i++ { + if in[i] == 0 { + break + } + out = append(out, byte(in[i])) + } + return string(out) +} diff --git a/vendor/github.com/iovisor/gobpf/elf/utsname_uint8.go b/vendor/github.com/iovisor/gobpf/elf/utsname_uint8.go new file mode 100644 index 0000000000..654ed469c3 --- /dev/null +++ b/vendor/github.com/iovisor/gobpf/elf/utsname_uint8.go @@ -0,0 +1,14 @@ +// +build linux,arm linux,ppc64 linux,ppc64le s390x + +package elf + +func utsnameStr(in []uint8) string { + out := make([]byte, len(in)) + for i := 0; i < len(in); i++ { + if in[i] == 0 { + break + } + out = append(out, byte(in[i])) + } + return string(out) +} diff --git a/vendor/github.com/weaveworks/tcptracer-bpf/LICENSE b/vendor/github.com/weaveworks/tcptracer-bpf/LICENSE new file mode 100644 index 0000000000..c7f988ef32 --- /dev/null +++ b/vendor/github.com/weaveworks/tcptracer-bpf/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2016-2017 Weaveworks Ltd. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/weaveworks/tcptracer-bpf/bpf_helpers.h b/vendor/github.com/weaveworks/tcptracer-bpf/bpf_helpers.h new file mode 100644 index 0000000000..b93c366172 --- /dev/null +++ b/vendor/github.com/weaveworks/tcptracer-bpf/bpf_helpers.h @@ -0,0 +1,141 @@ +#ifndef __BPF_HELPERS_H +#define __BPF_HELPERS_H + +/* helper macro to place programs, maps, license in + * different sections in elf_bpf file. Section names + * are interpreted by elf_bpf loader + */ +#define SEC(NAME) __attribute__((section(NAME), used)) + +/* helper functions called from eBPF programs written in C */ +static void *(*bpf_map_lookup_elem)(void *map, void *key) = + (void *) BPF_FUNC_map_lookup_elem; +static int (*bpf_map_update_elem)(void *map, void *key, void *value, + unsigned long long flags) = + (void *) BPF_FUNC_map_update_elem; +static int (*bpf_map_delete_elem)(void *map, void *key) = + (void *) BPF_FUNC_map_delete_elem; +static int (*bpf_probe_read)(void *dst, int size, void *unsafe_ptr) = + (void *) BPF_FUNC_probe_read; +static unsigned long long (*bpf_ktime_get_ns)(void) = + (void *) BPF_FUNC_ktime_get_ns; +static int (*bpf_trace_printk)(const char *fmt, int fmt_size, ...) = + (void *) BPF_FUNC_trace_printk; +static unsigned long long (*bpf_get_smp_processor_id)(void) = + (void *) BPF_FUNC_get_smp_processor_id; +static unsigned long long (*bpf_get_current_pid_tgid)(void) = + (void *) BPF_FUNC_get_current_pid_tgid; +static unsigned long long (*bpf_get_current_uid_gid)(void) = + (void *) BPF_FUNC_get_current_uid_gid; +static int (*bpf_get_current_comm)(void *buf, int buf_size) = + (void *) BPF_FUNC_get_current_comm; +static int (*bpf_perf_event_read)(void *map, int index) = + (void *) BPF_FUNC_perf_event_read; +static int (*bpf_clone_redirect)(void *ctx, int ifindex, int flags) = + (void *) BPF_FUNC_clone_redirect; +static int (*bpf_redirect)(int ifindex, int flags) = + (void *) BPF_FUNC_redirect; +static int (*bpf_perf_event_output)(void *ctx, void *map, + unsigned long long flags, void *data, + int size) = + (void *) BPF_FUNC_perf_event_output; +static int (*bpf_skb_get_tunnel_key)(void *ctx, void *key, int size, int flags) = + (void *) BPF_FUNC_skb_get_tunnel_key; +static int (*bpf_skb_set_tunnel_key)(void *ctx, void *key, int size, int flags) = + (void *) BPF_FUNC_skb_set_tunnel_key; +static unsigned long long (*bpf_get_prandom_u32)(void) = + (void *) BPF_FUNC_get_prandom_u32; + +/* llvm builtin functions that eBPF C program may use to + * emit BPF_LD_ABS and BPF_LD_IND instructions + */ +struct sk_buff; +unsigned long long load_byte(void *skb, + unsigned long long off) asm("llvm.bpf.load.byte"); +unsigned long long load_half(void *skb, + unsigned long long off) asm("llvm.bpf.load.half"); +unsigned long long load_word(void *skb, + unsigned long long off) asm("llvm.bpf.load.word"); + +/* a helper structure used by eBPF C program + * to describe map attributes to elf_bpf loader + */ +struct bpf_map_def { + unsigned int type; + unsigned int key_size; + unsigned int value_size; + unsigned int max_entries; + unsigned int map_flags; +}; + +static int (*bpf_skb_store_bytes)(void *ctx, int off, void *from, int len, int flags) = + (void *) BPF_FUNC_skb_store_bytes; +static int (*bpf_l3_csum_replace)(void *ctx, int off, int from, int to, int flags) = + (void *) BPF_FUNC_l3_csum_replace; +static int (*bpf_l4_csum_replace)(void *ctx, int off, int from, int to, int flags) = + (void *) BPF_FUNC_l4_csum_replace; + +#if defined(__x86_64__) + +#define PT_REGS_PARM1(x) ((x)->di) +#define PT_REGS_PARM2(x) ((x)->si) +#define PT_REGS_PARM3(x) ((x)->dx) +#define PT_REGS_PARM4(x) ((x)->cx) +#define PT_REGS_PARM5(x) ((x)->r8) +#define PT_REGS_RET(x) ((x)->sp) +#define PT_REGS_FP(x) ((x)->bp) +#define PT_REGS_RC(x) ((x)->ax) +#define PT_REGS_SP(x) ((x)->sp) +#define PT_REGS_IP(x) ((x)->ip) + +#elif defined(__s390x__) + +#define PT_REGS_PARM1(x) ((x)->gprs[2]) +#define PT_REGS_PARM2(x) ((x)->gprs[3]) +#define PT_REGS_PARM3(x) ((x)->gprs[4]) +#define PT_REGS_PARM4(x) ((x)->gprs[5]) +#define PT_REGS_PARM5(x) ((x)->gprs[6]) +#define PT_REGS_RET(x) ((x)->gprs[14]) +#define PT_REGS_FP(x) ((x)->gprs[11]) /* Works only with CONFIG_FRAME_POINTER */ +#define PT_REGS_RC(x) ((x)->gprs[2]) +#define PT_REGS_SP(x) ((x)->gprs[15]) +#define PT_REGS_IP(x) ((x)->ip) + +#elif defined(__aarch64__) + +#define PT_REGS_PARM1(x) ((x)->regs[0]) +#define PT_REGS_PARM2(x) ((x)->regs[1]) +#define PT_REGS_PARM3(x) ((x)->regs[2]) +#define PT_REGS_PARM4(x) ((x)->regs[3]) +#define PT_REGS_PARM5(x) ((x)->regs[4]) +#define PT_REGS_RET(x) ((x)->regs[30]) +#define PT_REGS_FP(x) ((x)->regs[29]) /* Works only with CONFIG_FRAME_POINTER */ +#define PT_REGS_RC(x) ((x)->regs[0]) +#define PT_REGS_SP(x) ((x)->sp) +#define PT_REGS_IP(x) ((x)->pc) + +#elif defined(__powerpc__) + +#define PT_REGS_PARM1(x) ((x)->gpr[3]) +#define PT_REGS_PARM2(x) ((x)->gpr[4]) +#define PT_REGS_PARM3(x) ((x)->gpr[5]) +#define PT_REGS_PARM4(x) ((x)->gpr[6]) +#define PT_REGS_PARM5(x) ((x)->gpr[7]) +#define PT_REGS_RC(x) ((x)->gpr[3]) +#define PT_REGS_SP(x) ((x)->sp) +#define PT_REGS_IP(x) ((x)->nip) + +#endif + +#ifdef __powerpc__ +#define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ (ip) = (ctx)->link; }) +#define BPF_KRETPROBE_READ_RET_IP BPF_KPROBE_READ_RET_IP +#else +#define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ \ + bpf_probe_read(&(ip), sizeof(ip), (void *)PT_REGS_RET(ctx)); }) +#define BPF_KRETPROBE_READ_RET_IP(ip, ctx) ({ \ + bpf_probe_read(&(ip), sizeof(ip), \ + (void *)(PT_REGS_FP(ctx) + sizeof(ip))); }) +#endif + +#endif diff --git a/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/byteorder.go b/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/byteorder.go new file mode 100644 index 0000000000..8186736c1a --- /dev/null +++ b/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/byteorder.go @@ -0,0 +1,21 @@ +package tracer + +import ( + "encoding/binary" + "unsafe" +) + +var nativeEndian binary.ByteOrder + +// In lack of binary.NativeEndian ... +func init() { + var i int32 = 0x01020304 + u := unsafe.Pointer(&i) + pb := (*byte)(u) + b := *pb + if b == 0x04 { + nativeEndian = binary.LittleEndian + } else { + nativeEndian = binary.BigEndian + } +} diff --git a/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/event.go b/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/event.go new file mode 100644 index 0000000000..e13237376f --- /dev/null +++ b/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/event.go @@ -0,0 +1,74 @@ +package tracer + +import ( + "encoding/binary" + "net" + "unsafe" +) + +/* +#include "../../tcptracer-bpf.h" +*/ +import "C" + +func tcpV4ToGo(data *[]byte) (ret TcpV4) { + eventC := (*C.struct_tcp_ipv4_event_t)(unsafe.Pointer(&(*data)[0])) + + ret.Timestamp = uint64(eventC.timestamp) + ret.CPU = uint64(eventC.cpu) + ret.Type = EventType(eventC._type) + ret.Pid = uint32(eventC.pid & 0xffffffff) + ret.Comm = C.GoString(&eventC.comm[0]) + + saddrbuf := make([]byte, 4) + daddrbuf := make([]byte, 4) + + binary.LittleEndian.PutUint32(saddrbuf, uint32(eventC.saddr)) + binary.LittleEndian.PutUint32(daddrbuf, uint32(eventC.daddr)) + + ret.SAddr = net.IPv4(saddrbuf[0], saddrbuf[1], saddrbuf[2], saddrbuf[3]) + ret.DAddr = net.IPv4(daddrbuf[0], daddrbuf[1], daddrbuf[2], daddrbuf[3]) + + ret.SPort = uint16(eventC.sport) + ret.DPort = uint16(eventC.dport) + ret.NetNS = uint32(eventC.netns) + + return +} + +func tcpV4Timestamp(data *[]byte) uint64 { + eventC := (*C.struct_tcp_ipv4_event_t)(unsafe.Pointer(&(*data)[0])) + return uint64(eventC.timestamp) +} + +func tcpV6ToGo(data *[]byte) (ret TcpV6) { + eventC := (*C.struct_tcp_ipv6_event_t)(unsafe.Pointer(&(*data)[0])) + + ret.Timestamp = uint64(eventC.timestamp) + ret.CPU = uint64(eventC.cpu) + ret.Type = EventType(eventC._type) + ret.Pid = uint32(eventC.pid & 0xffffffff) + ret.Comm = C.GoString(&eventC.comm[0]) + + saddrbuf := make([]byte, 16) + daddrbuf := make([]byte, 16) + + binary.LittleEndian.PutUint64(saddrbuf, uint64(eventC.saddr_h)) + binary.LittleEndian.PutUint64(saddrbuf[8:], uint64(eventC.saddr_l)) + binary.LittleEndian.PutUint64(daddrbuf, uint64(eventC.daddr_h)) + binary.LittleEndian.PutUint64(daddrbuf[8:], uint64(eventC.daddr_l)) + + ret.SAddr = net.IP(saddrbuf) + ret.DAddr = net.IP(daddrbuf) + + ret.SPort = uint16(eventC.sport) + ret.DPort = uint16(eventC.dport) + ret.NetNS = uint32(eventC.netns) + + return +} + +func tcpV6Timestamp(data *[]byte) uint64 { + eventC := (*C.struct_tcp_ipv6_event_t)(unsafe.Pointer(&(*data)[0])) + return uint64(eventC.timestamp) +} diff --git a/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/event_common.go b/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/event_common.go new file mode 100644 index 0000000000..96c2efb613 --- /dev/null +++ b/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/event_common.go @@ -0,0 +1,55 @@ +package tracer + +import ( + "net" +) + +type EventType uint32 + +// These constants should be in sync with the equivalent definitions in the ebpf program. +const ( + EventConnect EventType = 1 + EventAccept = 2 + EventClose = 3 +) + +func (e EventType) String() string { + switch e { + case EventConnect: + return "connect" + case EventAccept: + return "accept" + case EventClose: + return "close" + default: + return "unknown" + } +} + +// TcpV4 represents a TCP event (connect, accept or close) on IPv4 +type TcpV4 struct { + Timestamp uint64 // Monotonic timestamp + CPU uint64 // CPU index + Type EventType // connect, accept or close + Pid uint32 // Process ID, who triggered the event + Comm string // The process command (as in /proc/$pid/comm) + SAddr net.IP // Local IP address + DAddr net.IP // Remote IP address + SPort uint16 // Local TCP port + DPort uint16 // Remote TCP port + NetNS uint32 // Network namespace ID (as in /proc/$pid/ns/net) +} + +// TcpV6 represents a TCP event (connect, accept or close) on IPv6 +type TcpV6 struct { + Timestamp uint64 // Monotonic timestamp + CPU uint64 // CPU index + Type EventType // connect, accept or close + Pid uint32 // Process ID, who triggered the event + Comm string // The process command (as in /proc/$pid/comm) + SAddr net.IP // Local IP address + DAddr net.IP // Remote IP address + SPort uint16 // Local TCP port + DPort uint16 // Remote TCP port + NetNS uint32 // Network namespace ID (as in /proc/$pid/ns/net) +} diff --git a/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/offsetguess.go b/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/offsetguess.go new file mode 100644 index 0000000000..b277d4ae38 --- /dev/null +++ b/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/offsetguess.go @@ -0,0 +1,394 @@ +// +build linux + +package tracer + +import ( + "encoding/binary" + "fmt" + "math/rand" + "net" + "os" + "strconv" + "strings" + "syscall" + "time" + "unsafe" + + "github.com/iovisor/gobpf/elf" +) + +/* +#include "../../tcptracer-bpf.h" +*/ +import "C" + +type tcpTracerStatus C.struct_tcptracer_status_t + +const ( + // When reading kernel structs at different offsets, don't go over that + // limit. This is an arbitrary choice to avoid infinite loops. + threshold = 200 + + // The source port is much further away in the inet sock. + thresholdInetSock = 2000 +) + +// These constants should be in sync with the equivalent definitions in the ebpf program. +const ( + stateUninitialized C.__u64 = 0 + stateChecking = 1 // status set by userspace, waiting for eBPF + stateChecked = 2 // status set by eBPF, waiting for userspace + stateReady = 3 // fully initialized, all offset known +) + +var stateString = map[C.__u64]string{ + stateUninitialized: "uninitialized", + stateChecking: "checking", + stateChecked: "checked", + stateReady: "ready", +} + +// These constants should be in sync with the equivalent definitions in the ebpf program. +const ( + guessSaddr C.__u64 = 0 + guessDaddr = 1 + guessFamily = 2 + guessSport = 3 + guessDport = 4 + guessNetns = 5 + guessDaddrIPv6 = 6 +) + +var whatString = map[C.__u64]string{ + guessSaddr: "source address", + guessDaddr: "destination address", + guessFamily: "family", + guessSport: "source port", + guessDport: "destination port", + guessNetns: "network namespace", + guessDaddrIPv6: "destination address IPv6", +} + +const listenIP = "127.0.0.2" + +var zero uint64 + +type freePort struct { + port uint16 + err error +} + +type fieldValues struct { + saddr uint32 + daddr uint32 + sport uint16 + dport uint16 + netns uint32 + family uint16 + daddrIPv6 [4]uint32 +} + +func startServer() (chan struct{}, uint16, error) { + // port 0 means we let the kernel choose a free port + addr := fmt.Sprintf("%s:0", listenIP) + l, err := net.Listen("tcp4", addr) + if err != nil { + return nil, 0, err + } + lport, err := strconv.Atoi(strings.Split(l.Addr().String(), ":")[1]) + if err != nil { + return nil, 0, err + } + + stop := make(chan struct{}) + go acceptV4(l, stop) + + return stop, uint16(lport), nil +} + +func acceptV4(l net.Listener, stop chan struct{}) { + for { + _, ok := <-stop + if ok { + conn, err := l.Accept() + if err != nil { + l.Close() + return + } + conn.Close() + } else { + // the main thread closed the channel, which signals there + // won't be any more connections + l.Close() + return + } + } +} + +func compareIPv6(a [4]C.__u32, b [4]uint32) bool { + for i := 0; i < 4; i++ { + if a[i] != C.__u32(b[i]) { + return false + } + } + return true +} + +func ownNetNS() (uint64, error) { + var s syscall.Stat_t + if err := syscall.Stat("/proc/self/ns/net", &s); err != nil { + return 0, err + } + return s.Ino, nil +} + +func ipv6FromUint32Arr(ipv6Addr [4]uint32) net.IP { + buf := make([]byte, 16) + for i := 0; i < 16; i++ { + buf[i] = *(*byte)(unsafe.Pointer((uintptr(unsafe.Pointer(&ipv6Addr[0])) + uintptr(i)))) + } + return net.IP(buf) +} + +func htons(a uint16) uint16 { + var arr [2]byte + binary.BigEndian.PutUint16(arr[:], a) + return nativeEndian.Uint16(arr[:]) +} + +func generateRandomIPv6Address() (addr [4]uint32) { + // multicast (ff00::/8) or link-local (fe80::/10) addresses don't work for + // our purposes so let's choose a "random number" for the first 32 bits. + // + // chosen by fair dice roll. + // guaranteed to be random. + // https://xkcd.com/221/ + addr[0] = 0x87586031 + addr[1] = rand.Uint32() + addr[2] = rand.Uint32() + addr[3] = rand.Uint32() + + return +} + +// tryCurrentOffset creates a IPv4 or IPv6 connection so the corresponding +// tcp_v{4,6}_connect kprobes get triggered and save the value at the current +// offset in the eBPF map +func tryCurrentOffset(module *elf.Module, mp *elf.Map, status *tcpTracerStatus, expected *fieldValues, stop chan struct{}) error { + // for ipv6, we don't need the source port because we already guessed + // it doing ipv4 connections so we use a random destination address and + // try to connect to it + expected.daddrIPv6 = generateRandomIPv6Address() + + ip := ipv6FromUint32Arr(expected.daddrIPv6) + + bindAddress := fmt.Sprintf("%s:%d", listenIP, expected.dport) + if status.what != guessDaddrIPv6 { + // signal the server that we're about to connect, this will block until + // the channel is free so we don't overload the server + stop <- struct{}{} + conn, err := net.Dial("tcp4", bindAddress) + if err != nil { + return fmt.Errorf("error dialing %q: %v", bindAddress, err) + } + + // get the source port assigned by the kernel + sport, err := strconv.Atoi(strings.Split(conn.LocalAddr().String(), ":")[1]) + if err != nil { + return fmt.Errorf("error converting source port: %v", err) + } + + expected.sport = uint16(sport) + + // set SO_LINGER to 0 so the connection state after closing is + // CLOSE instead of TIME_WAIT. In this way, they will disappear + // from the conntrack table after around 10 seconds instead of 2 + // minutes + if tcpConn, ok := conn.(*net.TCPConn); ok { + tcpConn.SetLinger(0) + } else { + return fmt.Errorf("not a tcp connection unexpectedly") + } + + conn.Close() + } else { + conn, err := net.DialTimeout("tcp6", fmt.Sprintf("[%s]:9092", ip), 10*time.Millisecond) + // Since we connect to a random IP, this will most likely fail. + // In the unlikely case where it connects successfully, we close + // the connection to avoid a leak. + if err == nil { + conn.Close() + } + } + + return nil +} + +// checkAndUpdateCurrentOffset checks the value for the current offset stored +// in the eBPF map against the expected value, incrementing the offset if it +// doesn't match, or going to the next field to guess if it does +func checkAndUpdateCurrentOffset(module *elf.Module, mp *elf.Map, status *tcpTracerStatus, expected *fieldValues) error { + // get the updated map value so we can check if the current offset is + // the right one + if err := module.LookupElement(mp, unsafe.Pointer(&zero), unsafe.Pointer(status)); err != nil { + return fmt.Errorf("error reading tcptracer_status: %v", err) + } + + if status.state != stateChecked { + return fmt.Errorf("invalid guessing state while guessing %v, got %v expected %v", whatString[status.what], stateString[status.state], stateString[stateChecked]) + } + + switch status.what { + case guessSaddr: + if status.saddr == C.__u32(expected.saddr) { + status.what = guessDaddr + } else { + status.offset_saddr++ + status.saddr = C.__u32(expected.saddr) + } + status.state = stateChecking + case guessDaddr: + if status.daddr == C.__u32(expected.daddr) { + status.what = guessFamily + } else { + status.offset_daddr++ + status.daddr = C.__u32(expected.daddr) + } + status.state = stateChecking + case guessFamily: + if status.family == C.__u16(expected.family) { + status.what = guessSport + // we know the sport ((struct inet_sock)->inet_sport) is + // after the family field, so we start from there + status.offset_sport = status.offset_family + } else { + status.offset_family++ + } + status.state = stateChecking + case guessSport: + if status.sport == C.__u16(htons(expected.sport)) { + status.what = guessDport + } else { + status.offset_sport++ + } + status.state = stateChecking + case guessDport: + if status.dport == C.__u16(htons(expected.dport)) { + status.what = guessNetns + } else { + status.offset_dport++ + } + status.state = stateChecking + case guessNetns: + if status.netns == C.__u32(expected.netns) { + status.what = guessDaddrIPv6 + } else { + status.offset_ino++ + // go to the next offset_netns if we get an error + if status.err != 0 || status.offset_ino >= threshold { + status.offset_ino = 0 + status.offset_netns++ + } + } + status.state = stateChecking + case guessDaddrIPv6: + if compareIPv6(status.daddr_ipv6, expected.daddrIPv6) { + // at this point, we've guessed all the offsets we need, + // set the status to "stateReady" + status.state = stateReady + } else { + status.offset_daddr_ipv6++ + status.state = stateChecking + } + default: + return fmt.Errorf("unexpected field to guess: %v", whatString[status.what]) + } + + // update the map with the new offset/field to check + if err := module.UpdateElement(mp, unsafe.Pointer(&zero), unsafe.Pointer(status), 0); err != nil { + return fmt.Errorf("error updating tcptracer_status: %v", err) + } + + return nil +} + +// guess expects elf.Module to hold a tcptracer-bpf object and initializes the +// tracer by guessing the right struct sock kernel struct offsets. Results are +// stored in the `tcptracer_status` map as used by the module. +// +// To guess the offsets, we create connections from localhost (127.0.0.1) to +// 127.0.0.2:$PORT, where we have a server listening. We store the current +// possible offset and expected value of each field in a eBPF map. Each +// connection will trigger the eBPF program attached to tcp_v{4,6}_connect +// where, for each field to guess, we store the value of +// (struct sock *)skp + possible_offset +// in the eBPF map. Then, back in userspace (checkAndUpdateCurrentOffset()), we +// check that value against the expected value of the field, advancing the +// offset and repeating the process until we find the value we expect. Then, we +// guess the next field. +func guess(b *elf.Module) error { + currentNetns, err := ownNetNS() + if err != nil { + return fmt.Errorf("error getting current netns: %v", err) + } + + mp := b.Map("tcptracer_status") + + pidTgid := uint64(os.Getpid())<<32 | uint64(syscall.Gettid()) + + status := &tcpTracerStatus{ + state: stateChecking, + pid_tgid: C.__u64(pidTgid), + } + + // if we already have the offsets, just return + err = b.LookupElement(mp, unsafe.Pointer(&zero), unsafe.Pointer(status)) + if err == nil && status.state == stateReady { + return nil + } + + stop, listenPort, err := startServer() + if err != nil { + return err + } + defer close(stop) + + // initialize map + if err := b.UpdateElement(mp, unsafe.Pointer(&zero), unsafe.Pointer(status), 0); err != nil { + return fmt.Errorf("error initializing tcptracer_status map: %v", err) + } + + expected := &fieldValues{ + // 127.0.0.1 + saddr: 0x0100007F, + // 127.0.0.2 + daddr: 0x0200007F, + // will be set later + sport: 0, + dport: listenPort, + netns: uint32(currentNetns), + family: syscall.AF_INET, + } + + for status.state != stateReady { + if err := tryCurrentOffset(b, mp, status, expected, stop); err != nil { + return err + } + + if err := checkAndUpdateCurrentOffset(b, mp, status, expected); err != nil { + return err + } + + // Stop at a reasonable offset so we don't run forever. + // Reading too far away in kernel memory is not a big deal: + // probe_kernel_read() handles faults gracefully. + if status.offset_saddr >= threshold || status.offset_daddr >= threshold || + status.offset_sport >= thresholdInetSock || status.offset_dport >= threshold || + status.offset_netns >= threshold || status.offset_family >= threshold || + status.offset_daddr_ipv6 >= threshold { + return fmt.Errorf("overflow while guessing %v, bailing out", whatString[status.what]) + } + } + + return nil +} diff --git a/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/offsetguess_unsupported.go b/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/offsetguess_unsupported.go new file mode 100644 index 0000000000..2f35863d5e --- /dev/null +++ b/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/offsetguess_unsupported.go @@ -0,0 +1,13 @@ +// +build !linux + +package tracer + +import ( + "fmt" + + "github.com/iovisor/gobpf/elf" +) + +func guess(b *elf.Module) error { + return fmt.Errorf("not supported on non-Linux systems") +} diff --git a/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/tcptracer-ebpf.go b/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/tcptracer-ebpf.go new file mode 100644 index 0000000000..364b175a16 --- /dev/null +++ b/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/tcptracer-ebpf.go @@ -0,0 +1,235 @@ +// Code generated by go-bindata. +// sources: +// ../dist/tcptracer-ebpf.o +// DO NOT EDIT! + +package tracer + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _tcptracerEbpfO = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xc4\x5c\x0f\x6c\x5b\xc7\x79\xbf\x47\x8a\x26\x25\xc7\x95\xe6\x84\x09\xcd\xfd\x81\xb2\x06\x89\xa6\xd6\xa9\xfe\x59\x56\xb5\xb5\xd3\x92\x26\xd5\x84\x00\xe2\x02\x6b\x11\x8c\x76\x14\xcb\xd2\x96\x42\xd7\xa6\x45\x3a\xd2\x0b\x8b\xce\x5b\xe1\xd6\x13\xd2\x42\x4e\xd3\x42\x35\xb2\x41\x94\xe5\x46\xdb\xb2\x45\xc0\xb6\xda\x18\x86\x51\x28\x82\x4d\x1b\xf6\x47\x18\xbc\x41\x03\x5c\x4c\x18\xb2\x42\x1b\xd0\x41\x2d\x32\x4f\x0d\xbc\x71\xe0\x7d\xbf\xc7\xf7\xee\x7b\xf7\x1e\xc9\x38\xcd\x08\xa4\x9f\xef\x77\xf7\xdd\x77\x7f\xbe\xfb\xee\x77\xf7\x4e\xfd\xf5\xa7\x9e\x79\x3a\x60\x18\xc2\xfa\x19\xf8\xcf\xf5\x7b\xd2\xfe\xe7\x08\xfe\xf7\x27\x85\x21\xca\x0f\x12\x76\x49\x08\xf1\x01\x21\x44\xb1\x6d\xaf\x52\x4d\x9b\xa9\x9c\xc4\x8b\xf1\x7d\x99\x2e\xaf\x50\xb9\x70\x40\x88\xbd\x4a\xa5\x52\xbe\x86\x74\x50\x88\xfd\x4a\xa5\x12\x63\x46\x6f\xb4\xd8\xf5\x06\xaa\x69\xe0\x5f\x87\x34\x1f\x4a\x30\xbb\xa3\xd2\xce\x0d\xd4\x53\x8c\x8f\xb8\xec\x8e\x6a\xec\x5c\x42\x7f\xa3\xe2\x21\x99\x63\x1e\x20\xbc\x11\xbd\xa0\x10\xe2\x74\x58\x88\x4e\x21\xc4\x1c\xe4\x44\xf8\x03\x06\xd7\x1f\xf1\xb1\x5b\x0e\x53\x3a\x1a\x0e\x93\xfd\xf3\x48\x1b\x21\x4a\xaf\xf0\x7e\xf5\xd0\xf8\xe6\x51\x2e\x38\x29\xdb\x31\x11\xac\xc8\xfa\xcc\xf3\x11\x6a\xff\xfd\x94\xff\xc6\x07\x49\xc6\x02\x42\x54\x2a\x95\x8a\x65\xff\xe8\x07\x7f\x44\xe3\xdc\x8a\x7a\xaf\xee\x56\x48\xee\x40\x6e\x43\x6e\x41\x6e\x42\x6e\x40\xde\x84\x5c\x87\x5c\x83\x5c\x86\x5c\x82\x5c\x84\xbc\x0c\x79\x11\x72\x1e\x32\x07\x39\x4d\xfd\x0c\xd0\x7c\x17\xbb\x29\x5d\x8c\x53\xbe\x79\xbe\x03\xfd\x27\x3d\x73\x36\x46\xe9\xee\x2e\x2a\xd7\x7d\x11\x78\x27\xf0\x4e\xe0\x64\xd7\xcc\x75\x11\xde\x46\xed\x31\x2f\xf4\x50\xfa\x24\xb5\xd3\x2c\x0c\x91\x5f\xcd\x93\x77\x17\x17\xd0\x9f\x31\xea\x8f\x39\x3f\x4a\xf9\xb3\x09\xd4\x8f\xfe\x2f\xac\x2b\xe3\x92\x9a\x2d\xc8\xfc\x74\xf7\x6d\xb4\x67\x1e\x69\x1a\xd7\xf2\xcf\xd1\x78\xcf\xb5\x92\xbf\x64\xaf\xbe\x25\xf1\xb9\x80\x10\xd5\x16\x65\xbb\xbf\x87\x7a\x72\xd0\xa3\x79\x98\x99\xbd\x28\xd3\xd9\xee\x5d\xe4\x7f\x56\xa6\x4f\x07\xa8\x9e\x94\x39\x25\xd3\xe3\x2f\x53\xfd\x29\x73\x9a\x64\xfe\x0c\x95\x0b\x52\xb9\xf1\xaf\x61\xbe\x87\x31\xaf\xdd\x34\xaf\x19\x23\x2d\xc7\x3d\x6a\x7c\x01\xfe\xf7\x0a\xad\x0b\xc3\x90\x78\x48\xfc\x91\x70\xfa\x61\x36\xfe\xac\xd4\x6b\xff\x12\xa5\xcb\x25\x92\x55\x2f\x7e\xb6\x52\xa9\x58\xf3\x58\x4e\x13\x5e\xf5\xf7\xea\x92\x9e\x29\x91\x5e\x36\x4e\xfd\x08\x89\x65\xea\x67\x7c\xa8\xe2\xf4\xef\x74\x7c\x12\xf2\x2e\xca\x9f\x80\xbc\x83\x72\xd3\xc8\xdf\x87\x7f\x90\x3f\x58\xf6\xda\xef\x77\xb7\x6b\x12\xed\x6a\x71\xb4\xc7\x3c\xdf\x59\x57\xef\xae\x56\xaf\xab\xae\xde\x09\xc7\x38\xd8\x7a\x3d\x75\xf5\xee\x68\xf5\xc8\x3f\xdb\x1f\x74\x97\x9f\x46\xf9\x88\x66\xbc\xcd\xf3\xe4\xcf\xe6\x35\x1a\x2f\x9d\xbd\x7d\x4d\xff\x52\xab\x18\xff\x31\x9a\x8f\xd4\x35\x9a\x87\xf4\xf0\x00\xf9\xe3\x0a\xe6\xa3\x9b\xe6\x6d\xa6\x74\x07\xf3\x33\x4c\xe5\xaf\xd3\xbc\xa4\x4f\xfe\x82\x94\xd1\x96\xac\xe2\x87\x73\x90\xd1\x20\xf9\xe9\x73\x32\x3e\x09\x11\x0d\x9c\xa2\xb4\x81\xb4\x41\x7e\x6e\x96\x46\x95\x79\xb7\xe3\xe1\xbc\x12\x27\x9c\xfd\xca\xa1\x5f\x1d\xe8\x57\x07\x8b\xcb\x3d\x6c\x1f\x9a\xd6\xc4\xe9\x90\x78\x56\xca\xa8\xf1\x98\x8c\xb3\x51\xe3\x63\x72\x7c\xaa\xeb\x22\x24\xf3\x29\x1e\x58\xed\x49\xc3\x4f\x8b\xf0\xdf\xf6\xa7\xbd\xfd\xd0\x7f\xbe\x26\x3d\xe7\xeb\x84\x66\xbe\x4e\x0b\x8c\x2b\x64\xb5\x1f\x3f\x74\xc4\xfb\x4f\x77\xf4\xd6\xda\x69\xc8\xf6\x6d\x60\xfd\xf5\xb0\xf6\xd3\x3c\x9b\x25\x8a\xab\x3a\x7f\x73\xae\x07\xde\xfe\x54\x09\x7e\x12\xbf\x8d\xfa\x1f\x51\xea\xb7\xd6\x6f\xfb\x01\x77\xbd\x77\x7c\xe3\x86\xe5\x5f\x6f\xa1\xde\x23\xac\xdd\x88\x03\xa5\x4e\xcf\x76\xef\xfb\xb6\x1b\xfe\x1a\xdf\x41\xfd\x87\x58\xbb\x4f\x51\xbd\xbf\xea\xae\xf7\x94\x6f\xbb\x4f\xa1\xdd\xdf\x43\xbd\x01\x99\x9f\x2a\x9d\x80\xbd\x6d\x17\x4f\x69\xc4\x2f\xeb\xf1\xa3\xff\x37\x5e\x16\xe3\xbc\x2c\xc1\xd6\xeb\xa8\xcb\x6e\xc2\x8f\x1f\xc1\x4f\xa2\x07\xee\x73\xf1\x2a\x2f\xbd\xea\x3a\x35\x4f\x61\x1c\x1a\xe4\x7f\x36\x0f\xa3\x25\x63\xf3\xb0\xff\xa1\x74\x29\x51\xe3\x27\xc2\xc1\x2f\xcc\x59\x94\x0b\x7c\x8a\x78\x58\xe0\xbf\x89\x87\xcd\x82\x87\x81\x7f\xbd\x81\x75\x1c\x33\x18\x0f\xbb\xff\x07\x42\x1d\x1f\xf0\xae\x38\xf6\xe7\x38\x78\x17\xd6\x6b\x31\x0e\xde\x11\x07\xef\x88\x83\xa7\xc4\xc1\xbb\xe2\xe0\x5d\x71\xf0\xae\x38\x78\x57\x1c\xbc\x8b\xc5\x4b\x2b\x9e\x16\xe3\x53\x4a\xdc\xba\x11\x04\x0f\x1b\x9e\x04\x4f\x98\x42\x7f\x89\x87\x99\x79\xf0\xaf\x61\xd4\x03\xbe\x66\xe6\x69\x3f\x35\x0b\xe0\x5b\x63\xb0\x3b\x0c\xde\x96\xef\x41\xfe\x10\xf2\xd1\xce\x61\xf0\xb4\x3c\xe2\x5f\x61\x14\xf9\xe8\xd7\x30\x78\x5a\x1e\xfc\x6b\x78\x4d\x19\x87\x54\x01\xbc\x6b\xec\x6f\xc0\xe7\xc0\xbb\xc6\x68\x1c\xcb\x4f\xd1\xf8\xce\x85\xc0\xbb\x4e\xde\x22\xde\xd5\x02\xde\x35\xf6\x4f\xa8\x07\xbc\x6b\x8c\xc6\x7d\xa6\x00\xde\x35\xb6\x8d\x7c\xf0\xa9\x16\xf0\xae\x0b\xb4\x7f\x8d\x9f\xc0\xfc\x8d\x81\x07\x16\x88\x8f\xa5\x2e\x80\xa7\xc1\xee\x38\xda\x51\x3c\x69\xcd\xa3\xca\x7b\x26\x02\x6f\x88\xaa\xcb\x97\x5f\xa3\x72\xe1\xb0\x10\x37\x2b\x95\x4a\xfb\x20\xa5\x9d\x71\xa7\xc7\xb1\xbf\xb9\xf6\x91\x12\xea\x7d\x28\x02\xff\xed\x41\x1a\xf5\x38\xd6\x45\x27\x5b\xf7\x93\xda\x7d\xf0\x2a\xf5\x23\x82\x7d\x06\x72\x22\xf2\x4d\xe6\xbf\x8d\xf9\x97\xd5\xdf\x62\x1c\x3c\x3d\xbe\x07\xb9\x8b\x78\xf9\x0e\xe4\x3e\xf0\x1d\x85\x0f\x9a\xe7\xc9\x0f\x38\x8f\xaa\xb6\xbf\xc3\x63\x9c\x22\xca\x3e\xeb\xad\x1f\x63\xfa\x9d\x4d\xe8\x3b\xf5\xf6\x9a\xb4\x1b\x61\xfa\xbb\x5a\xfd\xfa\xbc\xf3\x9d\x77\xc9\x3b\xf7\x9b\xe4\x9d\x3b\x0d\xf1\x98\x1d\x4f\x1e\xa3\xe5\xd5\xbf\x0b\xbf\xb8\x4a\x7e\x62\x7e\x0b\xfe\xf1\x3c\xf9\x4f\x39\x45\xfa\xe3\x57\x48\x9a\xaf\xc1\x6f\x5e\x44\xfc\x59\xc3\x39\x76\x81\xfc\x6f\x66\x15\x7e\x34\x36\x0f\xde\xba\x0f\xde\xfa\x22\xe3\xb5\x9f\x27\x9e\x6a\xfc\x22\xf9\xf1\x70\x8c\xec\xfd\x06\xec\xd5\xf6\x83\x9f\xa7\x7e\xfc\x12\xa5\x6d\x9e\x4a\xe3\x54\x7e\x98\xe3\xc7\xa8\xbe\xb1\x08\xe2\x64\x07\xdb\x0f\x47\xb4\x71\xc0\xda\x6f\xac\xb4\x73\xdc\x86\x34\xbc\x76\xa2\x95\x36\x4a\x6b\x7c\x4e\x1b\x58\xa7\x90\x31\xb9\x6f\xd9\xfb\xce\x4f\x3f\x4c\x1b\xde\x44\x98\x78\x94\xd5\xcf\x7a\x7a\x9f\x7e\x98\x26\x72\xee\x00\xe5\xa7\x9f\xa7\xf9\xb1\xe2\x41\x7a\xe1\x11\xb4\x9f\xfa\x7b\xdb\xa0\xf6\x65\xe3\x5d\xc0\x3b\x18\xde\x0d\x3c\x86\xf5\xfd\x61\x17\x3f\xe0\xf1\xa9\x47\x1b\x9f\xd0\x7f\x87\xde\x64\x03\x7a\xcd\xf0\xa8\x88\xe4\x33\x23\x6c\xfe\x68\x5f\x30\x4f\xd3\x3e\x60\x4e\xe7\x5c\xed\xd8\xf6\xe3\x1d\xb8\x7f\x89\xb6\x52\xc0\x37\xe1\xd7\x13\xc6\x75\xc9\x27\xca\xe7\xf5\xf3\x52\x3d\x7f\x84\x65\xb9\x65\x59\x0f\xe7\x57\x59\xec\x8b\x8d\xdf\x7f\x11\xb1\x31\x91\x1f\x35\xda\xa4\xfd\x7b\xaf\xf7\x25\xcc\xcf\x6f\xa1\x7e\xda\xd7\xcb\x5f\xa4\x72\xba\xb8\xb0\xae\x89\x43\x33\x25\xb2\x3b\x61\x7c\xbf\x12\x50\x78\xbf\xca\x7b\xd2\x68\x5f\x1a\xfb\x49\x16\xfb\x46\x16\xfb\x86\xd5\x8f\x34\xf6\x19\xf3\x4a\xac\xe1\xf6\x28\x71\xea\x4a\x67\x5d\xbd\x3d\xad\x5e\x57\x5d\xbd\x1d\x5d\x1c\xbe\xd2\x53\x57\xef\xae\x56\x0f\xf1\x3b\xe6\x2e\x3f\xea\x8c\xdf\x5f\xb4\xe7\x8d\xf4\xac\xf8\x3d\xea\x19\xbf\x77\xb5\xf7\x06\x18\xff\xb1\x25\xc4\xd7\x3d\xc4\xd7\x57\x11\x7f\x31\x1f\xdd\x34\x6f\x33\x25\xeb\x7e\x67\x85\xca\x5f\xa7\x79\x49\x9f\x5c\xa5\x78\xdc\xf2\x09\x69\xcf\x7d\x6f\x40\x01\xd8\xbe\x37\xa0\xb8\x6d\xc7\xdd\x8f\x91\x9e\xeb\x7e\x96\xd6\x4d\x48\xd0\x79\xd8\xe9\xc7\x4b\xbe\x7e\xdc\x2f\xd3\xf6\x7a\xd0\xf3\x71\x9d\xdf\x48\x3e\x11\x54\xc7\xf7\x92\x6c\x83\x7d\x6f\x6d\x5e\xa3\xb8\xc2\xfb\x59\x1c\xb6\xcf\x4f\x86\xe3\x5e\x2c\x55\xfa\x63\xf8\xf1\xa7\x90\xb6\xfc\x3f\x87\xf4\xab\x48\x17\x90\xa6\xf1\x2e\x1f\xa6\x76\xdc\xc6\xfe\x91\xed\xa6\x7d\x71\x0e\xf1\xd8\x8e\xcb\xd8\x1f\x4b\xab\xa8\xe7\xf3\x88\xd3\x1b\x0a\xaf\x33\x4b\x37\x15\x7e\x57\x5e\x45\xff\x5b\xc8\xbf\xac\xfd\x28\x16\x50\xc7\xf5\x46\x48\x88\x21\x8c\xc3\x91\x06\xe7\x21\xe8\x38\x3f\xdb\x71\x2a\x40\x71\x0a\xe5\x6a\xe7\xd0\x7b\x8e\x33\xff\x55\x69\x53\xe6\xbb\xd1\xf3\xd8\xa8\x12\x8f\x9a\xe6\xb5\x57\x12\xda\x76\xeb\x78\xed\xa8\x8e\x1f\xfa\xe8\x73\x5e\xbb\xd4\x84\x7e\x5d\x5e\xeb\x63\xb7\x21\x5e\xdb\x40\x5c\xd4\xf2\xda\x06\xe2\xa2\x96\xd7\xfa\xc4\xc5\x9d\x86\xe2\x62\x93\xbc\xb6\x76\x7f\x49\x7e\x62\x5e\x87\x7f\xe0\x1c\x38\x1e\x85\xdf\x22\x5e\x16\x87\xc9\xbf\xcc\x55\xf0\x59\xeb\x3c\xba\x02\xff\xe9\xde\x42\xfc\x04\x9f\x8d\xff\x03\xad\xd7\x65\xf0\xd9\x36\x3a\xdf\x46\x43\x14\x07\xc6\x07\xa8\xfe\x68\xcb\x55\x99\xb6\xe3\x26\xb5\xc3\x8e\x9b\xdf\xa0\x38\xe4\x8a\x9b\x07\x64\xdc\x74\xae\xd3\x75\xdf\x75\xfa\x15\x17\x4f\xf0\x2a\x4f\xf1\xf5\xcb\x8c\x57\xe9\xef\x35\x74\xfe\xfb\xe3\x89\xaf\xaf\x2a\xf1\xd5\x44\x5c\x30\x57\xb0\xde\xbb\xd5\x73\xae\x15\x17\xcd\x15\xc4\x87\x6e\xf5\xde\xc5\x2c\x6d\x79\xc4\x5f\xba\xff\xe0\x3c\xbb\xfc\x00\xc9\x24\x5d\x9f\xca\x7b\x0a\x19\x9f\x21\xb3\x63\x2f\xd7\xe2\xb6\xe4\xdd\xf1\x57\x60\x67\x8d\xc5\xe7\xe5\x77\x1d\x9f\x13\x8e\xf8\x1c\x12\xd4\x3f\x8b\x0f\x73\xfe\x6b\xdf\xf7\x0d\xd5\xd2\x72\xfd\x44\xe0\xd7\x33\x4d\xf2\xe2\xda\x7d\x5c\x41\xf9\x2e\x3a\x61\xcc\x1a\x41\x79\x1e\xc4\xb9\x08\xdf\x9f\x6e\xc0\x4e\x71\x81\xc6\x3f\xbd\x40\xfe\x6f\x9d\x5f\xcb\xf8\xee\xa5\x5b\xaf\x9b\xda\x73\xb6\xb5\xce\x37\x3d\xd7\xf9\x2d\xcd\x3a\x2f\x2e\x90\x9f\x65\x17\x96\x5d\xfe\xef\xf7\x1d\xd8\xcd\xbf\x0f\x31\xfe\x8d\x7a\xe3\xcd\xd6\x4b\x0b\x23\x04\xdc\xae\xff\x59\xac\xd3\x51\x1a\x5f\x6b\xbf\xf4\x19\xa7\x65\xed\x7e\xb9\x8c\xfd\xf2\x3f\x18\x2f\x57\xd7\x6f\x1a\xf7\x91\xde\xbc\x7c\x44\xe5\xe5\xd6\xf7\x3c\x9f\xf6\x2c\xfa\x7d\xcf\xf3\xd1\xd3\xf2\x72\xeb\x5e\xc5\x47\x4f\xcb\xcb\xad\x7b\x15\x1f\x3d\x2d\x2f\xb7\xee\x55\x8e\xb8\xcb\x8f\x38\xf7\x9f\xaf\xd9\xf3\xa9\xfa\xe5\x48\x93\xbc\x1c\xe3\x8f\xfb\x54\x9b\x97\xaf\x32\x5e\xbe\xc6\x78\xf9\xef\x31\x5e\xfe\x3a\x78\x39\x2d\x50\x37\x2f\xa7\x03\xb5\xbd\xbf\x90\x5f\x5b\xfb\xcb\x84\x71\x8b\xda\xeb\xf0\xdf\xaa\x5f\x85\xc4\x5f\x8a\xf7\xc6\x0f\xff\x53\xb4\x35\xc0\xd3\xdd\xfb\x8b\x75\x0f\xb3\xf8\xee\x78\x9b\x75\xaf\xc7\xda\xad\xe3\x6d\x23\x7e\xf7\x82\x1a\x7d\xce\xdb\x16\x9b\xd0\x6f\xf8\x3e\x52\x63\xb7\xa9\xfb\x48\x1f\xbb\xbe\xf7\x91\x3e\x7a\xbe\xf7\x91\x9a\x75\xb3\xd3\xd0\xba\x69\x92\xb7\xad\xc2\x2f\xac\x77\x20\xd7\xac\xef\x15\x6b\xd8\xef\xe1\x27\xdd\xe0\x07\xa5\x5d\xc5\xdf\x66\x96\xe1\x37\x6d\xe4\x87\x33\xf8\x2e\x9e\x3d\xf9\x1d\x5a\x57\xaf\x81\xaf\xbd\xf8\x26\xc5\xf5\xe3\xd4\x9e\xf1\x4f\x92\x8c\x86\xe9\x5e\xa9\x8c\x7b\xfb\xf1\xc7\x2c\x3c\x23\xe5\x73\x60\x0d\x51\x41\x17\xb0\xcf\x85\x90\x0e\xd1\xc6\x6e\xc5\xfd\x89\x16\xfa\x17\x5f\xaf\x31\xb9\x5c\x1d\xf7\x82\x4f\xd2\x3d\xda\x44\xe0\xcb\x54\xbe\xee\x7d\xe0\x6f\xd6\xc6\x39\xe4\xb8\x1f\x3b\x8d\xfb\x41\xeb\x9e\xb0\xf8\xbc\xfd\x8e\x29\x28\xd7\x4d\x02\xe3\x35\xc4\x78\xcc\x09\xc6\x03\xd5\xef\x06\xa9\xd2\xeb\x58\x77\xd3\x48\xff\x19\xd2\x67\x14\x1e\xe6\xe6\x59\xc4\xd3\x62\x61\xb5\xfd\x56\xb9\x24\xbe\xe3\xcd\xa1\xbc\xad\x37\xcb\xda\x57\x70\xf1\xd0\x49\xcd\x7d\xac\x59\xc2\x39\x7c\x05\xfc\x10\xef\x2f\xca\xe0\xe1\x73\xf8\x7e\x54\xa6\x65\xe0\xfa\x7e\x5f\xc6\x79\x20\x2a\xb0\x0f\x5a\xe9\x16\x22\x60\x65\xfa\x0c\x57\x9b\x7f\xbb\xfc\x61\x25\x3f\x99\x13\xaa\xbe\x64\x69\x2a\x0f\xac\xc6\x23\xeb\xfb\x10\xe7\x81\xd6\xbd\x3e\x3f\xaf\x5b\xfd\xf3\xec\xd7\x61\xd5\x6e\x7a\xd8\xda\x7f\x88\xa7\xde\x0e\x61\x7c\xf1\xfe\xca\x7b\xfc\xaf\xb2\xf1\x7f\xd5\xc5\x7f\x16\x7d\xcf\x21\x1f\x16\xbc\xfc\xb2\x6f\xf9\x47\x6b\xe9\xf7\xc7\x9f\x97\x99\x3f\xaf\x2a\xfe\x9c\x02\x9f\xf7\xf2\x67\xf7\xbd\xcd\x2c\x5b\x27\x8d\xf9\xeb\xbd\xfa\x03\xbf\x7f\x79\xbf\xc6\xcf\x3a\xdf\x98\x2b\xd8\xcf\xbb\x73\xec\xfc\xc3\xcf\x6d\xea\x77\x70\xef\x78\x71\xd9\xd1\x7f\x47\xbc\xc0\xbe\x91\xc4\x77\xfd\x39\xec\xef\xb7\x21\xb3\xc3\x0b\xac\xfd\x2f\xfd\xd8\xc6\x3f\x21\xc7\xf8\xef\x95\xf1\xb0\xdf\x27\xe0\x3d\xcc\x11\xf5\x3d\x86\xf5\x9e\x82\x9f\xcf\xa2\xad\x03\xae\xf7\x15\xba\xf7\x1f\xee\xf3\xd9\x51\x76\x3e\xfb\x10\x9d\xcf\x16\x70\xae\x46\xfd\xd9\x05\xbc\xdf\x59\x78\x1b\x7c\xa9\xfe\xfe\xff\x76\x13\xbc\x21\x8c\xf7\x0a\xf5\xde\xd1\x15\x17\xc0\x63\x17\xf0\x1e\xa6\x81\xf3\xa1\x9e\xf7\x58\x7c\x62\xd7\x93\x4f\xe8\xce\x19\xc5\x85\x25\x8c\xc3\x94\x2b\x8e\xf9\xdf\x3f\xd7\x3b\x1f\xa2\xde\x78\xb3\xf5\x7a\x9d\x0f\xc7\x11\x0f\x7f\x00\xbc\x3e\x2f\x9f\xaa\xf6\x37\x82\xef\x68\x2e\x7e\x3e\x05\x7e\xfe\xef\x95\x80\x12\x1f\xf0\xae\x03\xdf\x3b\x39\xdf\x28\xc6\x13\x0a\x4f\x4f\xe3\x5d\xae\x7d\xce\xc4\x79\x3f\xfe\x26\xe4\x06\xa4\xc5\x0b\x6e\x42\x5a\xf7\xe5\xd6\xf7\xa1\xd7\x11\x27\x71\x0e\x8a\xd3\xbb\x11\xfb\xbb\xe6\xeb\xf0\x8f\xfa\xe7\xd0\x0d\x9f\x73\xa8\x8e\xa7\xbe\xe9\x7c\x87\xc6\x78\xaa\xfd\xce\x0d\xf7\x7d\x25\x5a\x2f\x76\x7c\xff\x6b\x57\x3c\x59\xd7\xf2\x0f\x35\xae\x59\xef\x84\xe7\x20\xa3\x81\x6f\x4b\xdc\x19\x5f\xaa\x43\x7a\xf4\x30\xbd\xf3\x4d\x95\x36\x15\x7d\xfb\x3c\xf7\x86\xd0\xcd\x13\xc5\x1d\x43\x1c\x3d\xfc\xfb\xd4\x5f\x47\x3c\x5b\x92\xf1\x6c\x52\x1b\xcf\xcc\x6b\x88\x13\xd8\x4f\x42\x62\xe5\x3d\xf6\xb7\x6f\xca\xf3\xe0\x7b\xe7\x6f\xd6\xb9\xef\x36\xe4\x36\xe4\x2d\xc8\x2d\xc5\x9f\xde\x3b\xff\x7c\xcb\xc3\x3f\xeb\x9f\xfb\xb6\xee\xf1\xdc\xb7\xdd\xa4\x3e\x3f\xef\x6e\xf8\xe8\x5b\xeb\x23\xdc\xea\x3e\xe7\x6e\xfa\x9c\xe7\xec\x75\xb2\xe3\xb1\x4e\xfe\xb5\xc1\x75\xb2\x09\x7e\x40\xf3\x32\x11\xc0\x3b\x02\xf4\xa7\xfe\x79\xa9\x15\x7e\x8c\x77\x00\xab\x34\xff\x13\x2d\xf4\x60\xab\x7c\x0c\xf5\xe0\xfd\x97\xf5\xfe\x8c\xa2\x8a\xe3\x1c\x15\xea\xa9\xf5\x8f\xfc\x14\xef\xed\x5e\x83\x3f\xbe\x68\xbf\x7b\x0e\x38\xbe\xf3\xa5\x9f\x2f\x30\x7e\xa4\xf2\x9b\x54\xc9\xf2\xc3\xcb\x48\x5b\x7e\xfb\x12\xd2\xd6\x3d\xdc\xa2\xc2\x27\xb3\xf1\x97\x99\xff\xbd\xe2\x1a\xcf\x79\xed\x78\x5e\xae\x13\x77\xe8\x00\xeb\x8e\x3b\x74\xd1\x9d\x2a\x2d\x7a\xc4\x1d\x8a\xa7\xde\x71\xe7\xa7\x68\xbc\x1d\x71\x67\xca\x27\xee\x94\xf1\xf7\x33\x76\xdc\xa1\x09\x1f\xc7\xfb\x9d\xa8\x41\x0d\x18\xc7\xdf\x3d\x45\x83\x1d\xc2\x39\x5e\xee\xf6\xdd\x57\xa7\x7d\x11\x57\xfb\x1a\x8d\x8b\x09\x1f\x9e\xdd\xe2\xf8\x4f\xb4\x78\x63\x06\xf8\x5e\xa4\x01\xac\xc3\xf2\x4f\x07\xd6\xa5\xc1\xaa\x7a\x97\x1d\x7f\x2f\xf6\xc9\xc4\x33\xe2\x7f\x1d\x6f\x5b\x75\xbf\x45\x49\x37\x42\x22\x77\x40\xc5\x63\xc0\x13\x61\x15\xff\x83\x00\xe1\x91\x88\x8a\x9f\x03\x3e\x1f\x54\xf1\xfb\x80\xaf\xb3\x7a\xfe\xcd\x20\x7c\x9a\xd5\xf3\xe7\xc0\x17\x59\x7b\x2e\xa2\x3d\x1b\xac\x7c\x02\xf8\x16\xc3\x7b\x2c\x9c\xb5\xe7\x6f\x51\xbf\x68\x55\xf1\xdf\x01\xde\xc1\xf0\xeb\xb2\xfd\xad\xae\xf6\xb7\x00\xdf\x65\xf8\x77\x0d\xc2\x2f\xb2\xf6\x7c\x1b\xf8\x3a\xc3\xd7\x83\x84\xf3\x7e\xfd\x05\xca\xef\xb0\xf6\x77\xc8\xf4\x41\x17\x7e\x3a\x40\xf8\x74\x48\xc5\xef\x1a\x84\x2f\x31\xfc\x1f\x81\xaf\x31\xfc\x0f\x81\xdf\x64\xf8\xd7\x51\x3f\x1f\x9f\x24\xf0\x11\x86\xff\x08\xf8\x9a\x0a\x8b\x49\xe0\xfc\x8f\x1a\xbf\x0f\xbb\x09\x86\xdf\x94\xfd\x3c\x24\x46\x5b\x54\xfc\x32\xf0\xa5\x36\x15\x9f\x02\xbe\xcb\xf0\x11\xe0\x1b\xac\xfe\x08\xf0\x2d\x86\xff\x76\x80\xf0\x29\x66\x77\x0d\xe5\x6f\xb2\xfa\xe7\x2d\x9c\xf9\x43\x97\xd5\x1e\x86\x7f\x05\xf5\xc7\xd8\xbc\x2f\xa3\xfc\x10\xab\x3f\x07\xbc\xeb\x3e\x15\x1f\x05\xde\xc1\xf0\x4e\xe0\x31\x86\xff\x10\x76\x73\xac\xbf\x5f\x00\xbe\xcd\xf0\x49\x59\x4f\xbb\x18\x65\xf1\x70\x08\xf8\x24\xc3\x05\xf0\x2d\x36\x6e\x6f\x1b\x84\x5f\x64\xe3\xf0\x2d\xe0\x9b\x0c\xbf\x14\x80\x5d\xe6\x57\x4b\xa8\x7f\x87\xe1\xd3\xc0\x77\x19\x3e\x87\x7a\xf6\x59\xbf\x7e\x05\xf8\x14\x6b\xff\x25\xb9\xdf\xc6\x04\xff\x3d\x2a\xf1\x23\x2e\xfc\xef\xe4\x7e\x7b\xbf\x0b\x3f\x2a\xf1\x07\x5c\xf8\xaf\xc9\x7a\x0e\xb9\xf0\x8f\x48\xfc\xa0\x0b\xff\x8e\x8c\xff\x41\x17\x7e\x52\xe2\x61\x17\x1e\x96\x78\xbb\x0b\x5f\x93\x78\xc8\x85\x8f\x48\xbc\xd5\x85\xff\xa9\x6c\xff\x4f\xb8\xf0\x23\x12\x3f\xec\xc2\xa3\xb2\xfd\x0f\xb9\xf0\xb7\x64\xf9\xa8\x0b\x7f\x52\xe2\x0f\xba\xf0\x51\xc8\xea\x74\x7d\x54\x10\x17\x70\xa6\x73\x2c\xbd\xee\x48\x7f\xa2\xea\xaf\x07\xec\xf4\x53\x42\x88\xbd\x88\x9a\xef\xac\x7f\x98\xd5\x3f\xcc\xea\xaf\xa6\x97\x59\xfd\x9b\x41\x35\x3d\x1d\x51\xed\x2d\x3b\xd2\x4f\x33\x7b\xd5\xf2\x5b\x2c\xdd\x65\xa8\xe9\xbd\xa0\x5a\xdf\x68\xc8\x4e\x3f\x51\x8d\x33\x21\x35\xbf\xb3\x55\xb5\x97\x60\x69\x2b\x0e\x1a\xf8\x93\xf1\x04\xb3\x3f\xc2\xec\x2f\xb1\xf4\x4e\xab\x6a\x7f\xa9\x4d\xb5\xbf\xd9\xa6\xda\xdb\x3b\xa8\x96\xcf\x1d\x52\xed\x4f\xf2\xf1\x64\xf6\x3a\x03\x6a\x7a\xad\x45\xad\xaf\x93\xb5\x67\xa3\x55\xad\x5f\x3c\x5e\xc8\xcc\x17\x44\x76\x36\x53\xc8\xcd\x9e\xfb\x4c\x26\x99\x9c\x39\x9b\x29\x24\xd3\xf9\x6c\x32\x95\x4e\x67\x72\x05\xf1\xf8\x6c\xe6\x4c\x2d\xfb\x23\x3c\xd7\xa1\x58\x48\xe7\x92\x2f\x0c\x26\xd3\xe7\xce\x9e\xcd\xa4\x0b\x22\xab\x87\xd5\xea\x74\x99\xda\x1c\x6e\x67\x40\x6f\x67\xc0\xcf\xce\x80\xa7\x1d\x3b\xe7\x73\xa9\x5c\xbe\x8a\x15\x66\x53\xe9\xcc\x6c\x32\x5f\x48\x15\x2e\xe4\x45\xf2\x85\xcc\x6c\x7e\xe6\xdc\x59\xc5\x58\x3e\x53\x90\xf9\x19\x5e\x9d\x9d\xe1\x2c\x9e\x3e\x73\x2e\xef\x2a\x4a\x60\xf2\xcc\x4c\x3a\x73\xb6\x9a\x9b\x2f\xcc\x16\x52\x9f\x11\x8f\xe7\xcd\xcf\x55\xe5\x33\x4f\x3c\xd1\x9b\xec\xfb\x68\x55\x0e\x26\x7b\xa5\x1c\x80\xec\x87\xec\xad\xa5\x87\x50\x7c\x08\xc5\x86\x50\x0c\x78\x2f\xf0\xe3\xa8\xed\x38\x8a\x1d\x47\xb1\xe3\x28\x76\xbc\x36\x06\xc9\xcc\x0b\x99\xb3\x85\xe4\x4c\xee\x85\x41\xc2\x30\x48\xf9\x73\xe9\xac\x03\x2d\x5c\xc8\x9d\xc9\xe4\x66\x3e\x4b\x90\xac\x7c\x90\xea\x86\xe8\x83\xec\x1d\x84\x09\x94\x3a\x56\x15\xc7\x48\x0c\x26\xfb\x90\x24\x39\x50\x4b\xf7\x1e\x43\x03\x8f\x41\xfb\x98\xa6\x81\x03\xda\x06\x0e\xb8\x1b\x38\x40\x95\x0e\x90\x8d\x01\x8c\x05\xd0\x5e\xc0\x24\x7b\x6b\x78\x7f\x3f\x9a\xd6\x8f\x21\xee\x87\x1e\x70\x4b\xf6\xf7\x21\xbf\x0f\xf9\x7d\xc8\x47\xba\xbf\x17\xe5\x20\xfb\x7a\x51\x1e\xe9\x5e\xa4\x49\x0e\x26\xfb\x7b\x50\x1e\xb2\xaf\x87\x86\xa2\x0f\xe9\xde\x1e\x71\xaf\xbf\xaf\xe2\xfb\x05\xff\x8d\xd0\x73\x64\xb1\xc1\x32\x19\x2d\xa8\xfd\xff\x6d\x1c\xe0\xfa\x1e\xf6\x18\xcd\x11\xef\xd4\xd1\x9f\x67\x38\xa3\x7f\xe2\x8e\x70\x1d\x4d\xe8\x47\xcf\xa7\x05\x3e\x23\x8a\x38\xa8\x96\xa5\x6f\xe1\xdf\xf5\xb0\x7f\x13\xb2\xb3\xd5\xdf\xfe\x3f\x7b\xd8\xef\x80\xfd\x84\xc3\x7e\x48\x63\xff\x65\x0f\xfb\xdb\xa8\xb4\x5e\xff\xbf\xea\x61\x7f\x4a\xd3\xff\xb0\xc6\xfe\x05\x0f\xfb\x09\xec\x53\xfc\x9c\xc8\xed\x9f\xf7\xb0\x9f\x83\xfd\x29\x87\xfd\x56\x8d\xfd\x5f\x36\xf4\xf6\x3b\x41\x11\x3b\x0e\xfa\xdb\x7f\xca\xd0\xdb\xdf\x82\xfd\x45\x87\xfd\x83\x1a\xfb\x05\x0f\xfb\x3d\x3f\x43\x72\xe9\x90\xbf\xfd\x9c\x87\xfd\xa9\x8f\x93\xbc\xe8\xb0\x7f\x48\x63\xff\x51\x8f\xf1\xdf\xfc\x10\xc9\xfd\x3a\xfe\xf7\xb3\x1e\xe3\xbf\x03\xfb\xce\xf1\x6f\xd7\xd8\xff\x93\x00\xd9\xe7\x31\x60\x03\xf7\x7e\x9c\x91\xf3\xf5\xfb\x80\x87\xfe\xad\x06\xf5\xff\xca\x43\x7f\xb7\x41\xfd\x2e\x0f\xfd\xbb\x0d\xea\xff\x8b\x87\x7e\xc7\x60\x63\xfa\x1f\xf7\xd0\x7f\xa4\x41\xfd\x0e\x43\xaf\x3f\xd4\xa0\xfe\x97\x3c\xf4\x9f\x19\xd4\x97\xe7\xf1\xfb\x31\x0f\xfd\x84\x87\x3e\x4f\x7f\x03\xf7\xb9\xfc\x37\x09\xfd\x35\xc7\xf9\xe2\xb8\xc3\xff\xac\x13\xe3\xff\x05\x00\x00\xff\xff\x10\xfc\xc7\xa6\xc0\x49\x00\x00") + +func tcptracerEbpfOBytes() ([]byte, error) { + return bindataRead( + _tcptracerEbpfO, + "tcptracer-ebpf.o", + ) +} + +func tcptracerEbpfO() (*asset, error) { + bytes, err := tcptracerEbpfOBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "tcptracer-ebpf.o", size: 18880, mode: os.FileMode(420), modTime: time.Unix(1, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "tcptracer-ebpf.o": tcptracerEbpfO, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} +var _bintree = &bintree{nil, map[string]*bintree{ + "tcptracer-ebpf.o": &bintree{tcptracerEbpfO, map[string]*bintree{}}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} + diff --git a/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/tracer.go b/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/tracer.go new file mode 100644 index 0000000000..cf7f9ab2b7 --- /dev/null +++ b/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/tracer.go @@ -0,0 +1,113 @@ +// +build linux + +package tracer + +import ( + "bytes" + "fmt" + + bpflib "github.com/iovisor/gobpf/elf" +) + +type Tracer struct { + m *bpflib.Module + perfMapIPV4 *bpflib.PerfMap + perfMapIPV6 *bpflib.PerfMap +} + +func TracerAsset() ([]byte, error) { + buf, err := Asset("tcptracer-ebpf.o") + if err != nil { + return nil, fmt.Errorf("couldn't find asset: %s", err) + } + return buf, nil +} + +func NewTracer(tcpEventCbV4 func(TcpV4), tcpEventCbV6 func(TcpV6)) (*Tracer, error) { + buf, err := Asset("tcptracer-ebpf.o") + if err != nil { + return nil, fmt.Errorf("couldn't find asset: %s", err) + } + reader := bytes.NewReader(buf) + + m := bpflib.NewModuleFromReader(reader) + if m == nil { + return nil, fmt.Errorf("BPF not supported") + } + + err = m.Load() + if err != nil { + return nil, err + } + + err = m.EnableKprobes() + if err != nil { + return nil, err + } + + channelV4 := make(chan []byte) + channelV6 := make(chan []byte) + + perfMapIPV4, err := initializeIPv4(m, channelV4) + if err != nil { + return nil, fmt.Errorf("failed to init perf map for IPv4 events: %s", err) + } + + perfMapIPV6, err := initializeIPv6(m, channelV6) + if err != nil { + return nil, fmt.Errorf("failed to init perf map for IPv6 events: %s", err) + } + + perfMapIPV4.SetTimestampFunc(tcpV4Timestamp) + perfMapIPV6.SetTimestampFunc(tcpV6Timestamp) + + go func() { + for { + data := <-channelV4 + tcpEventCbV4(tcpV4ToGo(&data)) + } + }() + + go func() { + for { + data := <-channelV6 + tcpEventCbV6(tcpV6ToGo(&data)) + } + }() + + perfMapIPV4.PollStart() + perfMapIPV6.PollStart() + + return &Tracer{ + m: m, + perfMapIPV4: perfMapIPV4, + perfMapIPV6: perfMapIPV6, + }, nil +} + +func (t *Tracer) Stop() { + t.perfMapIPV4.PollStop() + t.perfMapIPV6.PollStop() +} + +func initialize(module *bpflib.Module, eventMapName string, eventChan chan []byte) (*bpflib.PerfMap, error) { + if err := guess(module); err != nil { + return nil, fmt.Errorf("error guessing offsets: %v", err) + } + + pm, err := bpflib.InitPerfMap(module, eventMapName, eventChan) + if err != nil { + return nil, fmt.Errorf("error initializing perf map for %q: %v", eventMapName, err) + } + + return pm, nil + +} + +func initializeIPv4(module *bpflib.Module, eventChan chan []byte) (*bpflib.PerfMap, error) { + return initialize(module, "tcp_event_ipv4", eventChan) +} + +func initializeIPv6(module *bpflib.Module, eventChan chan []byte) (*bpflib.PerfMap, error) { + return initialize(module, "tcp_event_ipv6", eventChan) +} diff --git a/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/tracer_unsupported.go b/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/tracer_unsupported.go new file mode 100644 index 0000000000..9e94594b19 --- /dev/null +++ b/vendor/github.com/weaveworks/tcptracer-bpf/pkg/tracer/tracer_unsupported.go @@ -0,0 +1,20 @@ +// +build !linux + +package tracer + +import ( + "fmt" +) + +type Tracer struct{} + +func TracerAsset() ([]byte, error) { + return nil, fmt.Errorf("not supported on non-Linux systems") +} + +func NewTracer(tcpEventCbV4 func(TcpV4), tcpEventCbV6 func(TcpV6)) (*Tracer, error) { + return nil, fmt.Errorf("not supported on non-Linux systems") +} + +func (t *Tracer) Stop() { +} diff --git a/vendor/github.com/weaveworks/tcptracer-bpf/tcptracer-bpf.c b/vendor/github.com/weaveworks/tcptracer-bpf/tcptracer-bpf.c new file mode 100644 index 0000000000..b964b44b80 --- /dev/null +++ b/vendor/github.com/weaveworks/tcptracer-bpf/tcptracer-bpf.c @@ -0,0 +1,819 @@ +#include + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgnu-variable-sized-type-not-at-end" +#include +#pragma clang diagnostic pop +#include +#include +#include "bpf_helpers.h" +#include "tcptracer-bpf.h" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-compare" +#include +#pragma clang diagnostic pop +#include +#include + +/* This is a key/value store with the keys being the cpu number + * and the values being a perf file descriptor. + */ +struct bpf_map_def SEC("maps/tcp_event_ipv4") tcp_event_ipv4 = { + .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, + .key_size = sizeof(int), + .value_size = sizeof(__u32), + .max_entries = 1024, +}; + +/* This is a key/value store with the keys being the cpu number + * and the values being a perf file descriptor. + */ +struct bpf_map_def SEC("maps/tcp_event_ipv6") tcp_event_ipv6 = { + .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY, + .key_size = sizeof(int), + .value_size = sizeof(__u32), + .max_entries = 1024, +}; + +/* These maps are used to match the kprobe & kretprobe of connect */ + +/* This is a key/value store with the keys being a pid + * and the values being a struct sock *. + */ +struct bpf_map_def SEC("maps/connectsock_ipv4") connectsock_ipv4 = { + .type = BPF_MAP_TYPE_HASH, + .key_size = sizeof(__u64), + .value_size = sizeof(void *), + .max_entries = 1024, +}; + +/* This is a key/value store with the keys being a pid + * and the values being a struct sock *. + */ +struct bpf_map_def SEC("maps/connectsock_ipv6") connectsock_ipv6 = { + .type = BPF_MAP_TYPE_HASH, + .key_size = sizeof(__u64), + .value_size = sizeof(void *), + .max_entries = 1024, +}; + +/* This is a key/value store with the keys being an ipv4_tuple_t + * and the values being a struct pid_comm_t. + */ +struct bpf_map_def SEC("maps/tuplepid_ipv4") tuplepid_ipv4 = { + .type = BPF_MAP_TYPE_HASH, + .key_size = sizeof(struct ipv4_tuple_t), + .value_size = sizeof(struct pid_comm_t), + .max_entries = 1024, +}; + +/* This is a key/value store with the keys being an ipv6_tuple_t + * and the values being a struct pid_comm_t. + */ +struct bpf_map_def SEC("maps/tuplepid_ipv6") tuplepid_ipv6 = { + .type = BPF_MAP_TYPE_HASH, + .key_size = sizeof(struct ipv6_tuple_t), + .value_size = sizeof(struct pid_comm_t), + .max_entries = 1024, +}; + +/* http://stackoverflow.com/questions/1001307/detecting-endianness-programmatically-in-a-c-program */ +__attribute__((always_inline)) +static bool is_big_endian(void) +{ + union { + uint32_t i; + char c[4]; + } bint = {0x01020304}; + + return bint.c[0] == 1; +} + +/* check if IPs are IPv4 mapped to IPv6 ::ffff:xxxx:xxxx + * https://tools.ietf.org/html/rfc4291#section-2.5.5 + * the addresses are stored in network byte order so IPv4 adddress is stored + * in the most significant 32 bits of part saddr_l and daddr_l. + * Meanwhile the end of the mask is stored in the least significant 32 bits. + */ +__attribute__((always_inline)) +static bool is_ipv4_mapped_ipv6(u64 saddr_h, u64 saddr_l, u64 daddr_h, u64 daddr_l) { + if (is_big_endian()) { + return ((saddr_h == 0 && ((u32)(saddr_l >> 32) == 0x0000FFFF)) || + (daddr_h == 0 && ((u32)(daddr_l >> 32) == 0x0000FFFF))); + } else { + return ((saddr_h == 0 && ((u32)saddr_l == 0xFFFF0000)) || + (daddr_h == 0 && ((u32)daddr_l == 0xFFFF0000))); + } +} + +struct bpf_map_def SEC("maps/tcptracer_status") tcptracer_status = { + .type = BPF_MAP_TYPE_HASH, + .key_size = sizeof(__u64), + .value_size = sizeof(struct tcptracer_status_t), + .max_entries = 1, +}; + +__attribute__((always_inline)) +static int are_offsets_ready_v4(struct tcptracer_status_t *status, struct sock *skp, u64 pid) { + u64 zero = 0; + + switch (status->state) { + case TCPTRACER_STATE_UNINITIALIZED: + return 0; + case TCPTRACER_STATE_CHECKING: + break; + case TCPTRACER_STATE_CHECKED: + return 0; + case TCPTRACER_STATE_READY: + return 1; + default: + return 0; + } + + if (status->pid_tgid >> 32 != pid >> 32) + return 0; + + struct tcptracer_status_t new_status = { }; + new_status.state = TCPTRACER_STATE_CHECKED; + new_status.pid_tgid = status->pid_tgid; + new_status.what = status->what; + new_status.offset_saddr = status->offset_saddr; + new_status.offset_daddr = status->offset_daddr; + new_status.offset_sport = status->offset_sport; + new_status.offset_dport = status->offset_dport; + new_status.offset_netns = status->offset_netns; + new_status.offset_ino = status->offset_ino; + new_status.offset_family = status->offset_family; + new_status.offset_daddr_ipv6 = status->offset_daddr_ipv6; + new_status.err = 0; + new_status.saddr = status->saddr; + new_status.daddr = status->daddr; + new_status.sport = status->sport; + new_status.dport = status->dport; + new_status.netns = status->netns; + new_status.family = status->family; + + int i; + for (i = 0; i < 4; i++) { + new_status.daddr_ipv6[i] = status->daddr_ipv6[i]; + } + + u32 possible_saddr; + u32 possible_daddr; + u16 possible_sport; + u16 possible_dport; + possible_net_t *possible_skc_net; + u32 possible_netns; + u16 possible_family; + long ret = 0; + + switch (status->what) { + case GUESS_SADDR: + possible_saddr = 0; + bpf_probe_read(&possible_saddr, sizeof(possible_saddr), ((char *)skp) + status->offset_saddr); + new_status.saddr = possible_saddr; + break; + case GUESS_DADDR: + possible_daddr = 0; + bpf_probe_read(&possible_daddr, sizeof(possible_daddr), ((char *)skp) + status->offset_daddr); + new_status.daddr = possible_daddr; + break; + case GUESS_FAMILY: + possible_family = 0; + bpf_probe_read(&possible_family, sizeof(possible_family), ((char *)skp) + status->offset_family); + new_status.family = possible_family; + break; + case GUESS_SPORT: + possible_sport = 0; + bpf_probe_read(&possible_sport, sizeof(possible_sport), ((char *)skp) + status->offset_sport); + new_status.sport = possible_sport; + break; + case GUESS_DPORT: + possible_dport = 0; + bpf_probe_read(&possible_dport, sizeof(possible_dport), ((char *)skp) + status->offset_dport); + new_status.dport = possible_dport; + break; + case GUESS_NETNS: + possible_netns = 0; + possible_skc_net = NULL; + bpf_probe_read(&possible_skc_net, sizeof(possible_net_t *), ((char *)skp) + status->offset_netns); + // if we get a kernel fault, it means possible_skc_net + // is an invalid pointer, signal an error so we can go + // to the next offset_netns + ret = bpf_probe_read(&possible_netns, sizeof(possible_netns), ((char *)possible_skc_net) + status->offset_ino); + if (ret == -EFAULT) { + new_status.err = 1; + break; + } + new_status.netns = possible_netns; + break; + default: + // not for us + return 0; + } + + bpf_map_update_elem(&tcptracer_status, &zero, &new_status, BPF_ANY); + + return 0; +} + +__attribute__((always_inline)) +static int are_offsets_ready_v6(struct tcptracer_status_t *status, struct sock *skp, u64 pid) { + u64 zero = 0; + + switch (status->state) { + case TCPTRACER_STATE_UNINITIALIZED: + return 0; + case TCPTRACER_STATE_CHECKING: + break; + case TCPTRACER_STATE_CHECKED: + return 0; + case TCPTRACER_STATE_READY: + return 1; + default: + return 0; + } + + if (status->pid_tgid >> 32 != pid >> 32) + return 0; + + struct tcptracer_status_t new_status = { }; + new_status.state = TCPTRACER_STATE_CHECKED; + new_status.pid_tgid = status->pid_tgid; + new_status.what = status->what; + new_status.offset_saddr = status->offset_saddr; + new_status.offset_daddr = status->offset_daddr; + new_status.offset_sport = status->offset_sport; + new_status.offset_dport = status->offset_dport; + new_status.offset_netns = status->offset_netns; + new_status.offset_ino = status->offset_ino; + new_status.offset_family = status->offset_family; + new_status.offset_daddr_ipv6 = status->offset_daddr_ipv6; + new_status.err = 0; + new_status.saddr = status->saddr; + new_status.daddr = status->daddr; + new_status.sport = status->sport; + new_status.dport = status->dport; + new_status.netns = status->netns; + new_status.family = status->family; + + int i; + for (i = 0; i < 4; i++) { + new_status.daddr_ipv6[i] = status->daddr_ipv6[i]; + } + + u32 possible_daddr_ipv6[4] = { }; + switch (status->what) { + case GUESS_DADDR_IPV6: + bpf_probe_read(&possible_daddr_ipv6, sizeof(possible_daddr_ipv6), ((char *)skp) + status->offset_daddr_ipv6); + + int i; + for (i = 0; i < 4; i++) { + new_status.daddr_ipv6[i] = possible_daddr_ipv6[i]; + } + break; + default: + // not for us + return 0; + } + + bpf_map_update_elem(&tcptracer_status, &zero, &new_status, BPF_ANY); + + return 0; +} + +__attribute__((always_inline)) +static bool check_family(struct sock *sk, u16 expected_family) { + struct tcptracer_status_t *status; + u64 zero = 0; + u16 family; + family = 0; + + status = bpf_map_lookup_elem(&tcptracer_status, &zero); + if (status == NULL || status->state != TCPTRACER_STATE_READY) { + return 0; + } + + bpf_probe_read(&family, sizeof(u16), ((char *)sk) + status->offset_family); + + return family == expected_family; +} + +__attribute__((always_inline)) +static int read_ipv4_tuple(struct ipv4_tuple_t *tuple, struct tcptracer_status_t *status, struct sock *skp) +{ + u32 saddr, daddr, net_ns_inum; + u16 sport, dport; + possible_net_t *skc_net; + + saddr = 0; + daddr = 0; + sport = 0; + dport = 0; + skc_net = NULL; + net_ns_inum = 0; + + bpf_probe_read(&saddr, sizeof(saddr), ((char *)skp) + status->offset_saddr); + bpf_probe_read(&daddr, sizeof(daddr), ((char *)skp) + status->offset_daddr); + bpf_probe_read(&sport, sizeof(sport), ((char *)skp) + status->offset_sport); + bpf_probe_read(&dport, sizeof(dport), ((char *)skp) + status->offset_dport); + // Get network namespace id + bpf_probe_read(&skc_net, sizeof(void *), ((char *)skp) + status->offset_netns); + bpf_probe_read(&net_ns_inum, sizeof(net_ns_inum), ((char *)skc_net) + status->offset_ino); + + tuple->saddr = saddr; + tuple->daddr = daddr; + tuple->sport = sport; + tuple->dport = dport; + tuple->netns = net_ns_inum; + + // if addresses or ports are 0, ignore + if (saddr == 0 || daddr == 0 || sport == 0 || dport == 0) { + return 0; + } + + return 1; +} + +__attribute__((always_inline)) +static int read_ipv6_tuple(struct ipv6_tuple_t *tuple, struct tcptracer_status_t *status, struct sock *skp) +{ + u32 net_ns_inum; + u16 sport, dport; + u64 saddr_h, saddr_l, daddr_h, daddr_l; + possible_net_t *skc_net; + + saddr_h = 0; + saddr_l = 0; + daddr_h = 0; + daddr_l = 0; + sport = 0; + dport = 0; + skc_net = NULL; + net_ns_inum = 0; + + bpf_probe_read(&saddr_h, sizeof(saddr_h), ((char *)skp) + status->offset_daddr_ipv6 + 2 * sizeof(u64)); + bpf_probe_read(&saddr_l, sizeof(saddr_l), ((char *)skp) + status->offset_daddr_ipv6 + 3 * sizeof(u64)); + bpf_probe_read(&daddr_h, sizeof(daddr_h), ((char *)skp) + status->offset_daddr_ipv6); + bpf_probe_read(&daddr_l, sizeof(daddr_l), ((char *)skp) + status->offset_daddr_ipv6 + sizeof(u64)); + bpf_probe_read(&sport, sizeof(sport), ((char *)skp) + status->offset_sport); + bpf_probe_read(&dport, sizeof(dport), ((char *)skp) + status->offset_dport); + // Get network namespace id + bpf_probe_read(&skc_net, sizeof(void *), ((char *)skp) + status->offset_netns); + bpf_probe_read(&net_ns_inum, sizeof(net_ns_inum), ((char *)skc_net) + status->offset_ino); + + tuple->saddr_h = saddr_h; + tuple->saddr_l = saddr_l; + tuple->daddr_h = daddr_h; + tuple->daddr_l = daddr_l; + tuple->sport = sport; + tuple->dport = dport; + tuple->netns = net_ns_inum; + + // if addresses or ports are 0, ignore + if (!(saddr_h || saddr_l) || !(daddr_h || daddr_l) || sport == 0 || dport == 0) { + return 0; + } + + return 1; +} + +SEC("kprobe/tcp_v4_connect") +int kprobe__tcp_v4_connect(struct pt_regs *ctx) +{ + struct sock *sk; + u64 pid = bpf_get_current_pid_tgid(); + + sk = (struct sock *) PT_REGS_PARM1(ctx); + + bpf_map_update_elem(&connectsock_ipv4, &pid, &sk, BPF_ANY); + + return 0; +} + +SEC("kretprobe/tcp_v4_connect") +int kretprobe__tcp_v4_connect(struct pt_regs *ctx) +{ + int ret = PT_REGS_RC(ctx); + u64 pid = bpf_get_current_pid_tgid(); + struct sock **skpp; + u64 zero = 0; + struct tcptracer_status_t *status; + + skpp = bpf_map_lookup_elem(&connectsock_ipv4, &pid); + if (skpp == 0) { + return 0; // missed entry + } + + struct sock *skp = *skpp; + + bpf_map_delete_elem(&connectsock_ipv4, &pid); + + if (ret != 0) { + // failed to send SYNC packet, may not have populated + // socket __sk_common.{skc_rcv_saddr, ...} + return 0; + } + + status = bpf_map_lookup_elem(&tcptracer_status, &zero); + if (status == NULL || status->state == TCPTRACER_STATE_UNINITIALIZED) { + return 0; + } + + if (!are_offsets_ready_v4(status, skp, pid)) { + return 0; + } + + // output + struct ipv4_tuple_t t = { }; + if (!read_ipv4_tuple(&t, status, skp)) { + return 0; + } + + struct pid_comm_t p = { .pid = pid }; + bpf_get_current_comm(p.comm, sizeof(p.comm)); + bpf_map_update_elem(&tuplepid_ipv4, &t, &p, BPF_ANY); + + return 0; +} + +SEC("kprobe/tcp_v6_connect") +int kprobe__tcp_v6_connect(struct pt_regs *ctx) +{ + struct sock *sk; + u64 pid = bpf_get_current_pid_tgid(); + + sk = (struct sock *) PT_REGS_PARM1(ctx); + + bpf_map_update_elem(&connectsock_ipv6, &pid, &sk, BPF_ANY); + + return 0; +} + +SEC("kretprobe/tcp_v6_connect") +int kretprobe__tcp_v6_connect(struct pt_regs *ctx) +{ + int ret = PT_REGS_RC(ctx); + u64 pid = bpf_get_current_pid_tgid(); + u64 zero = 0; + struct sock **skpp; + struct tcptracer_status_t *status; + skpp = bpf_map_lookup_elem(&connectsock_ipv6, &pid); + if (skpp == 0) { + return 0; // missed entry + } + + bpf_map_delete_elem(&connectsock_ipv6, &pid); + + struct sock *skp = *skpp; + + status = bpf_map_lookup_elem(&tcptracer_status, &zero); + if (status == NULL || status->state == TCPTRACER_STATE_UNINITIALIZED) { + return 0; + } + + if (!are_offsets_ready_v6(status, skp, pid)) { + return 0; + } + + if (ret != 0) { + // failed to send SYNC packet, may not have populated + // socket __sk_common.{skc_rcv_saddr, ...} + return 0; + } + + // output + struct ipv6_tuple_t t = { }; + if (!read_ipv6_tuple(&t, status, skp)) { + return 0; + } + + struct pid_comm_t p = { }; + p.pid = pid; + bpf_get_current_comm(p.comm, sizeof(p.comm)); + + if (is_ipv4_mapped_ipv6(t.saddr_h, t.saddr_l, t.daddr_h, t.daddr_l)) { + struct ipv4_tuple_t t4 = { + .netns = t.netns, + .saddr = (u32)(t.saddr_l >> 32), + .daddr = (u32)(t.daddr_l >> 32), + .sport = ntohs(t.sport), + .dport = ntohs(t.dport), + }; + bpf_map_update_elem(&tuplepid_ipv4, &t4, &p, BPF_ANY); + return 0; + } + + bpf_map_update_elem(&tuplepid_ipv6, &t, &p, BPF_ANY); + return 0; +} + +SEC("kprobe/tcp_set_state") +int kprobe__tcp_set_state(struct pt_regs *ctx) +{ + u32 cpu = bpf_get_smp_processor_id(); + struct sock *skp; + struct tcptracer_status_t *status; + int state; + u64 zero = 0; + skp = (struct sock *) PT_REGS_PARM1(ctx); + state = (int) PT_REGS_PARM2(ctx); + + status = bpf_map_lookup_elem(&tcptracer_status, &zero); + if (status == NULL || status->state != TCPTRACER_STATE_READY) { + return 0; + } + + if (state != TCP_ESTABLISHED && state != TCP_CLOSE) { + return 0; + } + + if (check_family(skp, AF_INET)) { + // output + struct ipv4_tuple_t t = { }; + if (!read_ipv4_tuple(&t, status, skp)) { + return 0; + } + if (state == TCP_CLOSE) { + bpf_map_delete_elem(&tuplepid_ipv4, &t); + return 0; + } + + struct pid_comm_t *pp; + + pp = bpf_map_lookup_elem(&tuplepid_ipv4, &t); + if (pp == 0) { + return 0; // missed entry + } + struct pid_comm_t p = { }; + bpf_probe_read(&p, sizeof(struct pid_comm_t), pp); + + struct tcp_ipv4_event_t evt4 = { + .timestamp = bpf_ktime_get_ns(), + .cpu = cpu, + .type = TCP_EVENT_TYPE_CONNECT, + .pid = p.pid >> 32, + .saddr = t.saddr, + .daddr = t.daddr, + .sport = ntohs(t.sport), + .dport = ntohs(t.dport), + .netns = t.netns, + }; + int i; + for (i = 0; i < TASK_COMM_LEN; i++) { + evt4.comm[i] = p.comm[i]; + } + + bpf_perf_event_output(ctx, &tcp_event_ipv4, cpu, &evt4, sizeof(evt4)); + bpf_map_delete_elem(&tuplepid_ipv4, &t); + } else if (check_family(skp, AF_INET6)) { + // output + struct ipv6_tuple_t t = { }; + if (!read_ipv6_tuple(&t, status, skp)) { + return 0; + } + if (state == TCP_CLOSE) { + bpf_map_delete_elem(&tuplepid_ipv6, &t); + return 0; + } + + struct pid_comm_t *pp; + pp = bpf_map_lookup_elem(&tuplepid_ipv6, &t); + if (pp == 0) { + return 0; // missed entry + } + struct pid_comm_t p = { }; + bpf_probe_read(&p, sizeof(struct pid_comm_t), pp); + struct tcp_ipv6_event_t evt6 = { + .timestamp = bpf_ktime_get_ns(), + .cpu = cpu, + .type = TCP_EVENT_TYPE_CONNECT, + .pid = p.pid >> 32, + .saddr_h = t.saddr_h, + .saddr_l = t.saddr_l, + .daddr_h = t.daddr_h, + .daddr_l = t.daddr_l, + .sport = ntohs(t.sport), + .dport = ntohs(t.dport), + .netns = t.netns, + }; + int i; + for (i = 0; i < TASK_COMM_LEN; i++) { + evt6.comm[i] = p.comm[i]; + } + + bpf_perf_event_output(ctx, &tcp_event_ipv6, cpu, &evt6, sizeof(evt6)); + bpf_map_delete_elem(&tuplepid_ipv6, &t); + } + + return 0; +} + +SEC("kprobe/tcp_close") +int kprobe__tcp_close(struct pt_regs *ctx) +{ + struct sock *sk; + struct tcptracer_status_t *status; + u64 zero = 0; + u64 pid = bpf_get_current_pid_tgid(); + u32 cpu = bpf_get_smp_processor_id(); + sk = (struct sock *) PT_REGS_PARM1(ctx); + + status = bpf_map_lookup_elem(&tcptracer_status, &zero); + if (status == NULL || status->state != TCPTRACER_STATE_READY) { + return 0; + } + + u32 net_ns_inum; + u16 sport, dport; + sport = 0; + dport = 0; + + // Get network namespace id + possible_net_t *skc_net; + + skc_net = NULL; + net_ns_inum = 0; + bpf_probe_read(&skc_net, sizeof(possible_net_t *), ((char *)sk) + status->offset_netns); + bpf_probe_read(&net_ns_inum, sizeof(net_ns_inum), ((char *)skc_net) + status->offset_ino); + + if (check_family(sk, AF_INET)) { + // output + struct ipv4_tuple_t t = { }; + if (!read_ipv4_tuple(&t, status, sk)) { + bpf_map_delete_elem(&tuplepid_ipv4, &t); + return 0; + } + + // output + struct tcp_ipv4_event_t evt = { + .timestamp = bpf_ktime_get_ns(), + .cpu = cpu, + .type = TCP_EVENT_TYPE_CLOSE, + .pid = pid >> 32, + .saddr = t.saddr, + .daddr = t.daddr, + .sport = ntohs(t.sport), + .dport = ntohs(t.dport), + .netns = t.netns, + }; + bpf_get_current_comm(&evt.comm, sizeof(evt.comm)); + + bpf_perf_event_output(ctx, &tcp_event_ipv4, cpu, &evt, sizeof(evt)); + } else if (check_family(sk, AF_INET6)) { + // output + struct ipv6_tuple_t t = { }; + if (!read_ipv6_tuple(&t, status, sk)) { + bpf_map_delete_elem(&tuplepid_ipv6, &t); + return 0; + } + + if (is_ipv4_mapped_ipv6(t.saddr_h, t.saddr_l, t.daddr_h, t.daddr_l)) { + struct tcp_ipv4_event_t evt4 = { + .timestamp = bpf_ktime_get_ns(), + .cpu = cpu, + .type = TCP_EVENT_TYPE_CLOSE, + .pid = pid >> 32, + .saddr = (u32)(t.saddr_l >> 32), + .daddr = (u32)(t.daddr_l >> 32), + .sport = ntohs(t.sport), + .dport = ntohs(t.dport), + .netns = t.netns, + }; + bpf_get_current_comm(&evt4.comm, sizeof(evt4.comm)); + if (evt4.saddr != 0 && evt4.daddr != 0 && evt4.sport != 0 && evt4.dport != 0) { + bpf_perf_event_output(ctx, &tcp_event_ipv4, cpu, &evt4, sizeof(evt4)); + } + + struct ipv4_tuple_t t = { + t.saddr = evt4.saddr, + t.daddr = evt4.daddr, + t.sport = ntohs(evt4.sport), + t.dport = ntohs(evt4.dport), + t.netns = evt4.netns, + }; + bpf_map_delete_elem(&tuplepid_ipv4, &t); + return 0; + } + + struct tcp_ipv6_event_t evt = { + .timestamp = bpf_ktime_get_ns(), + .cpu = cpu, + .type = TCP_EVENT_TYPE_CLOSE, + .pid = pid >> 32, + .saddr_h = t.saddr_h, + .saddr_l = t.saddr_l, + .daddr_h = t.daddr_h, + .daddr_l = t.daddr_l, + .sport = ntohs(t.sport), + .dport = ntohs(t.dport), + .netns = t.netns, + }; + bpf_get_current_comm(&evt.comm, sizeof(evt.comm)); + + bpf_perf_event_output(ctx, &tcp_event_ipv6, cpu, &evt, sizeof(evt)); + } + return 0; +} + +SEC("kretprobe/inet_csk_accept") +int kretprobe__inet_csk_accept(struct pt_regs *ctx) +{ + struct tcptracer_status_t *status; + u64 zero = 0; + struct sock *newsk = (struct sock *)PT_REGS_RC(ctx); + u64 pid = bpf_get_current_pid_tgid(); + u32 cpu = bpf_get_smp_processor_id(); + + if (newsk == NULL) + return 0; + + status = bpf_map_lookup_elem(&tcptracer_status, &zero); + if (status == NULL || status->state != TCPTRACER_STATE_READY) { + return 0; + } + + // pull in details + u16 lport, dport; + u32 net_ns_inum; + + lport = 0; + dport = 0; + + bpf_probe_read(&dport, sizeof(dport), ((char *)newsk) + status->offset_dport); + // lport is right after dport + bpf_probe_read(&lport, sizeof(lport), ((char *)newsk) + status->offset_dport + sizeof(dport)); + // Get network namespace id + possible_net_t *skc_net; + + skc_net = NULL; + net_ns_inum = 0; + bpf_probe_read(&skc_net, sizeof(possible_net_t *), ((char *)newsk) + status->offset_netns); + bpf_probe_read(&net_ns_inum, sizeof(net_ns_inum), ((char *)skc_net) + status->offset_ino); + + if (check_family(newsk, AF_INET)) { + struct tcp_ipv4_event_t evt = { + .timestamp = bpf_ktime_get_ns(), + .cpu = cpu, + .type = TCP_EVENT_TYPE_ACCEPT, + .netns = net_ns_inum, + }; + evt.pid = pid >> 32; + bpf_probe_read(&evt.saddr, sizeof(u32), ((char *)newsk) + status->offset_saddr); + bpf_probe_read(&evt.daddr, sizeof(u32), ((char *)newsk) + status->offset_daddr); + + evt.sport = lport; + evt.dport = ntohs(dport); + bpf_get_current_comm(&evt.comm, sizeof(evt.comm)); + + // do not send event if IP address is 0.0.0.0 or port is 0 + if (evt.saddr != 0 && evt.daddr != 0 && evt.sport != 0 && evt.dport != 0) { + bpf_perf_event_output(ctx, &tcp_event_ipv4, cpu, &evt, sizeof(evt)); + } + } else if (check_family(newsk, AF_INET6)) { + struct tcp_ipv6_event_t evt = { + .timestamp = bpf_ktime_get_ns(), + .cpu = cpu, + .type = TCP_EVENT_TYPE_ACCEPT, + .netns = net_ns_inum, + }; + evt.pid = pid >> 32; + bpf_probe_read(&evt.daddr_h, sizeof(u64), ((char *)newsk) + status->offset_daddr_ipv6); + bpf_probe_read(&evt.daddr_l, sizeof(u64), ((char *)newsk) + status->offset_daddr_ipv6 + sizeof(u64)); + bpf_probe_read(&evt.saddr_h, sizeof(u64), ((char *)newsk) + status->offset_daddr_ipv6 + 2 * sizeof(u64)); + bpf_probe_read(&evt.saddr_l, sizeof(u64), ((char *)newsk) + status->offset_daddr_ipv6 + 3 * sizeof(u64)); + + evt.sport = lport; + evt.dport = ntohs(dport); + bpf_get_current_comm(&evt.comm, sizeof(evt.comm)); + if (is_ipv4_mapped_ipv6(evt.saddr_h, evt.saddr_l, evt.daddr_h, evt.daddr_l)) { + struct tcp_ipv4_event_t evt4 = { + .timestamp = bpf_ktime_get_ns(), + .cpu = cpu, + .type = TCP_EVENT_TYPE_ACCEPT, + .pid = pid >> 32, + .saddr = (u32)(evt.saddr_l >> 32), + .daddr = (u32)(evt.daddr_l >> 32), + .sport = evt.sport, + .dport = evt.dport, + .netns = net_ns_inum, + }; + bpf_get_current_comm(&evt4.comm, sizeof(evt4.comm)); + if (evt4.saddr != 0 && evt4.daddr != 0 && evt4.sport != 0 && evt4.dport != 0) { + bpf_perf_event_output(ctx, &tcp_event_ipv4, cpu, &evt4, sizeof(evt4)); + } + return 0; + } + // do not send event if IP address is :: or port is 0 + if ((evt.saddr_h || evt.saddr_l) && (evt.daddr_h || evt.daddr_l) && evt.sport != 0 && evt.dport != 0) { + bpf_perf_event_output(ctx, &tcp_event_ipv6, cpu, &evt, sizeof(evt)); + } + } + return 0; +} + +char _license[] SEC("license") = "GPL"; +// this number will be interpreted by gobpf-elf-loader to set the current +// running kernel version +__u32 _version SEC("version") = 0xFFFFFFFE; diff --git a/vendor/github.com/weaveworks/tcptracer-bpf/tcptracer-bpf.h b/vendor/github.com/weaveworks/tcptracer-bpf/tcptracer-bpf.h new file mode 100644 index 0000000000..8cdbfb78d9 --- /dev/null +++ b/vendor/github.com/weaveworks/tcptracer-bpf/tcptracer-bpf.h @@ -0,0 +1,109 @@ +#ifndef __TCPTRACER_BPF_H +#define __TCPTRACER_BPF_H + +#include + +#define TCP_EVENT_TYPE_CONNECT 1 +#define TCP_EVENT_TYPE_ACCEPT 2 +#define TCP_EVENT_TYPE_CLOSE 3 + +#define GUESS_SADDR 0 +#define GUESS_DADDR 1 +#define GUESS_FAMILY 2 +#define GUESS_SPORT 3 +#define GUESS_DPORT 4 +#define GUESS_NETNS 5 +#define GUESS_DADDR_IPV6 6 + +#ifndef TASK_COMM_LEN +#define TASK_COMM_LEN 16 +#endif + +struct tcp_ipv4_event_t { + __u64 timestamp; + __u64 cpu; + __u32 type; + __u32 pid; + char comm[TASK_COMM_LEN]; + __u32 saddr; + __u32 daddr; + __u16 sport; + __u16 dport; + __u32 netns; +}; + +struct tcp_ipv6_event_t { + __u64 timestamp; + __u64 cpu; + __u32 type; + __u32 pid; + char comm[TASK_COMM_LEN]; + /* Using the type unsigned __int128 generates an error in the ebpf verifier */ + __u64 saddr_h; + __u64 saddr_l; + __u64 daddr_h; + __u64 daddr_l; + __u16 sport; + __u16 dport; + __u32 netns; +}; + +// tcp_set_state doesn't run in the context of the process that initiated the +// connection so we need to store a map TUPLE -> PID to send the right PID on +// the event +struct ipv4_tuple_t { + __u32 saddr; + __u32 daddr; + __u16 sport; + __u16 dport; + __u32 netns; +}; + +struct ipv6_tuple_t { + /* Using the type unsigned __int128 generates an error in the ebpf verifier */ + __u64 saddr_h; + __u64 saddr_l; + __u64 daddr_h; + __u64 daddr_l; + __u16 sport; + __u16 dport; + __u32 netns; +}; + +struct pid_comm_t { + __u64 pid; + char comm[TASK_COMM_LEN]; +}; + +#define TCPTRACER_STATE_UNINITIALIZED 0 +#define TCPTRACER_STATE_CHECKING 1 +#define TCPTRACER_STATE_CHECKED 2 +#define TCPTRACER_STATE_READY 3 +struct tcptracer_status_t { + __u64 state; + + /* checking */ + __u64 pid_tgid; + __u64 what; + __u64 offset_saddr; + __u64 offset_daddr; + __u64 offset_sport; + __u64 offset_dport; + __u64 offset_netns; + __u64 offset_ino; + __u64 offset_family; + __u64 offset_daddr_ipv6; + + __u64 err; + + __u32 daddr_ipv6[4]; + __u32 netns; + __u32 saddr; + __u32 daddr; + __u16 sport; + __u16 dport; + __u16 family; + __u16 padding; +}; + +#endif diff --git a/vendor/github.com/weaveworks/tcptracer-bpf/tests/tracer.go b/vendor/github.com/weaveworks/tcptracer-bpf/tests/tracer.go new file mode 100644 index 0000000000..8016e3331e --- /dev/null +++ b/vendor/github.com/weaveworks/tcptracer-bpf/tests/tracer.go @@ -0,0 +1,55 @@ +package main + +import ( + "fmt" + "os" + "os/signal" + + "github.com/weaveworks/tcptracer-bpf/pkg/tracer" +) + +var lastTimestampV4 uint64 +var lastTimestampV6 uint64 + +func tcpEventCbV4(e tracer.TcpV4) { + fmt.Printf("%v cpu#%d %s %v %s %v:%v %v:%v %v\n", + e.Timestamp, e.CPU, e.Type, e.Pid, e.Comm, e.SAddr, e.SPort, e.DAddr, e.DPort, e.NetNS) + + if lastTimestampV4 > e.Timestamp { + fmt.Printf("ERROR: late event!\n") + os.Exit(1) + } + + lastTimestampV4 = e.Timestamp +} + +func tcpEventCbV6(e tracer.TcpV6) { + fmt.Printf("%v cpu#%d %s %v %s %v:%v %v:%v %v\n", + e.Timestamp, e.CPU, e.Type, e.Pid, e.Comm, e.SAddr, e.SPort, e.DAddr, e.DPort, e.NetNS) + + if lastTimestampV6 > e.Timestamp { + fmt.Printf("ERROR: late event!\n") + os.Exit(1) + } + + lastTimestampV6 = e.Timestamp +} + +func main() { + if len(os.Args) != 1 { + fmt.Fprintf(os.Stderr, "Usage: %s\n", os.Args[0]) + os.Exit(1) + } + + t, err := tracer.NewTracer(tcpEventCbV4, tcpEventCbV6) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } + + sig := make(chan os.Signal, 1) + signal.Notify(sig, os.Interrupt, os.Kill) + + <-sig + t.Stop() +} diff --git a/vendor/github.com/weaveworks/tcptracer-bpf/tools/cover/cover.go b/vendor/github.com/weaveworks/tcptracer-bpf/tools/cover/cover.go new file mode 100644 index 0000000000..4c5fcfd7d6 --- /dev/null +++ b/vendor/github.com/weaveworks/tcptracer-bpf/tools/cover/cover.go @@ -0,0 +1,97 @@ +package main + +import ( + "fmt" + "os" + "sort" + + "golang.org/x/tools/cover" +) + +func merge(p1, p2 *cover.Profile) *cover.Profile { + output := cover.Profile{ + FileName: p1.FileName, + Mode: p1.Mode, + } + + i, j := 0, 0 + for i < len(p1.Blocks) && j < len(p2.Blocks) { + bi, bj := p1.Blocks[i], p2.Blocks[j] + if bi.StartLine == bj.StartLine && bi.StartCol == bj.StartCol { + + if bi.EndLine != bj.EndLine || + bi.EndCol != bj.EndCol || + bi.NumStmt != bj.NumStmt { + panic("Not run on same source!") + } + + output.Blocks = append(output.Blocks, cover.ProfileBlock{ + StartLine: bi.StartLine, + StartCol: bi.StartCol, + EndLine: bi.EndLine, + EndCol: bi.EndCol, + NumStmt: bi.NumStmt, + Count: bi.Count + bj.Count, + }) + i++ + j++ + } else if bi.StartLine < bj.StartLine || bi.StartLine == bj.StartLine && bi.StartCol < bj.StartCol { + output.Blocks = append(output.Blocks, bi) + i++ + } else { + output.Blocks = append(output.Blocks, bj) + j++ + } + } + + for ; i < len(p1.Blocks); i++ { + output.Blocks = append(output.Blocks, p1.Blocks[i]) + } + + for ; j < len(p2.Blocks); j++ { + output.Blocks = append(output.Blocks, p2.Blocks[j]) + } + + return &output +} + +func print(profiles []*cover.Profile) { + fmt.Println("mode: atomic") + for _, profile := range profiles { + for _, block := range profile.Blocks { + fmt.Printf("%s:%d.%d,%d.%d %d %d\n", profile.FileName, block.StartLine, block.StartCol, + block.EndLine, block.EndCol, block.NumStmt, block.Count) + } + } +} + +// Copied from https://github.com/golang/tools/blob/master/cover/profile.go +type byFileName []*cover.Profile + +func (p byFileName) Len() int { return len(p) } +func (p byFileName) Less(i, j int) bool { return p[i].FileName < p[j].FileName } +func (p byFileName) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +func main() { + outputProfiles := map[string]*cover.Profile{} + for _, input := range os.Args[1:] { + inputProfiles, err := cover.ParseProfiles(input) + if err != nil { + panic(fmt.Sprintf("Error parsing %s: %v", input, err)) + } + for _, ip := range inputProfiles { + op := outputProfiles[ip.FileName] + if op == nil { + outputProfiles[ip.FileName] = ip + } else { + outputProfiles[ip.FileName] = merge(op, ip) + } + } + } + profiles := make([]*cover.Profile, 0, len(outputProfiles)) + for _, profile := range outputProfiles { + profiles = append(profiles, profile) + } + sort.Sort(byFileName(profiles)) + print(profiles) +} diff --git a/vendor/github.com/weaveworks/tcptracer-bpf/tools/runner/runner.go b/vendor/github.com/weaveworks/tcptracer-bpf/tools/runner/runner.go new file mode 100644 index 0000000000..38e5a62c98 --- /dev/null +++ b/vendor/github.com/weaveworks/tcptracer-bpf/tools/runner/runner.go @@ -0,0 +1,290 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" + "os" + "os/exec" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/mgutz/ansi" + "github.com/weaveworks/common/mflag" +) + +const ( + defaultSchedulerHost = "positive-cocoa-90213.appspot.com" + jsonContentType = "application/json" +) + +var ( + start = ansi.ColorCode("black+ub") + fail = ansi.ColorCode("red+b") + succ = ansi.ColorCode("green+b") + reset = ansi.ColorCode("reset") + + schedulerHost = defaultSchedulerHost + useScheduler = false + runParallel = false + verbose = false + timeout = 180 // In seconds. Three minutes ought to be enough for any test + + consoleLock = sync.Mutex{} +) + +type test struct { + name string + hosts int +} + +type schedule struct { + Tests []string `json:"tests"` +} + +type result struct { + test + errored bool + hosts []string +} + +type tests []test + +func (ts tests) Len() int { return len(ts) } +func (ts tests) Swap(i, j int) { ts[i], ts[j] = ts[j], ts[i] } +func (ts tests) Less(i, j int) bool { + if ts[i].hosts != ts[j].hosts { + return ts[i].hosts < ts[j].hosts + } + return ts[i].name < ts[j].name +} + +func (ts *tests) pick(available int) (test, bool) { + // pick the first test that fits in the available hosts + for i, test := range *ts { + if test.hosts <= available { + *ts = append((*ts)[:i], (*ts)[i+1:]...) + return test, true + } + } + + return test{}, false +} + +func (t test) run(hosts []string) bool { + consoleLock.Lock() + fmt.Printf("%s>>> Running %s on %s%s\n", start, t.name, hosts, reset) + consoleLock.Unlock() + + var out bytes.Buffer + + cmd := exec.Command(t.name) + cmd.Env = os.Environ() + cmd.Stdout = &out + cmd.Stderr = &out + + // replace HOSTS in env + for i, env := range cmd.Env { + if strings.HasPrefix(env, "HOSTS") { + cmd.Env[i] = fmt.Sprintf("HOSTS=%s", strings.Join(hosts, " ")) + break + } + } + + start := time.Now() + var err error + + c := make(chan error, 1) + go func() { c <- cmd.Run() }() + select { + case err = <-c: + case <-time.After(time.Duration(timeout) * time.Second): + err = fmt.Errorf("timed out") + } + + duration := float64(time.Now().Sub(start)) / float64(time.Second) + + consoleLock.Lock() + if err != nil { + fmt.Printf("%s>>> Test %s finished after %0.1f secs with error: %v%s\n", fail, t.name, duration, err, reset) + } else { + fmt.Printf("%s>>> Test %s finished with success after %0.1f secs%s\n", succ, t.name, duration, reset) + } + if err != nil || verbose { + fmt.Print(out.String()) + fmt.Println() + } + consoleLock.Unlock() + + if err != nil && useScheduler { + updateScheduler(t.name, duration) + } + + return err != nil +} + +func updateScheduler(test string, duration float64) { + req := &http.Request{ + Method: "POST", + Host: schedulerHost, + URL: &url.URL{ + Opaque: fmt.Sprintf("/record/%s/%0.2f", url.QueryEscape(test), duration), + Scheme: "http", + Host: schedulerHost, + }, + Close: true, + } + if resp, err := http.DefaultClient.Do(req); err != nil { + fmt.Printf("Error updating scheduler: %v\n", err) + } else { + resp.Body.Close() + } +} + +func getSchedule(tests []string) ([]string, error) { + var ( + userName = os.Getenv("CIRCLE_PROJECT_USERNAME") + project = os.Getenv("CIRCLE_PROJECT_REPONAME") + buildNum = os.Getenv("CIRCLE_BUILD_NUM") + testRun = userName + "-" + project + "-integration-" + buildNum + shardCount = os.Getenv("CIRCLE_NODE_TOTAL") + shardID = os.Getenv("CIRCLE_NODE_INDEX") + requestBody = &bytes.Buffer{} + ) + if err := json.NewEncoder(requestBody).Encode(schedule{tests}); err != nil { + return []string{}, err + } + url := fmt.Sprintf("http://%s/schedule/%s/%s/%s", schedulerHost, testRun, shardCount, shardID) + resp, err := http.Post(url, jsonContentType, requestBody) + if err != nil { + return []string{}, err + } + var sched schedule + if err := json.NewDecoder(resp.Body).Decode(&sched); err != nil { + return []string{}, err + } + return sched.Tests, nil +} + +func getTests(testNames []string) (tests, error) { + var err error + if useScheduler { + testNames, err = getSchedule(testNames) + if err != nil { + return tests{}, err + } + } + tests := tests{} + for _, name := range testNames { + parts := strings.Split(strings.TrimSuffix(name, "_test.sh"), "_") + numHosts, err := strconv.Atoi(parts[len(parts)-1]) + if err != nil { + numHosts = 1 + } + tests = append(tests, test{name, numHosts}) + fmt.Printf("Test %s needs %d hosts\n", name, numHosts) + } + return tests, nil +} + +func summary(tests, failed tests) { + if len(failed) > 0 { + fmt.Printf("%s>>> Ran %d tests, %d failed%s\n", fail, len(tests), len(failed), reset) + for _, test := range failed { + fmt.Printf("%s>>> Fail %s%s\n", fail, test.name, reset) + } + } else { + fmt.Printf("%s>>> Ran %d tests, all succeeded%s\n", succ, len(tests), reset) + } +} + +func parallel(ts tests, hosts []string) bool { + testsCopy := ts + sort.Sort(sort.Reverse(ts)) + resultsChan := make(chan result) + outstanding := 0 + failed := tests{} + for len(ts) > 0 || outstanding > 0 { + // While we have some free hosts, try and schedule + // a test on them + for len(hosts) > 0 { + test, ok := ts.pick(len(hosts)) + if !ok { + break + } + testHosts := hosts[:test.hosts] + hosts = hosts[test.hosts:] + + go func() { + errored := test.run(testHosts) + resultsChan <- result{test, errored, testHosts} + }() + outstanding++ + } + + // Otherwise, wait for the test to finish and return + // the hosts to the pool + result := <-resultsChan + hosts = append(hosts, result.hosts...) + outstanding-- + if result.errored { + failed = append(failed, result.test) + } + } + summary(testsCopy, failed) + return len(failed) > 0 +} + +func sequential(ts tests, hosts []string) bool { + failed := tests{} + for _, test := range ts { + if test.run(hosts) { + failed = append(failed, test) + } + } + summary(ts, failed) + return len(failed) > 0 +} + +func main() { + mflag.BoolVar(&useScheduler, []string{"scheduler"}, false, "Use scheduler to distribute tests across shards") + mflag.BoolVar(&runParallel, []string{"parallel"}, false, "Run tests in parallel on hosts where possible") + mflag.BoolVar(&verbose, []string{"v"}, false, "Print output from all tests (Also enabled via DEBUG=1)") + mflag.StringVar(&schedulerHost, []string{"scheduler-host"}, defaultSchedulerHost, "Hostname of scheduler.") + mflag.IntVar(&timeout, []string{"timeout"}, 180, "Max time to run one test for, in seconds") + mflag.Parse() + + if len(os.Getenv("DEBUG")) > 0 { + verbose = true + } + + testArgs := mflag.Args() + tests, err := getTests(testArgs) + if err != nil { + fmt.Printf("Error parsing tests: %v (%v)\n", err, testArgs) + os.Exit(1) + } + + hosts := strings.Fields(os.Getenv("HOSTS")) + maxHosts := len(hosts) + if maxHosts == 0 { + fmt.Print("No HOSTS specified.\n") + os.Exit(1) + } + + var errored bool + if runParallel { + errored = parallel(tests, hosts) + } else { + errored = sequential(tests, hosts) + } + + if errored { + os.Exit(1) + } +} diff --git a/vendor/github.com/weaveworks/tcptracer-bpf/tools/socks/main.go b/vendor/github.com/weaveworks/tcptracer-bpf/tools/socks/main.go new file mode 100644 index 0000000000..7cd8c70863 --- /dev/null +++ b/vendor/github.com/weaveworks/tcptracer-bpf/tools/socks/main.go @@ -0,0 +1,97 @@ +package main + +import ( + "fmt" + "net" + "net/http" + "os" + "strings" + "text/template" + + socks5 "github.com/armon/go-socks5" + "github.com/weaveworks/common/mflag" + "github.com/weaveworks/common/mflagext" + "golang.org/x/net/context" +) + +type pacFileParameters struct { + HostMatch string + Aliases map[string]string +} + +const ( + pacfile = ` +function FindProxyForURL(url, host) { + if(shExpMatch(host, "{{.HostMatch}}")) { + return "SOCKS5 localhost:8000"; + } + {{range $key, $value := .Aliases}} + if (host == "{{$key}}") { + return "SOCKS5 localhost:8000"; + } + {{end}} + return "DIRECT"; +} +` +) + +func main() { + var ( + as []string + hostMatch string + ) + mflagext.ListVar(&as, []string{"a", "-alias"}, []string{}, "Specify hostname aliases in the form alias:hostname. Can be repeated.") + mflag.StringVar(&hostMatch, []string{"h", "-host-match"}, "*.weave.local", "Specify main host shExpMatch expression in pacfile") + mflag.Parse() + + var aliases = map[string]string{} + for _, a := range as { + parts := strings.SplitN(a, ":", 2) + if len(parts) != 2 { + fmt.Printf("'%s' is not a valid alias.\n", a) + mflag.Usage() + os.Exit(1) + } + aliases[parts[0]] = parts[1] + } + + go socksProxy(aliases) + + t := template.Must(template.New("pacfile").Parse(pacfile)) + http.HandleFunc("/proxy.pac", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/x-ns-proxy-autoconfig") + t.Execute(w, pacFileParameters{hostMatch, aliases}) + }) + + if err := http.ListenAndServe(":8080", nil); err != nil { + panic(err) + } +} + +type aliasingResolver struct { + aliases map[string]string + socks5.NameResolver +} + +func (r aliasingResolver) Resolve(ctx context.Context, name string) (context.Context, net.IP, error) { + if alias, ok := r.aliases[name]; ok { + return r.NameResolver.Resolve(ctx, alias) + } + return r.NameResolver.Resolve(ctx, name) +} + +func socksProxy(aliases map[string]string) { + conf := &socks5.Config{ + Resolver: aliasingResolver{ + aliases: aliases, + NameResolver: socks5.DNSResolver{}, + }, + } + server, err := socks5.New(conf) + if err != nil { + panic(err) + } + if err := server.ListenAndServe("tcp", ":8000"); err != nil { + panic(err) + } +} diff --git a/vendor/manifest b/vendor/manifest index 26ee94ac0b..3e3ce967a0 100644 --- a/vendor/manifest +++ b/vendor/manifest @@ -1016,6 +1016,15 @@ "revision": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75", "branch": "master" }, + { + "importpath": "github.com/iovisor/gobpf/elf", + "repository": "https://github.com/iovisor/gobpf", + "vcs": "git", + "revision": "21a9e281bf73650b9245d4ebcf111716338ae652", + "branch": "master", + "path": "/elf", + "notests": true + }, { "importpath": "github.com/jmespath/go-jmespath", "repository": "https://github.com/jmespath/go-jmespath", @@ -1441,6 +1450,14 @@ "branch": "master", "notests": true }, + { + "importpath": "github.com/weaveworks/tcptracer-bpf", + "repository": "https://github.com/weaveworks/tcptracer-bpf", + "vcs": "git", + "revision": "9065e4672b606b5f8d2f2d664938641fa0ee0d0f", + "branch": "master", + "notests": true + }, { "importpath": "github.com/weaveworks/weave/common", "repository": "https://github.com/weaveworks/weave",