From 66e4d843c13806435fee17979a30338f49eea14e Mon Sep 17 00:00:00 2001 From: Percy Wegmann Date: Tue, 26 Mar 2024 07:27:58 -0500 Subject: [PATCH] ipn/localapi: add support for multipart POST to file-put This allows sending multiple files via Taildrop in one request. Progress is tracked via ipn.Notify. Updates tailscale/corp#18202 Signed-off-by: Percy Wegmann --- cmd/tailscaled/depaware.txt | 5 +++-- ipn/backend.go | 4 ++-- ipn/ipnlocal/local.go | 2 +- ipn/ipnlocal/taildrop.go | 9 +++++---- ipn/localapi/localapi.go | 29 +++++++++++------------------ 5 files changed, 22 insertions(+), 27 deletions(-) diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 73ef1bd29..71426e42a 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -102,7 +102,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de L github.com/google/nftables/expr from github.com/google/nftables+ L github.com/google/nftables/internal/parseexprfunc from github.com/google/nftables+ L github.com/google/nftables/xt from github.com/google/nftables/expr+ - github.com/google/uuid from tailscale.com/clientupdate + github.com/google/uuid from tailscale.com/clientupdate+ github.com/gorilla/csrf from tailscale.com/client/web github.com/gorilla/securecookie from github.com/gorilla/csrf github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+ @@ -376,6 +376,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de W 💣 tailscale.com/util/osdiag/internal/wsc from tailscale.com/util/osdiag tailscale.com/util/osshare from tailscale.com/cmd/tailscaled+ tailscale.com/util/osuser from tailscale.com/ipn/localapi+ + tailscale.com/util/progresstracking from tailscale.com/ipn/localapi tailscale.com/util/race from tailscale.com/net/dns/resolver tailscale.com/util/racebuild from tailscale.com/logpolicy tailscale.com/util/rands from tailscale.com/ipn/ipnlocal+ @@ -522,7 +523,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de math/rand from github.com/mdlayher/netlink+ math/rand/v2 from tailscale.com/util/rands mime from github.com/tailscale/xnet/webdav+ - mime/multipart from net/http + mime/multipart from net/http+ mime/quotedprintable from mime/multipart net from crypto/tls+ net/http from expvar+ diff --git a/ipn/backend.go b/ipn/backend.go index 9d5714c1f..c4c64b9ee 100644 --- a/ipn/backend.go +++ b/ipn/backend.go @@ -202,8 +202,8 @@ type PartialFile struct { // OutgoingFile represents an in-progress outgoing file transfer. type OutgoingFile struct { - ID string `json:"-"` // unique identifier for this transfer (a type 4 UUID) - PeerID tailcfg.StableNodeID // identifier for the peer to which this is being transferred + ID string `json:",omitempty"` // unique identifier for this transfer (a type 4 UUID) + PeerID tailcfg.StableNodeID `json:",omitempty"` // identifier for the peer to which this is being transferred Name string `json:",omitempty"` // e.g. "foo.jpg" Started time.Time // time transfer started DeclaredSize int64 // or -1 if unknown diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 4ed69ef96..625c76379 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -320,7 +320,7 @@ type LocalBackend struct { // notified about. lastNotifiedTailFSShares atomic.Pointer[views.SliceView[*tailfs.Share, tailfs.ShareView]] - // outgoingFiles keeps track of Taildrop outgoing files + // outgoingFiles keeps track of Taildrop outgoing files keyed to their OutgoingFile.ID outgoingFiles map[string]*ipn.OutgoingFile } diff --git a/ipn/ipnlocal/taildrop.go b/ipn/ipnlocal/taildrop.go index 99abfbaaf..db7d8e12a 100644 --- a/ipn/ipnlocal/taildrop.go +++ b/ipn/ipnlocal/taildrop.go @@ -4,20 +4,21 @@ package ipnlocal import ( + "maps" "slices" "strings" "tailscale.com/ipn" ) -func (b *LocalBackend) UpdateOutgoingFiles(updates map[string]ipn.OutgoingFile) { +// UpdateOutgoingFiles updates b.outgoingFiles to reflect the given updates and +// sends an ipn.Notify with the full list of outgoingFiles. +func (b *LocalBackend) UpdateOutgoingFiles(updates map[string]*ipn.OutgoingFile) { b.mu.Lock() if b.outgoingFiles == nil { b.outgoingFiles = make(map[string]*ipn.OutgoingFile, len(updates)) } - for id, file := range updates { - b.outgoingFiles[id] = &file - } + maps.Copy(b.outgoingFiles, updates) outgoingFiles := make([]*ipn.OutgoingFile, 0, len(b.outgoingFiles)) for _, file := range b.outgoingFiles { outgoingFiles = append(outgoingFiles, file) diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 87f12fe3a..53e5770f1 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -1535,11 +1535,10 @@ func (h *Handler) serveFileTargets(w http.ResponseWriter, r *http.Request) { // directly, as the Windows GUI always runs in tun mode anyway. // // In addition to single file PUTs, this endpoint accepts multipart file -// POSTS encoded as multipart/form-data. Each part must include a -// "Content-Length" in the MIME header indicating the size of the file. -// The first part should be an application/json file that contains a JSON map -// of filename -> length, which we can use for tracking progress even before -// reading the file parts. +// POSTS encoded as multipart/form-data.The first part should be an +// application/json file that contains a manifest consisting of a JSON array of +// OutgoingFiles which wecan use for tracking progress even before reading the +// file parts. // // URL format: // @@ -1599,9 +1598,9 @@ func (h *Handler) serveFilePut(w http.ResponseWriter, r *http.Request) { return } - // Report progress on outgoing files every 5 seconds - outgoingFiles := make(map[string]ipn.OutgoingFile) - t := time.NewTicker(5 * time.Second) + // Periodically report progress of outgoing files. + outgoingFiles := make(map[string]*ipn.OutgoingFile) + t := time.NewTicker(1 * time.Second) progressUpdates := make(chan ipn.OutgoingFile) defer close(progressUpdates) @@ -1614,7 +1613,7 @@ func (h *Handler) serveFilePut(w http.ResponseWriter, r *http.Request) { if !ok { return } - outgoingFiles[u.ID] = u + outgoingFiles[u.ID] = &u case <-t.C: h.b.UpdateOutgoingFiles(outgoingFiles) } @@ -1672,21 +1671,15 @@ func (h *Handler) multiFilePost(progressUpdates chan (ipn.OutgoingFile), w http. return } - var manifest map[string]int64 + var manifest []ipn.OutgoingFile err := json.NewDecoder(part).Decode(&manifest) if err != nil { http.Error(ww, fmt.Sprintf("invalid manifest: %s", err), http.StatusBadRequest) return } - for filename, size := range manifest { - file := ipn.OutgoingFile{ - ID: uuid.Must(uuid.NewRandom()).String(), - Name: filename, - PeerID: peerID, - DeclaredSize: size, - } - outgoingFilesByName[filename] = file + for _, file := range manifest { + outgoingFilesByName[file.Name] = file progressUpdates <- file }