ipn{,/ipnlocal}: set new Notify.FilesWaiting when server has file(s)

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/1621/head
Brad Fitzpatrick 3 years ago committed by Brad Fitzpatrick
parent 0301ccd275
commit 672b9fd4bd

@ -68,6 +68,8 @@ type Notify struct {
BackendLogID *string // public logtail id used by backend BackendLogID *string // public logtail id used by backend
PingResult *ipnstate.PingResult PingResult *ipnstate.PingResult
FilesWaiting *empty.Message `json:",omitempty"`
// LocalTCPPort, if non-nil, informs the UI frontend which // LocalTCPPort, if non-nil, informs the UI frontend which
// (non-zero) localhost TCP port it's listening on. // (non-zero) localhost TCP port it's listening on.
// This is currently only used by Tailscale when run in the // This is currently only used by Tailscale when run in the

@ -107,6 +107,7 @@ type LocalBackend struct {
authURL string authURL string
interact bool interact bool
prevIfState *interfaces.State prevIfState *interfaces.State
peerAPIServer *peerAPIServer // or nil
peerAPIListeners []*peerAPIListener peerAPIListeners []*peerAPIListener
// statusLock must be held before calling statusChanged.Wait() or // statusLock must be held before calling statusChanged.Wait() or
@ -909,15 +910,20 @@ func (b *LocalBackend) readPoller() {
// connected, the notification is dropped without being delivered. // connected, the notification is dropped without being delivered.
func (b *LocalBackend) send(n ipn.Notify) { func (b *LocalBackend) send(n ipn.Notify) {
b.mu.Lock() b.mu.Lock()
notify := b.notify notifyFunc := b.notify
apiSrv := b.peerAPIServer
b.mu.Unlock() b.mu.Unlock()
if notify != nil { if notifyFunc == nil {
n.Version = version.Long
notify(n)
} else {
b.logf("nil notify callback; dropping %+v", n) b.logf("nil notify callback; dropping %+v", n)
return
}
n.Version = version.Long
if apiSrv != nil && apiSrv.hasFilesWaiting() {
n.FilesWaiting = &empty.Message{}
} }
notifyFunc(n)
} }
// popBrowserAuthNow shuts down the data plane and sends an auth URL // popBrowserAuthNow shuts down the data plane and sends an auth URL
@ -1489,6 +1495,7 @@ func (b *LocalBackend) initPeerAPIListener() {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
b.peerAPIServer = nil
for _, pln := range b.peerAPIListeners { for _, pln := range b.peerAPIListeners {
pln.Close() pln.Close()
} }
@ -1526,6 +1533,7 @@ func (b *LocalBackend) initPeerAPIListener() {
tunName: tunName, tunName: tunName,
selfNode: selfNode, selfNode: selfNode,
} }
b.peerAPIServer = ps
isNetstack := wgengine.IsNetstack(b.e) isNetstack := wgengine.IsNetstack(b.e)
for i, a := range b.netMap.Addresses { for i, a := range b.netMap.Addresses {

@ -22,7 +22,9 @@ import (
"strings" "strings"
"inet.af/netaddr" "inet.af/netaddr"
"tailscale.com/ipn"
"tailscale.com/net/interfaces" "tailscale.com/net/interfaces"
"tailscale.com/syncs"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/wgengine" "tailscale.com/wgengine"
) )
@ -30,10 +32,52 @@ import (
var initListenConfig func(*net.ListenConfig, netaddr.IP, *interfaces.State, string) error var initListenConfig func(*net.ListenConfig, netaddr.IP, *interfaces.State, string) error
type peerAPIServer struct { type peerAPIServer struct {
b *LocalBackend b *LocalBackend
rootDir string rootDir string
tunName string tunName string
selfNode *tailcfg.Node selfNode *tailcfg.Node
knownEmpty syncs.AtomicBool
}
const partialSuffix = ".tspartial"
// hasFilesWaiting reports whether any files are buffered in the
// tailscaled daemon storage.
func (s *peerAPIServer) hasFilesWaiting() bool {
if s.rootDir == "" {
return false
}
if s.knownEmpty.Get() {
// 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 {
if strings.HasSuffix(de.Name(), partialSuffix) {
continue
}
if de.Type().IsRegular() {
return true
}
}
if err == io.EOF {
s.knownEmpty.Set(true)
}
if err != nil {
break
}
}
return false
} }
func (s *peerAPIServer) listen(ip netaddr.IP, ifState *interfaces.State) (ln net.Listener, err error) { func (s *peerAPIServer) listen(ip netaddr.IP, ifState *interfaces.State) (ln net.Listener, err error) {
@ -221,7 +265,7 @@ func (h *peerAPIHandler) put(w http.ResponseWriter, r *http.Request) {
return return
} }
name := path.Base(r.URL.Path) name := path.Base(r.URL.Path)
if name == "." || name == "/" { if name == "." || name == "/" || strings.HasSuffix(name, partialSuffix) {
http.Error(w, "bad filename", http.StatusForbidden) http.Error(w, "bad filename", http.StatusForbidden)
return return
} }
@ -258,4 +302,6 @@ func (h *peerAPIHandler) put(w http.ResponseWriter, r *http.Request) {
// TODO: some real response // TODO: some real response
success = true success = true
io.WriteString(w, "{}\n") io.WriteString(w, "{}\n")
h.ps.knownEmpty.Set(false)
h.ps.b.send(ipn.Notify{}) // it will set FilesWaiting
} }

Loading…
Cancel
Save