mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
cmd/tailscale, ipn/localapi: add "tailscale bugreport" subcommand
Adding a subcommand which prints and logs a log marker. This should help diagnose any issues that users face. Fixes #1466 Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
parent
09148c07ba
commit
db13b2d0c8
@ -16,6 +16,7 @@
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/paths"
|
"tailscale.com/paths"
|
||||||
@ -109,6 +110,28 @@ func Goroutines(ctx context.Context) ([]byte, error) {
|
|||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BugReport logs and returns a log marker that can be shared by the user with support.
|
||||||
|
func BugReport(ctx context.Context, note string) (string, error) {
|
||||||
|
u := fmt.Sprintf("http://local-tailscaled.sock/localapi/v0/bugreport?note=%s", url.QueryEscape(note))
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "POST", u, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
res, err := DoLocalRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return "", fmt.Errorf("HTTP %s: %s", res.Status, body)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(body)), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Status returns the Tailscale daemon's status.
|
// Status returns the Tailscale daemon's status.
|
||||||
func Status(ctx context.Context) (*ipnstate.Status, error) {
|
func Status(ctx context.Context) (*ipnstate.Status, error) {
|
||||||
return status(ctx, "")
|
return status(ctx, "")
|
||||||
|
38
cmd/tailscale/cli/bugreport.go
Normal file
38
cmd/tailscale/cli/bugreport.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/peterbourgon/ff/v2/ffcli"
|
||||||
|
"tailscale.com/client/tailscale"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bugReportCmd = &ffcli.Command{
|
||||||
|
Name: "bugreport",
|
||||||
|
Exec: runBugReport,
|
||||||
|
ShortHelp: "Print a shareable identifier to help diagnose issues",
|
||||||
|
ShortUsage: "bugreport [note]",
|
||||||
|
}
|
||||||
|
|
||||||
|
func runBugReport(ctx context.Context, args []string) error {
|
||||||
|
var note string
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
note = args[0]
|
||||||
|
default:
|
||||||
|
return errors.New("unknown argumets")
|
||||||
|
}
|
||||||
|
logMarker, err := tailscale.BugReport(ctx, note)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(logMarker)
|
||||||
|
return nil
|
||||||
|
}
|
@ -71,6 +71,7 @@ func Run(args []string) error {
|
|||||||
versionCmd,
|
versionCmd,
|
||||||
webCmd,
|
webCmd,
|
||||||
pushCmd,
|
pushCmd,
|
||||||
|
bugReportCmd,
|
||||||
},
|
},
|
||||||
FlagSet: rootfs,
|
FlagSet: rootfs,
|
||||||
Exec: func(context.Context, []string) error { return flag.ErrHelp },
|
Exec: func(context.Context, []string) error { return flag.ErrHelp },
|
||||||
|
@ -97,8 +97,9 @@ type Options struct {
|
|||||||
// server is an IPN backend and its set of 0 or more active connections
|
// server is an IPN backend and its set of 0 or more active connections
|
||||||
// talking to an IPN backend.
|
// talking to an IPN backend.
|
||||||
type server struct {
|
type server struct {
|
||||||
b *ipnlocal.LocalBackend
|
b *ipnlocal.LocalBackend
|
||||||
logf logger.Logf
|
logf logger.Logf
|
||||||
|
backendLogID string
|
||||||
// resetOnZero is whether to call bs.Reset on transition from
|
// resetOnZero is whether to call bs.Reset on transition from
|
||||||
// 1->0 connections. That is, this is whether the backend is
|
// 1->0 connections. That is, this is whether the backend is
|
||||||
// being run in "client mode" that requires an active GUI
|
// being run in "client mode" that requires an active GUI
|
||||||
@ -610,8 +611,9 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
|
|||||||
}
|
}
|
||||||
|
|
||||||
server := &server{
|
server := &server{
|
||||||
logf: logf,
|
backendLogID: logid,
|
||||||
resetOnZero: !opts.SurviveDisconnects,
|
logf: logf,
|
||||||
|
resetOnZero: !opts.SurviveDisconnects,
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the context is closed or when we return, whichever is first, close our listner
|
// When the context is closed or when we return, whichever is first, close our listner
|
||||||
@ -982,7 +984,7 @@ func (psc *protoSwitchConn) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) localhostHandler(ci connIdentity) http.Handler {
|
func (s *server) localhostHandler(ci connIdentity) http.Handler {
|
||||||
lah := localapi.NewHandler(s.b)
|
lah := localapi.NewHandler(s.b, s.logf, s.backendLogID)
|
||||||
lah.PermitRead, lah.PermitWrite = s.localAPIPermissions(ci)
|
lah.PermitRead, lah.PermitWrite = s.localAPIPermissions(ci)
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
package localapi
|
package localapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -14,15 +16,23 @@
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/ipn/ipnlocal"
|
"tailscale.com/ipn/ipnlocal"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
|
"tailscale.com/types/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewHandler(b *ipnlocal.LocalBackend) *Handler {
|
func randHex(n int) string {
|
||||||
return &Handler{b: b}
|
b := make([]byte, n)
|
||||||
|
rand.Read(b)
|
||||||
|
return hex.EncodeToString(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(b *ipnlocal.LocalBackend, logf logger.Logf, logID string) *Handler {
|
||||||
|
return &Handler{b: b, logf: logf, backendLogID: logID}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
@ -37,7 +47,9 @@ type Handler struct {
|
|||||||
// PermitWrite is whether mutating HTTP handlers are allowed.
|
// PermitWrite is whether mutating HTTP handlers are allowed.
|
||||||
PermitWrite bool
|
PermitWrite bool
|
||||||
|
|
||||||
b *ipnlocal.LocalBackend
|
b *ipnlocal.LocalBackend
|
||||||
|
logf logger.Logf
|
||||||
|
backendLogID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -69,11 +81,28 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.serveStatus(w, r)
|
h.serveStatus(w, r)
|
||||||
case "/localapi/v0/check-ip-forwarding":
|
case "/localapi/v0/check-ip-forwarding":
|
||||||
h.serveCheckIPForwarding(w, r)
|
h.serveCheckIPForwarding(w, r)
|
||||||
|
case "/localapi/v0/bugreport":
|
||||||
|
h.serveBugReport(w, r)
|
||||||
default:
|
default:
|
||||||
io.WriteString(w, "tailscaled\n")
|
io.WriteString(w, "tailscaled\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) serveBugReport(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !h.PermitRead {
|
||||||
|
http.Error(w, "bugreport access denied", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logMarker := fmt.Sprintf("BUG-%v-%v-%v", h.backendLogID, time.Now().UTC().Format("20060102150405Z"), randHex(8))
|
||||||
|
h.logf("user bugreport: %s", logMarker)
|
||||||
|
if note := r.FormValue("note"); len(note) > 0 {
|
||||||
|
h.logf("user bugreport note: %s", note)
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
fmt.Fprintln(w, logMarker)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) serveWhoIs(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) serveWhoIs(w http.ResponseWriter, r *http.Request) {
|
||||||
if !h.PermitRead {
|
if !h.PermitRead {
|
||||||
http.Error(w, "whois access denied", http.StatusForbidden)
|
http.Error(w, "whois access denied", http.StatusForbidden)
|
||||||
|
Loading…
Reference in New Issue
Block a user