tailfs: clean up naming and package structure

- Restyles tailfs -> tailFS
- Defines interfaces for main TailFS types
- Moves implemenatation of TailFS into tailfsimpl package

Updates tailscale/corp#16827

Signed-off-by: Percy Wegmann <percy@tailscale.com>
This commit is contained in:
Percy Wegmann
2024-02-09 11:26:43 -06:00
committed by Percy Wegmann
parent 79b547804b
commit abab0d4197
50 changed files with 753 additions and 683 deletions

View File

@@ -66,7 +66,7 @@ const (
NotifyInitialNetMap // if set, the first Notify message (sent immediately) will contain the current NetMap
NotifyNoPrivateKeys // if set, private keys that would normally be sent in updates are zeroed out
NotifyInitialTailfsShares // if set, the first Notify message (sent immediately) will contain the current Tailfs Shares
NotifyInitialTailFSShares // if set, the first Notify message (sent immediately) will contain the current TailFS Shares
)
// Notify is a communication from a backend (e.g. tailscaled) to a frontend
@@ -122,11 +122,12 @@ type Notify struct {
// is available.
ClientVersion *tailcfg.ClientVersion `json:",omitempty"`
// Full set of current TailfsShares that we're publishing as name->path.
// Some client applications, like the MacOS and Windows clients, will
// listen for updates to this and handle serving these shares under the
// identity of the unprivileged user that is running the application.
TailfsShares map[string]string `json:",omitempty"`
// TailFSShares tracks the full set of current TailFSShares that we're
// publishing as name->path. Some client applications, like the MacOS and
// Windows clients, will listen for updates to this and handle serving
// these shares under the identity of the unprivileged user that is running
// the application.
TailFSShares map[string]string `json:",omitempty"`
// type is mirrored in xcode/Shared/IPN.swift
}

View File

