mirror of https://github.com/tailscale/tailscale/
cmd/tailscale: add status subcommand
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>reviewable/pr236/r1
parent
810c1e9704
commit
a4ef345737
@ -0,0 +1,142 @@
|
|||||||
|
// Copyright (c) 2020 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/peterbourgon/ff/v2/ffcli"
|
||||||
|
"github.com/toqueteos/webbrowser"
|
||||||
|
"tailscale.com/ipn"
|
||||||
|
"tailscale.com/ipn/ipnstate"
|
||||||
|
"tailscale.com/net/interfaces"
|
||||||
|
)
|
||||||
|
|
||||||
|
var statusCmd = &ffcli.Command{
|
||||||
|
Name: "status",
|
||||||
|
ShortUsage: "status [-web] [-json]",
|
||||||
|
ShortHelp: "Show state of tailscaled and its connections",
|
||||||
|
Exec: runStatus,
|
||||||
|
FlagSet: (func() *flag.FlagSet {
|
||||||
|
fs := flag.NewFlagSet("status", flag.ExitOnError)
|
||||||
|
fs.BoolVar(&statusArgs.json, "json", false, "output in JSON format (WARNING: format subject to change)")
|
||||||
|
fs.BoolVar(&statusArgs.web, "web", false, "run webserver with HTML showing status")
|
||||||
|
fs.StringVar(&statusArgs.listen, "listen", "127.0.0.1:8384", "listen address; use port 0 for automatic")
|
||||||
|
fs.BoolVar(&statusArgs.browser, "browser", true, "Open a browser in web mode")
|
||||||
|
return fs
|
||||||
|
})(),
|
||||||
|
}
|
||||||
|
|
||||||
|
var statusArgs struct {
|
||||||
|
json bool // JSON output mode
|
||||||
|
web bool // run webserver
|
||||||
|
listen string // in web mode, webserver address to listen on, empty means auto
|
||||||
|
browser bool // in web mode, whether to open browser
|
||||||
|
}
|
||||||
|
|
||||||
|
func runStatus(ctx context.Context, args []string) error {
|
||||||
|
c, bc, ctx, cancel := connect(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ch := make(chan *ipnstate.Status, 1)
|
||||||
|
bc.SetNotifyCallback(func(n ipn.Notify) {
|
||||||
|
if n.ErrMessage != nil {
|
||||||
|
log.Fatal(*n.ErrMessage)
|
||||||
|
}
|
||||||
|
if n.Status != nil {
|
||||||
|
ch <- n.Status
|
||||||
|
}
|
||||||
|
})
|
||||||
|
go pump(ctx, bc, c)
|
||||||
|
|
||||||
|
getStatus := func() (*ipnstate.Status, error) {
|
||||||
|
bc.RequestStatus()
|
||||||
|
select {
|
||||||
|
case st := <-ch:
|
||||||
|
return st, nil
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
st, err := getStatus()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if statusArgs.json {
|
||||||
|
j, err := json.MarshalIndent(st, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("%s", j)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if statusArgs.web {
|
||||||
|
ln, err := net.Listen("tcp", statusArgs.listen)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
statusURL := interfaces.HTTPOfListener(ln)
|
||||||
|
fmt.Printf("Serving Tailscale status at %v ...\n", statusURL)
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
ln.Close()
|
||||||
|
}()
|
||||||
|
if statusArgs.browser {
|
||||||
|
go webbrowser.Open(statusURL)
|
||||||
|
}
|
||||||
|
err = http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.RequestURI != "/" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
st, err := getStatus()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
st.WriteHTML(w)
|
||||||
|
}))
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
f := func(format string, a ...interface{}) { fmt.Fprintf(&buf, format, a...) }
|
||||||
|
for _, peer := range st.Peers() {
|
||||||
|
ps := st.Peer[peer]
|
||||||
|
f("%s %-7s %-15s %-18s tx=%8d rx=%8d ",
|
||||||
|
peer.ShortString(),
|
||||||
|
ps.OS,
|
||||||
|
ps.TailAddr,
|
||||||
|
ps.SimpleHostName(),
|
||||||
|
ps.TxBytes,
|
||||||
|
ps.RxBytes,
|
||||||
|
)
|
||||||
|
for i, addr := range ps.Addrs {
|
||||||
|
if i != 0 {
|
||||||
|
f(", ")
|
||||||
|
}
|
||||||
|
if addr == ps.CurAddr {
|
||||||
|
f("*%s*", addr)
|
||||||
|
} else {
|
||||||
|
f("%s", addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f("\n")
|
||||||
|
}
|
||||||
|
os.Stdout.Write(buf.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue