mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
cmd/tailscale, ipn/localapi: use localapi for status, not IPN acrobatics
Yay simpler code. Tested on Linux, macOS and Windows. Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
0c3e9722cc
commit
d0dffe33c0
@ -15,6 +15,7 @@
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/safesocket"
|
"tailscale.com/safesocket"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
@ -79,6 +80,7 @@ func WhoIs(ctx context.Context, remoteAddr string) (*tailcfg.WhoIsResponse, erro
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Goroutines returns a dump of the Tailscale daemon's current goroutines.
|
||||||
func Goroutines(ctx context.Context) ([]byte, error) {
|
func Goroutines(ctx context.Context) ([]byte, error) {
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/goroutines", nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/goroutines", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -98,3 +100,25 @@ func Goroutines(ctx context.Context) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Status returns the Tailscale daemon's status.
|
||||||
|
func Status(ctx context.Context) (*ipnstate.Status, error) {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/status", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res, err := DoLocalRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
body, _ := ioutil.ReadAll(res.Body)
|
||||||
|
return nil, fmt.Errorf("HTTP %s: %s", res.Status, body)
|
||||||
|
}
|
||||||
|
st := new(ipnstate.Status)
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(st); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return st, nil
|
||||||
|
}
|
||||||
|
@ -6,10 +6,12 @@
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v2/ffcli"
|
"github.com/peterbourgon/ff/v2/ffcli"
|
||||||
|
"tailscale.com/client/tailscale"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,6 +28,16 @@ func runDown(ctx context.Context, args []string) error {
|
|||||||
log.Fatalf("too many non-flag arguments: %q", args)
|
log.Fatalf("too many non-flag arguments: %q", args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
st, err := tailscale.Status(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error fetching current status: %w", err)
|
||||||
|
}
|
||||||
|
if st.BackendState == "Stopped" {
|
||||||
|
log.Printf("already stopped")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Printf("was in state %q", st.BackendState)
|
||||||
|
|
||||||
c, bc, ctx, cancel := connect(ctx)
|
c, bc, ctx, cancel := connect(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@ -38,17 +50,6 @@ func runDown(ctx context.Context, args []string) error {
|
|||||||
if n.ErrMessage != nil {
|
if n.ErrMessage != nil {
|
||||||
log.Fatal(*n.ErrMessage)
|
log.Fatal(*n.ErrMessage)
|
||||||
}
|
}
|
||||||
if n.Status != nil {
|
|
||||||
cur := n.Status.BackendState
|
|
||||||
switch cur {
|
|
||||||
case "Stopped":
|
|
||||||
log.Printf("already stopped")
|
|
||||||
cancel()
|
|
||||||
default:
|
|
||||||
log.Printf("was in state %q", cur)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if n.State != nil {
|
if n.State != nil {
|
||||||
log.Printf("now in state %q", *n.State)
|
log.Printf("now in state %q", *n.State)
|
||||||
if *n.State == ipn.Stopped {
|
if *n.State == ipn.Stopped {
|
||||||
@ -58,7 +59,6 @@ func runDown(ctx context.Context, args []string) error {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
bc.RequestStatus()
|
|
||||||
bc.SetWantRunning(false)
|
bc.SetWantRunning(false)
|
||||||
pump(ctx, bc, c)
|
pump(ctx, bc, c)
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v2/ffcli"
|
"github.com/peterbourgon/ff/v2/ffcli"
|
||||||
|
"tailscale.com/client/tailscale"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
)
|
)
|
||||||
@ -69,7 +70,6 @@ func runPing(ctx context.Context, args []string) error {
|
|||||||
}
|
}
|
||||||
var ip string
|
var ip string
|
||||||
prc := make(chan *ipnstate.PingResult, 1)
|
prc := make(chan *ipnstate.PingResult, 1)
|
||||||
stc := make(chan *ipnstate.Status, 1)
|
|
||||||
bc.SetNotifyCallback(func(n ipn.Notify) {
|
bc.SetNotifyCallback(func(n ipn.Notify) {
|
||||||
if n.ErrMessage != nil {
|
if n.ErrMessage != nil {
|
||||||
log.Fatal(*n.ErrMessage)
|
log.Fatal(*n.ErrMessage)
|
||||||
@ -77,9 +77,6 @@ func runPing(ctx context.Context, args []string) error {
|
|||||||
if pr := n.PingResult; pr != nil && pr.IP == ip {
|
if pr := n.PingResult; pr != nil && pr.IP == ip {
|
||||||
prc <- pr
|
prc <- pr
|
||||||
}
|
}
|
||||||
if n.Status != nil {
|
|
||||||
stc <- n.Status
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
go pump(ctx, bc, c)
|
go pump(ctx, bc, c)
|
||||||
|
|
||||||
@ -92,17 +89,15 @@ func runPing(ctx context.Context, args []string) error {
|
|||||||
|
|
||||||
// Otherwise, try to resolve it first from the network peer list.
|
// Otherwise, try to resolve it first from the network peer list.
|
||||||
if ip == "" {
|
if ip == "" {
|
||||||
bc.RequestStatus()
|
st, err := tailscale.Status(ctx)
|
||||||
select {
|
if err != nil {
|
||||||
case st := <-stc:
|
return err
|
||||||
for _, ps := range st.Peer {
|
}
|
||||||
if hostOrIP == dnsOrQuoteHostname(st, ps) || hostOrIP == ps.DNSName {
|
for _, ps := range st.Peer {
|
||||||
ip = ps.TailAddr
|
if hostOrIP == dnsOrQuoteHostname(st, ps) || hostOrIP == ps.DNSName {
|
||||||
break
|
ip = ps.TailAddr
|
||||||
}
|
break
|
||||||
}
|
}
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -19,6 +18,7 @@
|
|||||||
|
|
||||||
"github.com/peterbourgon/ff/v2/ffcli"
|
"github.com/peterbourgon/ff/v2/ffcli"
|
||||||
"github.com/toqueteos/webbrowser"
|
"github.com/toqueteos/webbrowser"
|
||||||
|
"tailscale.com/client/tailscale"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/net/interfaces"
|
"tailscale.com/net/interfaces"
|
||||||
@ -53,47 +53,8 @@
|
|||||||
peers bool // in CLI mode, show status of peer machines
|
peers bool // in CLI mode, show status of peer machines
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStatusFromServer(ctx context.Context, c net.Conn, bc *ipn.BackendClient) func() (*ipnstate.Status, error) {
|
|
||||||
ch := make(chan *ipnstate.Status, 1)
|
|
||||||
bc.SetNotifyCallback(func(n ipn.Notify) {
|
|
||||||
if n.ErrMessage != nil {
|
|
||||||
log.Fatal(*n.ErrMessage)
|
|
||||||
}
|
|
||||||
if n.Status != nil {
|
|
||||||
select {
|
|
||||||
case ch <- n.Status:
|
|
||||||
default:
|
|
||||||
// A status update from somebody else's request.
|
|
||||||
// Ignoring this matters mostly for "tailscale status -web"
|
|
||||||
// mode, otherwise the channel send would block forever
|
|
||||||
// and pump would stop reading from tailscaled, which
|
|
||||||
// previously caused tailscaled to block (while holding
|
|
||||||
// a mutex), backing up unrelated clients.
|
|
||||||
// See https://github.com/tailscale/tailscale/issues/1234
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
go pump(ctx, bc, c)
|
|
||||||
|
|
||||||
return func() (*ipnstate.Status, error) {
|
|
||||||
bc.RequestStatus()
|
|
||||||
select {
|
|
||||||
case st := <-ch:
|
|
||||||
return st, nil
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runStatus(ctx context.Context, args []string) error {
|
func runStatus(ctx context.Context, args []string) error {
|
||||||
c, bc, ctx, cancel := connect(ctx)
|
st, err := tailscale.Status(ctx)
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
bc.AllowVersionSkew = true
|
|
||||||
|
|
||||||
getStatus := getStatusFromServer(ctx, c, bc)
|
|
||||||
st, err := getStatus()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -131,7 +92,7 @@ func runStatus(ctx context.Context, args []string) error {
|
|||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
st, err := getStatus()
|
st, err := tailscale.Status(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), 500)
|
||||||
return
|
return
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
"github.com/peterbourgon/ff/v2/ffcli"
|
"github.com/peterbourgon/ff/v2/ffcli"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
|
"tailscale.com/client/tailscale"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/types/preftype"
|
"tailscale.com/types/preftype"
|
||||||
@ -253,7 +254,7 @@ func runUp(ctx context.Context, args []string) error {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if !prefs.ExitNodeIP.IsZero() {
|
if !prefs.ExitNodeIP.IsZero() {
|
||||||
st, err := getStatusFromServer(ctx, c, bc)()
|
st, err := tailscale.Status(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("can't fetch status from tailscaled: %v", err)
|
fatalf("can't fetch status from tailscaled: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -53,14 +53,14 @@ func runVersion(ctx context.Context, args []string) error {
|
|||||||
if n.ErrMessage != nil {
|
if n.ErrMessage != nil {
|
||||||
log.Fatal(*n.ErrMessage)
|
log.Fatal(*n.ErrMessage)
|
||||||
}
|
}
|
||||||
if n.Status != nil {
|
if n.Engine != nil {
|
||||||
fmt.Printf("Daemon: %s\n", n.Version)
|
fmt.Printf("Daemon: %s\n", n.Version)
|
||||||
close(done)
|
close(done)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
go pump(ctx, bc, c)
|
go pump(ctx, bc, c)
|
||||||
|
|
||||||
bc.RequestStatus()
|
bc.RequestEngineStatus()
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case <-done:
|
||||||
return nil
|
return nil
|
||||||
|
@ -65,7 +65,6 @@ type Notify struct {
|
|||||||
Prefs *Prefs // preferences were changed
|
Prefs *Prefs // preferences were changed
|
||||||
NetMap *netmap.NetworkMap // new netmap received
|
NetMap *netmap.NetworkMap // new netmap received
|
||||||
Engine *EngineStatus // wireguard engine stats
|
Engine *EngineStatus // wireguard engine stats
|
||||||
Status *ipnstate.Status // full status
|
|
||||||
BrowseToURL *string // UI should open a browser right now
|
BrowseToURL *string // UI should open a browser right now
|
||||||
BackendLogID *string // public logtail id used by backend
|
BackendLogID *string // public logtail id used by backend
|
||||||
PingResult *ipnstate.PingResult
|
PingResult *ipnstate.PingResult
|
||||||
@ -159,9 +158,6 @@ type Backend interface {
|
|||||||
// counts. Connection events are emitted automatically without
|
// counts. Connection events are emitted automatically without
|
||||||
// polling.
|
// polling.
|
||||||
RequestEngineStatus()
|
RequestEngineStatus()
|
||||||
// RequestStatus requests that a full Status update
|
|
||||||
// notification is sent.
|
|
||||||
RequestStatus()
|
|
||||||
// FakeExpireAfter pretends that the current key is going to
|
// FakeExpireAfter pretends that the current key is going to
|
||||||
// expire after duration x. This is useful for testing GUIs to
|
// expire after duration x. This is useful for testing GUIs to
|
||||||
// make sure they react properly with keys that are going to
|
// make sure they react properly with keys that are going to
|
||||||
|
@ -87,10 +87,6 @@ func (b *FakeBackend) RequestEngineStatus() {
|
|||||||
b.notify(Notify{Engine: &EngineStatus{}})
|
b.notify(Notify{Engine: &EngineStatus{}})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *FakeBackend) RequestStatus() {
|
|
||||||
b.notify(Notify{Status: &ipnstate.Status{}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *FakeBackend) FakeExpireAfter(x time.Duration) {
|
func (b *FakeBackend) FakeExpireAfter(x time.Duration) {
|
||||||
b.notify(Notify{NetMap: &netmap.NetworkMap{}})
|
b.notify(Notify{NetMap: &netmap.NetworkMap{}})
|
||||||
}
|
}
|
||||||
|
@ -167,10 +167,6 @@ func (h *Handle) RequestEngineStatus() {
|
|||||||
h.b.RequestEngineStatus()
|
h.b.RequestEngineStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handle) RequestStatus() {
|
|
||||||
h.b.RequestStatus()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Handle) FakeExpireAfter(x time.Duration) {
|
func (h *Handle) FakeExpireAfter(x time.Duration) {
|
||||||
h.b.FakeExpireAfter(x)
|
h.b.FakeExpireAfter(x)
|
||||||
}
|
}
|
||||||
|
@ -1617,12 +1617,6 @@ func (b *LocalBackend) RequestEngineStatus() {
|
|||||||
b.e.RequestStatus()
|
b.e.RequestStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestStatus implements Backend.
|
|
||||||
func (b *LocalBackend) RequestStatus() {
|
|
||||||
st := b.Status()
|
|
||||||
b.send(ipn.Notify{Status: st})
|
|
||||||
}
|
|
||||||
|
|
||||||
// stateMachine updates the state machine state based on other things
|
// stateMachine updates the state machine state based on other things
|
||||||
// that have happened. It is invoked from the various callbacks that
|
// that have happened. It is invoked from the various callbacks that
|
||||||
// feed events into LocalBackend.
|
// feed events into LocalBackend.
|
||||||
|
@ -56,6 +56,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.serveWhoIs(w, r)
|
h.serveWhoIs(w, r)
|
||||||
case "/localapi/v0/goroutines":
|
case "/localapi/v0/goroutines":
|
||||||
h.serveGoroutines(w, r)
|
h.serveGoroutines(w, r)
|
||||||
|
case "/localapi/v0/status":
|
||||||
|
h.serveStatus(w, r)
|
||||||
default:
|
default:
|
||||||
io.WriteString(w, "tailscaled\n")
|
io.WriteString(w, "tailscaled\n")
|
||||||
}
|
}
|
||||||
@ -109,3 +111,14 @@ func (h *Handler) serveGoroutines(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
w.Write(buf)
|
w.Write(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) serveStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !h.PermitRead {
|
||||||
|
http.Error(w, "status access denied", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
e := json.NewEncoder(w)
|
||||||
|
e.SetIndent("", "\t")
|
||||||
|
e.Encode(h.b.Status())
|
||||||
|
}
|
||||||
|
@ -173,9 +173,6 @@ func (bs *BackendServer) GotCommand(ctx context.Context, cmd *Command) error {
|
|||||||
if c := cmd.RequestEngineStatus; c != nil {
|
if c := cmd.RequestEngineStatus; c != nil {
|
||||||
bs.b.RequestEngineStatus()
|
bs.b.RequestEngineStatus()
|
||||||
return nil
|
return nil
|
||||||
} else if c := cmd.RequestStatus; c != nil {
|
|
||||||
bs.b.RequestStatus()
|
|
||||||
return nil
|
|
||||||
} else if c := cmd.Ping; c != nil {
|
} else if c := cmd.Ping; c != nil {
|
||||||
bs.b.Ping(c.IP)
|
bs.b.Ping(c.IP)
|
||||||
return nil
|
return nil
|
||||||
|
Loading…
Reference in New Issue
Block a user