@@ -67,7 +67,6 @@ import (
"tailscale.com/syncs"
"tailscale.com/tailcfg"
"tailscale.com/taildrop"
"tailscale.com/tailfs"
"tailscale.com/tka"
"tailscale.com/tsd"
"tailscale.com/tstime"
@@ -288,8 +287,7 @@ type LocalBackend struct {
serveListeners map[netip.AddrPort]*localListener // listeners for local serve traffic
serveProxyHandlers sync.Map // string (HTTPHandler.Proxy) => *reverseProxy
tailfsListeners map[netip.AddrPort]*localListener // listeners for local tailfs traffic
tailfsForRemote *tailfs.FileSystemForRemote
tailFSListeners map[netip.AddrPort]*localListener // listeners for local tailfs traffic
// statusLock must be held before calling statusChanged.Wait() or
// statusChanged.Broadcast().
@@ -432,13 +430,15 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
}
}
// initialize Tailfs shares from saved state
b.mu.Lock()
b.tailfsForRemote = tailfs.NewFileSystemForRemote(logf)
shares, err := b.tailfsGetSharesLocked()
b.mu.Unlock()
if err == nil && len(shares) > 0 {
b.tailfsForRemote.SetShares(shares)
// initialize TailFS shares from saved state
fs, ok := b.sys.TailFSForRemote.GetOK()
if !ok {
b.mu.Lock()
shares, err := b.tailFSGetSharesLocked()
b.mu.Unlock()
if err == nil && len(shares) > 0 {
fs.SetShares(shares)
}
}
return b, nil
@@ -2268,7 +2268,7 @@ func (b *LocalBackend) WatchNotifications(ctx context.Context, mask ipn.NotifyWa
b.mu.Lock()
b.activeWatchSessions.Add(sessionID)
const initialBits = ipn.NotifyInitialState | ipn.NotifyInitialPrefs | ipn.NotifyInitialNetMap | ipn.NotifyInitialTailfsShares
const initialBits = ipn.NotifyInitialState | ipn.NotifyInitialPrefs | ipn.NotifyInitialNetMap | ipn.NotifyInitialTailFSShares
if mask&initialBits != 0 {
ini = &ipn.Notify{Version: version.Long()}
if mask&ipn.NotifyInitialState != 0 {
@@ -2284,14 +2284,14 @@ func (b *LocalBackend) WatchNotifications(ctx context.Context, mask ipn.NotifyWa
if mask&ipn.NotifyInitialNetMap != 0 {
ini.NetMap = b.netMap
}
if mask&ipn.NotifyInitialTailfsShares != 0 && b.tailfsSharingEnabledLocked() {
shares, err := b.tailfsGetSharesLocked()
if mask&ipn.NotifyInitialTailFSShares != 0 && b.tailFSSharingEnabledLocked() {
shares, err := b.tailFSGetSharesLocked()
if err != nil {
b.logf("unable to notify initial tailfs shares: %v", err)
} else {
ini.TailfsShares = make(map[string]string, len(shares))
ini.TailFSShares = make(map[string]string, len(shares))
for _, share := range shares {
ini.TailfsShares[share.Name] = share.Path
ini.TailFSShares[share.Name] = share.Path
}
}
}
@@ -3337,8 +3337,8 @@ func (b *LocalBackend) TCPHandlerForDst(src, dst netip.AddrPort) (handler func(c
if dst.Port() == webClientPort && b.ShouldRunWebClient() {
return b.handleWebClientConn, opts
}
if dst.Port() == TailfsLocalPort {
fs, ok := b.sys.TailfsForLocal.GetOK()
if dst.Port() == TailFSLocalPort {
fs, ok := b.sys.TailFSForLocal.GetOK()
if ok {
return func(conn net.Conn) error {
return fs.HandleConn(conn, conn.RemoteAddr())
@@ -4642,9 +4642,9 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
}
}
if b.tailfsSharingEnabledLocked() {
b.updateTailfsPeersLocked(nm)
b.tailfsNotifyCurrentSharesLocked()
if b.tailFSSharingEnabledLocked() {
b.updateTailFSPeersLocked(nm)
b.tailFSNotifyCurrentSharesLocked()
}
}
@@ -4672,14 +4672,14 @@ func (b *LocalBackend) updatePeersFromNetmapLocked(nm *netmap.NetworkMap) {
}
}
// tailfsTransport is an http.RoundTripper that uses the latest value of
// tailFSTransport is an http.RoundTripper that uses the latest value of
// b.Dialer().PeerAPITransport() for each round trip and imposes a short
// dial timeout to avoid hanging on connecting to offline/unreachable hosts.
type tailfsTransport struct {
type tailFSTransport struct {
b *LocalBackend
}
func (t *tailfsTransport) RoundTrip(req *http.Request) (*http.Response, error) {
func (t *tailFSTransport) RoundTrip(req *http.Request) (*http.Response, error) {
// dialTimeout is fairly aggressive to avoid hangs on contacting offline or
// unreachable hosts.
dialTimeout := 1 * time.Second // TODO(oxtoacart): tune this
@@ -4767,7 +4767,7 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn.
}
if !b.sys.IsNetstack() {
b.updateTailfsListenersLocked()
b.updateTailFSListenersLocked()
}
b.reloadServeConfigLocked(prefs)

View File

@@ -46,7 +46,7 @@ import (
)
const (
tailfsPrefix = "/v0/tailfs"
tailFSPrefix = "/v0/tailfs"
)
var initListenConfig func(*net.ListenConfig, netip.Addr, *interfaces.State, string) error
@@ -322,8 +322,8 @@ func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.handleDNSQuery(w, r)
return
}
if strings.HasPrefix(r.URL.Path, tailfsPrefix) {
h.handleServeTailfs(w, r)
if strings.HasPrefix(r.URL.Path, tailFSPrefix) {
h.handleServeTailFS(w, r)
return
}
switch r.URL.Path {
@@ -1103,14 +1103,14 @@ func writePrettyDNSReply(w io.Writer, res []byte) (err error) {
return nil
}
func (h *peerAPIHandler) handleServeTailfs(w http.ResponseWriter, r *http.Request) {
if !h.ps.b.TailfsSharingEnabled() {
func (h *peerAPIHandler) handleServeTailFS(w http.ResponseWriter, r *http.Request) {
if !h.ps.b.TailFSSharingEnabled() {
http.Error(w, "tailfs not enabled", http.StatusNotFound)
return
}
capsMap := h.peerCaps()
tailfsCaps, ok := capsMap[tailcfg.PeerCapabilityTailfs]
tailfsCaps, ok := capsMap[tailcfg.PeerCapabilityTailFS]
if !ok {
http.Error(w, "tailfs not permitted", http.StatusForbidden)
return
@@ -1127,14 +1127,12 @@ func (h *peerAPIHandler) handleServeTailfs(w http.ResponseWriter, r *http.Reques
return
}
h.ps.b.mu.Lock()
fs := h.ps.b.tailfsForRemote
h.ps.b.mu.Unlock()
if fs == nil {
fs, ok := h.ps.b.sys.TailFSForRemote.GetOK()
if !ok {
http.Error(w, "tailfs not enabled", http.StatusNotFound)
return
}
r.URL.Path = strings.TrimPrefix(r.URL.Path, tailfsPrefix)
r.URL.Path = strings.TrimPrefix(r.URL.Path, tailFSPrefix)
fs.ServeHTTPWithPerms(p, w, r)
}

View File

@@ -24,9 +24,9 @@ import (
)
const (
// TailfsLocalPort is the port on which the Tailfs listens for location
// TailFSLocalPort is the port on which the TailFS listens for location
// connections on quad 100.
TailfsLocalPort = 8080
TailFSLocalPort = 8080
tailfsSharesStateKey = ipn.StateKey("_tailfs-shares")
)
@@ -36,27 +36,25 @@ var (
errInvalidShareName = errors.New("Share names may only contain the letters a-z, underscore _, parentheses (), or spaces")
)
// TailfsSharingEnabled reports whether sharing to remote nodes via tailfs is
// TailFSSharingEnabled reports whether sharing to remote nodes via tailfs is
// enabled. This is currently based on checking for the tailfs:share node
// attribute.
func (b *LocalBackend) TailfsSharingEnabled() bool {
func (b *LocalBackend) TailFSSharingEnabled() bool {
b.mu.Lock()
defer b.mu.Unlock()
return b.tailfsSharingEnabledLocked()
return b.tailFSSharingEnabledLocked()
}
func (b *LocalBackend) tailfsSharingEnabledLocked() bool {
return b.netMap != nil && b.netMap.SelfNode.HasCap(tailcfg.NodeAttrsTailfsSharingEnabled)
func (b *LocalBackend) tailFSSharingEnabledLocked() bool {
return b.netMap != nil && b.netMap.SelfNode.HasCap(tailcfg.NodeAttrsTailFSSharingEnabled)
}
// TailfsSetFileServerAddr tells tailfs to use the given address for connecting
// TailFSSetFileServerAddr tells tailfs to use the given address for connecting
// to the tailfs.FileServer that's exposing local files as an unprivileged
// user.
func (b *LocalBackend) TailfsSetFileServerAddr(addr string) error {
b.mu.Lock()
fs := b.tailfsForRemote
b.mu.Unlock()
if fs == nil {
func (b *LocalBackend) TailFSSetFileServerAddr(addr string) error {
fs, ok := b.sys.TailFSForRemote.GetOK()
if !ok {
return errors.New("tailfs not enabled")
}
@@ -64,11 +62,11 @@ func (b *LocalBackend) TailfsSetFileServerAddr(addr string) error {
return nil
}
// TailfsAddShare adds the given share if no share with that name exists, or
// TailFSAddShare adds the given share if no share with that name exists, or
// replaces the existing share if one with the same name already exists.
// To avoid potential incompatibilities across file systems, share names are
// limited to alphanumeric characters and the underscore _.
func (b *LocalBackend) TailfsAddShare(share *tailfs.Share) error {
func (b *LocalBackend) TailFSAddShare(share *tailfs.Share) error {
var err error
share.Name, err = normalizeShareName(share.Name)
if err != nil {
@@ -104,11 +102,12 @@ func normalizeShareName(name string) (string, error) {
}
func (b *LocalBackend) tailfsAddShareLocked(share *tailfs.Share) (map[string]string, error) {
if b.tailfsForRemote == nil {
fs, ok := b.sys.TailFSForRemote.GetOK()
if !ok {
return nil, errors.New("tailfs not enabled")
}
shares, err := b.tailfsGetSharesLocked()
shares, err := b.tailFSGetSharesLocked()
if err != nil {
return nil, err
}
@@ -121,17 +120,21 @@ func (b *LocalBackend) tailfsAddShareLocked(share *tailfs.Share) (map[string]str
if err != nil {
return nil, fmt.Errorf("write state: %w", err)
}
b.tailfsForRemote.SetShares(shares)
fs.SetShares(shares)
return shareNameMap(shares), nil
}
// TailfsRemoveShare removes the named share. Share names are forced to
// TailFSRemoveShare removes the named share. Share names are forced to
// lowercase.
func (b *LocalBackend) TailfsRemoveShare(name string) error {
func (b *LocalBackend) TailFSRemoveShare(name string) error {
// Force all share names to lowercase to avoid potential incompatibilities
// with clients that don't support case-sensitive filenames.
name = strings.ToLower(name)
var err error
name, err = normalizeShareName(name)
if err != nil {
return err
}
b.mu.Lock()
shares, err := b.tailfsRemoveShareLocked(name)
@@ -145,11 +148,12 @@ func (b *LocalBackend) TailfsRemoveShare(name string) error {
}
func (b *LocalBackend) tailfsRemoveShareLocked(name string) (map[string]string, error) {
if b.tailfsForRemote == nil {
fs, ok := b.sys.TailFSForRemote.GetOK()
if !ok {
return nil, errors.New("tailfs not enabled")
}
shares, err := b.tailfsGetSharesLocked()
shares, err := b.tailFSGetSharesLocked()
if err != nil {
return nil, err
}
@@ -166,7 +170,7 @@ func (b *LocalBackend) tailfsRemoveShareLocked(name string) (map[string]string,
if err != nil {
return nil, fmt.Errorf("write state: %w", err)
}
b.tailfsForRemote.SetShares(shares)
fs.SetShares(shares)
return shareNameMap(shares), nil
}
@@ -182,13 +186,13 @@ func shareNameMap(sharesByName map[string]*tailfs.Share) map[string]string {
// tailfsNotifyShares notifies IPN bus listeners (e.g. Mac Application process)
// about the latest set of shares, supplied as a map of name -> directory.
func (b *LocalBackend) tailfsNotifyShares(shares map[string]string) {
b.send(ipn.Notify{TailfsShares: shares})
b.send(ipn.Notify{TailFSShares: shares})
}
// tailfsNotifyCurrentSharesLocked sends an ipn.Notify with the current set of
// tailfs shares.
func (b *LocalBackend) tailfsNotifyCurrentSharesLocked() {
shares, err := b.tailfsGetSharesLocked()
// tailFSNotifyCurrentSharesLocked sends an ipn.Notify with the current set of
// TailFS shares.
func (b *LocalBackend) tailFSNotifyCurrentSharesLocked() {
shares, err := b.tailFSGetSharesLocked()
if err != nil {
b.logf("error notifying current tailfs shares: %v", err)
return
@@ -197,15 +201,16 @@ func (b *LocalBackend) tailfsNotifyCurrentSharesLocked() {
go b.tailfsNotifyShares(shareNameMap(shares))
}
// TailfsGetShares() returns the current set of shares from the state store.
func (b *LocalBackend) TailfsGetShares() (map[string]*tailfs.Share, error) {
// TailFSGetShares returns the current set of shares from the state store,
// stored under ipn.StateKey("_tailfs-shares").
func (b *LocalBackend) TailFSGetShares() (map[string]*tailfs.Share, error) {
b.mu.Lock()
defer b.mu.Unlock()
return b.tailfsGetSharesLocked()
return b.tailFSGetSharesLocked()
}
func (b *LocalBackend) tailfsGetSharesLocked() (map[string]*tailfs.Share, error) {
func (b *LocalBackend) tailFSGetSharesLocked() (map[string]*tailfs.Share, error) {
data, err := b.store.ReadState(tailfsSharesStateKey)
if err != nil {
if errors.Is(err, ipn.ErrStateNotExist) {
@@ -223,27 +228,27 @@ func (b *LocalBackend) tailfsGetSharesLocked() (map[string]*tailfs.Share, error)
return shares, nil
}
// updateTailfsListenersLocked creates listeners on the local Tailfs port.
// updateTailFSListenersLocked creates listeners on the local TailFS port.
// This is needed to properly route local traffic when using kernel networking
// mode.
func (b *LocalBackend) updateTailfsListenersLocked() {
func (b *LocalBackend) updateTailFSListenersLocked() {
if b.netMap == nil {
return
}
addrs := b.netMap.GetAddresses()
oldListeners := b.tailfsListeners
oldListeners := b.tailFSListeners
newListeners := make(map[netip.AddrPort]*localListener, addrs.Len())
for i := range addrs.LenIter() {
if fs, ok := b.sys.TailfsForLocal.GetOK(); ok {
addrPort := netip.AddrPortFrom(addrs.At(i).Addr(), TailfsLocalPort)
if sl, ok := b.tailfsListeners[addrPort]; ok {
if fs, ok := b.sys.TailFSForLocal.GetOK(); ok {
addrPort := netip.AddrPortFrom(addrs.At(i).Addr(), TailFSLocalPort)
if sl, ok := b.tailFSListeners[addrPort]; ok {
newListeners[addrPort] = sl
delete(oldListeners, addrPort)
continue // already listening
}
sl := b.newTailfsListener(context.Background(), fs, addrPort, b.logf)
sl := b.newTailFSListener(context.Background(), fs, addrPort, b.logf)
newListeners[addrPort] = sl
go sl.Run()
}
@@ -255,9 +260,9 @@ func (b *LocalBackend) updateTailfsListenersLocked() {
}
}
// newTailfsListener returns a listener for local connections to a tailfs
// newTailFSListener returns a listener for local connections to a tailfs
// WebDAV FileSystem.
func (b *LocalBackend) newTailfsListener(ctx context.Context, fs *tailfs.FileSystemForLocal, ap netip.AddrPort, logf logger.Logf) *localListener {
func (b *LocalBackend) newTailFSListener(ctx context.Context, fs tailfs.FileSystemForLocal, ap netip.AddrPort, logf logger.Logf) *localListener {
ctx, cancel := context.WithCancel(ctx)
return &localListener{
b: b,
@@ -273,10 +278,10 @@ func (b *LocalBackend) newTailfsListener(ctx context.Context, fs *tailfs.FileSys
}
}
// updateTailfsPeersLocked sets all applicable peers from the netmap as tailfs
// updateTailFSPeersLocked sets all applicable peers from the netmap as tailfs
// remotes.
func (b *LocalBackend) updateTailfsPeersLocked(nm *netmap.NetworkMap) {
fs, ok := b.sys.TailfsForLocal.GetOK()
func (b *LocalBackend) updateTailFSPeersLocked(nm *netmap.NetworkMap) {
fs, ok := b.sys.TailFSForLocal.GetOK()
if !ok {
return
}
@@ -284,7 +289,7 @@ func (b *LocalBackend) updateTailfsPeersLocked(nm *netmap.NetworkMap) {
tailfsRemotes := make([]*tailfs.Remote, 0, len(nm.Peers))
for _, p := range nm.Peers {
peerID := p.ID()
url := fmt.Sprintf("%s/%s", peerAPIBase(nm, p), tailfsPrefix[1:])
url := fmt.Sprintf("%s/%s", peerAPIBase(nm, p), tailFSPrefix[1:])
tailfsRemotes = append(tailfsRemotes, &tailfs.Remote{
Name: p.DisplayName(false),
URL: url,
@@ -314,5 +319,5 @@ func (b *LocalBackend) updateTailfsPeersLocked(nm *netmap.NetworkMap) {
},
})
}
fs.SetRemotes(b.netMap.Domain, tailfsRemotes, &tailfsTransport{b: b})
fs.SetRemotes(b.netMap.Domain, tailfsRemotes, &tailFSTransport{b: b})
}

View File

@@ -110,7 +110,7 @@ var handler = map[string]localAPIHandler{
"serve-config": (*Handler).serveServeConfig,
"set-dns": (*Handler).serveSetDNS,
"set-expiry-sooner": (*Handler).serveSetExpirySooner,
"tailfs/fileserver-address": (*Handler).serveTailfsFileServerAddr,
"tailfs/fileserver-address": (*Handler).serveTailFSFileServerAddr,
"tailfs/shares": (*Handler).serveShares,
"start": (*Handler).serveStart,
"status": (*Handler).serveStatus,
@@ -2531,8 +2531,8 @@ func (h *Handler) serveUpdateProgress(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(ups)
}
// serveTailfsFileServerAddr handles updates of the tailfs file server address.
func (h *Handler) serveTailfsFileServerAddr(w http.ResponseWriter, r *http.Request) {
// serveTailFSFileServerAddr handles updates of the tailfs file server address.
func (h *Handler) serveTailFSFileServerAddr(w http.ResponseWriter, r *http.Request) {
if r.Method != "PUT" {
http.Error(w, "only PUT allowed", http.StatusMethodNotAllowed)
return
@@ -2544,13 +2544,13 @@ func (h *Handler) serveTailfsFileServerAddr(w http.ResponseWriter, r *http.Reque
return
}
h.b.TailfsSetFileServerAddr(string(b))
h.b.TailFSSetFileServerAddr(string(b))
w.WriteHeader(http.StatusCreated)
}
// serveShares handles the management of tailfs shares.
func (h *Handler) serveShares(w http.ResponseWriter, r *http.Request) {
if !h.b.TailfsSharingEnabled() {
if !h.b.TailFSSharingEnabled() {
http.Error(w, `tailfs sharing not enabled, please add the attribute "tailfs:share" to this node in your ACLs' "nodeAttrs" section`, http.StatusInternalServerError)
return
}
@@ -2581,7 +2581,7 @@ func (h *Handler) serveShares(w http.ResponseWriter, r *http.Request) {
}
share.As = username
}
err = h.b.TailfsAddShare(&share)
err = h.b.TailFSAddShare(&share)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -2594,7 +2594,7 @@ func (h *Handler) serveShares(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
err = h.b.TailfsRemoveShare(share.Name)
err = h.b.TailFSRemoveShare(share.Name)
if err != nil {
if os.IsNotExist(err) {
http.Error(w, "share not found", http.StatusNotFound)
@@ -2605,7 +2605,7 @@ func (h *Handler) serveShares(w http.ResponseWriter, r *http.Request) {
}
w.WriteHeader(http.StatusNoContent)
case "GET":
shares, err := h.b.TailfsGetShares()
shares, err := h.b.TailFSGetShares()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return