ipn/ipnlocal/peerapi: refactoring taildrop to just one endpoint (#9832)

Updates #14772

Signed-off-by: Rhea Ghosh <rhea@tailscale.com>
This commit is contained in:
Rhea Ghosh 2023-10-16 14:35:11 -05:00 committed by GitHub
parent 4899c2c1f4
commit 9d3c6bf52e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 68 additions and 97 deletions

View File

@ -305,12 +305,10 @@ func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Frame-Options", "DENY") w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("X-Content-Type-Options", "nosniff") w.Header().Set("X-Content-Type-Options", "nosniff")
} }
if strings.HasPrefix(r.URL.Path, "/v0/partial-files/") {
h.handlePartialFileGet(w, r)
return
}
if strings.HasPrefix(r.URL.Path, "/v0/put/") { if strings.HasPrefix(r.URL.Path, "/v0/put/") {
if r.Method == "PUT" {
metricPutCalls.Add(1) metricPutCalls.Add(1)
}
h.handlePeerPut(w, r) h.handlePeerPut(w, r)
return return
} }
@ -631,45 +629,35 @@ func (h *peerAPIHandler) peerHasCap(wantCap tailcfg.PeerCapability) bool {
return h.ps.b.PeerCaps(h.remoteAddr.Addr()).HasCapability(wantCap) return h.ps.b.PeerCaps(h.remoteAddr.Addr()).HasCapability(wantCap)
} }
var errMisconfiguredInternals = errors.New("misconfigured internals") func (h *peerAPIHandler) handlePeerPut(w http.ResponseWriter, r *http.Request) {
if !h.canPutFile() {
func (h *peerAPIHandler) extractBaseName(rawPath, prefix string) (ret string, err error) { http.Error(w, taildrop.ErrNoTaildrop.Error(), http.StatusForbidden)
prefix, ok := strings.CutPrefix(rawPath, prefix) return
if !ok {
return "", errMisconfiguredInternals
} }
if prefix == "" {
return "", taildrop.ErrInvalidFileName
}
if strings.Contains(prefix, "/") {
return "", taildrop.ErrInvalidFileName
}
baseName, err := url.PathUnescape(prefix)
if err == errMisconfiguredInternals {
return "", errMisconfiguredInternals
} else if err != nil {
return "", taildrop.ErrInvalidFileName
}
return baseName, nil
}
func (h *peerAPIHandler) handlePartialFileGet(w http.ResponseWriter, r *http.Request) {
if !h.ps.b.hasCapFileSharing() { if !h.ps.b.hasCapFileSharing() {
http.Error(w, taildrop.ErrNoTaildrop.Error(), http.StatusForbidden) http.Error(w, taildrop.ErrNoTaildrop.Error(), http.StatusForbidden)
return return
} }
if r.Method != "GET" { rawPath := r.URL.EscapedPath()
http.Error(w, "expected method GET", http.StatusMethodNotAllowed) prefix, ok := strings.CutPrefix(rawPath, "/v0/put/")
if !ok {
http.Error(w, "misconfigured internals", http.StatusForbidden)
return return
} }
baseName, err := url.PathUnescape(prefix)
if err != nil {
http.Error(w, taildrop.ErrInvalidFileName.Error(), http.StatusBadRequest)
return
}
switch r.Method {
case "GET":
var resp any var resp any
var err error var err error
id := taildrop.ClientID(h.peerNode.StableID()) id := taildrop.ClientID(h.peerNode.StableID())
if r.URL.Path == "/v0/partial-files/" { if r.URL.Path == "/v0/put/"+baseName {
resp, err = h.ps.taildrop.PartialFiles(id) resp, err = h.ps.taildrop.PartialFiles(id)
} else { } else {
baseName, _ := h.extractBaseName(r.URL.EscapedPath(), "/v0/partial-files/")
ranges, ok := httphdr.ParseRange(r.Header.Get("Range")) ranges, ok := httphdr.ParseRange(r.Header.Get("Range"))
if !ok || len(ranges) != 1 || ranges[0].Length < 0 { if !ok || len(ranges) != 1 || ranges[0].Length < 0 {
http.Error(w, "invalid Range header", http.StatusBadRequest) http.Error(w, "invalid Range header", http.StatusBadRequest)
@ -690,27 +678,7 @@ func (h *peerAPIHandler) handlePartialFileGet(w http.ResponseWriter, r *http.Req
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
case "PUT":
}
func (h *peerAPIHandler) handlePeerPut(w http.ResponseWriter, r *http.Request) {
if !h.canPutFile() {
http.Error(w, taildrop.ErrNoTaildrop.Error(), http.StatusForbidden)
return
}
if !h.ps.b.hasCapFileSharing() {
http.Error(w, "file sharing not enabled by Tailscale admin", http.StatusForbidden)
return
}
if r.Method != "PUT" {
http.Error(w, "expected method PUT", http.StatusMethodNotAllowed)
return
}
baseName, err := h.extractBaseName(r.URL.EscapedPath(), "/v0/put/")
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
t0 := h.ps.b.clock.Now() t0 := h.ps.b.clock.Now()
id := taildrop.ClientID(h.peerNode.StableID()) id := taildrop.ClientID(h.peerNode.StableID())
@ -738,6 +706,9 @@ func (h *peerAPIHandler) handlePeerPut(w http.ResponseWriter, r *http.Request) {
default: default:
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
} }
default:
http.Error(w, "expected method GET or PUT", http.StatusMethodNotAllowed)
}
} }
func approxSize(n int64) string { func approxSize(n int64) string {

View File

@ -183,7 +183,7 @@ func TestHandlePeerAPI(t *testing.T) {
reqs: []*http.Request{httptest.NewRequest("PUT", "/v0/put/foo", nil)}, reqs: []*http.Request{httptest.NewRequest("PUT", "/v0/put/foo", nil)},
checks: checks( checks: checks(
httpStatus(http.StatusForbidden), httpStatus(http.StatusForbidden),
bodyContains("file sharing not enabled by Tailscale admin"), bodyContains("Taildrop disabled"),
), ),
}, },
{ {
@ -204,7 +204,7 @@ func TestHandlePeerAPI(t *testing.T) {
reqs: []*http.Request{httptest.NewRequest("POST", "/v0/put/foo", nil)}, reqs: []*http.Request{httptest.NewRequest("POST", "/v0/put/foo", nil)},
checks: checks( checks: checks(
httpStatus(405), httpStatus(405),
bodyContains("expected method PUT"), bodyContains("expected method GET or PUT"),
), ),
}, },
{ {

View File

@ -1325,7 +1325,7 @@ func (h *Handler) serveFilePut(w http.ResponseWriter, r *http.Request) {
Transport: h.b.Dialer().PeerAPITransport(), Transport: h.b.Dialer().PeerAPITransport(),
Timeout: 10 * time.Second, Timeout: 10 * time.Second,
} }
req, err := http.NewRequestWithContext(r.Context(), "GET", "http://peer/v0/partial-files/"+filenameEscaped, nil) req, err := http.NewRequestWithContext(r.Context(), "GET", "http://peer/v0/put/"+filenameEscaped, nil)
if err != nil { if err != nil {
return taildrop.FileChecksums{}, err return taildrop.FileChecksums{}, err
} }