summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@tailscale.com>2021-11-30 08:47:52 -0800
committerBrad Fitzpatrick <brad@danga.com>2021-11-30 11:47:27 -0800
commit3181bbb8e4aea869a67c19cea6877bc9a6c4467d (patch)
treed704a40682d1915064ad43e394ccfefb89f1a99f
parent46a9782322e969af26f6fe9d191599354077dd02 (diff)
downloadtailscale-3181bbb8e4aea869a67c19cea6877bc9a6c4467d.tar.xz
tailscale-3181bbb8e4aea869a67c19cea6877bc9a6c4467d.zip
cmd/tailscale: make file cp send files via tailscaled localapi
So Taildrop sends work even if the local tailscaled is running in netstack mode, as it often is on Synology, etc. Updates #2179 (which is primarily about receiving, but both important) Change-Id: I9bd1afdc8d25717e0ab6802c7cf2f5e0bd89a3b2 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
-rw-r--r--client/tailscale/tailscale.go59
-rw-r--r--cmd/tailscale/cli/file.go28
-rw-r--r--ipn/localapi/localapi.go19
3 files changed, 73 insertions, 33 deletions
diff --git a/client/tailscale/tailscale.go b/client/tailscale/tailscale.go
index 80f41ac4d..696af8a6f 100644
--- a/client/tailscale/tailscale.go
+++ b/client/tailscale/tailscale.go
@@ -90,6 +90,27 @@ func DoLocalRequest(req *http.Request) (*http.Response, error) {
return tsClient.Do(req)
}
+func doLocalRequestNiceError(req *http.Request) (*http.Response, error) {
+ res, err := DoLocalRequest(req)
+ if err == nil {
+ if server := res.Header.Get("Tailscale-Version"); server != "" && server != version.Long && onVersionMismatch != nil {
+ onVersionMismatch(version.Long, server)
+ }
+ return res, nil
+ }
+ if ue, ok := err.(*url.Error); ok {
+ if oe, ok := ue.Err.(*net.OpError); ok && oe.Op == "dial" {
+ path := req.URL.Path
+ pathPrefix := path
+ if i := strings.Index(path, "?"); i != -1 {
+ pathPrefix = path[:i]
+ }
+ return nil, fmt.Errorf("Failed to connect to local Tailscale daemon for %s; %s Error: %w", pathPrefix, tailscaledConnectHint(), oe)
+ }
+ }
+ return nil, err
+}
+
type errorJSON struct {
Error string
}
@@ -140,23 +161,11 @@ func send(ctx context.Context, method, path string, wantStatus int, body io.Read
if err != nil {
return nil, err
}
- res, err := DoLocalRequest(req)
+ res, err := doLocalRequestNiceError(req)
if err != nil {
- if ue, ok := err.(*url.Error); ok {
- if oe, ok := ue.Err.(*net.OpError); ok && oe.Op == "dial" {
- pathPrefix := path
- if i := strings.Index(path, "?"); i != -1 {
- pathPrefix = path[:i]
- }
- return nil, fmt.Errorf("Failed to connect to local Tailscale daemon for %s; %s Error: %w", pathPrefix, tailscaledConnectHint(), oe)
- }
- }
return nil, err
}
defer res.Body.Close()
- if server := res.Header.Get("Tailscale-Version"); server != "" && server != version.Long && onVersionMismatch != nil {
- onVersionMismatch(version.Long, server)
- }
slurp, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
@@ -295,6 +304,30 @@ func FileTargets(ctx context.Context) ([]apitype.FileTarget, error) {
return fts, nil
}
+// PushFile sends Taildrop file r to target.
+//
+// A size of -1 means unknown.
+// 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 {
+ req, err := http.NewRequestWithContext(ctx, "PUT", "http://local-tailscaled.sock/localapi/v0/file-put/"+string(target)+"/"+url.PathEscape(name), r)
+ if err != nil {
+ return err
+ }
+ if size != -1 {
+ req.ContentLength = size
+ }
+ res, err := doLocalRequestNiceError(req)
+ if err != nil {
+ return err
+ }
+ if res.StatusCode == 200 {
+ io.Copy(io.Discard, res.Body)
+ return nil
+ }
+ all, _ := io.ReadAll(res.Body)
+ return fmt.Errorf("%s: %s", res.Status, all)
+}
+
func CheckIPForwarding(ctx context.Context) error {
body, err := get200(ctx, "/localapi/v0/check-ip-forwarding")
if err != nil {
diff --git a/cmd/tailscale/cli/file.go b/cmd/tailscale/cli/file.go
index 9f42c1d04..775c44933 100644
--- a/cmd/tailscale/cli/file.go
+++ b/cmd/tailscale/cli/file.go
@@ -11,11 +11,9 @@ import (
"flag"
"fmt"
"io"
- "io/ioutil"
"log"
"mime"
"net/http"
- "net/url"
"os"
"path/filepath"
"strconv"
@@ -30,6 +28,7 @@ import (
"tailscale.com/client/tailscale/apitype"
"tailscale.com/ipn"
"tailscale.com/net/tsaddr"
+ "tailscale.com/tailcfg"
"tailscale.com/version"
)
@@ -96,7 +95,7 @@ func runCp(ctx context.Context, args []string) error {
return err
}
- peerAPIBase, isOffline, err := discoverPeerAPIBase(ctx, ip)
+ stableID, isOffline, err := getTargetStableID(ctx, ip)
if err != nil {
return fmt.Errorf("can't send to %s: %v", target, err)
}
@@ -154,32 +153,21 @@ func runCp(ctx context.Context, args []string) error {
}
}
- dstURL := peerAPIBase + "/v0/put/" + url.PathEscape(name)
- req, err := http.NewRequestWithContext(ctx, "PUT", dstURL, fileContents)
- if err != nil {
- return err
- }
- req.ContentLength = contentLength
if cpArgs.verbose {
- log.Printf("sending to %v ...", dstURL)
+ log.Printf("sending %q to %v/%v/%v ...", name, target, ip, stableID)
}
- res, err := http.DefaultClient.Do(req)
+ err := tailscale.PushFile(ctx, stableID, contentLength, name, fileContents)
if err != nil {
return err
}
- if res.StatusCode == 200 {
- io.Copy(ioutil.Discard, res.Body)
- res.Body.Close()
- continue
+ if cpArgs.verbose {
+ log.Printf("sent %q", name)
}
- io.Copy(Stdout, res.Body)
- res.Body.Close()
- return errors.New(res.Status)
}
return nil
}
-func discoverPeerAPIBase(ctx context.Context, ipStr string) (base string, isOffline bool, err error) {
+func getTargetStableID(ctx context.Context, ipStr string) (id tailcfg.StableNodeID, isOffline bool, err error) {
ip, err := netaddr.ParseIP(ipStr)
if err != nil {
return "", false, err
@@ -195,7 +183,7 @@ func discoverPeerAPIBase(ctx context.Context, ipStr string) (base string, isOffl
continue
}
isOffline = n.Online != nil && !*n.Online
- return ft.PeerAPIURL, isOffline, nil
+ return n.StableID, isOffline, nil
}
}
return "", false, fileTargetErrorDetail(ctx, ip)
diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go
index 00673730e..b8651ae74 100644
--- a/ipn/localapi/localapi.go
+++ b/ipn/localapi/localapi.go
@@ -376,6 +376,25 @@ func (h *Handler) serveFileTargets(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(fts)
}
+// serveFilePut sends a file to another node.
+//
+// It's sometimes possible for clients to do this themselves, without
+// tailscaled, except in the case of tailscaled running in
+// userspace-networking ("netstack") mode, in which case tailscaled
+// needs to a do a netstack dial out.
+//
+// Instead, the CLI also goes through tailscaled so it doesn't need to be
+// aware of the network mode in use.
+//
+// macOS/iOS have always used this localapi method to simplify the GUI
+// clients.
+//
+// The Windows client currently (2021-11-30) uses the peerapi (/v0/put/)
+// directly, as the Windows GUI always runs in tun mode anyway.
+//
+// URL format:
+//
+// * PUT /localapi/v0/file-put/:stableID/:escaped-filename
func (h *Handler) serveFilePut(w http.ResponseWriter, r *http.Request) {
if !h.PermitWrite {
http.Error(w, "file access denied", http.StatusForbidden)