mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-16 19:51:41 +00:00
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>
This commit is contained in:
parent
46a9782322
commit
3181bbb8e4
@ -90,6 +90,27 @@ func DoLocalRequest(req *http.Request) (*http.Response, error) {
|
|||||||
return tsClient.Do(req)
|
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 {
|
type errorJSON struct {
|
||||||
Error string
|
Error string
|
||||||
}
|
}
|
||||||
@ -140,23 +161,11 @@ func send(ctx context.Context, method, path string, wantStatus int, body io.Read
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
res, err := DoLocalRequest(req)
|
res, err := doLocalRequestNiceError(req)
|
||||||
if err != nil {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
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)
|
slurp, err := ioutil.ReadAll(res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -295,6 +304,30 @@ func FileTargets(ctx context.Context) ([]apitype.FileTarget, error) {
|
|||||||
return fts, nil
|
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 {
|
func CheckIPForwarding(ctx context.Context) error {
|
||||||
body, err := get200(ctx, "/localapi/v0/check-ip-forwarding")
|
body, err := get200(ctx, "/localapi/v0/check-ip-forwarding")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -11,11 +11,9 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -30,6 +28,7 @@ import (
|
|||||||
"tailscale.com/client/tailscale/apitype"
|
"tailscale.com/client/tailscale/apitype"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/version"
|
"tailscale.com/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -96,7 +95,7 @@ func runCp(ctx context.Context, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
peerAPIBase, isOffline, err := discoverPeerAPIBase(ctx, ip)
|
stableID, isOffline, err := getTargetStableID(ctx, ip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't send to %s: %v", target, err)
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if res.StatusCode == 200 {
|
if cpArgs.verbose {
|
||||||
io.Copy(ioutil.Discard, res.Body)
|
log.Printf("sent %q", name)
|
||||||
res.Body.Close()
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
io.Copy(Stdout, res.Body)
|
|
||||||
res.Body.Close()
|
|
||||||
return errors.New(res.Status)
|
|
||||||
}
|
}
|
||||||
return nil
|
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)
|
ip, err := netaddr.ParseIP(ipStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false, err
|
return "", false, err
|
||||||
@ -195,7 +183,7 @@ func discoverPeerAPIBase(ctx context.Context, ipStr string) (base string, isOffl
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
isOffline = n.Online != nil && !*n.Online
|
isOffline = n.Online != nil && !*n.Online
|
||||||
return ft.PeerAPIURL, isOffline, nil
|
return n.StableID, isOffline, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", false, fileTargetErrorDetail(ctx, ip)
|
return "", false, fileTargetErrorDetail(ctx, ip)
|
||||||
|
@ -376,6 +376,25 @@ func (h *Handler) serveFileTargets(w http.ResponseWriter, r *http.Request) {
|
|||||||
json.NewEncoder(w).Encode(fts)
|
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) {
|
func (h *Handler) serveFilePut(w http.ResponseWriter, r *http.Request) {
|
||||||
if !h.PermitWrite {
|
if !h.PermitWrite {
|
||||||
http.Error(w, "file access denied", http.StatusForbidden)
|
http.Error(w, "file access denied", http.StatusForbidden)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user