diff options
Diffstat (limited to 'ipn/ipnlocal/peerapi.go')
| -rw-r--r-- | ipn/ipnlocal/peerapi.go | 636 |
1 files changed, 29 insertions, 607 deletions
diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go index d9801d90d..4ce43f7c0 100644 --- a/ipn/ipnlocal/peerapi.go +++ b/ipn/ipnlocal/peerapi.go @@ -9,18 +9,13 @@ import ( "encoding/json" "errors" "fmt" - "hash/adler32" "hash/crc32" "html" "io" - "io/fs" "net" "net/http" "net/netip" - "net/url" "os" - "path" - "path/filepath" "runtime" "slices" "sort" @@ -29,18 +24,14 @@ import ( "sync" "sync/atomic" "time" - "unicode" - "unicode/utf8" "github.com/kortschak/wol" "golang.org/x/net/dns/dnsmessage" "golang.org/x/net/http/httpguts" - "tailscale.com/client/tailscale/apitype" "tailscale.com/envknob" "tailscale.com/health" "tailscale.com/hostinfo" "tailscale.com/ipn" - "tailscale.com/logtail/backoff" "tailscale.com/net/dns/resolver" "tailscale.com/net/interfaces" "tailscale.com/net/netaddr" @@ -49,8 +40,6 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/views" "tailscale.com/util/clientmetric" - "tailscale.com/util/multierr" - "tailscale.com/version/distro" "tailscale.com/wgengine/filter" ) @@ -60,7 +49,7 @@ var initListenConfig func(*net.ListenConfig, netip.Addr, *interfaces.State, stri // ("cleartext" HTTP/2) support to the peerAPI. var addH2C func(*http.Server) -type peerAPIServer struct { +type PeerAPIServer struct { b *LocalBackend rootDir string // empty means file receiving unavailable knownEmpty atomic.Bool @@ -82,373 +71,7 @@ type peerAPIServer struct { directFileDoFinalRename bool } -const ( - // partialSuffix is the suffix appended to files while they're - // still in the process of being transferred. - partialSuffix = ".partial" - - // deletedSuffix is the suffix for a deleted marker file - // that's placed next to a file (without the suffix) that we - // tried to delete, but Windows wouldn't let us. These are - // only written on Windows (and in tests), but they're not - // permitted to be uploaded directly on any platform, like - // partial files. - deletedSuffix = ".deleted" -) - -func validFilenameRune(r rune) bool { - switch r { - case '/': - return false - case '\\', ':', '*', '"', '<', '>', '|': - // Invalid stuff on Windows, but we reject them everywhere - // for now. - // TODO(bradfitz): figure out a better plan. We initially just - // wrote things to disk URL path-escaped, but that's gross - // when debugging, and just moves the problem to callers. - // So now we put the UTF-8 filenames on disk directly as - // sent. - return false - } - return unicode.IsPrint(r) -} - -func (s *peerAPIServer) diskPath(baseName string) (fullPath string, ok bool) { - if !utf8.ValidString(baseName) { - return "", false - } - if strings.TrimSpace(baseName) != baseName { - return "", false - } - if len(baseName) > 255 { - return "", false - } - // TODO: validate unicode normalization form too? Varies by platform. - clean := path.Clean(baseName) - if clean != baseName || - clean == "." || clean == ".." || - strings.HasSuffix(clean, deletedSuffix) || - strings.HasSuffix(clean, partialSuffix) { - return "", false - } - for _, r := range baseName { - if !validFilenameRune(r) { - return "", false - } - } - if !filepath.IsLocal(baseName) { - return "", false - } - return filepath.Join(s.rootDir, baseName), true -} - -// hasFilesWaiting reports whether any files are buffered in the -// tailscaled daemon storage. -func (s *peerAPIServer) hasFilesWaiting() bool { - if s == nil || s.rootDir == "" || s.directFileMode { - return false - } - if s.knownEmpty.Load() { - // Optimization: this is usually empty, so avoid opening - // the directory and checking. We can't cache the actual - // has-files-or-not values as the macOS/iOS client might - // in the future use+delete the files directly. So only - // keep this negative cache. - return false - } - f, err := os.Open(s.rootDir) - if err != nil { - return false - } - defer f.Close() - for { - des, err := f.ReadDir(10) - for _, de := range des { - name := de.Name() - if strings.HasSuffix(name, partialSuffix) { - continue - } - if name, ok := strings.CutSuffix(name, deletedSuffix); ok { // for Windows + tests - // After we're done looping over files, then try - // to delete this file. Don't do it proactively, - // as the OS may return "foo.jpg.deleted" before "foo.jpg" - // and we don't want to delete the ".deleted" file before - // enumerating to the "foo.jpg" file. - defer tryDeleteAgain(filepath.Join(s.rootDir, name)) - continue - } - if de.Type().IsRegular() { - _, err := os.Stat(filepath.Join(s.rootDir, name+deletedSuffix)) - if os.IsNotExist(err) { - return true - } - if err == nil { - tryDeleteAgain(filepath.Join(s.rootDir, name)) - continue - } - } - } - if err == io.EOF { - s.knownEmpty.Store(true) - } - if err != nil { - break - } - } - return false -} - -// WaitingFiles returns the list of files that have been sent by a -// peer that are waiting in the buffered "pick up" directory owned by -// the Tailscale daemon. -// -// As a side effect, it also does any lazy deletion of files as -// required by Windows. -func (s *peerAPIServer) WaitingFiles() (ret []apitype.WaitingFile, err error) { - if s == nil { - return nil, errNilPeerAPIServer - } - if s.rootDir == "" { - return nil, errNoTaildrop - } - if s.directFileMode { - return nil, nil - } - f, err := os.Open(s.rootDir) - if err != nil { - return nil, err - } - defer f.Close() - var deleted map[string]bool // "foo.jpg" => true (if "foo.jpg.deleted" exists) - for { - des, err := f.ReadDir(10) - for _, de := range des { - name := de.Name() - if strings.HasSuffix(name, partialSuffix) { - continue - } - if name, ok := strings.CutSuffix(name, deletedSuffix); ok { // for Windows + tests - if deleted == nil { - deleted = map[string]bool{} - } - deleted[name] = true - continue - } - if de.Type().IsRegular() { - fi, err := de.Info() - if err != nil { - continue - } - ret = append(ret, apitype.WaitingFile{ - Name: filepath.Base(name), - Size: fi.Size(), - }) - } - } - if err == io.EOF { - break - } - if err != nil { - return nil, err - } - } - if len(deleted) > 0 { - // Filter out any return values "foo.jpg" where a - // "foo.jpg.deleted" marker file exists on disk. - all := ret - ret = ret[:0] - for _, wf := range all { - if !deleted[wf.Name] { - ret = append(ret, wf) - } - } - // And do some opportunistic deleting while we're here. - // Maybe Windows is done virus scanning the file we tried - // to delete a long time ago and will let us delete it now. - for name := range deleted { - tryDeleteAgain(filepath.Join(s.rootDir, name)) - } - } - sort.Slice(ret, func(i, j int) bool { return ret[i].Name < ret[j].Name }) - return ret, nil -} - -var ( - errNilPeerAPIServer = errors.New("peerapi unavailable; not listening") - errNoTaildrop = errors.New("Taildrop disabled; no storage directory") -) - -// tryDeleteAgain tries to delete path (and path+deletedSuffix) after -// it failed earlier. This happens on Windows when various anti-virus -// tools hook into filesystem operations and have the file open still -// while we're trying to delete it. In that case we instead mark it as -// deleted (writing a "foo.jpg.deleted" marker file), but then we -// later try to clean them up. -// -// fullPath is the full path to the file without the deleted suffix. -func tryDeleteAgain(fullPath string) { - if err := os.Remove(fullPath); err == nil || os.IsNotExist(err) { - os.Remove(fullPath + deletedSuffix) - } -} - -func (s *peerAPIServer) DeleteFile(baseName string) error { - if s == nil { - return errNilPeerAPIServer - } - if s.rootDir == "" { - return errNoTaildrop - } - if s.directFileMode { - return errors.New("deletes not allowed in direct mode") - } - path, ok := s.diskPath(baseName) - if !ok { - return errors.New("bad filename") - } - var bo *backoff.Backoff - logf := s.b.logf - t0 := s.b.clock.Now() - for { - err := os.Remove(path) - if err != nil && !os.IsNotExist(err) { - err = redactErr(err) - // Put a retry loop around deletes on Windows. Windows - // file descriptor closes are effectively asynchronous, - // as a bunch of hooks run on/after close, and we can't - // necessarily delete the file for a while after close, - // as we need to wait for everybody to be done with - // it. (on Windows, unlike Unix, a file can't be deleted - // if it's open anywhere) - // So try a few times but ultimately just leave a - // "foo.jpg.deleted" marker file to note that it's - // deleted and we clean it up later. - if runtime.GOOS == "windows" { - if bo == nil { - bo = backoff.NewBackoff("delete-retry", logf, 1*time.Second) - } - if s.b.clock.Since(t0) < 5*time.Second { - bo.BackOff(context.Background(), err) - continue - } - if err := touchFile(path + deletedSuffix); err != nil { - logf("peerapi: failed to leave deleted marker: %v", err) - } - } - logf("peerapi: failed to DeleteFile: %v", err) - return err - } - return nil - } -} - -// redacted is a fake path name we use in errors, to avoid -// accidentally logging actual filenames anywhere. -const redacted = "redacted" - -type redactedErr struct { - msg string - inner error -} - -func (re *redactedErr) Error() string { - return re.msg -} - -func (re *redactedErr) Unwrap() error { - return re.inner -} - -func redactString(s string) string { - hash := adler32.Checksum([]byte(s)) - - var buf [len(redacted) + len(".12345678")]byte - b := append(buf[:0], []byte(redacted)...) - b = append(b, '.') - b = strconv.AppendUint(b, uint64(hash), 16) - return string(b) -} - -func redactErr(root error) error { - // redactStrings is a list of sensitive strings that were redacted. - // It is not sufficient to just snub out sensitive fields in Go errors - // since some wrapper errors like fmt.Errorf pre-cache the error string, - // which would unfortunately remain unaffected. - var redactStrings []string - - // Redact sensitive fields in known Go error types. - var unknownErrors int - multierr.Range(root, func(err error) bool { - switch err := err.(type) { - case *os.PathError: - redactStrings = append(redactStrings, err.Path) - err.Path = redactString(err.Path) - case *os.LinkError: - redactStrings = append(redactStrings, err.New, err.Old) - err.New = redactString(err.New) - err.Old = redactString(err.Old) - default: - unknownErrors++ - } - return true - }) - - // If there are no redacted strings or no unknown error types, - // then we can return the possibly modified root error verbatim. - // Otherwise, we must replace redacted strings from any wrappers. - if len(redactStrings) == 0 || unknownErrors == 0 { - return root - } - - // Stringify and replace any paths that we found above, then return - // the error wrapped in a type that uses the newly-redacted string - // while also allowing Unwrap()-ing to the inner error type(s). - s := root.Error() - for _, toRedact := range redactStrings { - s = strings.ReplaceAll(s, toRedact, redactString(toRedact)) - } - return &redactedErr{msg: s, inner: root} -} - -func touchFile(path string) error { - f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0666) - if err != nil { - return redactErr(err) - } - return f.Close() -} - -func (s *peerAPIServer) OpenFile(baseName string) (rc io.ReadCloser, size int64, err error) { - if s == nil { - return nil, 0, errNilPeerAPIServer - } - if s.rootDir == "" { - return nil, 0, errNoTaildrop - } - if s.directFileMode { - return nil, 0, errors.New("opens not allowed in direct mode") - } - path, ok := s.diskPath(baseName) - if !ok { - return nil, 0, errors.New("bad filename") - } - if fi, err := os.Stat(path + deletedSuffix); err == nil && fi.Mode().IsRegular() { - tryDeleteAgain(path) - return nil, 0, &fs.PathError{Op: "open", Path: redacted, Err: fs.ErrNotExist} - } - f, err := os.Open(path) - if err != nil { - return nil, 0, redactErr(err) - } - fi, err := f.Stat() - if err != nil { - f.Close() - return nil, 0, redactErr(err) - } - return f, fi.Size(), nil -} - -func (s *peerAPIServer) listen(ip netip.Addr, ifState *interfaces.State) (ln net.Listener, err error) { +func (s *PeerAPIServer) listen(ip netip.Addr, ifState *interfaces.State) (ln net.Listener, err error) { // Android for whatever reason often has problems creating the peerapi listener. // But since we started intercepting it with netstack, it's not even important that // we have a real kernel-level listener. So just create a dummy listener on Android @@ -511,7 +134,7 @@ func (s *peerAPIServer) listen(ip netip.Addr, ifState *interfaces.State) (ln net } type peerAPIListener struct { - ps *peerAPIServer + ps *PeerAPIServer ip netip.Addr lb *LocalBackend @@ -578,7 +201,7 @@ func (pln *peerAPIListener) ServeConn(src netip.AddrPort, c net.Conn) { c.Close() return } - h := &peerAPIHandler{ + h := &PeerAPIHandler{ ps: pln.ps, isSelf: nm.SelfNode.User() == peerNode.User(), remoteAddr: src, @@ -595,9 +218,9 @@ func (pln *peerAPIListener) ServeConn(src netip.AddrPort, c net.Conn) { go httpServer.Serve(netutil.NewOneConnListener(c, nil)) } -// peerAPIHandler serves the PeerAPI for a source specific client. -type peerAPIHandler struct { - ps *peerAPIServer +// PeerAPIHandler serves the PeerAPI for a source specific client. +type PeerAPIHandler struct { + ps *PeerAPIServer remoteAddr netip.AddrPort isSelf bool // whether peerNode is owned by same user as this node selfNode tailcfg.NodeView // this node; always non-nil @@ -605,13 +228,13 @@ type peerAPIHandler struct { peerUser tailcfg.UserProfile // profile of peerNode } -func (h *peerAPIHandler) logf(format string, a ...any) { +func (h *PeerAPIHandler) logf(format string, a ...any) { h.ps.b.logf("peerapi: "+format, a...) } // isAddressValid reports whether addr is a valid destination address for this // node originating from the peer. -func (h *peerAPIHandler) isAddressValid(addr netip.Addr) bool { +func (h *PeerAPIHandler) isAddressValid(addr netip.Addr) bool { if v := h.peerNode.SelfNodeV4MasqAddrForThisPeer(); v != nil { return *v == addr } @@ -622,7 +245,7 @@ func (h *peerAPIHandler) isAddressValid(addr netip.Addr) bool { return views.SliceContains(h.selfNode.Addresses(), pfx) } -func (h *peerAPIHandler) validateHost(r *http.Request) error { +func (h *PeerAPIHandler) validateHost(r *http.Request) error { if r.Host == "peer" { return nil } @@ -636,7 +259,7 @@ func (h *peerAPIHandler) validateHost(r *http.Request) error { return nil } -func (h *peerAPIHandler) validatePeerAPIRequest(r *http.Request) error { +func (h *PeerAPIHandler) validatePeerAPIRequest(r *http.Request) error { if r.Referer() != "" { return errors.New("unexpected Referer") } @@ -679,7 +302,7 @@ func peerAPIRequestShouldGetSecurityHeaders(r *http.Request) bool { return false } -func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { +func (h *PeerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if err := h.validatePeerAPIRequest(r); err != nil { metricInvalidRequests.Add(1) h.logf("invalid request from %v: %v", h.remoteAddr, err) @@ -693,6 +316,7 @@ func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } if strings.HasPrefix(r.URL.Path, "/v0/put/") { metricPutCalls.Add(1) + //TODO: fix this h.handlePeerPut(w, r) return } @@ -747,7 +371,7 @@ This is my Tailscale device. Your device is %v. } } -func (h *peerAPIHandler) handleServeIngress(w http.ResponseWriter, r *http.Request) { +func (h *PeerAPIHandler) handleServeIngress(w http.ResponseWriter, r *http.Request) { // http.Errors only useful if hitting endpoint manually // otherwise rely on log lines when debugging ingress connections // as connection is hijacked for bidi and is encrypted tls @@ -808,7 +432,7 @@ func (h *peerAPIHandler) handleServeIngress(w http.ResponseWriter, r *http.Reque h.ps.b.HandleIngressTCPConn(h.peerNode, target, srcAddr, getConnOrReset, sendRST) } -func (h *peerAPIHandler) handleServeInterfaces(w http.ResponseWriter, r *http.Request) { +func (h *PeerAPIHandler) handleServeInterfaces(w http.ResponseWriter, r *http.Request) { if !h.canDebug() { http.Error(w, "denied; no debug access", http.StatusForbidden) return @@ -855,7 +479,7 @@ func (h *peerAPIHandler) handleServeInterfaces(w http.ResponseWriter, r *http.Re fmt.Fprintln(w, "</table>") } -func (h *peerAPIHandler) handleServeDoctor(w http.ResponseWriter, r *http.Request) { +func (h *PeerAPIHandler) handleServeDoctor(w http.ResponseWriter, r *http.Request) { if !h.canDebug() { http.Error(w, "denied; no debug access", http.StatusForbidden) return @@ -873,7 +497,7 @@ func (h *peerAPIHandler) handleServeDoctor(w http.ResponseWriter, r *http.Reques fmt.Fprintln(w, "</pre>") } -func (h *peerAPIHandler) handleServeSockStats(w http.ResponseWriter, r *http.Request) { +func (h *PeerAPIHandler) handleServeSockStats(w http.ResponseWriter, r *http.Request) { if !h.canDebug() { http.Error(w, "denied; no debug access", http.StatusForbidden) return @@ -971,76 +595,9 @@ func (h *peerAPIHandler) handleServeSockStats(w http.ResponseWriter, r *http.Req fmt.Fprintln(w, "</pre>") } -type incomingFile struct { - name string // "foo.jpg" - started time.Time - size int64 // or -1 if unknown; never 0 - w io.Writer // underlying writer - ph *peerAPIHandler - partialPath string // non-empty in direct mode - - mu sync.Mutex - copied int64 - done bool - lastNotify time.Time -} - -func (f *incomingFile) markAndNotifyDone() { - f.mu.Lock() - f.done = true - f.mu.Unlock() - b := f.ph.ps.b - b.sendFileNotify() -} - -func (f *incomingFile) Write(p []byte) (n int, err error) { - n, err = f.w.Write(p) - - b := f.ph.ps.b - var needNotify bool - defer func() { - if needNotify { - b.sendFileNotify() - } - }() - if n > 0 { - f.mu.Lock() - defer f.mu.Unlock() - f.copied += int64(n) - now := b.clock.Now() - if f.lastNotify.IsZero() || now.Sub(f.lastNotify) > time.Second { - f.lastNotify = now - needNotify = true - } - } - return n, err -} - -func (f *incomingFile) PartialFile() ipn.PartialFile { - f.mu.Lock() - defer f.mu.Unlock() - return ipn.PartialFile{ - Name: f.name, - Started: f.started, - DeclaredSize: f.size, - Received: f.copied, - PartialPath: f.partialPath, - Done: f.done, - } -} - -// canPutFile reports whether h can put a file ("Taildrop") to this node. -func (h *peerAPIHandler) canPutFile() bool { - if h.peerNode.UnsignedPeerAPIOnly() { - // Unsigned peers can't send files. - return false - } - return h.isSelf || h.peerHasCap(tailcfg.PeerCapabilityFileSharingSend) -} - // canDebug reports whether h can debug this node (goroutines, metrics, // magicsock internal state, etc). -func (h *peerAPIHandler) canDebug() bool { +func (h *PeerAPIHandler) canDebug() bool { if !h.selfNode.HasCap(tailcfg.CapabilityDebug) { // This node does not expose debug info. return false @@ -1053,7 +610,7 @@ func (h *peerAPIHandler) canDebug() bool { } // canWakeOnLAN reports whether h can send a Wake-on-LAN packet from this node. -func (h *peerAPIHandler) canWakeOnLAN() bool { +func (h *PeerAPIHandler) canWakeOnLAN() bool { if h.peerNode.UnsignedPeerAPIOnly() { return false } @@ -1063,150 +620,15 @@ func (h *peerAPIHandler) canWakeOnLAN() bool { var allowSelfIngress = envknob.RegisterBool("TS_ALLOW_SELF_INGRESS") // canIngress reports whether h can send ingress requests to this node. -func (h *peerAPIHandler) canIngress() bool { +func (h *PeerAPIHandler) canIngress() bool { return h.peerHasCap(tailcfg.PeerCapabilityIngress) || (allowSelfIngress() && h.isSelf) } -func (h *peerAPIHandler) peerHasCap(wantCap tailcfg.PeerCapability) bool { +func (h *PeerAPIHandler) peerHasCap(wantCap tailcfg.PeerCapability) bool { return h.ps.b.PeerCaps(h.remoteAddr.Addr()).HasCapability(wantCap) } -func (h *peerAPIHandler) handlePeerPut(w http.ResponseWriter, r *http.Request) { - if !envknob.CanTaildrop() { - http.Error(w, "Taildrop disabled on device", http.StatusForbidden) - return - } - if !h.canPutFile() { - http.Error(w, "Taildrop access denied", 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 - } - if h.ps.rootDir == "" { - http.Error(w, errNoTaildrop.Error(), http.StatusInternalServerError) - return - } - if distro.Get() == distro.Unraid && !h.ps.directFileMode { - http.Error(w, "Taildrop folder not configured or accessible", http.StatusInternalServerError) - return - } - rawPath := r.URL.EscapedPath() - suffix, ok := strings.CutPrefix(rawPath, "/v0/put/") - if !ok { - http.Error(w, "misconfigured internals", 500) - return - } - if suffix == "" { - http.Error(w, "empty filename", 400) - return - } - if strings.Contains(suffix, "/") { - http.Error(w, "directories not supported", 400) - return - } - baseName, err := url.PathUnescape(suffix) - if err != nil { - http.Error(w, "bad path encoding", 400) - return - } - dstFile, ok := h.ps.diskPath(baseName) - if !ok { - http.Error(w, "bad filename", 400) - return - } - t0 := h.ps.b.clock.Now() - // TODO(bradfitz): prevent same filename being sent by two peers at once - - // prevent same filename being sent twice - if _, err := os.Stat(dstFile); err == nil { - http.Error(w, "file exists", http.StatusConflict) - return - } - - partialFile := dstFile + partialSuffix - f, err := os.Create(partialFile) - if err != nil { - h.logf("put Create error: %v", redactErr(err)) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - var success bool - defer func() { - if !success { - os.Remove(partialFile) - } - }() - var finalSize int64 - var inFile *incomingFile - if r.ContentLength != 0 { - inFile = &incomingFile{ - name: baseName, - started: h.ps.b.clock.Now(), - size: r.ContentLength, - w: f, - ph: h, - } - if h.ps.directFileMode { - inFile.partialPath = partialFile - } - h.ps.b.registerIncomingFile(inFile, true) - defer h.ps.b.registerIncomingFile(inFile, false) - n, err := io.Copy(inFile, r.Body) - if err != nil { - err = redactErr(err) - f.Close() - h.logf("put Copy error: %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - finalSize = n - } - if err := redactErr(f.Close()); err != nil { - h.logf("put Close error: %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if h.ps.directFileMode && !h.ps.directFileDoFinalRename { - if inFile != nil { // non-zero length; TODO: notify even for zero length - inFile.markAndNotifyDone() - } - } else { - if err := os.Rename(partialFile, dstFile); err != nil { - err = redactErr(err) - h.logf("put final rename: %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - } - - d := h.ps.b.clock.Since(t0).Round(time.Second / 10) - h.logf("got put of %s in %v from %v/%v", approxSize(finalSize), d, h.remoteAddr.Addr(), h.peerNode.ComputedName) - - // TODO: set modtime - // TODO: some real response - success = true - io.WriteString(w, "{}\n") - h.ps.knownEmpty.Store(false) - h.ps.b.sendFileNotify() -} - -func approxSize(n int64) string { - if n <= 1<<10 { - return "<=1KB" - } - if n <= 1<<20 { - return "<=1MB" - } - return fmt.Sprintf("~%dMB", n>>20) -} - -func (h *peerAPIHandler) handleServeGoroutines(w http.ResponseWriter, r *http.Request) { +func (h *PeerAPIHandler) handleServeGoroutines(w http.ResponseWriter, r *http.Request) { if !h.canDebug() { http.Error(w, "denied; no debug access", http.StatusForbidden) return @@ -1222,7 +644,7 @@ func (h *peerAPIHandler) handleServeGoroutines(w http.ResponseWriter, r *http.Re w.Write(buf) } -func (h *peerAPIHandler) handleServeEnv(w http.ResponseWriter, r *http.Request) { +func (h *PeerAPIHandler) handleServeEnv(w http.ResponseWriter, r *http.Request) { if !h.canDebug() { http.Error(w, "denied; no debug access", http.StatusForbidden) return @@ -1242,7 +664,7 @@ func (h *peerAPIHandler) handleServeEnv(w http.ResponseWriter, r *http.Request) json.NewEncoder(w).Encode(data) } -func (h *peerAPIHandler) handleServeMagicsock(w http.ResponseWriter, r *http.Request) { +func (h *PeerAPIHandler) handleServeMagicsock(w http.ResponseWriter, r *http.Request) { if !h.canDebug() { http.Error(w, "denied; no debug access", http.StatusForbidden) return @@ -1250,7 +672,7 @@ func (h *peerAPIHandler) handleServeMagicsock(w http.ResponseWriter, r *http.Req h.ps.b.magicConn().ServeHTTPDebug(w, r) } -func (h *peerAPIHandler) handleServeMetrics(w http.ResponseWriter, r *http.Request) { +func (h *PeerAPIHandler) handleServeMetrics(w http.ResponseWriter, r *http.Request) { if !h.canDebug() { http.Error(w, "denied; no debug access", http.StatusForbidden) return @@ -1259,7 +681,7 @@ func (h *peerAPIHandler) handleServeMetrics(w http.ResponseWriter, r *http.Reque clientmetric.WritePrometheusExpositionFormat(w) } -func (h *peerAPIHandler) handleServeDNSFwd(w http.ResponseWriter, r *http.Request) { +func (h *PeerAPIHandler) handleServeDNSFwd(w http.ResponseWriter, r *http.Request) { if !h.canDebug() { http.Error(w, "denied; no debug access", http.StatusForbidden) return @@ -1272,7 +694,7 @@ func (h *peerAPIHandler) handleServeDNSFwd(w http.ResponseWriter, r *http.Reques dh.ServeHTTP(w, r) } -func (h *peerAPIHandler) handleWakeOnLAN(w http.ResponseWriter, r *http.Request) { +func (h *PeerAPIHandler) handleWakeOnLAN(w http.ResponseWriter, r *http.Request) { if !h.canWakeOnLAN() { http.Error(w, "no WoL access", http.StatusForbidden) return @@ -1327,7 +749,7 @@ func (h *peerAPIHandler) handleWakeOnLAN(w http.ResponseWriter, r *http.Request) json.NewEncoder(w).Encode(res) } -func (h *peerAPIHandler) replyToDNSQueries() bool { +func (h *PeerAPIHandler) replyToDNSQueries() bool { if h.isSelf { // If the peer is owned by the same user, just allow it // without further checks. @@ -1371,7 +793,7 @@ func (h *peerAPIHandler) replyToDNSQueries() bool { // handleDNSQuery implements a DoH server (RFC 8484) over the peerapi. // It's not over HTTPS as the spec dictates, but rather HTTP-over-WireGuard. -func (h *peerAPIHandler) handleDNSQuery(w http.ResponseWriter, r *http.Request) { +func (h *PeerAPIHandler) handleDNSQuery(w http.ResponseWriter, r *http.Request) { if h.ps.resolver == nil { http.Error(w, "DNS not wired up", http.StatusNotImplemented) return |
