From 0d7a8e706d1ba7d9f276b5d836a90a6c70910870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Wed, 13 Apr 2016 14:17:46 +0200 Subject: [PATCH] first commit --- .envrc | 2 + .gitignore | 3 + Makefile | 2 + README.md | 6 ++ capture-connections.lua | 98 +++++++++++++++++++++++++++++++ captured-connections.tsv | 86 +++++++++++++++++++++++++++ main.go | 122 +++++++++++++++++++++++++++++++++++++++ rancher/client.go | 34 +++++++++++ rancher/types.go | 18 ++++++ sharelatex/client.go | 82 ++++++++++++++++++++++++++ sysdig.sh | 5 ++ sysdig/graph.go | 41 +++++++++++++ sysdig/graph_test.go | 62 ++++++++++++++++++++ sysdig/records.go | 97 +++++++++++++++++++++++++++++++ sysdig/start.go | 42 ++++++++++++++ 15 files changed, 700 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 capture-connections.lua create mode 100644 captured-connections.tsv create mode 100644 main.go create mode 100644 rancher/client.go create mode 100644 rancher/types.go create mode 100644 sharelatex/client.go create mode 100644 sysdig.sh create mode 100644 sysdig/graph.go create mode 100644 sysdig/graph_test.go create mode 100644 sysdig/records.go create mode 100644 sysdig/start.go diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..c778fe3 --- /dev/null +++ b/.envrc @@ -0,0 +1,2 @@ +layout python3 +. ~/.rancher-credentials diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..64d9d6c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +selenium-server-standalone-*.jar +callgraph +/.direnv diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2b3d683 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +test: + go test ./sysdig -cwd $(CURDIR) diff --git a/README.md b/README.md new file mode 100644 index 0000000..c70cba6 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# Get microservice callgraph + +$ go build . +$ wget http://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.0.jar +$ java -jar selenium-server-standalone-2.53.0.jar +$ ./callgraph http://sharelatex.local/login 192.168.8.33 192.168.8.34 192.168.8.35 192.168.8.36 192.168.8.37 192.168.8.38 192.168.8.17 192.168.8.18 192.168.8.19 | dot -Tpng > graph.png diff --git a/capture-connections.lua b/capture-connections.lua new file mode 100644 index 0000000..83ea261 --- /dev/null +++ b/capture-connections.lua @@ -0,0 +1,98 @@ +description = "description"; +short_description = "desc"; +category = "Net"; + +args = {} + +require "common" + +local fields = {} +function on_init() + local mapping = { + isread = "evt.is_io_read", + buflen = "evt.buflen", + proc = "proc.name", + pid = "proc.pid", + tid = "thread.tid", + container = "container.name", + sip = "fd.sip", + sport = "fd.sport", + cip = "fd.cip", + cport = "fd.cport", + evt_type = "evt.type", + proto = "fd.l4proto", + lip = "fd.lip", + } + for k,v in pairs(mapping) do + fields[k] = chisel.request_field(v) + end + key_fields = {fields.container, fields.proc, fields.pid, fields.tid, fields.proto, fields.sip, fields.sport, fields.cip, fields.cport} + + sysdig.set_snaplen(0) + chisel.set_filter("evt.is_io=true and (fd.type=ipv4 or fd.type=ipv6) and fd.rip exists and fd.lip exists and container.name!=host") + return true +end + +local stats = {} + +local DEBUG = false + +function on_event() + -- only capture connections of servers + local sip = evt.field(fields.sip) + if not (evt.field(fields.lip) == sip or evt.field(fields.rip) == sip) then + return true + end + + local dir + if evt.field(fields.isread) then + dir = "rx" + else + dir = "tx" + end + + if DEBUG then + function to_s(v) + return (evt.field(v) or "nil").." " + end + io.write("DEBUG: ", + to_s(fields.container), + to_s(fields.proc), + to_s(fields.pid), + to_s(fields.tid), + to_s(fields.proto), + to_s(fields.sip), + to_s(fields.sport), + to_s(fields.cip), + to_s(fields.cport), + to_s(fields.lip), + dir, "\n") + end + + local t = { } + for k,v in ipairs(key_fields) do + t[#t+1] = tostring(evt.field(v)) + end + + t[#t+1] = dir + local key = table.concat(t, "\t") + + stats[key] = (stats[key] or 0) + (evt.field(fields.buflen) or 0) + + return true +end + +function string.starts(string, prefix) + return string.sub(string, 1, string.len(start)) == prefix +end + +function on_capture_start() + hostname = sysdig.get_machine_info().hostname + return true +end + +function on_capture_end() + for k, v in pairs(stats) do + io.write(hostname, "\t", k, "\t", v, "\n") + end +end diff --git a/captured-connections.tsv b/captured-connections.tsv new file mode 100644 index 0000000..9040a52 --- /dev/null +++ b/captured-connections.tsv @@ -0,0 +1,86 @@ +macmini4 rancher-agent python 32703 32703 tcp 192.168.8.17 8080 192.168.8.34 59245 rx 3176 +macmini4 rancher-agent host-api 526 619 tcp 192.168.8.17 8080 192.168.8.34 59244 rx 12 +macmini4 rancher-agent python 32703 32703 tcp 192.168.8.17 8080 192.168.8.34 59247 tx 3304 +macmini4 rancher-agent host-api 526 526 tcp 192.168.8.17 8080 192.168.8.34 59244 tx 6 +macmini4 rancher-agent python 32703 32703 tcp 192.168.8.17 8080 192.168.8.34 59247 rx 1044 +macmini4 rancher-agent host-api 526 531 tcp 192.168.8.17 8080 192.168.8.34 59244 tx 6 +macmini4 rancher-agent host-api 526 528 tcp 192.168.8.17 8080 192.168.8.34 59244 tx 6 +macmini4 rancher-agent host-api 526 619 tcp 192.168.8.17 8080 192.168.8.34 59244 tx 18 +macmini6 rancher-agent host-api 1034 1034 tcp 192.168.8.17 8080 192.168.8.36 40729 tx 6 +macmini6 rancher-agent python 753 753 tcp 192.168.8.17 8080 192.168.8.36 40732 tx 3299 +macmini6 rancher-agent host-api 1034 1037 tcp 192.168.8.17 8080 192.168.8.36 40729 tx 6 +macmini6 rancher-agent host-api 1034 1041 tcp 192.168.8.17 8080 192.168.8.36 40729 tx 12 +macmini6 rancher-agent host-api 1034 1039 tcp 192.168.8.17 8080 192.168.8.36 40729 tx 24 +macmini6 rancher-agent host-api 1034 1039 tcp 192.168.8.17 8080 192.168.8.36 40729 rx 16 +macmini6 rancher-agent python 753 753 tcp 192.168.8.17 8080 192.168.8.36 40730 rx 2795 +macmini6 rancher-agent python 753 753 tcp 192.168.8.17 8080 192.168.8.36 40732 rx 1044 +macmini3 rancher-agent host-api 31449 31449 tcp 192.168.8.17 8080 192.168.8.33 52136 tx 24 +macmini3 rancher-agent host-api 31449 31454 tcp 192.168.8.17 8080 192.168.8.33 52136 tx 6 +macmini3 rancher-agent host-api 31449 31506 tcp 192.168.8.17 8080 192.168.8.33 52136 tx 12 +macmini3 rancher-agent host-api 31449 31455 tcp 192.168.8.17 8080 192.168.8.33 52136 tx 6 +macmini3 rancher-agent python 31167 31167 tcp 192.168.8.17 8080 192.168.8.33 52133 rx 3175 +macmini3 rancher-agent python 31167 31167 tcp 192.168.8.17 8080 192.168.8.33 52138 tx 3295 +macmini3 rancher-agent host-api 31449 31449 tcp 192.168.8.17 8080 192.168.8.33 52136 rx 16 +macmini3 rancher-agent python 31167 31167 tcp 192.168.8.17 8080 192.168.8.33 52138 rx 1044 +macmini5 rancher-agent host-api 404 412 tcp 192.168.8.17 8080 192.168.8.35 50962 rx 16 +macmini5 rancher-agent host-api 404 418 tcp 192.168.8.17 8080 192.168.8.35 50962 tx 6 +macmini5 rancher-agent host-api 404 412 tcp 192.168.8.17 8080 192.168.8.35 50962 tx 24 +macmini5 rancher-agent python 32581 32581 tcp 192.168.8.17 8080 192.168.8.35 50964 tx 3299 +macmini5 rancher-agent python 32581 32581 tcp 192.168.8.17 8080 192.168.8.35 50964 rx 1040 +macmini5 rancher-agent python 32581 32581 tcp 192.168.8.17 8080 192.168.8.35 50958 rx 2794 +macmini5 rancher-agent host-api 404 417 tcp 192.168.8.17 8080 192.168.8.35 50962 tx 18 +macmini7 rancher-agent python 5674 5674 tcp 192.168.8.17 8080 192.168.8.37 35624 tx 3296 +macmini7 rancher-agent host-api 5963 5965 tcp 192.168.8.17 8080 192.168.8.37 35618 tx 12 +macmini7 rancher-agent python 5674 5674 tcp 192.168.8.17 8080 192.168.8.37 35624 rx 1044 +macmini7 rancher-agent host-api 5963 5963 tcp 192.168.8.17 8080 192.168.8.37 35618 tx 12 +macmini7 rancher-agent host-api 5963 6026 tcp 192.168.8.17 8080 192.168.8.37 35618 rx 16 +macmini7 rancher-agent host-api 5963 6026 tcp 192.168.8.17 8080 192.168.8.37 35618 tx 24 +macmini7 rancher-agent python 5674 5674 tcp 192.168.8.17 8080 192.168.8.37 35619 rx 2795 +slfy79 rancher-agent haproxy 21608 21608 tcp 172.17.0.11 80 192.168.8.17 60579 tx 493799 +slfy79 rangerserver-v1 websocket-proxy 16398 16401 tcp 172.17.0.2 8080 192.168.8.17 50504 rx 553 +slfy79 rancher-agent haproxy 21608 21608 tcp 172.17.0.11 80 192.168.8.17 60574 tx 800550 +slfy79 rangerserver-v1 websocket-proxy 16398 16402 tcp 172.17.0.2 8080 192.168.8.17 50500 tx 2 +slfy79 rangerserver-v1 websocket-proxy 16398 16402 tcp 172.17.0.2 8080 192.168.8.17 42695 tx 381 +slfy79 rancher-agent haproxy 21608 21608 tcp 172.17.0.11 80 192.168.8.17 60580 rx 18573 +slfy79 rangerserver-v1 websocket-proxy 16398 16398 tcp 172.17.0.2 8080 192.168.8.17 42695 tx 381 +slfy79 rangerserver-v1 websocket-proxy 16398 16398 tcp 172.17.0.2 8080 192.168.8.17 50500 rx 12 +slfy79 rangerserver-v1 websocket-proxy 16398 16401 tcp 172.17.0.2 8080 192.168.8.17 50500 tx 4 +slfy79 rancher-agent haproxy 21608 21608 tcp 172.17.0.11 80 192.168.8.17 60595 tx 311083 +slfy79 rangerserver-v1 websocket-proxy 16398 16400 tcp 172.17.0.2 8080 192.168.8.17 42695 tx 381 +slfy79 rancher-agent haproxy 21608 21608 tcp 172.17.0.11 80 192.168.8.17 60579 rx 23733 +slfy79 rancher-agent haproxy 21608 21608 tcp 172.17.0.11 80 192.168.8.17 60574 rx 32808 +slfy79 rangerserver-v1 websocket-proxy 16398 16403 tcp 172.17.0.2 8080 192.168.8.17 50500 rx 6 +slfy79 rangerserver-v1 websocket-proxy 16398 16403 tcp 172.17.0.2 8080 192.168.8.17 42695 tx 381 +slfy79 rancher-agent haproxy 21608 21608 tcp 172.17.0.11 80 192.168.8.17 60872 tx 1882 +slfy79 rangerserver-v1 websocket-proxy 16398 16400 tcp 172.17.0.2 8080 192.168.8.17 50500 tx 2 +slfy79 rancher-agent haproxy 21608 21608 tcp 172.17.0.11 80 192.168.8.17 60872 rx 920 +slfy79 7461f018-c62e-4b02-9e1b-af6547e9a1e2 charon 13331 13335 udp 192.168.8.18 4500 192.168.8.17 4500 tx 1 +slfy79 rangerserver-v1 websocket-proxy 16398 16400 tcp 172.17.0.2 8080 192.168.8.17 50504 tx 522 +slfy79 rangerserver-v1 websocket-proxy 16398 16403 tcp 172.17.0.2 8080 192.168.8.17 50504 rx 8573 +slfy79 rancher-agent haproxy 21608 21608 tcp 172.17.0.11 80 192.168.8.17 60578 rx 30589 +slfy79 rancher-agent haproxy 21608 21608 tcp 172.17.0.11 80 192.168.8.17 60580 tx 265880 +slfy79 rancher-agent haproxy 21608 21608 tcp 172.17.0.11 80 192.168.8.17 60530 tx 6328 +slfy79 rangerserver-v1 websocket-proxy 16398 16398 tcp 172.17.0.2 8080 192.168.8.17 50495 tx 1171 +slfy79 rangerserver-v1 websocket-proxy 16398 16398 tcp 172.17.0.2 8080 192.168.8.17 50500 tx 8 +slfy79 rangerserver-v1 websocket-proxy 16398 16402 tcp 172.17.0.2 8080 192.168.8.17 50500 rx 12 +slfy79 rancher-agent haproxy 21608 21608 tcp 172.17.0.11 80 192.168.8.17 60595 rx 21729 +slfy79 rangerserver-v1 websocket-proxy 16398 16403 tcp 172.17.0.2 8080 192.168.8.17 50504 tx 522 +slfy79 rancher-agent haproxy 21608 21608 tcp 172.17.0.11 80 192.168.8.17 60530 rx 102 +slfy79 7461f018-c62e-4b02-9e1b-af6547e9a1e2 charon 13331 13335 udp 192.168.8.19 4500 192.168.8.17 4500 tx 2 +slfy79 rangerserver-v1 websocket-proxy 16398 16403 tcp 172.17.0.2 8080 192.168.8.17 50495 tx 408 +slfy79 rangerserver-v1 websocket-proxy 16398 16398 tcp 172.17.0.2 8080 192.168.8.17 50504 rx 1108 +slfy79 rancher-agent haproxy 21608 21608 tcp 172.17.0.11 80 192.168.8.17 60592 tx 524911 +slfy79 rangerserver-v1 websocket-proxy 16398 16401 tcp 172.17.0.2 8080 192.168.8.17 50500 rx 12 +slfy79 rancher-agent haproxy 21608 21608 tcp 172.17.0.11 80 192.168.8.17 60592 rx 24020 +slfy79 rangerserver-v1 websocket-proxy 16398 16400 tcp 172.17.0.2 8080 192.168.8.17 50500 rx 6 +slfy79 rangerserver-v1 websocket-proxy 16398 16402 tcp 172.17.0.2 8080 192.168.8.17 50495 tx 1596 +slfy85 rancher-agent host-api 26530 26536 tcp 192.168.8.17 8080 192.168.8.19 52183 rx 16 +slfy85 rancher-agent host-api 26530 26550 tcp 192.168.8.17 8080 192.168.8.19 52183 tx 12 +slfy85 rancher-agent python 26248 26248 tcp 192.168.8.17 8080 192.168.8.19 52185 tx 9748 +slfy85 rancher-agent python 26248 26248 tcp 192.168.8.17 8080 192.168.8.19 52179 rx 19204 +slfy85 rancher-agent host-api 26530 26544 tcp 192.168.8.17 8080 192.168.8.19 52183 tx 6 +slfy85 9d011c95-bfcd-4880-adc4-2215175de5da charon 3236 3242 udp 192.168.8.17 4500 192.168.8.19 4500 tx 2 +slfy85 rancher-agent python 26248 26248 tcp 192.168.8.17 8080 192.168.8.19 52185 rx 1044 +slfy85 rancher-agent host-api 26530 26535 tcp 192.168.8.17 8080 192.168.8.19 52183 tx 6 +slfy85 rancher-agent host-api 26530 26536 tcp 192.168.8.17 8080 192.168.8.19 52183 tx 24 +slfy85 9d011c95-bfcd-4880-adc4-2215175de5da charon 3236 3242 udp 192.168.8.18 4500 192.168.8.19 4500 tx 2 diff --git a/main.go b/main.go new file mode 100644 index 0000000..d0a2fb0 --- /dev/null +++ b/main.go @@ -0,0 +1,122 @@ +package main + +import ( + "flag" + "fmt" + "net" + "os" + "os/exec" + "syscall" + "time" + + "github.com/mic92/callgraph/rancher" + "github.com/mic92/callgraph/sharelatex" + "github.com/mic92/callgraph/sysdig" +) + +var ( + rancherHost = flag.String("rancher-host", "localhost:8080", "host of rancher server") + rancherAccessKey = flag.String("rancher-access-key", os.Getenv("RANCHER_ACCESS_KEY"), "api access key") + rancherSecretKey = flag.String("rancher-secret-key", os.Getenv("RANCHER_SECRET_KEY"), "api secret key") +) + +func parseArgs() (sharelatexUrl string, hosts []string) { + flag.Parse() + if len(os.Args) < 2 { + fmt.Fprintf(os.Stderr, "USAGE: %s SHARELATEXURL HOSTS...", os.Args[0]) + os.Exit(1) + } + if *rancherAccessKey == "" { + fmt.Fprintf(os.Stderr, "Set RANCHER_ACCESS_KEY environment variable") + os.Exit(1) + } + if *rancherSecretKey == "" { + fmt.Fprintf(os.Stderr, "Set RANCHER_SECRET_KEY environment variable") + os.Exit(1) + } + return os.Args[1], os.Args[2:] +} +func rancherServices() ([]sysdig.Service, error) { + c := rancher.New(*rancherHost, *rancherAccessKey, *rancherSecretKey) + var api rancher.Api + err := c.Get(fmt.Sprintf("http://%s/v1", c.Host), &api) + if err != nil { + return nil, fmt.Errorf("failed to request rancher api: %v", err) + } + if api.Links == nil { + return nil, fmt.Errorf("no links object found in api response") + } + val, ok := api.Links["containers"] + if !ok { + return nil, fmt.Errorf("no container link found in api response") + } + var containers rancher.Containers + err = c.Get(val, &containers) + if err != nil { + return nil, fmt.Errorf("could not get container list from api: %s", err) + } + serviceMap := make(map[string][]net.IP) + for _, container := range containers.Data { + var name string + if container.Labels.ProjectServiceName == "" { + name = container.Name + } else { + name = container.Labels.ProjectServiceName + } + ip := net.ParseIP(container.PrimaryIpAddress) + if ip == nil { + return nil, fmt.Errorf("Got invalid IP from api for '%s' container: %s", container.Name, ip) + } + serviceMap[name] = append(serviceMap[name], ip) + } + var services []sysdig.Service + for name, ips := range serviceMap { + services = append(services, sysdig.Service{name, ips}) + } + return services, nil +} + +func die(message string, args ...interface{}) { + fmt.Fprintf(os.Stderr, message, args...) + fmt.Fprint(os.Stderr, "\n") + os.Exit(1) +} + +func main() { + url, hosts := parseArgs() + + cmd, err := sysdig.Start(hosts) + if err != nil { + die("failed to start sysdig: %v", err) + } + time.Sleep(4 * time.Second) + err = sharelatex.IntegrationTest(url) + if err != nil { + die("selenium not running?: %v", err) + } + time.Sleep(1 * time.Second) + cmd.SendStopSignal() + + var records []sysdig.Record + for range hosts { + select { + case result := <-cmd.Results: + if exitError, ok := result.Error.(*exec.ExitError); ok { + waitStatus := exitError.Sys().(syscall.WaitStatus) + if waitStatus != 130 { // Interrupt + fmt.Printf("[%s] sysdig failed with %v\n", result.Host.Name, result.Error, result.Output) + } + } + r, err := sysdig.ParseOutput(result.Output) + if err != nil { + die("Error parsing results from %s", result.Host.Name) + } + records = append(records, r...) + } + } + services, err := rancherServices() + if err != nil { + die("getting rancher services failed: %s", err) + } + (sysdig.Records(records)).PrintGraph(services) +} diff --git a/rancher/client.go b/rancher/client.go new file mode 100644 index 0000000..d18485f --- /dev/null +++ b/rancher/client.go @@ -0,0 +1,34 @@ +package rancher + +import ( + "encoding/json" + "io/ioutil" + "net/http" +) + +type Client struct { + Host, SecretKey, AccessKey string + client http.Client +} + +func New(host, secretkey, accesskey string) Client { + return Client{host, secretkey, accesskey, http.Client{}} +} + +func (c *Client) Get(url string, result interface{}) error { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return err + } + req.SetBasicAuth(c.AccessKey, c.SecretKey) + resp, err := c.client.Do(req) + if err != nil { + return err + } + body, err := ioutil.ReadAll(resp.Body) + json.Unmarshal(body, result) + if err != nil { + return err + } + return nil +} diff --git a/rancher/types.go b/rancher/types.go new file mode 100644 index 0000000..df90910 --- /dev/null +++ b/rancher/types.go @@ -0,0 +1,18 @@ +package rancher + +type Api struct { + Links map[string]string `json:"links"` + Data []interface{} `json:"data"` +} + +type Containers struct { + Data []Container `json:"data"` +} + +type Container struct { + Name string `json:"name"` + PrimaryIpAddress string `json:"primaryIpAddress"` + Labels struct { + ProjectServiceName string `json:"io.rancher.project_service.name"` + } +} diff --git a/sharelatex/client.go b/sharelatex/client.go new file mode 100644 index 0000000..36253ed --- /dev/null +++ b/sharelatex/client.go @@ -0,0 +1,82 @@ +package sharelatex + +import ( + "fmt" + "math/rand" + "net/http" + "os" + "path/filepath" + "time" + + "github.com/tebeka/selenium" +) + +func IntegrationTest(url string) error { + _, err := http.Get(url) + if err != nil { + fmt.Printf("failed to get '%s': %s", url, err) + } + caps := selenium.Capabilities{"browserName": "firefox"} + wd, err := selenium.NewRemote(caps, "") + if err != nil { + return err + } + defer wd.Quit() + + // Get simple playground interface + wd.Get(url) + + e, _ := wd.FindElement(selenium.ByXPATH, "//input[@type='email']") + e.Clear() + e.SendKeys("joerg@higgsboson.tk") + + e, _ = wd.FindElement(selenium.ByXPATH, "//input[@type='password']") + e.Clear() + e.SendKeys("password") + + e, _ = wd.FindElement(selenium.ByXPATH, "//button[@type='submit']") + e.Click() + + for { + _, err = wd.FindElement(selenium.ByLinkText, "Account") + if err == nil { + break + } + time.Sleep(time.Millisecond * 100) + } + + time.Sleep(time.Millisecond * 4000) + e, _ = wd.FindElement(selenium.ByLinkText, "New Project") + e.Click() + + e, _ = wd.FindElement(selenium.ByLinkText, "Blank Project") + e.Click() + + e, _ = wd.FindElement(selenium.ByXPATH, "//input[@type='text']") + e.Clear() + e.SendKeys(fmt.Sprintf("p %d", rand.Int63())) + + e, _ = wd.FindElement(selenium.ByCSSSelector, "button.btn-primary") // Create + e.Click() + time.Sleep(time.Millisecond * 3000) + + e, _ = wd.FindElement(selenium.ByCSSSelector, `i.fa-comment`) + e.Click() + + e, _ = wd.FindElement(selenium.ByCSSSelector, `div.new-message textarea`) + e.SendKeys("hello world" + selenium.EnterKey) + + e, _ = wd.FindElement(selenium.ByCSSSelector, "i.fa.fa-upload") // Upload + e.Click() + + e, _ = wd.FindElement(selenium.ByName, "file") // Create + dir, err := filepath.Abs(filepath.Dir(os.Args[0])) + e.SendKeys(filepath.Join(dir, "./test.txt")) + + time.Sleep(time.Millisecond * 500) + e, _ = wd.FindElement(selenium.ByCSSSelector, "textarea") // Latex Doc + e.Clear() + e.SendKeys(`\documentclass{article} \begin{document} ello \end{document}`) + time.Sleep(time.Millisecond * 1000) + return nil +} diff --git a/sysdig.sh b/sysdig.sh new file mode 100644 index 0000000..0b98684 --- /dev/null +++ b/sysdig.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash -e +t="$(mktemp)"; f() { + rm "$t" +} +trap f EXIT; cat > "$t"; sudo sysdig -c "$t" diff --git a/sysdig/graph.go b/sysdig/graph.go new file mode 100644 index 0000000..17e8915 --- /dev/null +++ b/sysdig/graph.go @@ -0,0 +1,41 @@ +package sysdig + +import ( + "fmt" + "net" +) + +type Records []Record + +type Service struct { + Name string + Ips []net.IP +} + +type Connection struct { + From string + To string +} + +func (records Records) PrintGraph(services []Service) { + serviceMap := make(map[string]string) + for _, service := range services { + for _, ip := range service.Ips { + serviceMap[ip.String()] = service.Name + } + } + + connections := make(map[string]Connection) + for _, r := range records { + if clientService, ok := serviceMap[r.ClientIp.String()]; ok { + if serverService, ok := serviceMap[r.ServerIp.String()]; ok { + connections[clientService+":"+serverService] = Connection{clientService, serverService} + } + } + } + fmt.Printf("digraph G {\n") + for _, connection := range connections { + fmt.Printf("\"%s\" -> \"%s\"\n", connection.From, connection.To) + } + fmt.Printf("}\n") +} diff --git a/sysdig/graph_test.go b/sysdig/graph_test.go new file mode 100644 index 0000000..0c6084d --- /dev/null +++ b/sysdig/graph_test.go @@ -0,0 +1,62 @@ +package sysdig_test + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "reflect" + "runtime" + "testing" + + "github.com/mic92/callgraph/sysdig" +) + +func assert(tb testing.TB, condition bool, msg string, v ...interface{}) { + if !condition { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...) + tb.FailNow() + } +} + +func ok(tb testing.TB, err error) { + if err != nil { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("\033[31m%s:%d: unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error()) + tb.FailNow() + } +} + +func equals(tb testing.TB, exp, act interface{}) { + if !reflect.DeepEqual(exp, act) { + _, file, line, _ := runtime.Caller(1) + fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act) + tb.FailNow() + } +} + +var ( + cwd_arg = flag.String("cwd", "", "set cwd") +) + +func init() { + flag.Parse() + if *cwd_arg != "" { + if err := os.Chdir(*cwd_arg); err != nil { + fmt.Println("Chdir error:", err) + } + } +} + +func TestPrintGraph(t *testing.T) { + f, e := os.Open("./captured-connections.tsv") + ok(t, e) + c, e := ioutil.ReadAll(f) + ok(t, e) + _, e = sysdig.ParseOutput(string(c)) + ok(t, e) + //(sysdig.Records(r)).PrintGraph() + //t.Fatal() +} diff --git a/sysdig/records.go b/sysdig/records.go new file mode 100644 index 0000000..db87cc8 --- /dev/null +++ b/sysdig/records.go @@ -0,0 +1,97 @@ +package sysdig + +import ( + "encoding/csv" + "fmt" + "io" + "log" + "net" + "strconv" + "strings" +) + +const ( + RX = iota + TX +) + +type Record struct { + Host, Container, Process string + Pid, Tid uint64 + Proto string + ServerIp net.IP + ServerPort uint16 + ClientIp net.IP + ClientPort uint16 + Direction int + Bytes uint64 +} + +func ParseOutput(result string) ([]Record, error) { + r := csv.NewReader(strings.NewReader(result)) + r.Comma = '\t' + + var records []Record + for { + record, err := r.Read() + if err == io.EOF { + break + } + if err != nil { + log.Fatal(err) + } + if record[0] == "host" { + continue + } + pid, err := strconv.ParseUint(record[3], 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid pid %s: %v", record[2], err) + } + tid, err := strconv.ParseUint(record[4], 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid tid %s: %v", record[3], err) + } + sip := net.ParseIP(record[6]) + if sip == nil { + return nil, fmt.Errorf("invalid ip '%s'", record[6]) + } + sport, err := strconv.ParseUint(record[7], 10, 16) + if err != nil { + return nil, fmt.Errorf("invalid port %s: %v", record[7], err) + } + cip := net.ParseIP(record[8]) + if cip == nil { + return nil, fmt.Errorf("invalid ip '%s'", record[8]) + } + cport, err := strconv.ParseUint(record[9], 10, 16) + if err != nil { + return nil, fmt.Errorf("invalid port %s: %v", record[9], err) + } + var direction int + if record[10] == "rx" { + direction = RX + } else { + direction = TX + } + + bytes, err := strconv.ParseUint(record[11], 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid transmission size %s: %v", record[11], err) + } + records = append(records, Record{ + record[0], + record[1], + record[2], + pid, + tid, + record[5], + sip, + uint16(sport), + cip, + uint16(cport), + direction, + bytes, + }) + } + return records, nil +} diff --git a/sysdig/start.go b/sysdig/start.go new file mode 100644 index 0000000..d826dc2 --- /dev/null +++ b/sysdig/start.go @@ -0,0 +1,42 @@ +package sysdig + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/mic92/clusterssh" +) + +func Start(hostnames []string) (*clusterssh.Command, error) { + hosts := make([]clusterssh.Host, len(hostnames)) + for i, arg := range hostnames { + host, err := clusterssh.ParseHost(arg) + if err != nil { + return nil, fmt.Errorf("invalid host '%s': %v", arg, err) + } + hosts[i] = *host + } + dir, err := filepath.Abs(filepath.Dir(os.Args[0])) + f1, err := os.Open(filepath.Join(dir, "capture-connections.lua")) + + if err != nil { + return nil, fmt.Errorf("failed to open sysdig plugin") + } + sysdigPlugin, err := ioutil.ReadAll(f1) + if err != nil { + return nil, fmt.Errorf("failed to read sysdig plugin: %v", err) + } + f2, err := os.Open(filepath.Join(dir, "sysdig.sh")) + if err != nil { + return nil, fmt.Errorf("failed to open sysdig plugin: %v", err) + } + sysdigCommand, err := ioutil.ReadAll(f2) + if err != nil { + return nil, fmt.Errorf("failed to read sysdig command: %v", err) + } + cluster := clusterssh.Cluster{hosts} + cmd := cluster.Run(string(sysdigCommand), sysdigPlugin) + return &cmd, nil +}