client/tailscale: move/copy all package funcs to new LocalClient type
Remove all global variables, and clean up tsnet and cmd/tailscale's usage. This is in prep for using this package for the web API too (it has the best package name). RELNOTE=tailscale.com/client/tailscale package refactored w/ LocalClient type Change-Id: Iba9f162fff0c520a09d1d4bd8862f5c5acc9d7cd Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>pull/4580/head
parent
373176ea54
commit
87ba528ae0
|
@ -38,24 +38,59 @@ import (
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// defaultLocalClient is the default LocalClient when using the legacy
|
||||||
// TailscaledSocket is the tailscaled Unix socket. It's used by the TailscaledDialer.
|
// package-level functions.
|
||||||
TailscaledSocket = paths.DefaultTailscaledSocket()
|
var defaultLocalClient LocalClient
|
||||||
|
|
||||||
// TailscaledSocketSetExplicitly reports whether the user explicitly set TailscaledSocket.
|
// LocalClient is a client to Tailscale's "local API", communicating with the
|
||||||
TailscaledSocketSetExplicitly bool
|
// Tailscale daemon on the local machine. Its API is not necessarily stable and
|
||||||
|
// subject to changes between releases. Some API calls have stricter
|
||||||
|
// compatibility guarantees, once they've been widely adopted. See method docs
|
||||||
|
// for details.
|
||||||
|
//
|
||||||
|
// Its zero value is valid to use.
|
||||||
|
//
|
||||||
|
// Any exported fields should be set before using methods on the type
|
||||||
|
// and not changed thereafter.
|
||||||
|
type LocalClient struct {
|
||||||
|
// Dial optionally specifies an alternate func that connects to the local
|
||||||
|
// machine's tailscaled or equivalent. If nil, a default is used.
|
||||||
|
Dial func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
// TailscaledDialer is the DialContext func that connects to the local machine's
|
// Socket specifies an alternate path to the local Tailscale socket.
|
||||||
// tailscaled or equivalent.
|
// If empty, a platform-specific default is used.
|
||||||
TailscaledDialer = defaultDialer
|
Socket string
|
||||||
)
|
|
||||||
|
|
||||||
func defaultDialer(ctx context.Context, network, addr string) (net.Conn, error) {
|
// UseSocketOnly, if true, tries to only connect to tailscaled via the
|
||||||
|
// Unix socket and not via fallback mechanisms as done on macOS when
|
||||||
|
// connecting to the GUI client variants.
|
||||||
|
UseSocketOnly bool
|
||||||
|
|
||||||
|
// tsClient does HTTP requests to the local Tailscale daemon.
|
||||||
|
// It's lazily initialized on first use.
|
||||||
|
tsClient *http.Client
|
||||||
|
tsClientOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lc *LocalClient) socket() string {
|
||||||
|
if lc.Socket != "" {
|
||||||
|
return lc.Socket
|
||||||
|
}
|
||||||
|
return paths.DefaultTailscaledSocket()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lc *LocalClient) dialer() func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
if lc.Dial != nil {
|
||||||
|
return lc.Dial
|
||||||
|
}
|
||||||
|
return lc.defaultDialer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lc *LocalClient) defaultDialer(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
if addr != "local-tailscaled.sock:80" {
|
if addr != "local-tailscaled.sock:80" {
|
||||||
return nil, fmt.Errorf("unexpected URL address %q", addr)
|
return nil, fmt.Errorf("unexpected URL address %q", addr)
|
||||||
}
|
}
|
||||||
// TODO: make this part of a safesocket.ConnectionStrategy
|
if !lc.UseSocketOnly {
|
||||||
if !TailscaledSocketSetExplicitly {
|
|
||||||
// On macOS, when dialing from non-sandboxed program to sandboxed GUI running
|
// On macOS, when dialing from non-sandboxed program to sandboxed GUI running
|
||||||
// a TCP server on a random port, find the random port. For HTTP connections,
|
// a TCP server on a random port, find the random port. For HTTP connections,
|
||||||
// we don't send the token. It gets added in an HTTP Basic-Auth header.
|
// we don't send the token. It gets added in an HTTP Basic-Auth header.
|
||||||
|
@ -64,21 +99,13 @@ func defaultDialer(ctx context.Context, network, addr string) (net.Conn, error)
|
||||||
return d.DialContext(ctx, "tcp", "localhost:"+strconv.Itoa(port))
|
return d.DialContext(ctx, "tcp", "localhost:"+strconv.Itoa(port))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s := safesocket.DefaultConnectionStrategy(TailscaledSocket)
|
s := safesocket.DefaultConnectionStrategy(lc.socket())
|
||||||
// The user provided a non-default tailscaled socket address.
|
// The user provided a non-default tailscaled socket address.
|
||||||
// Connect only to exactly what they provided.
|
// Connect only to exactly what they provided.
|
||||||
s.UseFallback(false)
|
s.UseFallback(false)
|
||||||
return safesocket.Connect(s)
|
return safesocket.Connect(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
// tsClient does HTTP requests to the local Tailscale daemon.
|
|
||||||
// We lazily initialize the client in case the caller wants to
|
|
||||||
// override TailscaledDialer.
|
|
||||||
tsClient *http.Client
|
|
||||||
tsClientOnce sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
// DoLocalRequest makes an HTTP request to the local machine's Tailscale daemon.
|
// DoLocalRequest makes an HTTP request to the local machine's Tailscale daemon.
|
||||||
//
|
//
|
||||||
// URLs are of the form http://local-tailscaled.sock/localapi/v0/whois?ip=1.2.3.4.
|
// URLs are of the form http://local-tailscaled.sock/localapi/v0/whois?ip=1.2.3.4.
|
||||||
|
@ -88,22 +115,22 @@ var (
|
||||||
// authenticating to the local Tailscale daemon vary by platform.
|
// authenticating to the local Tailscale daemon vary by platform.
|
||||||
//
|
//
|
||||||
// DoLocalRequest may mutate the request to add Authorization headers.
|
// DoLocalRequest may mutate the request to add Authorization headers.
|
||||||
func DoLocalRequest(req *http.Request) (*http.Response, error) {
|
func (lc *LocalClient) DoLocalRequest(req *http.Request) (*http.Response, error) {
|
||||||
tsClientOnce.Do(func() {
|
lc.tsClientOnce.Do(func() {
|
||||||
tsClient = &http.Client{
|
lc.tsClient = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
DialContext: TailscaledDialer,
|
DialContext: lc.dialer(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if _, token, err := safesocket.LocalTCPPortAndToken(); err == nil {
|
if _, token, err := safesocket.LocalTCPPortAndToken(); err == nil {
|
||||||
req.SetBasicAuth("", token)
|
req.SetBasicAuth("", token)
|
||||||
}
|
}
|
||||||
return tsClient.Do(req)
|
return lc.tsClient.Do(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func doLocalRequestNiceError(req *http.Request) (*http.Response, error) {
|
func (lc *LocalClient) doLocalRequestNiceError(req *http.Request) (*http.Response, error) {
|
||||||
res, err := DoLocalRequest(req)
|
res, err := lc.DoLocalRequest(req)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if server := res.Header.Get("Tailscale-Version"); server != "" && server != ipn.IPCVersion() && onVersionMismatch != nil {
|
if server := res.Header.Get("Tailscale-Version"); server != "" && server != ipn.IPCVersion() && onVersionMismatch != nil {
|
||||||
onVersionMismatch(ipn.IPCVersion(), server)
|
onVersionMismatch(ipn.IPCVersion(), server)
|
||||||
|
@ -169,12 +196,12 @@ func SetVersionMismatchHandler(f func(clientVer, serverVer string)) {
|
||||||
onVersionMismatch = f
|
onVersionMismatch = f
|
||||||
}
|
}
|
||||||
|
|
||||||
func send(ctx context.Context, method, path string, wantStatus int, body io.Reader) ([]byte, error) {
|
func (lc *LocalClient) send(ctx context.Context, method, path string, wantStatus int, body io.Reader) ([]byte, error) {
|
||||||
req, err := http.NewRequestWithContext(ctx, method, "http://local-tailscaled.sock"+path, body)
|
req, err := http.NewRequestWithContext(ctx, method, "http://local-tailscaled.sock"+path, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
res, err := doLocalRequestNiceError(req)
|
res, err := lc.doLocalRequestNiceError(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -190,13 +217,20 @@ func send(ctx context.Context, method, path string, wantStatus int, body io.Read
|
||||||
return slurp, nil
|
return slurp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func get200(ctx context.Context, path string) ([]byte, error) {
|
func (lc *LocalClient) get200(ctx context.Context, path string) ([]byte, error) {
|
||||||
return send(ctx, "GET", path, 200, nil)
|
return lc.send(ctx, "GET", path, 200, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WhoIs returns the owner of the remoteAddr, which must be an IP or IP:port.
|
// WhoIs returns the owner of the remoteAddr, which must be an IP or IP:port.
|
||||||
|
//
|
||||||
|
// Deprecated: use LocalClient.WhoIs.
|
||||||
func WhoIs(ctx context.Context, remoteAddr string) (*apitype.WhoIsResponse, error) {
|
func WhoIs(ctx context.Context, remoteAddr string) (*apitype.WhoIsResponse, error) {
|
||||||
body, err := get200(ctx, "/localapi/v0/whois?addr="+url.QueryEscape(remoteAddr))
|
return defaultLocalClient.WhoIs(ctx, remoteAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WhoIs returns the owner of the remoteAddr, which must be an IP or IP:port.
|
||||||
|
func (lc *LocalClient) WhoIs(ctx context.Context, remoteAddr string) (*apitype.WhoIsResponse, error) {
|
||||||
|
body, err := lc.get200(ctx, "/localapi/v0/whois?addr="+url.QueryEscape(remoteAddr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -211,18 +245,18 @@ func WhoIs(ctx context.Context, remoteAddr string) (*apitype.WhoIsResponse, erro
|
||||||
}
|
}
|
||||||
|
|
||||||
// Goroutines returns a dump of the Tailscale daemon's current goroutines.
|
// Goroutines returns a dump of the Tailscale daemon's current goroutines.
|
||||||
func Goroutines(ctx context.Context) ([]byte, error) {
|
func (lc *LocalClient) Goroutines(ctx context.Context) ([]byte, error) {
|
||||||
return get200(ctx, "/localapi/v0/goroutines")
|
return lc.get200(ctx, "/localapi/v0/goroutines")
|
||||||
}
|
}
|
||||||
|
|
||||||
// DaemonMetrics returns the Tailscale daemon's metrics in
|
// DaemonMetrics returns the Tailscale daemon's metrics in
|
||||||
// the Prometheus text exposition format.
|
// the Prometheus text exposition format.
|
||||||
func DaemonMetrics(ctx context.Context) ([]byte, error) {
|
func (lc *LocalClient) DaemonMetrics(ctx context.Context) ([]byte, error) {
|
||||||
return get200(ctx, "/localapi/v0/metrics")
|
return lc.get200(ctx, "/localapi/v0/metrics")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profile returns a pprof profile of the Tailscale daemon.
|
// Profile returns a pprof profile of the Tailscale daemon.
|
||||||
func Profile(ctx context.Context, pprofType string, sec int) ([]byte, error) {
|
func (lc *LocalClient) Profile(ctx context.Context, pprofType string, sec int) ([]byte, error) {
|
||||||
var secArg string
|
var secArg string
|
||||||
if sec < 0 || sec > 300 {
|
if sec < 0 || sec > 300 {
|
||||||
return nil, errors.New("duration out of range")
|
return nil, errors.New("duration out of range")
|
||||||
|
@ -230,12 +264,12 @@ func Profile(ctx context.Context, pprofType string, sec int) ([]byte, error) {
|
||||||
if sec != 0 || pprofType == "profile" {
|
if sec != 0 || pprofType == "profile" {
|
||||||
secArg = fmt.Sprint(sec)
|
secArg = fmt.Sprint(sec)
|
||||||
}
|
}
|
||||||
return get200(ctx, fmt.Sprintf("/localapi/v0/profile?name=%s&seconds=%v", url.QueryEscape(pprofType), secArg))
|
return lc.get200(ctx, fmt.Sprintf("/localapi/v0/profile?name=%s&seconds=%v", url.QueryEscape(pprofType), secArg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// BugReport logs and returns a log marker that can be shared by the user with support.
|
// 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) {
|
func (lc *LocalClient) BugReport(ctx context.Context, note string) (string, error) {
|
||||||
body, err := send(ctx, "POST", "/localapi/v0/bugreport?note="+url.QueryEscape(note), 200, nil)
|
body, err := lc.send(ctx, "POST", "/localapi/v0/bugreport?note="+url.QueryEscape(note), 200, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -244,8 +278,8 @@ func BugReport(ctx context.Context, note string) (string, error) {
|
||||||
|
|
||||||
// DebugAction invokes a debug action, such as "rebind" or "restun".
|
// DebugAction invokes a debug action, such as "rebind" or "restun".
|
||||||
// These are development tools and subject to change or removal over time.
|
// These are development tools and subject to change or removal over time.
|
||||||
func DebugAction(ctx context.Context, action string) error {
|
func (lc *LocalClient) DebugAction(ctx context.Context, action string) error {
|
||||||
body, err := send(ctx, "POST", "/localapi/v0/debug?action="+url.QueryEscape(action), 200, nil)
|
body, err := lc.send(ctx, "POST", "/localapi/v0/debug?action="+url.QueryEscape(action), 200, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error %w: %s", err, body)
|
return fmt.Errorf("error %w: %s", err, body)
|
||||||
}
|
}
|
||||||
|
@ -254,16 +288,26 @@ func DebugAction(ctx context.Context, action string) error {
|
||||||
|
|
||||||
// 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 defaultLocalClient.Status(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status returns the Tailscale daemon's status.
|
||||||
|
func (lc *LocalClient) Status(ctx context.Context) (*ipnstate.Status, error) {
|
||||||
|
return lc.status(ctx, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatusWithoutPeers returns the Tailscale daemon's status, without the peer info.
|
// StatusWithoutPeers returns the Tailscale daemon's status, without the peer info.
|
||||||
func StatusWithoutPeers(ctx context.Context) (*ipnstate.Status, error) {
|
func StatusWithoutPeers(ctx context.Context) (*ipnstate.Status, error) {
|
||||||
return status(ctx, "?peers=false")
|
return defaultLocalClient.StatusWithoutPeers(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func status(ctx context.Context, queryString string) (*ipnstate.Status, error) {
|
// StatusWithoutPeers returns the Tailscale daemon's status, without the peer info.
|
||||||
body, err := get200(ctx, "/localapi/v0/status"+queryString)
|
func (lc *LocalClient) StatusWithoutPeers(ctx context.Context) (*ipnstate.Status, error) {
|
||||||
|
return lc.status(ctx, "?peers=false")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lc *LocalClient) status(ctx context.Context, queryString string) (*ipnstate.Status, error) {
|
||||||
|
body, err := lc.get200(ctx, "/localapi/v0/status"+queryString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -277,8 +321,8 @@ func status(ctx context.Context, queryString string) (*ipnstate.Status, error) {
|
||||||
// IDToken is a request to get an OIDC ID token for an audience.
|
// IDToken is a request to get an OIDC ID token for an audience.
|
||||||
// The token can be presented to any resource provider which offers OIDC
|
// The token can be presented to any resource provider which offers OIDC
|
||||||
// Federation.
|
// Federation.
|
||||||
func IDToken(ctx context.Context, aud string) (*tailcfg.TokenResponse, error) {
|
func (lc *LocalClient) IDToken(ctx context.Context, aud string) (*tailcfg.TokenResponse, error) {
|
||||||
body, err := get200(ctx, "/localapi/v0/id-token?aud="+url.QueryEscape(aud))
|
body, err := lc.get200(ctx, "/localapi/v0/id-token?aud="+url.QueryEscape(aud))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -289,8 +333,8 @@ func IDToken(ctx context.Context, aud string) (*tailcfg.TokenResponse, error) {
|
||||||
return tr, nil
|
return tr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func WaitingFiles(ctx context.Context) ([]apitype.WaitingFile, error) {
|
func (lc *LocalClient) WaitingFiles(ctx context.Context) ([]apitype.WaitingFile, error) {
|
||||||
body, err := get200(ctx, "/localapi/v0/files/")
|
body, err := lc.get200(ctx, "/localapi/v0/files/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -301,17 +345,17 @@ func WaitingFiles(ctx context.Context) ([]apitype.WaitingFile, error) {
|
||||||
return wfs, nil
|
return wfs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteWaitingFile(ctx context.Context, baseName string) error {
|
func (lc *LocalClient) DeleteWaitingFile(ctx context.Context, baseName string) error {
|
||||||
_, err := send(ctx, "DELETE", "/localapi/v0/files/"+url.PathEscape(baseName), http.StatusNoContent, nil)
|
_, err := lc.send(ctx, "DELETE", "/localapi/v0/files/"+url.PathEscape(baseName), http.StatusNoContent, nil)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetWaitingFile(ctx context.Context, baseName string) (rc io.ReadCloser, size int64, err error) {
|
func (lc *LocalClient) GetWaitingFile(ctx context.Context, baseName string) (rc io.ReadCloser, size int64, err error) {
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/files/"+url.PathEscape(baseName), nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/files/"+url.PathEscape(baseName), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
res, err := doLocalRequestNiceError(req)
|
res, err := lc.doLocalRequestNiceError(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
@ -327,8 +371,8 @@ func GetWaitingFile(ctx context.Context, baseName string) (rc io.ReadCloser, siz
|
||||||
return res.Body, res.ContentLength, nil
|
return res.Body, res.ContentLength, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func FileTargets(ctx context.Context) ([]apitype.FileTarget, error) {
|
func (lc *LocalClient) FileTargets(ctx context.Context) ([]apitype.FileTarget, error) {
|
||||||
body, err := get200(ctx, "/localapi/v0/file-targets")
|
body, err := lc.get200(ctx, "/localapi/v0/file-targets")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -343,7 +387,7 @@ func FileTargets(ctx context.Context) ([]apitype.FileTarget, error) {
|
||||||
//
|
//
|
||||||
// A size of -1 means unknown.
|
// A size of -1 means unknown.
|
||||||
// The name parameter is the original filename, not escaped.
|
// The name parameter is the original filename, not escaped.
|
||||||
func PushFile(ctx context.Context, target tailcfg.StableNodeID, size int64, name string, r io.Reader) error {
|
func (lc *LocalClient) PushFile(ctx context.Context, target tailcfg.StableNodeID, size int64, name string, r io.Reader) error {
|
||||||
req, err := http.NewRequestWithContext(ctx, "PUT", "http://local-tailscaled.sock/localapi/v0/file-put/"+string(target)+"/"+url.PathEscape(name), r)
|
req, err := http.NewRequestWithContext(ctx, "PUT", "http://local-tailscaled.sock/localapi/v0/file-put/"+string(target)+"/"+url.PathEscape(name), r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -351,7 +395,7 @@ func PushFile(ctx context.Context, target tailcfg.StableNodeID, size int64, name
|
||||||
if size != -1 {
|
if size != -1 {
|
||||||
req.ContentLength = size
|
req.ContentLength = size
|
||||||
}
|
}
|
||||||
res, err := doLocalRequestNiceError(req)
|
res, err := lc.doLocalRequestNiceError(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -363,8 +407,11 @@ func PushFile(ctx context.Context, target tailcfg.StableNodeID, size int64, name
|
||||||
return bestError(fmt.Errorf("%s: %s", res.Status, all), all)
|
return bestError(fmt.Errorf("%s: %s", res.Status, all), all)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckIPForwarding(ctx context.Context) error {
|
// CheckIPForwarding asks the local Tailscale daemon whether it looks like the
|
||||||
body, err := get200(ctx, "/localapi/v0/check-ip-forwarding")
|
// machine is properly configured to forward IP packets as a subnet router
|
||||||
|
// or exit node.
|
||||||
|
func (lc *LocalClient) CheckIPForwarding(ctx context.Context) error {
|
||||||
|
body, err := lc.get200(ctx, "/localapi/v0/check-ip-forwarding")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -386,17 +433,17 @@ func CheckIPForwarding(ctx context.Context) error {
|
||||||
// work. Currently (2022-04-18) this only checks for SSH server compatibility.
|
// work. Currently (2022-04-18) this only checks for SSH server compatibility.
|
||||||
// Note that EditPrefs does the same validation as this, so call CheckPrefs before
|
// Note that EditPrefs does the same validation as this, so call CheckPrefs before
|
||||||
// EditPrefs is not necessary.
|
// EditPrefs is not necessary.
|
||||||
func CheckPrefs(ctx context.Context, p *ipn.Prefs) error {
|
func (lc *LocalClient) CheckPrefs(ctx context.Context, p *ipn.Prefs) error {
|
||||||
pj, err := json.Marshal(p)
|
pj, err := json.Marshal(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = send(ctx, "POST", "/localapi/v0/check-prefs", http.StatusOK, bytes.NewReader(pj))
|
_, err = lc.send(ctx, "POST", "/localapi/v0/check-prefs", http.StatusOK, bytes.NewReader(pj))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPrefs(ctx context.Context) (*ipn.Prefs, error) {
|
func (lc *LocalClient) GetPrefs(ctx context.Context) (*ipn.Prefs, error) {
|
||||||
body, err := get200(ctx, "/localapi/v0/prefs")
|
body, err := lc.get200(ctx, "/localapi/v0/prefs")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -407,12 +454,12 @@ func GetPrefs(ctx context.Context) (*ipn.Prefs, error) {
|
||||||
return &p, nil
|
return &p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func EditPrefs(ctx context.Context, mp *ipn.MaskedPrefs) (*ipn.Prefs, error) {
|
func (lc *LocalClient) EditPrefs(ctx context.Context, mp *ipn.MaskedPrefs) (*ipn.Prefs, error) {
|
||||||
mpj, err := json.Marshal(mp)
|
mpj, err := json.Marshal(mp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
body, err := send(ctx, "PATCH", "/localapi/v0/prefs", http.StatusOK, bytes.NewReader(mpj))
|
body, err := lc.send(ctx, "PATCH", "/localapi/v0/prefs", http.StatusOK, bytes.NewReader(mpj))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -423,8 +470,8 @@ func EditPrefs(ctx context.Context, mp *ipn.MaskedPrefs) (*ipn.Prefs, error) {
|
||||||
return &p, nil
|
return &p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Logout(ctx context.Context) error {
|
func (lc *LocalClient) Logout(ctx context.Context) error {
|
||||||
_, err := send(ctx, "POST", "/localapi/v0/logout", http.StatusNoContent, nil)
|
_, err := lc.send(ctx, "POST", "/localapi/v0/logout", http.StatusNoContent, nil)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,11 +489,11 @@ func Logout(ctx context.Context) error {
|
||||||
// This is a low-level interface; it's expected that most Tailscale
|
// This is a low-level interface; it's expected that most Tailscale
|
||||||
// users use a higher level interface to getting/using TLS
|
// users use a higher level interface to getting/using TLS
|
||||||
// certificates.
|
// certificates.
|
||||||
func SetDNS(ctx context.Context, name, value string) error {
|
func (lc *LocalClient) SetDNS(ctx context.Context, name, value string) error {
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
v.Set("name", name)
|
v.Set("name", name)
|
||||||
v.Set("value", value)
|
v.Set("value", value)
|
||||||
_, err := send(ctx, "POST", "/localapi/v0/set-dns?"+v.Encode(), 200, nil)
|
_, err := lc.send(ctx, "POST", "/localapi/v0/set-dns?"+v.Encode(), 200, nil)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -456,7 +503,7 @@ func SetDNS(ctx context.Context, name, value string) error {
|
||||||
// tailscaled), a FQDN, or an IP address.
|
// tailscaled), a FQDN, or an IP address.
|
||||||
//
|
//
|
||||||
// The ctx is only used for the duration of the call, not the lifetime of the net.Conn.
|
// The ctx is only used for the duration of the call, not the lifetime of the net.Conn.
|
||||||
func DialTCP(ctx context.Context, host string, port uint16) (net.Conn, error) {
|
func (lc *LocalClient) DialTCP(ctx context.Context, host string, port uint16) (net.Conn, error) {
|
||||||
connCh := make(chan net.Conn, 1)
|
connCh := make(chan net.Conn, 1)
|
||||||
trace := httptrace.ClientTrace{
|
trace := httptrace.ClientTrace{
|
||||||
GotConn: func(info httptrace.GotConnInfo) {
|
GotConn: func(info httptrace.GotConnInfo) {
|
||||||
|
@ -474,7 +521,7 @@ func DialTCP(ctx context.Context, host string, port uint16) (net.Conn, error) {
|
||||||
"Dial-Host": []string{host},
|
"Dial-Host": []string{host},
|
||||||
"Dial-Port": []string{fmt.Sprint(port)},
|
"Dial-Port": []string{fmt.Sprint(port)},
|
||||||
}
|
}
|
||||||
res, err := DoLocalRequest(req)
|
res, err := lc.DoLocalRequest(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -506,9 +553,9 @@ func DialTCP(ctx context.Context, host string, port uint16) (net.Conn, error) {
|
||||||
|
|
||||||
// CurrentDERPMap returns the current DERPMap that is being used by the local tailscaled.
|
// CurrentDERPMap returns the current DERPMap that is being used by the local tailscaled.
|
||||||
// It is intended to be used with netcheck to see availability of DERPs.
|
// It is intended to be used with netcheck to see availability of DERPs.
|
||||||
func CurrentDERPMap(ctx context.Context) (*tailcfg.DERPMap, error) {
|
func (lc *LocalClient) CurrentDERPMap(ctx context.Context) (*tailcfg.DERPMap, error) {
|
||||||
var derpMap tailcfg.DERPMap
|
var derpMap tailcfg.DERPMap
|
||||||
res, err := send(ctx, "GET", "/localapi/v0/derpmap", 200, nil)
|
res, err := lc.send(ctx, "GET", "/localapi/v0/derpmap", 200, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -521,8 +568,19 @@ func CurrentDERPMap(ctx context.Context) (*tailcfg.DERPMap, error) {
|
||||||
// CertPair returns a cert and private key for the provided DNS domain.
|
// CertPair returns a cert and private key for the provided DNS domain.
|
||||||
//
|
//
|
||||||
// It returns a cached certificate from disk if it's still valid.
|
// It returns a cached certificate from disk if it's still valid.
|
||||||
|
//
|
||||||
|
// Deprecated: use LocalClient.CertPair.
|
||||||
func CertPair(ctx context.Context, domain string) (certPEM, keyPEM []byte, err error) {
|
func CertPair(ctx context.Context, domain string) (certPEM, keyPEM []byte, err error) {
|
||||||
res, err := send(ctx, "GET", "/localapi/v0/cert/"+domain+"?type=pair", 200, nil)
|
return defaultLocalClient.CertPair(ctx, domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CertPair returns a cert and private key for the provided DNS domain.
|
||||||
|
//
|
||||||
|
// It returns a cached certificate from disk if it's still valid.
|
||||||
|
//
|
||||||
|
// API maturity: this is considered a stable API.
|
||||||
|
func (lc *LocalClient) CertPair(ctx context.Context, domain string) (certPEM, keyPEM []byte, err error) {
|
||||||
|
res, err := lc.send(ctx, "GET", "/localapi/v0/cert/"+domain+"?type=pair", 200, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -546,7 +604,21 @@ func CertPair(ctx context.Context, domain string) (certPEM, keyPEM []byte, err e
|
||||||
//
|
//
|
||||||
// It's the right signature to use as the value of
|
// It's the right signature to use as the value of
|
||||||
// tls.Config.GetCertificate.
|
// tls.Config.GetCertificate.
|
||||||
|
//
|
||||||
|
// Deprecated: use LocalClient.GetCertificate.
|
||||||
func GetCertificate(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
func GetCertificate(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
|
return defaultLocalClient.GetCertificate(hi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCertificate fetches a TLS certificate for the TLS ClientHello in hi.
|
||||||
|
//
|
||||||
|
// It returns a cached certificate from disk if it's still valid.
|
||||||
|
//
|
||||||
|
// It's the right signature to use as the value of
|
||||||
|
// tls.Config.GetCertificate.
|
||||||
|
//
|
||||||
|
// API maturity: this is considered a stable API.
|
||||||
|
func (lc *LocalClient) GetCertificate(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
if hi == nil || hi.ServerName == "" {
|
if hi == nil || hi.ServerName == "" {
|
||||||
return nil, errors.New("no SNI ServerName")
|
return nil, errors.New("no SNI ServerName")
|
||||||
}
|
}
|
||||||
|
@ -555,11 +627,11 @@ func GetCertificate(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
|
|
||||||
name := hi.ServerName
|
name := hi.ServerName
|
||||||
if !strings.Contains(name, ".") {
|
if !strings.Contains(name, ".") {
|
||||||
if v, ok := ExpandSNIName(ctx, name); ok {
|
if v, ok := lc.ExpandSNIName(ctx, name); ok {
|
||||||
name = v
|
name = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
certPEM, keyPEM, err := CertPair(ctx, name)
|
certPEM, keyPEM, err := lc.CertPair(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -571,7 +643,14 @@ func GetCertificate(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExpandSNIName expands bare label name into the the most likely actual TLS cert name.
|
// ExpandSNIName expands bare label name into the the most likely actual TLS cert name.
|
||||||
|
//
|
||||||
|
// Deprecated: use LocalClient.ExpandSNIName.
|
||||||
func ExpandSNIName(ctx context.Context, name string) (fqdn string, ok bool) {
|
func ExpandSNIName(ctx context.Context, name string) (fqdn string, ok bool) {
|
||||||
|
return defaultLocalClient.ExpandSNIName(ctx, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandSNIName expands bare label name into the the most likely actual TLS cert name.
|
||||||
|
func (lc *LocalClient) ExpandSNIName(ctx context.Context, name string) (fqdn string, ok bool) {
|
||||||
st, err := StatusWithoutPeers(ctx)
|
st, err := StatusWithoutPeers(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false
|
return "", false
|
||||||
|
|
|
@ -62,6 +62,12 @@ func main() {
|
||||||
Hostname: *hostname,
|
Hostname: *hostname,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(bradfitz,maisem): move this to a method on tsnet.Server probably.
|
||||||
|
if err := ts.Start(); err != nil {
|
||||||
|
log.Fatalf("Error starting tsnet.Server: %v", err)
|
||||||
|
}
|
||||||
|
localClient, _ := ts.LocalClient()
|
||||||
|
|
||||||
url, err := url.Parse(fmt.Sprintf("http://%s", *backendAddr))
|
url, err := url.Parse(fmt.Sprintf("http://%s", *backendAddr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("couldn't parse backend address: %v", err)
|
log.Fatalf("couldn't parse backend address: %v", err)
|
||||||
|
@ -71,7 +77,7 @@ func main() {
|
||||||
originalDirector := proxy.Director
|
originalDirector := proxy.Director
|
||||||
proxy.Director = func(req *http.Request) {
|
proxy.Director = func(req *http.Request) {
|
||||||
originalDirector(req)
|
originalDirector(req)
|
||||||
modifyRequest(req)
|
modifyRequest(req, localClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ln net.Listener
|
var ln net.Listener
|
||||||
|
@ -84,7 +90,7 @@ func main() {
|
||||||
go func() {
|
go func() {
|
||||||
// wait for tailscale to start before trying to fetch cert names
|
// wait for tailscale to start before trying to fetch cert names
|
||||||
for i := 0; i < 60; i++ {
|
for i := 0; i < 60; i++ {
|
||||||
st, err := tailscale.Status(context.Background())
|
st, err := localClient.Status(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error retrieving tailscale status; retrying: %v", err)
|
log.Printf("error retrieving tailscale status; retrying: %v", err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -100,7 +106,7 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
name, ok := tailscale.ExpandSNIName(context.Background(), *hostname)
|
name, ok := localClient.ExpandSNIName(context.Background(), *hostname)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Fatalf("can't get hostname for https redirect")
|
log.Fatalf("can't get hostname for https redirect")
|
||||||
}
|
}
|
||||||
|
@ -120,14 +126,14 @@ func main() {
|
||||||
log.Fatal(http.Serve(ln, proxy))
|
log.Fatal(http.Serve(ln, proxy))
|
||||||
}
|
}
|
||||||
|
|
||||||
func modifyRequest(req *http.Request) {
|
func modifyRequest(req *http.Request, localClient *tailscale.LocalClient) {
|
||||||
// with enable_login_token set to true, we get a cookie that handles
|
// with enable_login_token set to true, we get a cookie that handles
|
||||||
// auth for paths that are not /login
|
// auth for paths that are not /login
|
||||||
if req.URL.Path != "/login" {
|
if req.URL.Path != "/login" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := getTailscaleUser(req.Context(), req.RemoteAddr)
|
user, err := getTailscaleUser(req.Context(), localClient, req.RemoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error getting Tailscale user: %v", err)
|
log.Printf("error getting Tailscale user: %v", err)
|
||||||
return
|
return
|
||||||
|
@ -137,8 +143,8 @@ func modifyRequest(req *http.Request) {
|
||||||
req.Header.Set("X-Webauth-Name", user.DisplayName)
|
req.Header.Set("X-Webauth-Name", user.DisplayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTailscaleUser(ctx context.Context, ipPort string) (*tailcfg.UserProfile, error) {
|
func getTailscaleUser(ctx context.Context, localClient *tailscale.LocalClient, ipPort string) (*tailcfg.UserProfile, error) {
|
||||||
whois, err := tailscale.WhoIs(ctx, ipPort)
|
whois, err := localClient.WhoIs(ctx, ipPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to identify remote host: %w", err)
|
return nil, fmt.Errorf("failed to identify remote host: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
"tailscale.com/client/tailscale"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var bugReportCmd = &ffcli.Command{
|
var bugReportCmd = &ffcli.Command{
|
||||||
|
@ -28,7 +27,7 @@ func runBugReport(ctx context.Context, args []string) error {
|
||||||
default:
|
default:
|
||||||
return errors.New("unknown argumets")
|
return errors.New("unknown argumets")
|
||||||
}
|
}
|
||||||
logMarker, err := tailscale.BugReport(ctx, note)
|
logMarker, err := localClient.BugReport(ctx, note)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ func runCert(ctx context.Context, args []string) error {
|
||||||
},
|
},
|
||||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.TLS != nil && !strings.Contains(r.Host, ".") && r.Method == "GET" {
|
if r.TLS != nil && !strings.Contains(r.Host, ".") && r.Method == "GET" {
|
||||||
if v, ok := tailscale.ExpandSNIName(r.Context(), r.Host); ok {
|
if v, ok := localClient.ExpandSNIName(r.Context(), r.Host); ok {
|
||||||
http.Redirect(w, r, "https://"+v+r.URL.Path, http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "https://"+v+r.URL.Path, http.StatusTemporaryRedirect)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ func runCert(ctx context.Context, args []string) error {
|
||||||
|
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
var hint bytes.Buffer
|
var hint bytes.Buffer
|
||||||
if st, err := tailscale.Status(ctx); err == nil {
|
if st, err := localClient.Status(ctx); err == nil {
|
||||||
if st.BackendState != ipn.Running.String() {
|
if st.BackendState != ipn.Running.String() {
|
||||||
fmt.Fprintf(&hint, "\nTailscale is not running.\n")
|
fmt.Fprintf(&hint, "\nTailscale is not running.\n")
|
||||||
} else if len(st.CertDomains) == 0 {
|
} else if len(st.CertDomains) == 0 {
|
||||||
|
|
|
@ -125,6 +125,8 @@ func CleanUpArgs(args []string) []string {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var localClient tailscale.LocalClient
|
||||||
|
|
||||||
// Run runs the CLI. The args do not include the binary name.
|
// Run runs the CLI. The args do not include the binary name.
|
||||||
func Run(args []string) (err error) {
|
func Run(args []string) (err error) {
|
||||||
if len(args) == 1 && (args[0] == "-V" || args[0] == "--version") {
|
if len(args) == 1 && (args[0] == "-V" || args[0] == "--version") {
|
||||||
|
@ -193,10 +195,10 @@ change in the future.
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tailscale.TailscaledSocket = rootArgs.socket
|
localClient.Socket = rootArgs.socket
|
||||||
rootfs.Visit(func(f *flag.Flag) {
|
rootfs.Visit(func(f *flag.Flag) {
|
||||||
if f.Name == "socket" {
|
if f.Name == "socket" {
|
||||||
tailscale.TailscaledSocketSetExplicitly = true
|
localClient.UseSocketOnly = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,6 @@ import (
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/client/tailscale"
|
|
||||||
"tailscale.com/hostinfo"
|
"tailscale.com/hostinfo"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
|
@ -155,7 +154,7 @@ func runDebug(ctx context.Context, args []string) error {
|
||||||
if out := debugArgs.cpuFile; out != "" {
|
if out := debugArgs.cpuFile; out != "" {
|
||||||
usedFlag = true // TODO(bradfitz): add "profile" subcommand
|
usedFlag = true // TODO(bradfitz): add "profile" subcommand
|
||||||
log.Printf("Capturing CPU profile for %v seconds ...", debugArgs.cpuSec)
|
log.Printf("Capturing CPU profile for %v seconds ...", debugArgs.cpuSec)
|
||||||
if v, err := tailscale.Profile(ctx, "profile", debugArgs.cpuSec); err != nil {
|
if v, err := localClient.Profile(ctx, "profile", debugArgs.cpuSec); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
if err := writeProfile(out, v); err != nil {
|
if err := writeProfile(out, v); err != nil {
|
||||||
|
@ -167,7 +166,7 @@ func runDebug(ctx context.Context, args []string) error {
|
||||||
if out := debugArgs.memFile; out != "" {
|
if out := debugArgs.memFile; out != "" {
|
||||||
usedFlag = true // TODO(bradfitz): add "profile" subcommand
|
usedFlag = true // TODO(bradfitz): add "profile" subcommand
|
||||||
log.Printf("Capturing memory profile ...")
|
log.Printf("Capturing memory profile ...")
|
||||||
if v, err := tailscale.Profile(ctx, "heap", 0); err != nil {
|
if v, err := localClient.Profile(ctx, "heap", 0); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
if err := writeProfile(out, v); err != nil {
|
if err := writeProfile(out, v); err != nil {
|
||||||
|
@ -179,7 +178,7 @@ func runDebug(ctx context.Context, args []string) error {
|
||||||
if debugArgs.file != "" {
|
if debugArgs.file != "" {
|
||||||
usedFlag = true // TODO(bradfitz): add "file" subcommand
|
usedFlag = true // TODO(bradfitz): add "file" subcommand
|
||||||
if debugArgs.file == "get" {
|
if debugArgs.file == "get" {
|
||||||
wfs, err := tailscale.WaitingFiles(ctx)
|
wfs, err := localClient.WaitingFiles(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fatalf("%v\n", err)
|
fatalf("%v\n", err)
|
||||||
}
|
}
|
||||||
|
@ -190,9 +189,9 @@ func runDebug(ctx context.Context, args []string) error {
|
||||||
}
|
}
|
||||||
delete := strings.HasPrefix(debugArgs.file, "delete:")
|
delete := strings.HasPrefix(debugArgs.file, "delete:")
|
||||||
if delete {
|
if delete {
|
||||||
return tailscale.DeleteWaitingFile(ctx, strings.TrimPrefix(debugArgs.file, "delete:"))
|
return localClient.DeleteWaitingFile(ctx, strings.TrimPrefix(debugArgs.file, "delete:"))
|
||||||
}
|
}
|
||||||
rc, size, err := tailscale.GetWaitingFile(ctx, debugArgs.file)
|
rc, size, err := localClient.GetWaitingFile(ctx, debugArgs.file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -227,7 +226,7 @@ var prefsArgs struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPrefs(ctx context.Context, args []string) error {
|
func runPrefs(ctx context.Context, args []string) error {
|
||||||
prefs, err := tailscale.GetPrefs(ctx)
|
prefs, err := localClient.GetPrefs(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -261,7 +260,7 @@ func runWatchIPN(ctx context.Context, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDERPMap(ctx context.Context, args []string) error {
|
func runDERPMap(ctx context.Context, args []string) error {
|
||||||
dm, err := tailscale.CurrentDERPMap(ctx)
|
dm, err := localClient.CurrentDERPMap(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"failed to get local derp map, instead `curl %s/derpmap/default`: %w", ipn.DefaultControlURL, err,
|
"failed to get local derp map, instead `curl %s/derpmap/default`: %w", ipn.DefaultControlURL, err,
|
||||||
|
@ -278,7 +277,7 @@ func localAPIAction(action string) func(context.Context, []string) error {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
return errors.New("unexpected arguments")
|
return errors.New("unexpected arguments")
|
||||||
}
|
}
|
||||||
return tailscale.DebugAction(ctx, action)
|
return localClient.DebugAction(ctx, action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,7 +318,7 @@ func runHostinfo(ctx context.Context, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDaemonGoroutines(ctx context.Context, args []string) error {
|
func runDaemonGoroutines(ctx context.Context, args []string) error {
|
||||||
goroutines, err := tailscale.Goroutines(ctx)
|
goroutines, err := localClient.Goroutines(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -334,7 +333,7 @@ var metricsArgs struct {
|
||||||
func runDaemonMetrics(ctx context.Context, args []string) error {
|
func runDaemonMetrics(ctx context.Context, args []string) error {
|
||||||
last := map[string]int64{}
|
last := map[string]int64{}
|
||||||
for {
|
for {
|
||||||
out, err := tailscale.DaemonMetrics(ctx)
|
out, err := localClient.DaemonMetrics(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
"tailscale.com/client/tailscale"
|
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,7 +25,7 @@ func runDown(ctx context.Context, args []string) error {
|
||||||
return fmt.Errorf("too many non-flag arguments: %q", args)
|
return fmt.Errorf("too many non-flag arguments: %q", args)
|
||||||
}
|
}
|
||||||
|
|
||||||
st, err := tailscale.Status(ctx)
|
st, err := localClient.Status(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error fetching current status: %w", err)
|
return fmt.Errorf("error fetching current status: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -34,7 +33,7 @@ func runDown(ctx context.Context, args []string) error {
|
||||||
fmt.Fprintf(Stderr, "Tailscale was already stopped.\n")
|
fmt.Fprintf(Stderr, "Tailscale was already stopped.\n")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
_, err = tailscale.EditPrefs(ctx, &ipn.MaskedPrefs{
|
_, err = localClient.EditPrefs(ctx, &ipn.MaskedPrefs{
|
||||||
Prefs: ipn.Prefs{
|
Prefs: ipn.Prefs{
|
||||||
WantRunning: false,
|
WantRunning: false,
|
||||||
},
|
},
|
||||||
|
|
|
@ -24,7 +24,6 @@ import (
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/client/tailscale"
|
|
||||||
"tailscale.com/client/tailscale/apitype"
|
"tailscale.com/client/tailscale/apitype"
|
||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
|
@ -157,7 +156,7 @@ func runCp(ctx context.Context, args []string) error {
|
||||||
if cpArgs.verbose {
|
if cpArgs.verbose {
|
||||||
log.Printf("sending %q to %v/%v/%v ...", name, target, ip, stableID)
|
log.Printf("sending %q to %v/%v/%v ...", name, target, ip, stableID)
|
||||||
}
|
}
|
||||||
err := tailscale.PushFile(ctx, stableID, contentLength, name, fileContents)
|
err := localClient.PushFile(ctx, stableID, contentLength, name, fileContents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -173,7 +172,7 @@ func getTargetStableID(ctx context.Context, ipStr string) (id tailcfg.StableNode
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false, err
|
return "", false, err
|
||||||
}
|
}
|
||||||
fts, err := tailscale.FileTargets(ctx)
|
fts, err := localClient.FileTargets(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false, err
|
return "", false, err
|
||||||
}
|
}
|
||||||
|
@ -194,7 +193,7 @@ func getTargetStableID(ctx context.Context, ipStr string) (id tailcfg.StableNode
|
||||||
// invalid file sharing target.
|
// invalid file sharing target.
|
||||||
func fileTargetErrorDetail(ctx context.Context, ip netaddr.IP) error {
|
func fileTargetErrorDetail(ctx context.Context, ip netaddr.IP) error {
|
||||||
found := false
|
found := false
|
||||||
if st, err := tailscale.Status(ctx); err == nil && st.Self != nil {
|
if st, err := localClient.Status(ctx); err == nil && st.Self != nil {
|
||||||
for _, peer := range st.Peer {
|
for _, peer := range st.Peer {
|
||||||
for _, pip := range peer.TailscaleIPs {
|
for _, pip := range peer.TailscaleIPs {
|
||||||
if pip == ip {
|
if pip == ip {
|
||||||
|
@ -261,7 +260,7 @@ func runCpTargets(ctx context.Context, args []string) error {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
return errors.New("invalid arguments with --targets")
|
return errors.New("invalid arguments with --targets")
|
||||||
}
|
}
|
||||||
fts, err := tailscale.FileTargets(ctx)
|
fts, err := localClient.FileTargets(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -385,7 +384,7 @@ func openFileOrSubstitute(dir, base string, action onConflict) (*os.File, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func receiveFile(ctx context.Context, wf apitype.WaitingFile, dir string) (targetFile string, size int64, err error) {
|
func receiveFile(ctx context.Context, wf apitype.WaitingFile, dir string) (targetFile string, size int64, err error) {
|
||||||
rc, size, err := tailscale.GetWaitingFile(ctx, wf.Name)
|
rc, size, err := localClient.GetWaitingFile(ctx, wf.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", 0, fmt.Errorf("opening inbox file %q: %w", wf.Name, err)
|
return "", 0, fmt.Errorf("opening inbox file %q: %w", wf.Name, err)
|
||||||
}
|
}
|
||||||
|
@ -407,7 +406,7 @@ func runFileGetOneBatch(ctx context.Context, dir string) []error {
|
||||||
var err error
|
var err error
|
||||||
var errs []error
|
var errs []error
|
||||||
for len(errs) == 0 {
|
for len(errs) == 0 {
|
||||||
wfs, err = tailscale.WaitingFiles(ctx)
|
wfs, err = localClient.WaitingFiles(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, fmt.Errorf("getting WaitingFiles: %w", err))
|
errs = append(errs, fmt.Errorf("getting WaitingFiles: %w", err))
|
||||||
break
|
break
|
||||||
|
@ -439,7 +438,7 @@ func runFileGetOneBatch(ctx context.Context, dir string) []error {
|
||||||
if getArgs.verbose {
|
if getArgs.verbose {
|
||||||
printf("wrote %v as %v (%d bytes)\n", wf.Name, writtenFile, size)
|
printf("wrote %v as %v (%d bytes)\n", wf.Name, writtenFile, size)
|
||||||
}
|
}
|
||||||
if err = tailscale.DeleteWaitingFile(ctx, wf.Name); err != nil {
|
if err = localClient.DeleteWaitingFile(ctx, wf.Name); err != nil {
|
||||||
errs = append(errs, fmt.Errorf("deleting %q from inbox: %v", wf.Name, err))
|
errs = append(errs, fmt.Errorf("deleting %q from inbox: %v", wf.Name, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -503,7 +502,7 @@ func wipeInbox(ctx context.Context) error {
|
||||||
if getArgs.wait {
|
if getArgs.wait {
|
||||||
return errors.New("can't use --wait with /dev/null target")
|
return errors.New("can't use --wait with /dev/null target")
|
||||||
}
|
}
|
||||||
wfs, err := tailscale.WaitingFiles(ctx)
|
wfs, err := localClient.WaitingFiles(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getting WaitingFiles: %w", err)
|
return fmt.Errorf("getting WaitingFiles: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -512,7 +511,7 @@ func wipeInbox(ctx context.Context) error {
|
||||||
if getArgs.verbose {
|
if getArgs.verbose {
|
||||||
log.Printf("deleting %v ...", wf.Name)
|
log.Printf("deleting %v ...", wf.Name)
|
||||||
}
|
}
|
||||||
if err := tailscale.DeleteWaitingFile(ctx, wf.Name); err != nil {
|
if err := localClient.DeleteWaitingFile(ctx, wf.Name); err != nil {
|
||||||
return fmt.Errorf("deleting %q: %v", wf.Name, err)
|
return fmt.Errorf("deleting %q: %v", wf.Name, err)
|
||||||
}
|
}
|
||||||
deleted++
|
deleted++
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
"tailscale.com/client/tailscale"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var idTokenCmd = &ffcli.Command{
|
var idTokenCmd = &ffcli.Command{
|
||||||
|
@ -25,7 +24,7 @@ func runIDToken(ctx context.Context, args []string) error {
|
||||||
return errors.New("usage: id-token <aud>")
|
return errors.New("usage: id-token <aud>")
|
||||||
}
|
}
|
||||||
|
|
||||||
tr, err := tailscale.IDToken(ctx, args[0])
|
tr, err := localClient.IDToken(ctx, args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/client/tailscale"
|
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -59,7 +58,7 @@ func runIP(ctx context.Context, args []string) error {
|
||||||
if !v4 && !v6 {
|
if !v4 && !v6 {
|
||||||
v4, v6 = true, true
|
v4, v6 = true, true
|
||||||
}
|
}
|
||||||
st, err := tailscale.Status(ctx)
|
st, err := localClient.Status(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
"tailscale.com/client/tailscale"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var logoutCmd = &ffcli.Command{
|
var logoutCmd = &ffcli.Command{
|
||||||
|
@ -30,5 +29,5 @@ func runLogout(ctx context.Context, args []string) error {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
return fmt.Errorf("too many non-flag arguments: %q", args)
|
return fmt.Errorf("too many non-flag arguments: %q", args)
|
||||||
}
|
}
|
||||||
return tailscale.Logout(ctx)
|
return localClient.Logout(ctx)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
"tailscale.com/client/tailscale"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var ncCmd = &ffcli.Command{
|
var ncCmd = &ffcli.Command{
|
||||||
|
@ -24,7 +23,7 @@ var ncCmd = &ffcli.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
func runNC(ctx context.Context, args []string) error {
|
func runNC(ctx context.Context, args []string) error {
|
||||||
st, err := tailscale.Status(ctx)
|
st, err := localClient.Status(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fixTailscaledConnectError(err)
|
return fixTailscaledConnectError(err)
|
||||||
}
|
}
|
||||||
|
@ -45,7 +44,7 @@ func runNC(ctx context.Context, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(bradfitz): also add UDP too, via flag?
|
// TODO(bradfitz): also add UDP too, via flag?
|
||||||
c, err := tailscale.DialTCP(ctx, hostOrIP, uint16(port))
|
c, err := localClient.DialTCP(ctx, hostOrIP, uint16(port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Dial(%q, %v): %w", hostOrIP, port, err)
|
return fmt.Errorf("Dial(%q, %v): %w", hostOrIP, port, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
"tailscale.com/client/tailscale"
|
|
||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/net/netcheck"
|
"tailscale.com/net/netcheck"
|
||||||
|
@ -63,7 +62,7 @@ func runNetcheck(ctx context.Context, args []string) error {
|
||||||
fmt.Fprintln(Stderr, "# Warning: this JSON format is not yet considered a stable interface")
|
fmt.Fprintln(Stderr, "# Warning: this JSON format is not yet considered a stable interface")
|
||||||
}
|
}
|
||||||
|
|
||||||
dm, err := tailscale.CurrentDERPMap(ctx)
|
dm, err := localClient.CurrentDERPMap(ctx)
|
||||||
noRegions := dm != nil && len(dm.Regions) == 0
|
noRegions := dm != nil && len(dm.Regions) == 0
|
||||||
if noRegions {
|
if noRegions {
|
||||||
log.Printf("No DERP map from tailscaled; using default.")
|
log.Printf("No DERP map from tailscaled; using default.")
|
||||||
|
|
|
@ -16,7 +16,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
"tailscale.com/client/tailscale"
|
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
)
|
)
|
||||||
|
@ -65,7 +64,7 @@ var pingArgs struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPing(ctx context.Context, args []string) error {
|
func runPing(ctx context.Context, args []string) error {
|
||||||
st, err := tailscale.Status(ctx)
|
st, err := localClient.Status(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fixTailscaledConnectError(err)
|
return fixTailscaledConnectError(err)
|
||||||
}
|
}
|
||||||
|
@ -173,7 +172,7 @@ func tailscaleIPFromArg(ctx context.Context, hostOrIP string) (ip string, self b
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, try to resolve it first from the network peer list.
|
// Otherwise, try to resolve it first from the network peer list.
|
||||||
st, err := tailscale.Status(ctx)
|
st, err := localClient.Status(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false, err
|
return "", false, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import (
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/client/tailscale"
|
|
||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
)
|
)
|
||||||
|
@ -47,7 +46,7 @@ func runSSH(ctx context.Context, args []string) error {
|
||||||
username = lu.Username
|
username = lu.Username
|
||||||
}
|
}
|
||||||
|
|
||||||
st, err := tailscale.Status(ctx)
|
st, err := localClient.Status(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import (
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
"github.com/toqueteos/webbrowser"
|
"github.com/toqueteos/webbrowser"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"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"
|
||||||
|
@ -73,9 +72,9 @@ func runStatus(ctx context.Context, args []string) error {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
return errors.New("unexpected non-flag arguments to 'tailscale status'")
|
return errors.New("unexpected non-flag arguments to 'tailscale status'")
|
||||||
}
|
}
|
||||||
getStatus := tailscale.Status
|
getStatus := localClient.Status
|
||||||
if !statusArgs.peers {
|
if !statusArgs.peers {
|
||||||
getStatus = tailscale.StatusWithoutPeers
|
getStatus = localClient.StatusWithoutPeers
|
||||||
}
|
}
|
||||||
st, err := getStatus(ctx)
|
st, err := getStatus(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -115,7 +114,7 @@ func runStatus(ctx context.Context, args []string) error {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
st, err := tailscale.Status(ctx)
|
st, err := localClient.Status(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), 500)
|
||||||
return
|
return
|
||||||
|
|
|
@ -24,7 +24,6 @@ import (
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
qrcode "github.com/skip2/go-qrcode"
|
qrcode "github.com/skip2/go-qrcode"
|
||||||
"inet.af/netaddr"
|
"inet.af/netaddr"
|
||||||
"tailscale.com/client/tailscale"
|
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/ipn/ipnstate"
|
"tailscale.com/ipn/ipnstate"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
|
@ -406,7 +405,7 @@ func runUp(ctx context.Context, args []string) error {
|
||||||
fatalf("too many non-flag arguments: %q", args)
|
fatalf("too many non-flag arguments: %q", args)
|
||||||
}
|
}
|
||||||
|
|
||||||
st, err := tailscale.Status(ctx)
|
st, err := localClient.Status(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fixTailscaledConnectError(err)
|
return fixTailscaledConnectError(err)
|
||||||
}
|
}
|
||||||
|
@ -447,12 +446,12 @@ func runUp(ctx context.Context, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(prefs.AdvertiseRoutes) > 0 {
|
if len(prefs.AdvertiseRoutes) > 0 {
|
||||||
if err := tailscale.CheckIPForwarding(context.Background()); err != nil {
|
if err := localClient.CheckIPForwarding(context.Background()); err != nil {
|
||||||
warnf("%v", err)
|
warnf("%v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
curPrefs, err := tailscale.GetPrefs(ctx)
|
curPrefs, err := localClient.GetPrefs(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -471,7 +470,7 @@ func runUp(ctx context.Context, args []string) error {
|
||||||
fatalf("%s", err)
|
fatalf("%s", err)
|
||||||
}
|
}
|
||||||
if justEditMP != nil {
|
if justEditMP != nil {
|
||||||
_, err := tailscale.EditPrefs(ctx, justEditMP)
|
_, err := localClient.EditPrefs(ctx, justEditMP)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -582,7 +581,7 @@ func runUp(ctx context.Context, args []string) error {
|
||||||
// Special case: bare "tailscale up" means to just start
|
// Special case: bare "tailscale up" means to just start
|
||||||
// running, if there's ever been a login.
|
// running, if there's ever been a login.
|
||||||
if simpleUp {
|
if simpleUp {
|
||||||
_, err := tailscale.EditPrefs(ctx, &ipn.MaskedPrefs{
|
_, err := localClient.EditPrefs(ctx, &ipn.MaskedPrefs{
|
||||||
Prefs: ipn.Prefs{
|
Prefs: ipn.Prefs{
|
||||||
WantRunning: true,
|
WantRunning: true,
|
||||||
},
|
},
|
||||||
|
@ -592,7 +591,7 @@ func runUp(ctx context.Context, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := tailscale.CheckPrefs(ctx, prefs); err != nil {
|
if err := localClient.CheckPrefs(ctx, prefs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/ffcli"
|
||||||
"tailscale.com/client/tailscale"
|
|
||||||
"tailscale.com/version"
|
"tailscale.com/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -41,7 +40,7 @@ func runVersion(ctx context.Context, args []string) error {
|
||||||
|
|
||||||
printf("Client: %s\n", version.String())
|
printf("Client: %s\n", version.String())
|
||||||
|
|
||||||
st, err := tailscale.StatusWithoutPeers(ctx)
|
st, err := localClient.StatusWithoutPeers(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@ import (
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v3/ffcli"
|
"github.com/peterbourgon/ff/v3/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"
|
||||||
|
@ -318,7 +317,7 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(mi{"error": err.Error()})
|
json.NewEncoder(w).Encode(mi{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
prefs, err := tailscale.GetPrefs(r.Context())
|
prefs, err := localClient.GetPrefs(r.Context())
|
||||||
if err != nil && !postData.Reauthenticate {
|
if err != nil && !postData.Reauthenticate {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
json.NewEncoder(w).Encode(mi{"error": err.Error()})
|
json.NewEncoder(w).Encode(mi{"error": err.Error()})
|
||||||
|
@ -348,12 +347,12 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
st, err := tailscale.Status(r.Context())
|
st, err := localClient.Status(r.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
prefs, err := tailscale.GetPrefs(r.Context())
|
prefs, err := localClient.GetPrefs(r.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -406,7 +405,7 @@ func tailscaleUp(ctx context.Context, prefs *ipn.Prefs, forceReauth bool) (authU
|
||||||
prefs.NetfilterMode = preftype.NetfilterOff
|
prefs.NetfilterMode = preftype.NetfilterOff
|
||||||
}
|
}
|
||||||
|
|
||||||
st, err := tailscale.Status(ctx)
|
st, err := localClient.Status(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("can't fetch status: %v", err)
|
return "", fmt.Errorf("can't fetch status: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,7 @@ type Server struct {
|
||||||
hostname string
|
hostname string
|
||||||
shutdownCtx context.Context
|
shutdownCtx context.Context
|
||||||
shutdownCancel context.CancelFunc
|
shutdownCancel context.CancelFunc
|
||||||
|
localClient *tailscale.LocalClient
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
listeners map[listenKey]*listener
|
listeners map[listenKey]*listener
|
||||||
|
@ -90,6 +91,17 @@ func (s *Server) Dial(ctx context.Context, network, address string) (net.Conn, e
|
||||||
return s.dialer.UserDial(ctx, network, address)
|
return s.dialer.UserDial(ctx, network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LocalClient returns a LocalClient that speaks to s.
|
||||||
|
//
|
||||||
|
// It will start the server if it has not been started yet. If the server's
|
||||||
|
// already been started successfully, it doesn't return an error.
|
||||||
|
func (s *Server) LocalClient() (*tailscale.LocalClient, error) {
|
||||||
|
if err := s.Start(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s.localClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Start connects the server to the tailnet.
|
// Start connects the server to the tailnet.
|
||||||
// Optional: any calls to Dial/Listen will also call Start.
|
// Optional: any calls to Dial/Listen will also call Start.
|
||||||
func (s *Server) Start() error {
|
func (s *Server) Start() error {
|
||||||
|
@ -261,9 +273,7 @@ func (s *Server) start() error {
|
||||||
// TODO(maisem): Rename nettest package to remove "test".
|
// TODO(maisem): Rename nettest package to remove "test".
|
||||||
lal := nettest.Listen("local-tailscaled.sock:80")
|
lal := nettest.Listen("local-tailscaled.sock:80")
|
||||||
s.localAPIListener = lal
|
s.localAPIListener = lal
|
||||||
|
s.localClient = &tailscale.LocalClient{Dial: lal.Dial}
|
||||||
// Override the Tailscale client to use the in-process listener.
|
|
||||||
tailscale.TailscaledDialer = lal.Dial
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := http.Serve(lal, lah); err != nil {
|
if err := http.Serve(lal, lah); err != nil {
|
||||||
logf("localapi serve error: %v", err)
|
logf("localapi serve error: %v", err)
|
||||||
|
|
Loading…
Reference in New Issue