func (c *appClient) controlConnection() (bool, error) { headers := http.Header{} c.ProbeConfig.authorizeHeaders(headers) url := sanitize.URL("ws://", 0, "/api/control/ws")(c.target) conn, _, err := xfer.DialWS(&c.wsDialer, url, headers) if err != nil { return false, err } defer conn.Close() doControl := func(req xfer.Request) xfer.Response { req.AppID = c.appID var res xfer.Response c.control.Handle(req, &res) return res } codec := xfer.NewJSONWebsocketCodec(conn) server := rpc.NewServer() if err := server.RegisterName("control", xfer.ControlHandlerFunc(doControl)); err != nil { return false, err } // Will return false if we are exiting if !c.registerConn("control", conn) { return true, nil } defer c.closeConn("control") server.ServeCodec(codec) return false, nil }
func TestSanitizeURL(t *testing.T) { for _, input := range []struct { scheme string port int path string input string want string }{ {"", 0, "", "", ""}, {"", 0, "", "foo", "http://foo"}, {"", 80, "", "foo", "http://foo:80"}, {"", 0, "some/path", "foo", "http://foo/some/path"}, {"", 0, "/some/path", "foo", "http://foo/some/path"}, {"https://", 0, "", "foo", "https://foo"}, {"https://", 80, "", "foo", "https://foo:80"}, {"https://", 0, "some/path", "foo", "https://foo/some/path"}, {"https://", 0, "", "http://foo", "http://foo"}, // specified scheme beats default... {"", 0, "", "https://foo", "https://foo"}, // https can be a specified scheme without default... {"http://", 0, "", "https://foo", "https://foo"}, // https can be a specified scheme with default... {"", 9999, "", "foo:80", "http://foo:80"}, // specified port beats default... {"", 0, "/bar", "foo/baz", "http://foo/bar"}, // ...but default path beats specified! {"", 0, "", "foo:443", "https://foo:443"}, // port 443 addrs default to https scheme } { if want, have := input.want, sanitize.URL(input.scheme, input.port, input.path)(input.input); want != have { t.Errorf("sanitize.URL(%q, %d, %q)(%q): want %q, have %q", input.scheme, input.port, input.path, input.input, want, have) continue } } }
// PipeClose closes the given pipe id on the app. func (c *appClient) PipeClose(id string) error { url := sanitize.URL("", 0, fmt.Sprintf("/api/pipe/%s", id))(c.target) req, err := c.ProbeConfig.authorizedRequest("DELETE", url, nil) if err != nil { return err } resp, err := c.client.Do(req) if err != nil { return err } resp.Body.Close() return nil }
// Details fetches the details (version, id) of the app. func (c *appClient) Details() (xfer.Details, error) { result := xfer.Details{} req, err := c.ProbeConfig.authorizedRequest("GET", sanitize.URL("", 0, "/api")(c.target), nil) if err != nil { return result, err } resp, err := c.client.Do(req) if err != nil { return result, err } defer resp.Body.Close() if err := codec.NewDecoder(resp.Body, &codec.JsonHandle{}).Decode(&result); err != nil { return result, err } c.appID = result.ID return result, nil }
func (c *appClient) publish(r io.Reader) error { url := sanitize.URL("", 0, "/api/report")(c.target) req, err := c.ProbeConfig.authorizedRequest("POST", url, r) if err != nil { return err } req.Header.Set("Content-Encoding", "gzip") req.Header.Set("Content-Type", "application/msgpack") // req.Header.Set("Content-Type", "application/binary") // TODO: we should use http.DetectContentType(..) on the gob'ed resp, err := c.client.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { text, _ := ioutil.ReadAll(resp.Body) return fmt.Errorf(resp.Status + ": " + string(text)) } return nil }
func (c *appClient) pipeConnection(id string, pipe xfer.Pipe) (bool, error) { headers := http.Header{} c.ProbeConfig.authorizeHeaders(headers) url := sanitize.URL("ws://", 0, fmt.Sprintf("/api/pipe/%s/probe", id))(c.target) conn, resp, err := xfer.DialWS(&c.wsDialer, url, headers) if resp != nil && resp.StatusCode == http.StatusNotFound { // Special handling - 404 means the app/user has closed the pipe pipe.Close() return true, nil } if err != nil { return false, err } // Will return false if we are exiting if !c.registerConn(id, conn) { return true, nil } defer c.closeConn(id) _, remote := pipe.Ends() return false, pipe.CopyToWebsocket(remote, conn) }
// Main runs the probe func probeMain(flags probeFlags) { setLogLevel(flags.logLevel) setLogFormatter(flags.logPrefix) // Setup in memory metrics sink inm := metrics.NewInmemSink(time.Minute, 2*time.Minute) sig := metrics.DefaultInmemSignal(inm) defer sig.Stop() metrics.NewGlobal(metrics.DefaultConfig("scope-probe"), inm) defer log.Info("probe exiting") if flags.spyProcs && os.Getegid() != 0 { log.Warn("--probe.process=true, but that requires root to find everything") } rand.Seed(time.Now().UnixNano()) var ( probeID = strconv.FormatInt(rand.Int63(), 16) hostName = hostname.Get() hostID = hostName // TODO(pb): we should sanitize the hostname ) log.Infof("probe starting, version %s, ID %s", version, probeID) log.Infof("command line: %v", os.Args) checkpointFlags := map[string]string{} if flags.kubernetesEnabled { checkpointFlags["kubernetes_enabled"] = "true" } go check(checkpointFlags) var targets = []string{} if !flags.noApp { targets = append(targets, fmt.Sprintf("localhost:%d", xfer.AppPort)) } if len(flag.Args()) > 0 { targets = append(targets, flag.Args()...) } log.Infof("publishing to: %s", strings.Join(targets, ", ")) probeConfig := appclient.ProbeConfig{ Token: flags.token, ProbeID: probeID, Insecure: flags.insecure, } clients := appclient.NewMultiAppClient(func(hostname, endpoint string) (appclient.AppClient, error) { return appclient.NewAppClient( probeConfig, hostname, endpoint, xfer.ControlHandlerFunc(controls.HandleControlRequest), ) }) defer clients.Stop() dnsLookupFn := net.LookupIP if flags.resolver != "" { dnsLookupFn = appclient.LookupUsing(flags.resolver) } resolver := appclient.NewResolver(targets, dnsLookupFn, clients.Set) defer resolver.Stop() processCache := process.NewCachingWalker(process.NewWalker(flags.procRoot)) scanner := procspy.NewConnectionScanner(processCache) endpointReporter := endpoint.NewReporter(hostID, hostName, flags.spyProcs, flags.useConntrack, scanner) defer endpointReporter.Stop() p := probe.New(flags.spyInterval, flags.publishInterval, clients) p.AddTicker(processCache) hostReporter := host.NewReporter(hostID, hostName, probeID, version, clients) defer hostReporter.Stop() p.AddReporter( endpointReporter, hostReporter, process.NewReporter(processCache, hostID, process.GetDeltaTotalJiffies), ) p.AddTagger(probe.NewTopologyTagger(), host.NewTagger(hostID)) if flags.dockerEnabled { // Don't add the bridge in Kubernetes since container IPs are global and // shouldn't be scoped if !flags.kubernetesEnabled { if err := report.AddLocalBridge(flags.dockerBridge); err != nil { log.Errorf("Docker: problem with bridge %s: %v", flags.dockerBridge, err) } } if registry, err := docker.NewRegistry(flags.dockerInterval, clients, true, hostID); err == nil { defer registry.Stop() p.AddTagger(docker.NewTagger(registry, processCache)) p.AddReporter(docker.NewReporter(registry, hostID, probeID, p)) } else { log.Errorf("Docker: failed to start registry: %v", err) } } if flags.kubernetesEnabled { if client, err := kubernetes.NewClient(flags.kubernetesAPI, flags.kubernetesInterval); err == nil { defer client.Stop() reporter := kubernetes.NewReporter(client, clients, probeID, hostID, p) defer reporter.Stop() p.AddReporter(reporter) p.AddTagger(reporter) } else { log.Errorf("Kubernetes: failed to start client: %v", err) log.Errorf("Kubernetes: make sure to run Scope inside a POD with a service account or provide a valid kubernetes.api url") } } if flags.weaveAddr != "" { client := weave.NewClient(sanitize.URL("http://", 6784, "")(flags.weaveAddr)) weave := overlay.NewWeave(hostID, client) defer weave.Stop() p.AddTagger(weave) p.AddReporter(weave) dockerBridgeIP, err := network.GetFirstAddressOf(flags.dockerBridge) if err != nil { log.Println("Error getting docker bridge ip:", err) } else { weaveDNSLookup := appclient.LookupUsing(dockerBridgeIP + ":53") weaveResolver := appclient.NewResolver([]string{flags.weaveHostname}, weaveDNSLookup, clients.Set) defer weaveResolver.Stop() } } pluginRegistry, err := plugins.NewRegistry( flags.pluginsRoot, pluginAPIVersion, map[string]string{ "probe_id": probeID, "api_version": pluginAPIVersion, }, ) if err != nil { log.Errorf("plugins: problem loading: %v", err) } else { defer pluginRegistry.Close() p.AddReporter(pluginRegistry) } if flags.httpListen != "" { go func() { log.Infof("Profiling data being exported to %s", flags.httpListen) log.Infof("go tool pprof http://%s/debug/pprof/{profile,heap,block}", flags.httpListen) log.Infof("Profiling endpoint %s terminated: %v", flags.httpListen, http.ListenAndServe(flags.httpListen, nil)) }() } p.Start() defer p.Stop() common.SignalHandlerLoop() }