diff --git a/client/tailscale/apitype/apitype.go b/client/tailscale/apitype/apitype.go new file mode 100644 index 000000000..cae573a4e --- /dev/null +++ b/client/tailscale/apitype/apitype.go @@ -0,0 +1,29 @@ +// 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 apitype contains types for the Tailscale local API. +package apitype + +import "tailscale.com/tailcfg" + +// WhoIsResponse is the JSON type returned by tailscaled debug server's /whois?ip=$IP handler. +type WhoIsResponse struct { + Node *tailcfg.Node + UserProfile *tailcfg.UserProfile +} + +// FileTarget is a node to which files can be sent, and the PeerAPI +// URL base to do so via. +type FileTarget struct { + Node *tailcfg.Node + + // PeerAPI is the http://ip:port URL base of the node's peer API, + // without any path (not even a single slash). + PeerAPIURL string +} + +type WaitingFile struct { + Name string + Size int64 +} diff --git a/client/tailscale/tailscale.go b/client/tailscale/tailscale.go index e386d7695..ba59a060c 100644 --- a/client/tailscale/tailscale.go +++ b/client/tailscale/tailscale.go @@ -19,11 +19,11 @@ import ( "strconv" "strings" + "tailscale.com/client/tailscale/apitype" "tailscale.com/ipn" "tailscale.com/ipn/ipnstate" "tailscale.com/paths" "tailscale.com/safesocket" - "tailscale.com/tailcfg" ) // TailscaledSocket is the tailscaled Unix socket. @@ -91,12 +91,12 @@ func get200(ctx context.Context, path string) ([]byte, error) { } // WhoIs returns the owner of the remoteAddr, which must be an IP or IP:port. -func WhoIs(ctx context.Context, remoteAddr string) (*tailcfg.WhoIsResponse, error) { +func WhoIs(ctx context.Context, remoteAddr string) (*apitype.WhoIsResponse, error) { body, err := get200(ctx, "/localapi/v0/whois?addr="+url.QueryEscape(remoteAddr)) if err != nil { return nil, err } - r := new(tailcfg.WhoIsResponse) + r := new(apitype.WhoIsResponse) if err := json.Unmarshal(body, r); err != nil { if max := 200; len(body) > max { body = append(body[:max], "..."...) @@ -142,17 +142,12 @@ func status(ctx context.Context, queryString string) (*ipnstate.Status, error) { return st, nil } -type WaitingFile struct { - Name string - Size int64 -} - -func WaitingFiles(ctx context.Context) ([]WaitingFile, error) { +func WaitingFiles(ctx context.Context) ([]apitype.WaitingFile, error) { body, err := get200(ctx, "/localapi/v0/files/") if err != nil { return nil, err } - var wfs []WaitingFile + var wfs []apitype.WaitingFile if err := json.Unmarshal(body, &wfs); err != nil { return nil, err } @@ -185,6 +180,18 @@ func GetWaitingFile(ctx context.Context, baseName string) (rc io.ReadCloser, siz return res.Body, res.ContentLength, nil } +func FileTargets(ctx context.Context) ([]apitype.FileTarget, error) { + body, err := get200(ctx, "/localapi/v0/file-targets") + if err != nil { + return nil, err + } + var fts []apitype.FileTarget + if err := json.Unmarshal(body, &fts); err != nil { + return nil, fmt.Errorf("invalid JSON: %w", err) + } + return fts, nil +} + func CheckIPForwarding(ctx context.Context) error { body, err := get200(ctx, "/localapi/v0/check-ip-forwarding") if err != nil { diff --git a/cmd/hello/hello.go b/cmd/hello/hello.go index 1c14fb71b..0e7b64890 100644 --- a/cmd/hello/hello.go +++ b/cmd/hello/hello.go @@ -18,7 +18,7 @@ import ( "strings" "tailscale.com/client/tailscale" - "tailscale.com/tailcfg" + "tailscale.com/client/tailscale/apitype" ) var ( @@ -107,7 +107,7 @@ type tmplData struct { IP string // "100.2.3.4" } -func tailscaleIP(who *tailcfg.WhoIsResponse) string { +func tailscaleIP(who *apitype.WhoIsResponse) string { if who == nil { return "" } diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index be3a096b8..2b2fa40ee 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -15,6 +15,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep rsc.io/goversion/version from tailscale.com/version tailscale.com/atomicfile from tailscale.com/ipn tailscale.com/client/tailscale from tailscale.com/cmd/tailscale/cli + tailscale.com/client/tailscale/apitype from tailscale.com/client/tailscale tailscale.com/cmd/tailscale/cli from tailscale.com/cmd/tailscale tailscale.com/derp from tailscale.com/derp/derphttp tailscale.com/derp/derphttp from tailscale.com/net/netcheck diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index e300e9af9..c4689dac8 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -71,6 +71,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de inet.af/peercred from tailscale.com/ipn/ipnserver rsc.io/goversion/version from tailscale.com/version tailscale.com/atomicfile from tailscale.com/ipn+ + tailscale.com/client/tailscale/apitype from tailscale.com/ipn/ipnlocal+ tailscale.com/control/controlclient from tailscale.com/ipn/ipnlocal+ tailscale.com/derp from tailscale.com/derp/derphttp+ tailscale.com/derp/derphttp from tailscale.com/net/netcheck+ diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index abb3aa866..e42065ac9 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -25,6 +25,7 @@ import ( "time" "inet.af/netaddr" + "tailscale.com/client/tailscale/apitype" "tailscale.com/control/controlclient" "tailscale.com/health" "tailscale.com/internal/deepprint" @@ -2173,7 +2174,7 @@ func (b *LocalBackend) TestOnlyPublicKeys() (machineKey tailcfg.MachineKey, node return tailcfg.MachineKey(mk), tailcfg.NodeKey(nk) } -func (b *LocalBackend) WaitingFiles() ([]WaitingFile, error) { +func (b *LocalBackend) WaitingFiles() ([]apitype.WaitingFile, error) { b.mu.Lock() apiSrv := b.peerAPIServer b.mu.Unlock() @@ -2203,19 +2204,9 @@ func (b *LocalBackend) OpenFile(name string) (rc io.ReadCloser, size int64, err return apiSrv.OpenFile(name) } -// FileTarget is a node to which files can be sent, and the PeerAPI -// URL base to do so via. -type FileTarget struct { - Node *tailcfg.Node - - // PeerAPI is the http://ip:port URL base of the node's peer API, - // without any path (not even a single slash). - PeerAPIURL string -} - // FileTargets lists nodes that the current node can send files to. -func (b *LocalBackend) FileTargets() ([]*FileTarget, error) { - var ret []*FileTarget +func (b *LocalBackend) FileTargets() ([]*apitype.FileTarget, error) { + var ret []*apitype.FileTarget b.mu.Lock() defer b.mu.Unlock() @@ -2232,7 +2223,7 @@ func (b *LocalBackend) FileTargets() ([]*FileTarget, error) { continue } - ret = append(ret, &FileTarget{ + ret = append(ret, &apitype.FileTarget{ Node: p, PeerAPIURL: peerAPI, }) diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go index 33ec9df0b..0834481e5 100644 --- a/ipn/ipnlocal/peerapi.go +++ b/ipn/ipnlocal/peerapi.go @@ -24,6 +24,7 @@ import ( "time" "inet.af/netaddr" + "tailscale.com/client/tailscale/apitype" "tailscale.com/ipn" "tailscale.com/net/interfaces" "tailscale.com/syncs" @@ -99,14 +100,7 @@ func (s *peerAPIServer) hasFilesWaiting() bool { return false } -// WaitingFile is a JSON-marshaled struct sent by the localapi to pick -// up queued files. -type WaitingFile struct { - Name string - Size int64 -} - -func (s *peerAPIServer) WaitingFiles() (ret []WaitingFile, err error) { +func (s *peerAPIServer) WaitingFiles() (ret []apitype.WaitingFile, err error) { if s.rootDir == "" { return nil, errors.New("peerapi disabled; no storage configured") } @@ -130,7 +124,7 @@ func (s *peerAPIServer) WaitingFiles() (ret []WaitingFile, err error) { if err != nil { continue } - ret = append(ret, WaitingFile{ + ret = append(ret, apitype.WaitingFile{ Name: filepath.Base(name), Size: fi.Size(), }) diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index d4f89f51f..8bd82c501 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -23,6 +23,7 @@ import ( "time" "inet.af/netaddr" + "tailscale.com/client/tailscale/apitype" "tailscale.com/ipn" "tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/ipnstate" @@ -143,7 +144,7 @@ func (h *Handler) serveWhoIs(w http.ResponseWriter, r *http.Request) { http.Error(w, "no match for IP:port", 404) return } - res := &tailcfg.WhoIsResponse{ + res := &apitype.WhoIsResponse{ Node: n, UserProfile: &u, } @@ -340,7 +341,7 @@ func (h *Handler) serveFilePut(w http.ResponseWriter, r *http.Request) { } stableID, filenameEscaped := tailcfg.StableNodeID(upath[:slash]), upath[slash+1:] - var ft *ipnlocal.FileTarget + var ft *apitype.FileTarget for _, x := range fts { if x.Node.StableID == stableID { ft = x diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 52c3911f9..c255de54b 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -1050,12 +1050,6 @@ func eqTimePtr(a, b *time.Time) bool { return ((a == nil) == (b == nil)) && (a == nil || a.Equal(*b)) } -// WhoIsResponse is the JSON type returned by tailscaled debug server's /whois?ip=$IP handler. -type WhoIsResponse struct { - Node *Node - UserProfile *UserProfile -} - // Oauth2Token is a copy of golang.org/x/oauth2.Token, to avoid the // go.mod dependency on App Engine and grpc, which was causing problems. // All we actually needed was this struct on the client side.