mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
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:
parent
79b547804b
commit
abab0d4197
@ -1418,25 +1418,25 @@ func (lc *LocalClient) CheckUpdate(ctx context.Context) (*tailcfg.ClientVersion,
|
|||||||
return &cv, nil
|
return &cv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TailfsSetFileServerAddr instructs Tailfs to use the server at addr to access
|
// TailFSSetFileServerAddr instructs TailFS to use the server at addr to access
|
||||||
// the filesystem. This is used on platforms like Windows and MacOS to let
|
// the filesystem. This is used on platforms like Windows and MacOS to let
|
||||||
// Tailfs know to use the file server running in the GUI app.
|
// TailFS know to use the file server running in the GUI app.
|
||||||
func (lc *LocalClient) TailfsSetFileServerAddr(ctx context.Context, addr string) error {
|
func (lc *LocalClient) TailFSSetFileServerAddr(ctx context.Context, addr string) error {
|
||||||
_, err := lc.send(ctx, "PUT", "/localapi/v0/tailfs/fileserver-address", http.StatusCreated, strings.NewReader(addr))
|
_, err := lc.send(ctx, "PUT", "/localapi/v0/tailfs/fileserver-address", http.StatusCreated, strings.NewReader(addr))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TailfsShareAdd adds the given share to the list of shares that Tailfs will
|
// TailFSShareAdd adds the given share to the list of shares that TailFS will
|
||||||
// serve to remote nodes. If a share with the same name already exists, the
|
// serve to remote nodes. If a share with the same name already exists, the
|
||||||
// existing share is replaced/updated.
|
// existing share is replaced/updated.
|
||||||
func (lc *LocalClient) TailfsShareAdd(ctx context.Context, share *tailfs.Share) error {
|
func (lc *LocalClient) TailFSShareAdd(ctx context.Context, share *tailfs.Share) error {
|
||||||
_, err := lc.send(ctx, "PUT", "/localapi/v0/tailfs/shares", http.StatusCreated, jsonBody(share))
|
_, err := lc.send(ctx, "PUT", "/localapi/v0/tailfs/shares", http.StatusCreated, jsonBody(share))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TailfsShareRemove removes the share with the given name from the list of
|
// TailFSShareRemove removes the share with the given name from the list of
|
||||||
// shares that Tailfs will serve to remote nodes.
|
// shares that TailFS will serve to remote nodes.
|
||||||
func (lc *LocalClient) TailfsShareRemove(ctx context.Context, name string) error {
|
func (lc *LocalClient) TailFSShareRemove(ctx context.Context, name string) error {
|
||||||
_, err := lc.send(
|
_, err := lc.send(
|
||||||
ctx,
|
ctx,
|
||||||
"DELETE",
|
"DELETE",
|
||||||
@ -1448,9 +1448,9 @@ func (lc *LocalClient) TailfsShareRemove(ctx context.Context, name string) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TailfsShareList returns the list of shares that Tailfs is currently serving
|
// TailFSShareList returns the list of shares that TailFS is currently serving
|
||||||
// to remote nodes.
|
// to remote nodes.
|
||||||
func (lc *LocalClient) TailfsShareList(ctx context.Context) (map[string]*tailfs.Share, error) {
|
func (lc *LocalClient) TailFSShareList(ctx context.Context) (map[string]*tailfs.Share, error) {
|
||||||
result, err := lc.get200(ctx, "/localapi/v0/tailfs/shares")
|
result, err := lc.get200(ctx, "/localapi/v0/tailfs/shares")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -5,7 +5,11 @@
|
|||||||
|
|
||||||
package tailscale
|
package tailscale
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"tailscale.com/tstest/deptest"
|
||||||
|
)
|
||||||
|
|
||||||
func TestGetServeConfigFromJSON(t *testing.T) {
|
func TestGetServeConfigFromJSON(t *testing.T) {
|
||||||
sc, err := getServeConfigFromJSON([]byte("null"))
|
sc, err := getServeConfigFromJSON([]byte("null"))
|
||||||
@ -25,3 +29,14 @@ func TestGetServeConfigFromJSON(t *testing.T) {
|
|||||||
t.Errorf("want non-nil TCP for object")
|
t.Errorf("want non-nil TCP for object")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeps(t *testing.T) {
|
||||||
|
deptest.DepChecker{
|
||||||
|
BadDeps: map[string]string{
|
||||||
|
// Make sure we don't again accidentally bring in a dependency on
|
||||||
|
// TailFS or its transitive dependencies
|
||||||
|
"tailscale.com/tailfs/tailfsimpl": "https://github.com/tailscale/tailscale/pull/10631",
|
||||||
|
"github.com/tailscale/gowebdav": "https://github.com/tailscale/tailscale/pull/10631",
|
||||||
|
},
|
||||||
|
}.Check(t)
|
||||||
|
}
|
||||||
|
@ -9,7 +9,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||||||
💣 github.com/cespare/xxhash/v2 from github.com/prometheus/client_golang/prometheus
|
💣 github.com/cespare/xxhash/v2 from github.com/prometheus/client_golang/prometheus
|
||||||
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
|
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
|
||||||
W 💣 github.com/dblohm7/wingoes from tailscale.com/util/winutil
|
W 💣 github.com/dblohm7/wingoes from tailscale.com/util/winutil
|
||||||
💣 github.com/djherbis/times from tailscale.com/tailfs
|
|
||||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||||
L github.com/google/nftables from tailscale.com/util/linuxfw
|
L github.com/google/nftables from tailscale.com/util/linuxfw
|
||||||
@ -20,7 +19,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||||||
L github.com/google/nftables/xt from github.com/google/nftables/expr+
|
L github.com/google/nftables/xt from github.com/google/nftables/expr+
|
||||||
github.com/google/uuid from tailscale.com/tsweb
|
github.com/google/uuid from tailscale.com/tsweb
|
||||||
github.com/hdevalence/ed25519consensus from tailscale.com/tka
|
github.com/hdevalence/ed25519consensus from tailscale.com/tka
|
||||||
github.com/jellydator/ttlcache/v3 from tailscale.com/tailfs/webdavfs
|
|
||||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
L github.com/josharian/native from github.com/mdlayher/netlink+
|
||||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
|
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
|
||||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||||
@ -43,10 +41,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||||||
W 💣 github.com/tailscale/go-winio/internal/socket from github.com/tailscale/go-winio
|
W 💣 github.com/tailscale/go-winio/internal/socket from github.com/tailscale/go-winio
|
||||||
W github.com/tailscale/go-winio/internal/stringbuffer from github.com/tailscale/go-winio/internal/fs
|
W github.com/tailscale/go-winio/internal/stringbuffer from github.com/tailscale/go-winio/internal/fs
|
||||||
W github.com/tailscale/go-winio/pkg/guid from github.com/tailscale/go-winio+
|
W github.com/tailscale/go-winio/pkg/guid from github.com/tailscale/go-winio+
|
||||||
github.com/tailscale/gowebdav from tailscale.com/tailfs/webdavfs
|
|
||||||
L 💣 github.com/tailscale/netlink from tailscale.com/util/linuxfw
|
L 💣 github.com/tailscale/netlink from tailscale.com/util/linuxfw
|
||||||
github.com/tailscale/xnet/webdav from tailscale.com/tailfs+
|
|
||||||
github.com/tailscale/xnet/webdav/internal/xml from github.com/tailscale/xnet/webdav
|
|
||||||
L 💣 github.com/vishvananda/netlink/nl from github.com/tailscale/netlink
|
L 💣 github.com/vishvananda/netlink/nl from github.com/tailscale/netlink
|
||||||
L github.com/vishvananda/netns from github.com/tailscale/netlink+
|
L github.com/vishvananda/netns from github.com/tailscale/netlink+
|
||||||
github.com/x448/float16 from github.com/fxamacker/cbor/v2
|
github.com/x448/float16 from github.com/fxamacker/cbor/v2
|
||||||
@ -115,13 +110,10 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+
|
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+
|
||||||
tailscale.com/net/wsconn from tailscale.com/cmd/derper+
|
tailscale.com/net/wsconn from tailscale.com/cmd/derper+
|
||||||
tailscale.com/paths from tailscale.com/client/tailscale
|
tailscale.com/paths from tailscale.com/client/tailscale
|
||||||
💣 tailscale.com/safesocket from tailscale.com/client/tailscale+
|
💣 tailscale.com/safesocket from tailscale.com/client/tailscale
|
||||||
tailscale.com/syncs from tailscale.com/cmd/derper+
|
tailscale.com/syncs from tailscale.com/cmd/derper+
|
||||||
tailscale.com/tailcfg from tailscale.com/client/tailscale+
|
tailscale.com/tailcfg from tailscale.com/client/tailscale+
|
||||||
tailscale.com/tailfs from tailscale.com/client/tailscale
|
tailscale.com/tailfs from tailscale.com/client/tailscale
|
||||||
tailscale.com/tailfs/compositefs from tailscale.com/tailfs
|
|
||||||
tailscale.com/tailfs/shared from tailscale.com/tailfs/compositefs+
|
|
||||||
tailscale.com/tailfs/webdavfs from tailscale.com/tailfs
|
|
||||||
tailscale.com/tka from tailscale.com/client/tailscale+
|
tailscale.com/tka from tailscale.com/client/tailscale+
|
||||||
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
||||||
tailscale.com/tstime from tailscale.com/derp+
|
tailscale.com/tstime from tailscale.com/derp+
|
||||||
@ -188,7 +180,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||||||
golang.org/x/net/proxy from tailscale.com/net/netns
|
golang.org/x/net/proxy from tailscale.com/net/netns
|
||||||
D golang.org/x/net/route from net+
|
D golang.org/x/net/route from net+
|
||||||
golang.org/x/sync/errgroup from github.com/mdlayher/socket+
|
golang.org/x/sync/errgroup from github.com/mdlayher/socket+
|
||||||
golang.org/x/sync/singleflight from github.com/jellydator/ttlcache/v3
|
|
||||||
golang.org/x/sys/cpu from github.com/josharian/native+
|
golang.org/x/sys/cpu from github.com/josharian/native+
|
||||||
LD golang.org/x/sys/unix from github.com/google/nftables+
|
LD golang.org/x/sys/unix from github.com/google/nftables+
|
||||||
W golang.org/x/sys/windows from github.com/dblohm7/wingoes+
|
W golang.org/x/sys/windows from github.com/dblohm7/wingoes+
|
||||||
@ -205,7 +196,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||||||
cmp from slices+
|
cmp from slices+
|
||||||
compress/flate from compress/gzip+
|
compress/flate from compress/gzip+
|
||||||
compress/gzip from google.golang.org/protobuf/internal/impl+
|
compress/gzip from google.golang.org/protobuf/internal/impl+
|
||||||
container/heap from github.com/jellydator/ttlcache/v3+
|
|
||||||
container/list from crypto/tls+
|
container/list from crypto/tls+
|
||||||
context from crypto/tls+
|
context from crypto/tls+
|
||||||
crypto from crypto/ecdh+
|
crypto from crypto/ecdh+
|
||||||
@ -239,7 +229,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||||||
encoding/hex from crypto/x509+
|
encoding/hex from crypto/x509+
|
||||||
encoding/json from expvar+
|
encoding/json from expvar+
|
||||||
encoding/pem from crypto/tls+
|
encoding/pem from crypto/tls+
|
||||||
encoding/xml from github.com/tailscale/gowebdav+
|
|
||||||
errors from bufio+
|
errors from bufio+
|
||||||
expvar from github.com/prometheus/client_golang/prometheus+
|
expvar from github.com/prometheus/client_golang/prometheus+
|
||||||
flag from tailscale.com/cmd/derper+
|
flag from tailscale.com/cmd/derper+
|
||||||
|
@ -63,7 +63,7 @@ func runShareAdd(ctx context.Context, args []string) error {
|
|||||||
|
|
||||||
name, path := args[0], args[1]
|
name, path := args[0], args[1]
|
||||||
|
|
||||||
err := localClient.TailfsShareAdd(ctx, &tailfs.Share{
|
err := localClient.TailFSShareAdd(ctx, &tailfs.Share{
|
||||||
Name: name,
|
Name: name,
|
||||||
Path: path,
|
Path: path,
|
||||||
})
|
})
|
||||||
@ -80,7 +80,7 @@ func runShareRemove(ctx context.Context, args []string) error {
|
|||||||
}
|
}
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
|
||||||
err := localClient.TailfsShareRemove(ctx, name)
|
err := localClient.TailFSShareRemove(ctx, name)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Printf("Removed share %q\n", name)
|
fmt.Printf("Removed share %q\n", name)
|
||||||
}
|
}
|
||||||
@ -93,7 +93,7 @@ func runShareList(ctx context.Context, args []string) error {
|
|||||||
return fmt.Errorf("usage: tailscale %v", shareListUsage)
|
return fmt.Errorf("usage: tailscale %v", shareListUsage)
|
||||||
}
|
}
|
||||||
|
|
||||||
sharesMap, err := localClient.TailfsShareList(ctx)
|
sharesMap, err := localClient.TailFSShareList(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
L github.com/coreos/go-systemd/v22/dbus from tailscale.com/clientupdate
|
L github.com/coreos/go-systemd/v22/dbus from tailscale.com/clientupdate
|
||||||
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/pe+
|
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/pe+
|
||||||
W 💣 github.com/dblohm7/wingoes/pe from tailscale.com/util/winutil/authenticode
|
W 💣 github.com/dblohm7/wingoes/pe from tailscale.com/util/winutil/authenticode
|
||||||
💣 github.com/djherbis/times from tailscale.com/tailfs
|
|
||||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||||
L 💣 github.com/godbus/dbus/v5 from github.com/coreos/go-systemd/v22/dbus
|
L 💣 github.com/godbus/dbus/v5 from github.com/coreos/go-systemd/v22/dbus
|
||||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||||
@ -23,7 +22,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
github.com/gorilla/csrf from tailscale.com/client/web
|
github.com/gorilla/csrf from tailscale.com/client/web
|
||||||
github.com/gorilla/securecookie from github.com/gorilla/csrf
|
github.com/gorilla/securecookie from github.com/gorilla/csrf
|
||||||
github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+
|
github.com/hdevalence/ed25519consensus from tailscale.com/clientupdate/distsign+
|
||||||
github.com/jellydator/ttlcache/v3 from tailscale.com/tailfs/webdavfs
|
|
||||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
L github.com/josharian/native from github.com/mdlayher/netlink+
|
||||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
|
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
|
||||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||||
@ -53,11 +51,8 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
github.com/tailscale/goupnp/scpd from github.com/tailscale/goupnp
|
github.com/tailscale/goupnp/scpd from github.com/tailscale/goupnp
|
||||||
github.com/tailscale/goupnp/soap from github.com/tailscale/goupnp+
|
github.com/tailscale/goupnp/soap from github.com/tailscale/goupnp+
|
||||||
github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp
|
github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp
|
||||||
github.com/tailscale/gowebdav from tailscale.com/tailfs/webdavfs
|
|
||||||
L 💣 github.com/tailscale/netlink from tailscale.com/util/linuxfw
|
L 💣 github.com/tailscale/netlink from tailscale.com/util/linuxfw
|
||||||
github.com/tailscale/web-client-prebuilt from tailscale.com/client/web
|
github.com/tailscale/web-client-prebuilt from tailscale.com/client/web
|
||||||
github.com/tailscale/xnet/webdav from tailscale.com/tailfs+
|
|
||||||
github.com/tailscale/xnet/webdav/internal/xml from github.com/tailscale/xnet/webdav
|
|
||||||
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
|
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
|
||||||
github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli
|
github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli
|
||||||
L 💣 github.com/vishvananda/netlink/nl from github.com/tailscale/netlink
|
L 💣 github.com/vishvananda/netlink/nl from github.com/tailscale/netlink
|
||||||
@ -123,10 +118,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
💣 tailscale.com/safesocket from tailscale.com/client/tailscale+
|
💣 tailscale.com/safesocket from tailscale.com/client/tailscale+
|
||||||
tailscale.com/syncs from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/syncs from tailscale.com/cmd/tailscale/cli+
|
||||||
tailscale.com/tailcfg from tailscale.com/client/tailscale+
|
tailscale.com/tailcfg from tailscale.com/client/tailscale+
|
||||||
tailscale.com/tailfs from tailscale.com/client/tailscale+
|
tailscale.com/tailfs from tailscale.com/cmd/tailscale/cli+
|
||||||
tailscale.com/tailfs/compositefs from tailscale.com/tailfs
|
|
||||||
tailscale.com/tailfs/shared from tailscale.com/tailfs/compositefs+
|
|
||||||
tailscale.com/tailfs/webdavfs from tailscale.com/tailfs
|
|
||||||
tailscale.com/tka from tailscale.com/client/tailscale+
|
tailscale.com/tka from tailscale.com/client/tailscale+
|
||||||
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
||||||
tailscale.com/tstime from tailscale.com/control/controlhttp+
|
tailscale.com/tstime from tailscale.com/control/controlhttp+
|
||||||
@ -205,7 +197,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
golang.org/x/oauth2/clientcredentials from tailscale.com/cmd/tailscale/cli
|
golang.org/x/oauth2/clientcredentials from tailscale.com/cmd/tailscale/cli
|
||||||
golang.org/x/oauth2/internal from golang.org/x/oauth2+
|
golang.org/x/oauth2/internal from golang.org/x/oauth2+
|
||||||
golang.org/x/sync/errgroup from github.com/mdlayher/socket+
|
golang.org/x/sync/errgroup from github.com/mdlayher/socket+
|
||||||
golang.org/x/sync/singleflight from github.com/jellydator/ttlcache/v3
|
|
||||||
golang.org/x/sys/cpu from github.com/josharian/native+
|
golang.org/x/sys/cpu from github.com/josharian/native+
|
||||||
LD golang.org/x/sys/unix from github.com/google/nftables+
|
LD golang.org/x/sys/unix from github.com/google/nftables+
|
||||||
W golang.org/x/sys/windows from github.com/dblohm7/wingoes+
|
W golang.org/x/sys/windows from github.com/dblohm7/wingoes+
|
||||||
@ -224,7 +215,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
compress/flate from compress/gzip+
|
compress/flate from compress/gzip+
|
||||||
compress/gzip from net/http+
|
compress/gzip from net/http+
|
||||||
compress/zlib from debug/pe+
|
compress/zlib from debug/pe+
|
||||||
container/heap from github.com/jellydator/ttlcache/v3+
|
|
||||||
container/list from crypto/tls+
|
container/list from crypto/tls+
|
||||||
context from crypto/tls+
|
context from crypto/tls+
|
||||||
crypto from crypto/ecdh+
|
crypto from crypto/ecdh+
|
||||||
@ -285,7 +275,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
math/big from crypto/dsa+
|
math/big from crypto/dsa+
|
||||||
math/bits from compress/flate+
|
math/bits from compress/flate+
|
||||||
math/rand from github.com/mdlayher/netlink+
|
math/rand from github.com/mdlayher/netlink+
|
||||||
mime from github.com/tailscale/xnet/webdav+
|
mime from golang.org/x/oauth2/internal+
|
||||||
mime/multipart from net/http
|
mime/multipart from net/http
|
||||||
mime/quotedprintable from mime/multipart
|
mime/quotedprintable from mime/multipart
|
||||||
net from crypto/tls+
|
net from crypto/tls+
|
||||||
@ -306,7 +296,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
reflect from archive/tar+
|
reflect from archive/tar+
|
||||||
regexp from github.com/coreos/go-iptables/iptables+
|
regexp from github.com/coreos/go-iptables/iptables+
|
||||||
regexp/syntax from regexp
|
regexp/syntax from regexp
|
||||||
runtime/debug from golang.org/x/sync/singleflight+
|
runtime/debug from nhooyr.io/websocket/internal/xsync+
|
||||||
runtime/trace from testing
|
runtime/trace from testing
|
||||||
slices from tailscale.com/client/web+
|
slices from tailscale.com/client/web+
|
||||||
sort from archive/tar+
|
sort from archive/tar+
|
||||||
|
@ -87,7 +87,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
W github.com/dblohm7/wingoes/internal from github.com/dblohm7/wingoes/com
|
W github.com/dblohm7/wingoes/internal from github.com/dblohm7/wingoes/com
|
||||||
W 💣 github.com/dblohm7/wingoes/pe from tailscale.com/util/osdiag+
|
W 💣 github.com/dblohm7/wingoes/pe from tailscale.com/util/osdiag+
|
||||||
LW 💣 github.com/digitalocean/go-smbios/smbios from tailscale.com/posture
|
LW 💣 github.com/digitalocean/go-smbios/smbios from tailscale.com/posture
|
||||||
💣 github.com/djherbis/times from tailscale.com/tailfs
|
💣 github.com/djherbis/times from tailscale.com/tailfs/tailfsimpl
|
||||||
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
github.com/fxamacker/cbor/v2 from tailscale.com/tka
|
||||||
W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+
|
W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+
|
||||||
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
|
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
|
||||||
@ -109,7 +109,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
L github.com/insomniacslk/dhcp/iana from github.com/insomniacslk/dhcp/dhcpv4
|
L github.com/insomniacslk/dhcp/iana from github.com/insomniacslk/dhcp/dhcpv4
|
||||||
L github.com/insomniacslk/dhcp/interfaces from github.com/insomniacslk/dhcp/dhcpv4
|
L github.com/insomniacslk/dhcp/interfaces from github.com/insomniacslk/dhcp/dhcpv4
|
||||||
L github.com/insomniacslk/dhcp/rfc1035label from github.com/insomniacslk/dhcp/dhcpv4
|
L github.com/insomniacslk/dhcp/rfc1035label from github.com/insomniacslk/dhcp/dhcpv4
|
||||||
github.com/jellydator/ttlcache/v3 from tailscale.com/tailfs/webdavfs
|
github.com/jellydator/ttlcache/v3 from tailscale.com/tailfs/tailfsimpl/webdavfs
|
||||||
L github.com/jmespath/go-jmespath from github.com/aws/aws-sdk-go-v2/service/ssm
|
L github.com/jmespath/go-jmespath from github.com/aws/aws-sdk-go-v2/service/ssm
|
||||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
L github.com/josharian/native from github.com/mdlayher/netlink+
|
||||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
|
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/net/interfaces+
|
||||||
@ -155,7 +155,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
github.com/tailscale/goupnp/scpd from github.com/tailscale/goupnp
|
github.com/tailscale/goupnp/scpd from github.com/tailscale/goupnp
|
||||||
github.com/tailscale/goupnp/soap from github.com/tailscale/goupnp+
|
github.com/tailscale/goupnp/soap from github.com/tailscale/goupnp+
|
||||||
github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp
|
github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp
|
||||||
github.com/tailscale/gowebdav from tailscale.com/tailfs/webdavfs
|
github.com/tailscale/gowebdav from tailscale.com/tailfs/tailfsimpl/webdavfs
|
||||||
github.com/tailscale/hujson from tailscale.com/ipn/conffile
|
github.com/tailscale/hujson from tailscale.com/ipn/conffile
|
||||||
L 💣 github.com/tailscale/netlink from tailscale.com/net/routetable+
|
L 💣 github.com/tailscale/netlink from tailscale.com/net/routetable+
|
||||||
github.com/tailscale/web-client-prebuilt from tailscale.com/client/web
|
github.com/tailscale/web-client-prebuilt from tailscale.com/client/web
|
||||||
@ -169,7 +169,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+
|
github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+
|
||||||
github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device
|
github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device
|
||||||
💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+
|
💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+
|
||||||
github.com/tailscale/xnet/webdav from tailscale.com/tailfs+
|
github.com/tailscale/xnet/webdav from tailscale.com/tailfs/tailfsimpl+
|
||||||
github.com/tailscale/xnet/webdav/internal/xml from github.com/tailscale/xnet/webdav
|
github.com/tailscale/xnet/webdav/internal/xml from github.com/tailscale/xnet/webdav
|
||||||
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
|
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
|
||||||
LD github.com/u-root/u-root/pkg/termios from tailscale.com/ssh/tailssh
|
LD github.com/u-root/u-root/pkg/termios from tailscale.com/ssh/tailssh
|
||||||
@ -321,9 +321,10 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
tailscale.com/tailcfg from tailscale.com/client/tailscale+
|
tailscale.com/tailcfg from tailscale.com/client/tailscale+
|
||||||
tailscale.com/taildrop from tailscale.com/ipn/ipnlocal+
|
tailscale.com/taildrop from tailscale.com/ipn/ipnlocal+
|
||||||
tailscale.com/tailfs from tailscale.com/client/tailscale+
|
tailscale.com/tailfs from tailscale.com/client/tailscale+
|
||||||
tailscale.com/tailfs/compositefs from tailscale.com/tailfs
|
tailscale.com/tailfs/tailfsimpl from tailscale.com/cmd/tailscaled
|
||||||
tailscale.com/tailfs/shared from tailscale.com/tailfs/compositefs+
|
tailscale.com/tailfs/tailfsimpl/compositefs from tailscale.com/tailfs/tailfsimpl
|
||||||
tailscale.com/tailfs/webdavfs from tailscale.com/tailfs
|
tailscale.com/tailfs/tailfsimpl/shared from tailscale.com/tailfs/tailfsimpl+
|
||||||
|
tailscale.com/tailfs/tailfsimpl/webdavfs from tailscale.com/tailfs/tailfsimpl
|
||||||
💣 tailscale.com/tempfork/device from tailscale.com/net/tstun/table
|
💣 tailscale.com/tempfork/device from tailscale.com/net/tstun/table
|
||||||
LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh
|
LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh
|
||||||
tailscale.com/tempfork/heap from tailscale.com/wgengine/magicsock
|
tailscale.com/tempfork/heap from tailscale.com/wgengine/magicsock
|
||||||
|
@ -52,7 +52,7 @@
|
|||||||
"tailscale.com/paths"
|
"tailscale.com/paths"
|
||||||
"tailscale.com/safesocket"
|
"tailscale.com/safesocket"
|
||||||
"tailscale.com/syncs"
|
"tailscale.com/syncs"
|
||||||
"tailscale.com/tailfs"
|
"tailscale.com/tailfs/tailfsimpl"
|
||||||
"tailscale.com/tsd"
|
"tailscale.com/tsd"
|
||||||
"tailscale.com/tsweb/varz"
|
"tailscale.com/tsweb/varz"
|
||||||
"tailscale.com/types/flagtype"
|
"tailscale.com/types/flagtype"
|
||||||
@ -141,7 +141,7 @@ func defaultPort() uint16 {
|
|||||||
"uninstall-system-daemon": uninstallSystemDaemon,
|
"uninstall-system-daemon": uninstallSystemDaemon,
|
||||||
"debug": debugModeFunc,
|
"debug": debugModeFunc,
|
||||||
"be-child": beChild,
|
"be-child": beChild,
|
||||||
"serve-tailfs": serveTailfs,
|
"serve-tailfs": serveTailFS,
|
||||||
}
|
}
|
||||||
|
|
||||||
var beCLI func() // non-nil if CLI is linked in
|
var beCLI func() // non-nil if CLI is linked in
|
||||||
@ -403,6 +403,8 @@ func run() (err error) {
|
|||||||
debugMux = newDebugMux()
|
debugMux = newDebugMux()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sys.Set(tailfsimpl.NewFileSystemForRemote(logf))
|
||||||
|
|
||||||
return startIPNServer(context.Background(), logf, pol.PublicID, sys)
|
return startIPNServer(context.Background(), logf, pol.PublicID, sys)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -625,12 +627,12 @@ func handleSubnetsInNetstack() bool {
|
|||||||
|
|
||||||
func tryEngine(logf logger.Logf, sys *tsd.System, name string) (onlyNetstack bool, err error) {
|
func tryEngine(logf logger.Logf, sys *tsd.System, name string) (onlyNetstack bool, err error) {
|
||||||
conf := wgengine.Config{
|
conf := wgengine.Config{
|
||||||
ListenPort: args.port,
|
ListenPort: args.port,
|
||||||
NetMon: sys.NetMon.Get(),
|
NetMon: sys.NetMon.Get(),
|
||||||
Dialer: sys.Dialer.Get(),
|
Dialer: sys.Dialer.Get(),
|
||||||
SetSubsystem: sys.Set,
|
SetSubsystem: sys.Set,
|
||||||
ControlKnobs: sys.ControlKnobs(),
|
ControlKnobs: sys.ControlKnobs(),
|
||||||
EnableTailfs: true,
|
TailFSForLocal: tailfsimpl.NewFileSystemForLocal(logf),
|
||||||
}
|
}
|
||||||
|
|
||||||
onlyNetstack = name == "userspace-networking"
|
onlyNetstack = name == "userspace-networking"
|
||||||
@ -733,7 +735,7 @@ func runDebugServer(mux *http.ServeMux, addr string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newNetstack(logf logger.Logf, sys *tsd.System) (*netstack.Impl, error) {
|
func newNetstack(logf logger.Logf, sys *tsd.System) (*netstack.Impl, error) {
|
||||||
tfs, _ := sys.TailfsForLocal.GetOK()
|
tfs, _ := sys.TailFSForLocal.GetOK()
|
||||||
ret, err := netstack.Create(logf,
|
ret, err := netstack.Create(logf,
|
||||||
sys.Tun.Get(),
|
sys.Tun.Get(),
|
||||||
sys.Engine.Get(),
|
sys.Engine.Get(),
|
||||||
@ -809,21 +811,21 @@ func beChild(args []string) error {
|
|||||||
return f(args[1:])
|
return f(args[1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveTailfs serves one or more tailfs on localhost using the WebDAV
|
// serveTailFS serves one or more tailfs on localhost using the WebDAV
|
||||||
// protocol. On UNIX and MacOS tailscaled environment, tailfs spawns child
|
// protocol. On UNIX and MacOS tailscaled environment, tailfs spawns child
|
||||||
// tailscaled processes in serve-tailfs mode in order to access the fliesystem
|
// tailscaled processes in serve-tailfs mode in order to access the fliesystem
|
||||||
// as specific (usually unprivileged) users.
|
// as specific (usually unprivileged) users.
|
||||||
//
|
//
|
||||||
// serveTailfs prints the address on which it's listening to stdout so that the
|
// serveTailFS prints the address on which it's listening to stdout so that the
|
||||||
// parent process knows where to connect to.
|
// parent process knows where to connect to.
|
||||||
func serveTailfs(args []string) error {
|
func serveTailFS(args []string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return errors.New("missing shares")
|
return errors.New("missing shares")
|
||||||
}
|
}
|
||||||
if len(args)%2 != 0 {
|
if len(args)%2 != 0 {
|
||||||
return errors.New("need <sharename> <path> pairs")
|
return errors.New("need <sharename> <path> pairs")
|
||||||
}
|
}
|
||||||
s, err := tailfs.NewFileServer()
|
s, err := tailfsimpl.NewFileServer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to start tailfs FileServer: %v", err)
|
return fmt.Errorf("unable to start tailfs FileServer: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ type EngineStatus struct {
|
|||||||
NotifyInitialNetMap // if set, the first Notify message (sent immediately) will contain the current NetMap
|
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
|
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
|
// Notify is a communication from a backend (e.g. tailscaled) to a frontend
|
||||||
@ -122,11 +122,12 @@ type Notify struct {
|
|||||||
// is available.
|
// is available.
|
||||||
ClientVersion *tailcfg.ClientVersion `json:",omitempty"`
|
ClientVersion *tailcfg.ClientVersion `json:",omitempty"`
|
||||||
|
|
||||||
// Full set of current TailfsShares that we're publishing as name->path.
|
// TailFSShares tracks the full set of current TailFSShares that we're
|
||||||
// Some client applications, like the MacOS and Windows clients, will
|
// publishing as name->path. Some client applications, like the MacOS and
|
||||||
// listen for updates to this and handle serving these shares under the
|
// Windows clients, will listen for updates to this and handle serving
|
||||||
// identity of the unprivileged user that is running the application.
|
// these shares under the identity of the unprivileged user that is running
|
||||||
TailfsShares map[string]string `json:",omitempty"`
|
// the application.
|
||||||
|
TailFSShares map[string]string `json:",omitempty"`
|
||||||
|
|
||||||
// type is mirrored in xcode/Shared/IPN.swift
|
// type is mirrored in xcode/Shared/IPN.swift
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,6 @@
|
|||||||
"tailscale.com/syncs"
|
"tailscale.com/syncs"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/taildrop"
|
"tailscale.com/taildrop"
|
||||||
"tailscale.com/tailfs"
|
|
||||||
"tailscale.com/tka"
|
"tailscale.com/tka"
|
||||||
"tailscale.com/tsd"
|
"tailscale.com/tsd"
|
||||||
"tailscale.com/tstime"
|
"tailscale.com/tstime"
|
||||||
@ -288,8 +287,7 @@ type LocalBackend struct {
|
|||||||
serveListeners map[netip.AddrPort]*localListener // listeners for local serve traffic
|
serveListeners map[netip.AddrPort]*localListener // listeners for local serve traffic
|
||||||
serveProxyHandlers sync.Map // string (HTTPHandler.Proxy) => *reverseProxy
|
serveProxyHandlers sync.Map // string (HTTPHandler.Proxy) => *reverseProxy
|
||||||
|
|
||||||
tailfsListeners map[netip.AddrPort]*localListener // listeners for local tailfs traffic
|
tailFSListeners map[netip.AddrPort]*localListener // listeners for local tailfs traffic
|
||||||
tailfsForRemote *tailfs.FileSystemForRemote
|
|
||||||
|
|
||||||
// statusLock must be held before calling statusChanged.Wait() or
|
// statusLock must be held before calling statusChanged.Wait() or
|
||||||
// statusChanged.Broadcast().
|
// statusChanged.Broadcast().
|
||||||
@ -432,13 +430,15 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize Tailfs shares from saved state
|
// initialize TailFS shares from saved state
|
||||||
b.mu.Lock()
|
fs, ok := b.sys.TailFSForRemote.GetOK()
|
||||||
b.tailfsForRemote = tailfs.NewFileSystemForRemote(logf)
|
if !ok {
|
||||||
shares, err := b.tailfsGetSharesLocked()
|
b.mu.Lock()
|
||||||
b.mu.Unlock()
|
shares, err := b.tailFSGetSharesLocked()
|
||||||
if err == nil && len(shares) > 0 {
|
b.mu.Unlock()
|
||||||
b.tailfsForRemote.SetShares(shares)
|
if err == nil && len(shares) > 0 {
|
||||||
|
fs.SetShares(shares)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return b, nil
|
return b, nil
|
||||||
@ -2268,7 +2268,7 @@ func (b *LocalBackend) WatchNotifications(ctx context.Context, mask ipn.NotifyWa
|
|||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
b.activeWatchSessions.Add(sessionID)
|
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 {
|
if mask&initialBits != 0 {
|
||||||
ini = &ipn.Notify{Version: version.Long()}
|
ini = &ipn.Notify{Version: version.Long()}
|
||||||
if mask&ipn.NotifyInitialState != 0 {
|
if mask&ipn.NotifyInitialState != 0 {
|
||||||
@ -2284,14 +2284,14 @@ func (b *LocalBackend) WatchNotifications(ctx context.Context, mask ipn.NotifyWa
|
|||||||
if mask&ipn.NotifyInitialNetMap != 0 {
|
if mask&ipn.NotifyInitialNetMap != 0 {
|
||||||
ini.NetMap = b.netMap
|
ini.NetMap = b.netMap
|
||||||
}
|
}
|
||||||
if mask&ipn.NotifyInitialTailfsShares != 0 && b.tailfsSharingEnabledLocked() {
|
if mask&ipn.NotifyInitialTailFSShares != 0 && b.tailFSSharingEnabledLocked() {
|
||||||
shares, err := b.tailfsGetSharesLocked()
|
shares, err := b.tailFSGetSharesLocked()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.logf("unable to notify initial tailfs shares: %v", err)
|
b.logf("unable to notify initial tailfs shares: %v", err)
|
||||||
} else {
|
} else {
|
||||||
ini.TailfsShares = make(map[string]string, len(shares))
|
ini.TailFSShares = make(map[string]string, len(shares))
|
||||||
for _, share := range 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() {
|
if dst.Port() == webClientPort && b.ShouldRunWebClient() {
|
||||||
return b.handleWebClientConn, opts
|
return b.handleWebClientConn, opts
|
||||||
}
|
}
|
||||||
if dst.Port() == TailfsLocalPort {
|
if dst.Port() == TailFSLocalPort {
|
||||||
fs, ok := b.sys.TailfsForLocal.GetOK()
|
fs, ok := b.sys.TailFSForLocal.GetOK()
|
||||||
if ok {
|
if ok {
|
||||||
return func(conn net.Conn) error {
|
return func(conn net.Conn) error {
|
||||||
return fs.HandleConn(conn, conn.RemoteAddr())
|
return fs.HandleConn(conn, conn.RemoteAddr())
|
||||||
@ -4642,9 +4642,9 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.tailfsSharingEnabledLocked() {
|
if b.tailFSSharingEnabledLocked() {
|
||||||
b.updateTailfsPeersLocked(nm)
|
b.updateTailFSPeersLocked(nm)
|
||||||
b.tailfsNotifyCurrentSharesLocked()
|
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
|
// b.Dialer().PeerAPITransport() for each round trip and imposes a short
|
||||||
// dial timeout to avoid hanging on connecting to offline/unreachable hosts.
|
// dial timeout to avoid hanging on connecting to offline/unreachable hosts.
|
||||||
type tailfsTransport struct {
|
type tailFSTransport struct {
|
||||||
b *LocalBackend
|
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
|
// dialTimeout is fairly aggressive to avoid hangs on contacting offline or
|
||||||
// unreachable hosts.
|
// unreachable hosts.
|
||||||
dialTimeout := 1 * time.Second // TODO(oxtoacart): tune this
|
dialTimeout := 1 * time.Second // TODO(oxtoacart): tune this
|
||||||
@ -4767,7 +4767,7 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !b.sys.IsNetstack() {
|
if !b.sys.IsNetstack() {
|
||||||
b.updateTailfsListenersLocked()
|
b.updateTailFSListenersLocked()
|
||||||
}
|
}
|
||||||
|
|
||||||
b.reloadServeConfigLocked(prefs)
|
b.reloadServeConfigLocked(prefs)
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tailfsPrefix = "/v0/tailfs"
|
tailFSPrefix = "/v0/tailfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
var initListenConfig func(*net.ListenConfig, netip.Addr, *interfaces.State, string) error
|
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)
|
h.handleDNSQuery(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(r.URL.Path, tailfsPrefix) {
|
if strings.HasPrefix(r.URL.Path, tailFSPrefix) {
|
||||||
h.handleServeTailfs(w, r)
|
h.handleServeTailFS(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
@ -1103,14 +1103,14 @@ func writePrettyDNSReply(w io.Writer, res []byte) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *peerAPIHandler) handleServeTailfs(w http.ResponseWriter, r *http.Request) {
|
func (h *peerAPIHandler) handleServeTailFS(w http.ResponseWriter, r *http.Request) {
|
||||||
if !h.ps.b.TailfsSharingEnabled() {
|
if !h.ps.b.TailFSSharingEnabled() {
|
||||||
http.Error(w, "tailfs not enabled", http.StatusNotFound)
|
http.Error(w, "tailfs not enabled", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
capsMap := h.peerCaps()
|
capsMap := h.peerCaps()
|
||||||
tailfsCaps, ok := capsMap[tailcfg.PeerCapabilityTailfs]
|
tailfsCaps, ok := capsMap[tailcfg.PeerCapabilityTailFS]
|
||||||
if !ok {
|
if !ok {
|
||||||
http.Error(w, "tailfs not permitted", http.StatusForbidden)
|
http.Error(w, "tailfs not permitted", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
@ -1127,14 +1127,12 @@ func (h *peerAPIHandler) handleServeTailfs(w http.ResponseWriter, r *http.Reques
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.ps.b.mu.Lock()
|
fs, ok := h.ps.b.sys.TailFSForRemote.GetOK()
|
||||||
fs := h.ps.b.tailfsForRemote
|
if !ok {
|
||||||
h.ps.b.mu.Unlock()
|
|
||||||
if fs == nil {
|
|
||||||
http.Error(w, "tailfs not enabled", http.StatusNotFound)
|
http.Error(w, "tailfs not enabled", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.URL.Path = strings.TrimPrefix(r.URL.Path, tailfsPrefix)
|
r.URL.Path = strings.TrimPrefix(r.URL.Path, tailFSPrefix)
|
||||||
fs.ServeHTTPWithPerms(p, w, r)
|
fs.ServeHTTPWithPerms(p, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,9 +24,9 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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.
|
// connections on quad 100.
|
||||||
TailfsLocalPort = 8080
|
TailFSLocalPort = 8080
|
||||||
|
|
||||||
tailfsSharesStateKey = ipn.StateKey("_tailfs-shares")
|
tailfsSharesStateKey = ipn.StateKey("_tailfs-shares")
|
||||||
)
|
)
|
||||||
@ -36,27 +36,25 @@
|
|||||||
errInvalidShareName = errors.New("Share names may only contain the letters a-z, underscore _, parentheses (), or spaces")
|
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
|
// enabled. This is currently based on checking for the tailfs:share node
|
||||||
// attribute.
|
// attribute.
|
||||||
func (b *LocalBackend) TailfsSharingEnabled() bool {
|
func (b *LocalBackend) TailFSSharingEnabled() bool {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
return b.tailfsSharingEnabledLocked()
|
return b.tailFSSharingEnabledLocked()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *LocalBackend) tailfsSharingEnabledLocked() bool {
|
func (b *LocalBackend) tailFSSharingEnabledLocked() bool {
|
||||||
return b.netMap != nil && b.netMap.SelfNode.HasCap(tailcfg.NodeAttrsTailfsSharingEnabled)
|
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
|
// to the tailfs.FileServer that's exposing local files as an unprivileged
|
||||||
// user.
|
// user.
|
||||||
func (b *LocalBackend) TailfsSetFileServerAddr(addr string) error {
|
func (b *LocalBackend) TailFSSetFileServerAddr(addr string) error {
|
||||||
b.mu.Lock()
|
fs, ok := b.sys.TailFSForRemote.GetOK()
|
||||||
fs := b.tailfsForRemote
|
if !ok {
|
||||||
b.mu.Unlock()
|
|
||||||
if fs == nil {
|
|
||||||
return errors.New("tailfs not enabled")
|
return errors.New("tailfs not enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,11 +62,11 @@ func (b *LocalBackend) TailfsSetFileServerAddr(addr string) error {
|
|||||||
return nil
|
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.
|
// replaces the existing share if one with the same name already exists.
|
||||||
// To avoid potential incompatibilities across file systems, share names are
|
// To avoid potential incompatibilities across file systems, share names are
|
||||||
// limited to alphanumeric characters and the underscore _.
|
// 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
|
var err error
|
||||||
share.Name, err = normalizeShareName(share.Name)
|
share.Name, err = normalizeShareName(share.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -104,11 +102,12 @@ func normalizeShareName(name string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *LocalBackend) tailfsAddShareLocked(share *tailfs.Share) (map[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")
|
return nil, errors.New("tailfs not enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
shares, err := b.tailfsGetSharesLocked()
|
shares, err := b.tailFSGetSharesLocked()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -121,17 +120,21 @@ func (b *LocalBackend) tailfsAddShareLocked(share *tailfs.Share) (map[string]str
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("write state: %w", err)
|
return nil, fmt.Errorf("write state: %w", err)
|
||||||
}
|
}
|
||||||
b.tailfsForRemote.SetShares(shares)
|
fs.SetShares(shares)
|
||||||
|
|
||||||
return shareNameMap(shares), nil
|
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.
|
// 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
|
// Force all share names to lowercase to avoid potential incompatibilities
|
||||||
// with clients that don't support case-sensitive filenames.
|
// 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()
|
b.mu.Lock()
|
||||||
shares, err := b.tailfsRemoveShareLocked(name)
|
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) {
|
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")
|
return nil, errors.New("tailfs not enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
shares, err := b.tailfsGetSharesLocked()
|
shares, err := b.tailFSGetSharesLocked()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -166,7 +170,7 @@ func (b *LocalBackend) tailfsRemoveShareLocked(name string) (map[string]string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("write state: %w", err)
|
return nil, fmt.Errorf("write state: %w", err)
|
||||||
}
|
}
|
||||||
b.tailfsForRemote.SetShares(shares)
|
fs.SetShares(shares)
|
||||||
|
|
||||||
return shareNameMap(shares), nil
|
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)
|
// tailfsNotifyShares notifies IPN bus listeners (e.g. Mac Application process)
|
||||||
// about the latest set of shares, supplied as a map of name -> directory.
|
// about the latest set of shares, supplied as a map of name -> directory.
|
||||||
func (b *LocalBackend) tailfsNotifyShares(shares map[string]string) {
|
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
|
// tailFSNotifyCurrentSharesLocked sends an ipn.Notify with the current set of
|
||||||
// tailfs shares.
|
// TailFS shares.
|
||||||
func (b *LocalBackend) tailfsNotifyCurrentSharesLocked() {
|
func (b *LocalBackend) tailFSNotifyCurrentSharesLocked() {
|
||||||
shares, err := b.tailfsGetSharesLocked()
|
shares, err := b.tailFSGetSharesLocked()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.logf("error notifying current tailfs shares: %v", err)
|
b.logf("error notifying current tailfs shares: %v", err)
|
||||||
return
|
return
|
||||||
@ -197,15 +201,16 @@ func (b *LocalBackend) tailfsNotifyCurrentSharesLocked() {
|
|||||||
go b.tailfsNotifyShares(shareNameMap(shares))
|
go b.tailfsNotifyShares(shareNameMap(shares))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TailfsGetShares() returns the current set of shares from the state store.
|
// TailFSGetShares returns the current set of shares from the state store,
|
||||||
func (b *LocalBackend) TailfsGetShares() (map[string]*tailfs.Share, error) {
|
// stored under ipn.StateKey("_tailfs-shares").
|
||||||
|
func (b *LocalBackend) TailFSGetShares() (map[string]*tailfs.Share, error) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
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)
|
data, err := b.store.ReadState(tailfsSharesStateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ipn.ErrStateNotExist) {
|
if errors.Is(err, ipn.ErrStateNotExist) {
|
||||||
@ -223,27 +228,27 @@ func (b *LocalBackend) tailfsGetSharesLocked() (map[string]*tailfs.Share, error)
|
|||||||
return shares, nil
|
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
|
// This is needed to properly route local traffic when using kernel networking
|
||||||
// mode.
|
// mode.
|
||||||
func (b *LocalBackend) updateTailfsListenersLocked() {
|
func (b *LocalBackend) updateTailFSListenersLocked() {
|
||||||
if b.netMap == nil {
|
if b.netMap == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
addrs := b.netMap.GetAddresses()
|
addrs := b.netMap.GetAddresses()
|
||||||
oldListeners := b.tailfsListeners
|
oldListeners := b.tailFSListeners
|
||||||
newListeners := make(map[netip.AddrPort]*localListener, addrs.Len())
|
newListeners := make(map[netip.AddrPort]*localListener, addrs.Len())
|
||||||
for i := range addrs.LenIter() {
|
for i := range addrs.LenIter() {
|
||||||
if fs, ok := b.sys.TailfsForLocal.GetOK(); ok {
|
if fs, ok := b.sys.TailFSForLocal.GetOK(); ok {
|
||||||
addrPort := netip.AddrPortFrom(addrs.At(i).Addr(), TailfsLocalPort)
|
addrPort := netip.AddrPortFrom(addrs.At(i).Addr(), TailFSLocalPort)
|
||||||
if sl, ok := b.tailfsListeners[addrPort]; ok {
|
if sl, ok := b.tailFSListeners[addrPort]; ok {
|
||||||
newListeners[addrPort] = sl
|
newListeners[addrPort] = sl
|
||||||
delete(oldListeners, addrPort)
|
delete(oldListeners, addrPort)
|
||||||
continue // already listening
|
continue // already listening
|
||||||
}
|
}
|
||||||
|
|
||||||
sl := b.newTailfsListener(context.Background(), fs, addrPort, b.logf)
|
sl := b.newTailFSListener(context.Background(), fs, addrPort, b.logf)
|
||||||
newListeners[addrPort] = sl
|
newListeners[addrPort] = sl
|
||||||
go sl.Run()
|
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.
|
// 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)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
return &localListener{
|
return &localListener{
|
||||||
b: b,
|
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.
|
// remotes.
|
||||||
func (b *LocalBackend) updateTailfsPeersLocked(nm *netmap.NetworkMap) {
|
func (b *LocalBackend) updateTailFSPeersLocked(nm *netmap.NetworkMap) {
|
||||||
fs, ok := b.sys.TailfsForLocal.GetOK()
|
fs, ok := b.sys.TailFSForLocal.GetOK()
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -284,7 +289,7 @@ func (b *LocalBackend) updateTailfsPeersLocked(nm *netmap.NetworkMap) {
|
|||||||
tailfsRemotes := make([]*tailfs.Remote, 0, len(nm.Peers))
|
tailfsRemotes := make([]*tailfs.Remote, 0, len(nm.Peers))
|
||||||
for _, p := range nm.Peers {
|
for _, p := range nm.Peers {
|
||||||
peerID := p.ID()
|
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{
|
tailfsRemotes = append(tailfsRemotes, &tailfs.Remote{
|
||||||
Name: p.DisplayName(false),
|
Name: p.DisplayName(false),
|
||||||
URL: url,
|
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})
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@
|
|||||||
"serve-config": (*Handler).serveServeConfig,
|
"serve-config": (*Handler).serveServeConfig,
|
||||||
"set-dns": (*Handler).serveSetDNS,
|
"set-dns": (*Handler).serveSetDNS,
|
||||||
"set-expiry-sooner": (*Handler).serveSetExpirySooner,
|
"set-expiry-sooner": (*Handler).serveSetExpirySooner,
|
||||||
"tailfs/fileserver-address": (*Handler).serveTailfsFileServerAddr,
|
"tailfs/fileserver-address": (*Handler).serveTailFSFileServerAddr,
|
||||||
"tailfs/shares": (*Handler).serveShares,
|
"tailfs/shares": (*Handler).serveShares,
|
||||||
"start": (*Handler).serveStart,
|
"start": (*Handler).serveStart,
|
||||||
"status": (*Handler).serveStatus,
|
"status": (*Handler).serveStatus,
|
||||||
@ -2531,8 +2531,8 @@ func (h *Handler) serveUpdateProgress(w http.ResponseWriter, r *http.Request) {
|
|||||||
json.NewEncoder(w).Encode(ups)
|
json.NewEncoder(w).Encode(ups)
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveTailfsFileServerAddr handles updates of the tailfs file server address.
|
// serveTailFSFileServerAddr handles updates of the tailfs file server address.
|
||||||
func (h *Handler) serveTailfsFileServerAddr(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) serveTailFSFileServerAddr(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != "PUT" {
|
if r.Method != "PUT" {
|
||||||
http.Error(w, "only PUT allowed", http.StatusMethodNotAllowed)
|
http.Error(w, "only PUT allowed", http.StatusMethodNotAllowed)
|
||||||
return
|
return
|
||||||
@ -2544,13 +2544,13 @@ func (h *Handler) serveTailfsFileServerAddr(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.b.TailfsSetFileServerAddr(string(b))
|
h.b.TailFSSetFileServerAddr(string(b))
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveShares handles the management of tailfs shares.
|
// serveShares handles the management of tailfs shares.
|
||||||
func (h *Handler) serveShares(w http.ResponseWriter, r *http.Request) {
|
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)
|
http.Error(w, `tailfs sharing not enabled, please add the attribute "tailfs:share" to this node in your ACLs' "nodeAttrs" section`, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2581,7 +2581,7 @@ func (h *Handler) serveShares(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
share.As = username
|
share.As = username
|
||||||
}
|
}
|
||||||
err = h.b.TailfsAddShare(&share)
|
err = h.b.TailFSAddShare(&share)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@ -2594,7 +2594,7 @@ func (h *Handler) serveShares(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = h.b.TailfsRemoveShare(share.Name)
|
err = h.b.TailFSRemoveShare(share.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
http.Error(w, "share not found", http.StatusNotFound)
|
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)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
case "GET":
|
case "GET":
|
||||||
shares, err := h.b.TailfsGetShares()
|
shares, err := h.b.TailFSGetShares()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -1345,8 +1345,8 @@ type CapGrant struct {
|
|||||||
// PeerCapabilityWebUI grants the ability for a peer to edit features from the
|
// PeerCapabilityWebUI grants the ability for a peer to edit features from the
|
||||||
// device Web UI.
|
// device Web UI.
|
||||||
PeerCapabilityWebUI PeerCapability = "tailscale.com/cap/webui"
|
PeerCapabilityWebUI PeerCapability = "tailscale.com/cap/webui"
|
||||||
// PeerCapabilityTailfs grants the ability for a peer to access tailfs shares.
|
// PeerCapabilityTailFS grants the ability for a peer to access tailfs shares.
|
||||||
PeerCapabilityTailfs PeerCapability = "tailscale.com/cap/tailfs"
|
PeerCapabilityTailFS PeerCapability = "tailscale.com/cap/tailfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NodeCapMap is a map of capabilities to their optional values. It is valid for
|
// NodeCapMap is a map of capabilities to their optional values. It is valid for
|
||||||
@ -2211,8 +2211,8 @@ type Oauth2Token struct {
|
|||||||
// tail end of an active direct connection in magicsock.
|
// tail end of an active direct connection in magicsock.
|
||||||
NodeAttrProbeUDPLifetime NodeCapability = "probe-udp-lifetime"
|
NodeAttrProbeUDPLifetime NodeCapability = "probe-udp-lifetime"
|
||||||
|
|
||||||
// NodeAttrsTailfsSharingEnabled enables sharing via Tailfs.
|
// NodeAttrsTailFSSharingEnabled enables sharing via TailFS.
|
||||||
NodeAttrsTailfsSharingEnabled NodeCapability = "tailfs:share"
|
NodeAttrsTailFSSharingEnabled NodeCapability = "tailfs:share"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetDNSRequest is a request to add a DNS record.
|
// SetDNSRequest is a request to add a DNS record.
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
. "tailscale.com/tailcfg"
|
. "tailscale.com/tailcfg"
|
||||||
|
"tailscale.com/tstest/deptest"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
"tailscale.com/types/opt"
|
"tailscale.com/types/opt"
|
||||||
"tailscale.com/types/ptr"
|
"tailscale.com/types/ptr"
|
||||||
@ -842,3 +843,14 @@ type rule struct {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeps(t *testing.T) {
|
||||||
|
deptest.DepChecker{
|
||||||
|
BadDeps: map[string]string{
|
||||||
|
// Make sure we don't again accidentally bring in a dependency on
|
||||||
|
// TailFS or its transitive dependencies
|
||||||
|
"tailscale.com/tailfs/tailfsimpl": "https://github.com/tailscale/tailscale/pull/10631",
|
||||||
|
"github.com/tailscale/gowebdav": "https://github.com/tailscale/tailscale/pull/10631",
|
||||||
|
},
|
||||||
|
}.Check(t)
|
||||||
|
}
|
||||||
|
@ -1,99 +1,37 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// Package tailfs provides a filesystem that allows sharing folders between
|
||||||
|
// Tailscale nodes using WebDAV. The actual implementation of the core TailFS
|
||||||
|
// functionality lives in package tailfsimpl. These packages are separated to
|
||||||
|
// allow users of tailfs to refer to the interfaces without having a hard
|
||||||
|
// dependency on tailfs, so that programs which don't actually use tailfs can
|
||||||
|
// avoid its transitive dependencies.
|
||||||
package tailfs
|
package tailfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/tailscale/xnet/webdav"
|
|
||||||
"tailscale.com/tailfs/compositefs"
|
|
||||||
"tailscale.com/tailfs/webdavfs"
|
|
||||||
"tailscale.com/types/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Remote represents a remote Tailfs node.
|
// Remote represents a remote TailFS node.
|
||||||
type Remote struct {
|
type Remote struct {
|
||||||
Name string
|
Name string
|
||||||
URL string
|
URL string
|
||||||
Available func() bool
|
Available func() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFileSystemForLocal starts serving a filesystem for local clients.
|
// FileSystemForLocal is the TailFS filesystem exposed to local clients. It
|
||||||
// Inbound connections must be handed to HandleConn.
|
// provides a unified WebDAV interface to remote TailFS shares on other nodes.
|
||||||
func NewFileSystemForLocal(logf logger.Logf) *FileSystemForLocal {
|
type FileSystemForLocal interface {
|
||||||
if logf == nil {
|
// HandleConn handles connections from local WebDAV clients
|
||||||
logf = log.Printf
|
HandleConn(conn net.Conn, remoteAddr net.Addr) error
|
||||||
}
|
|
||||||
fs := &FileSystemForLocal{
|
|
||||||
logf: logf,
|
|
||||||
cfs: compositefs.New(compositefs.Options{Logf: logf}),
|
|
||||||
listener: newConnListener(),
|
|
||||||
}
|
|
||||||
fs.startServing()
|
|
||||||
return fs
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileSystemForLocal is the Tailfs filesystem exposed to local clients. It
|
// SetRemotes sets the complete set of remotes on the given tailnet domain
|
||||||
// provides a unified WebDAV interface to remote Tailfs shares on other nodes.
|
// using a map of name -> url. If transport is specified, that transport
|
||||||
type FileSystemForLocal struct {
|
// will be used to connect to these remotes.
|
||||||
logf logger.Logf
|
SetRemotes(domain string, remotes []*Remote, transport http.RoundTripper)
|
||||||
cfs *compositefs.CompositeFileSystem
|
|
||||||
listener *connListener
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileSystemForLocal) startServing() {
|
// Close() stops serving the WebDAV content
|
||||||
hs := &http.Server{
|
Close() error
|
||||||
Handler: &webdav.Handler{
|
|
||||||
FileSystem: s.cfs,
|
|
||||||
LockSystem: webdav.NewMemLS(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
err := hs.Serve(s.listener)
|
|
||||||
if err != nil {
|
|
||||||
// TODO(oxtoacart): should we panic or something different here?
|
|
||||||
log.Printf("serve: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleConn handles connections from local WebDAV clients
|
|
||||||
func (s *FileSystemForLocal) HandleConn(conn net.Conn, remoteAddr net.Addr) error {
|
|
||||||
return s.listener.HandleConn(conn, remoteAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRemotes sets the complete set of remotes on the given tailnet domain
|
|
||||||
// using a map of name -> url. If transport is specified, that transport
|
|
||||||
// will be used to connect to these remotes.
|
|
||||||
func (s *FileSystemForLocal) SetRemotes(domain string, remotes []*Remote, transport http.RoundTripper) {
|
|
||||||
children := make([]*compositefs.Child, 0, len(remotes))
|
|
||||||
for _, remote := range remotes {
|
|
||||||
opts := webdavfs.Options{
|
|
||||||
URL: remote.URL,
|
|
||||||
Transport: transport,
|
|
||||||
StatCacheTTL: statCacheTTL,
|
|
||||||
Logf: s.logf,
|
|
||||||
}
|
|
||||||
children = append(children, &compositefs.Child{
|
|
||||||
Name: remote.Name,
|
|
||||||
FS: webdavfs.New(opts),
|
|
||||||
Available: remote.Available,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
domainChild, found := s.cfs.GetChild(domain)
|
|
||||||
if !found {
|
|
||||||
domainChild = compositefs.New(compositefs.Options{Logf: s.logf})
|
|
||||||
s.cfs.SetChildren(&compositefs.Child{Name: domain, FS: domainChild})
|
|
||||||
}
|
|
||||||
domainChild.(*compositefs.CompositeFileSystem).SetChildren(children...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close() stops serving the WebDAV content
|
|
||||||
func (s *FileSystemForLocal) Close() error {
|
|
||||||
s.cfs.Close()
|
|
||||||
return s.listener.Close()
|
|
||||||
}
|
}
|
||||||
|
389
tailfs/remote.go
389
tailfs/remote.go
@ -4,386 +4,57 @@
|
|||||||
package tailfs
|
package tailfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/tailscale/xnet/webdav"
|
|
||||||
"tailscale.com/safesocket"
|
|
||||||
"tailscale.com/tailfs/compositefs"
|
|
||||||
"tailscale.com/tailfs/shared"
|
|
||||||
"tailscale.com/tailfs/webdavfs"
|
|
||||||
"tailscale.com/types/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
disallowShareAs = false
|
// DisallowShareAs forcibly disables sharing as a specific user, only used
|
||||||
|
// for testing.
|
||||||
|
DisallowShareAs = false
|
||||||
)
|
)
|
||||||
|
|
||||||
// AllowShareAs reports whether sharing files as a specific user is allowed.
|
// AllowShareAs reports whether sharing files as a specific user is allowed.
|
||||||
func AllowShareAs() bool {
|
func AllowShareAs() bool {
|
||||||
return !disallowShareAs && doAllowShareAs()
|
return !DisallowShareAs && doAllowShareAs()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Share represents a folder that's shared with remote Tailfs nodes.
|
// Share configures a folder to be shared through TailFS.
|
||||||
type Share struct {
|
type Share struct {
|
||||||
// Name is how this share appears on remote nodes.
|
// Name is how this share appears on remote nodes.
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
// Path is the path to the directory on this machine that's being shared.
|
// Path is the path to the directory on this machine that's being shared.
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
|
|
||||||
// As is the UNIX or Windows username of the local account used for this
|
// As is the UNIX or Windows username of the local account used for this
|
||||||
// share. File read/write permissions are enforced based on this username.
|
// share. File read/write permissions are enforced based on this username.
|
||||||
|
// Can be left blank to use the default value of "whoever is running the
|
||||||
|
// Tailscale GUI".
|
||||||
As string `json:"who"`
|
As string `json:"who"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileSystemForRemote(logf logger.Logf) *FileSystemForRemote {
|
// FileSystemForRemote is the TailFS filesystem exposed to remote nodes. It
|
||||||
if logf == nil {
|
|
||||||
logf = log.Printf
|
|
||||||
}
|
|
||||||
fs := &FileSystemForRemote{
|
|
||||||
logf: logf,
|
|
||||||
lockSystem: webdav.NewMemLS(),
|
|
||||||
fileSystems: make(map[string]webdav.FileSystem),
|
|
||||||
userServers: make(map[string]*userServer),
|
|
||||||
}
|
|
||||||
return fs
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileSystemForRemote is the Tailfs filesystem exposed to remote nodes. It
|
|
||||||
// provides a unified WebDAV interface to local directories that have been
|
// provides a unified WebDAV interface to local directories that have been
|
||||||
// shared.
|
// shared.
|
||||||
type FileSystemForRemote struct {
|
type FileSystemForRemote interface {
|
||||||
logf logger.Logf
|
// SetFileServerAddr sets the address of the file server to which we
|
||||||
lockSystem webdav.LockSystem
|
// should proxy. This is used on platforms like Windows and MacOS
|
||||||
|
// sandboxed where we can't spawn user-specific sub-processes and instead
|
||||||
|
// rely on the UI application that's already running as an unprivileged
|
||||||
|
// user to access the filesystem for us.
|
||||||
|
SetFileServerAddr(addr string)
|
||||||
|
|
||||||
// mu guards the below values. Acquire a write lock before updating any of
|
// SetShares sets the complete set of shares exposed by this node. If
|
||||||
// them, acquire a read lock before reading any of them.
|
// AllowShareAs() reports true, we will use one subprocess per user to
|
||||||
mu sync.RWMutex
|
// access the filesystem (see userServer). Otherwise, we will use the file
|
||||||
fileServerAddr string
|
// server configured via SetFileServerAddr.
|
||||||
shares map[string]*Share
|
SetShares(shares map[string]*Share)
|
||||||
fileSystems map[string]webdav.FileSystem
|
|
||||||
userServers map[string]*userServer
|
// ServeHTTPWithPerms behaves like the similar method from http.Handler but
|
||||||
}
|
// also accepts a Permissions map that captures the permissions of the
|
||||||
|
// connecting node.
|
||||||
// SetFileServerAddr sets the address of the file server to which we
|
ServeHTTPWithPerms(permissions Permissions, w http.ResponseWriter, r *http.Request)
|
||||||
// should proxy. This is used on platforms like Windows and MacOS
|
|
||||||
// sandboxed where we can't spawn user-specific sub-processes and instead
|
// Close() stops serving the WebDAV content
|
||||||
// rely on the UI application that's already running as an unprivileged
|
Close() error
|
||||||
// user to access the filesystem for us.
|
|
||||||
func (s *FileSystemForRemote) SetFileServerAddr(addr string) {
|
|
||||||
s.mu.Lock()
|
|
||||||
s.fileServerAddr = addr
|
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetShares sets the complete set of shares exposed by this node. If
|
|
||||||
// AllowShareAs() reports true, we will use one subprocess per user to
|
|
||||||
// access the filesystem (see userServer). Otherwise, we will use the file
|
|
||||||
// server configured via SetFileServerAddr.
|
|
||||||
func (s *FileSystemForRemote) SetShares(shares map[string]*Share) {
|
|
||||||
userServers := make(map[string]*userServer)
|
|
||||||
if AllowShareAs() {
|
|
||||||
// set up per-user server
|
|
||||||
for _, share := range shares {
|
|
||||||
p, found := userServers[share.As]
|
|
||||||
if !found {
|
|
||||||
p = &userServer{
|
|
||||||
logf: s.logf,
|
|
||||||
}
|
|
||||||
userServers[share.As] = p
|
|
||||||
}
|
|
||||||
p.shares = append(p.shares, share)
|
|
||||||
}
|
|
||||||
for _, p := range userServers {
|
|
||||||
go p.runLoop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileSystems := make(map[string]webdav.FileSystem, len(shares))
|
|
||||||
for _, share := range shares {
|
|
||||||
fileSystems[share.Name] = s.buildWebDAVFS(share)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mu.Lock()
|
|
||||||
s.shares = shares
|
|
||||||
oldFileSystems := s.fileSystems
|
|
||||||
oldUserServers := s.userServers
|
|
||||||
s.fileSystems = fileSystems
|
|
||||||
s.userServers = userServers
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
s.stopUserServers(oldUserServers)
|
|
||||||
s.closeFileSystems(oldFileSystems)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileSystemForRemote) buildWebDAVFS(share *Share) webdav.FileSystem {
|
|
||||||
return webdavfs.New(webdavfs.Options{
|
|
||||||
Logf: s.logf,
|
|
||||||
URL: fmt.Sprintf("http://%v/%v", hex.EncodeToString([]byte(share.Name)), share.Name),
|
|
||||||
Transport: &http.Transport{
|
|
||||||
Dial: func(_, shareAddr string) (net.Conn, error) {
|
|
||||||
shareNameHex, _, err := net.SplitHostPort(shareAddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to parse share address %v: %w", shareAddr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We had to encode the share name in hex to make sure it's a valid hostname
|
|
||||||
shareNameBytes, err := hex.DecodeString(shareNameHex)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode share name from host %v: %v", shareNameHex, err)
|
|
||||||
}
|
|
||||||
shareName := string(shareNameBytes)
|
|
||||||
|
|
||||||
s.mu.RLock()
|
|
||||||
share, shareFound := s.shares[shareName]
|
|
||||||
userServers := s.userServers
|
|
||||||
fileServerAddr := s.fileServerAddr
|
|
||||||
s.mu.RUnlock()
|
|
||||||
|
|
||||||
if !shareFound {
|
|
||||||
return nil, fmt.Errorf("unknown share %v", shareName)
|
|
||||||
}
|
|
||||||
|
|
||||||
var addr string
|
|
||||||
if !AllowShareAs() {
|
|
||||||
addr = fileServerAddr
|
|
||||||
} else {
|
|
||||||
userServer, found := userServers[share.As]
|
|
||||||
if found {
|
|
||||||
userServer.mu.RLock()
|
|
||||||
addr = userServer.addr
|
|
||||||
userServer.mu.RUnlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if addr == "" {
|
|
||||||
return nil, fmt.Errorf("unable to determine address for share %v", shareName)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = netip.ParseAddrPort(addr)
|
|
||||||
if err == nil {
|
|
||||||
// this is a regular network address, dial normally
|
|
||||||
return net.Dial("tcp", addr)
|
|
||||||
}
|
|
||||||
// assume this is a safesocket address
|
|
||||||
return safesocket.Connect(addr)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
StatRoot: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTPWithPerms behaves like the similar method from http.Handler but
|
|
||||||
// also accepts a Permissions map that captures the permissions of the
|
|
||||||
// connecting node.
|
|
||||||
func (s *FileSystemForRemote) ServeHTTPWithPerms(permissions Permissions, w http.ResponseWriter, r *http.Request) {
|
|
||||||
isWrite := writeMethods[r.Method]
|
|
||||||
if isWrite {
|
|
||||||
share := shared.CleanAndSplit(r.URL.Path)[0]
|
|
||||||
switch permissions.For(share) {
|
|
||||||
case PermissionNone:
|
|
||||||
// If we have no permissions to this share, treat it as not found
|
|
||||||
// to avoid leaking any information about the share's existence.
|
|
||||||
http.Error(w, "not found", http.StatusNotFound)
|
|
||||||
return
|
|
||||||
case PermissionReadOnly:
|
|
||||||
http.Error(w, "permission denied", http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mu.RLock()
|
|
||||||
fileSystems := s.fileSystems
|
|
||||||
s.mu.RUnlock()
|
|
||||||
|
|
||||||
children := make([]*compositefs.Child, 0, len(fileSystems))
|
|
||||||
// filter out shares to which the connecting principal has no access
|
|
||||||
for name, fs := range fileSystems {
|
|
||||||
if permissions.For(name) == PermissionNone {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
children = append(children, &compositefs.Child{Name: name, FS: fs})
|
|
||||||
}
|
|
||||||
|
|
||||||
cfs := compositefs.New(
|
|
||||||
compositefs.Options{
|
|
||||||
Logf: s.logf,
|
|
||||||
StatChildren: true,
|
|
||||||
})
|
|
||||||
cfs.SetChildren(children...)
|
|
||||||
h := webdav.Handler{
|
|
||||||
FileSystem: cfs,
|
|
||||||
LockSystem: s.lockSystem,
|
|
||||||
}
|
|
||||||
h.ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileSystemForRemote) stopUserServers(userServers map[string]*userServer) {
|
|
||||||
for _, server := range userServers {
|
|
||||||
if err := server.Close(); err != nil {
|
|
||||||
s.logf("error closing tailfs user server: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FileSystemForRemote) closeFileSystems(fileSystems map[string]webdav.FileSystem) {
|
|
||||||
for _, fs := range fileSystems {
|
|
||||||
closer, ok := fs.(interface{ Close() error })
|
|
||||||
if ok {
|
|
||||||
if err := closer.Close(); err != nil {
|
|
||||||
s.logf("error closing tailfs filesystem: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close() stops serving the WebDAV content
|
|
||||||
func (s *FileSystemForRemote) Close() error {
|
|
||||||
s.mu.Lock()
|
|
||||||
userServers := s.userServers
|
|
||||||
fileSystems := s.fileSystems
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
s.stopUserServers(userServers)
|
|
||||||
s.closeFileSystems(fileSystems)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// userServer runs tailscaled serve-tailfs to serve webdav content for the
|
|
||||||
// given Shares. All Shares are assumed to have the same Share.As, and the
|
|
||||||
// content is served as that Share.As user.
|
|
||||||
type userServer struct {
|
|
||||||
logf logger.Logf
|
|
||||||
shares []*Share
|
|
||||||
|
|
||||||
// mu guards the below values. Acquire a write lock before updating any of
|
|
||||||
// them, acquire a read lock before reading any of them.
|
|
||||||
mu sync.RWMutex
|
|
||||||
cmd *exec.Cmd
|
|
||||||
addr string
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *userServer) Close() error {
|
|
||||||
s.mu.Lock()
|
|
||||||
cmd := s.cmd
|
|
||||||
s.closed = true
|
|
||||||
s.mu.Unlock()
|
|
||||||
if cmd != nil && cmd.Process != nil {
|
|
||||||
return cmd.Process.Kill()
|
|
||||||
}
|
|
||||||
// not running, that's okay
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *userServer) runLoop() {
|
|
||||||
executable, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
s.logf("can't find executable: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
maxSleepTime := 30 * time.Second
|
|
||||||
consecutiveFailures := float64(0)
|
|
||||||
var timeOfLastFailure time.Time
|
|
||||||
for {
|
|
||||||
s.mu.RLock()
|
|
||||||
closed := s.closed
|
|
||||||
s.mu.RUnlock()
|
|
||||||
if closed {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err := s.run(executable)
|
|
||||||
now := time.Now()
|
|
||||||
timeSinceLastFailure := now.Sub(timeOfLastFailure)
|
|
||||||
timeOfLastFailure = now
|
|
||||||
if timeSinceLastFailure < maxSleepTime {
|
|
||||||
consecutiveFailures++
|
|
||||||
} else {
|
|
||||||
consecutiveFailures = 1
|
|
||||||
}
|
|
||||||
sleepTime := time.Duration(math.Pow(2, consecutiveFailures)) * time.Millisecond
|
|
||||||
if sleepTime > maxSleepTime {
|
|
||||||
sleepTime = maxSleepTime
|
|
||||||
}
|
|
||||||
s.logf("user server % v stopped with error %v, will try again in %v", executable, err, sleepTime)
|
|
||||||
time.Sleep(sleepTime)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run runs the executable (tailscaled). This function only works on UNIX systems,
|
|
||||||
// but those are the only ones on which we use userServers anyway.
|
|
||||||
func (s *userServer) run(executable string) error {
|
|
||||||
// set up the command
|
|
||||||
args := []string{"serve-tailfs"}
|
|
||||||
for _, s := range s.shares {
|
|
||||||
args = append(args, s.Name, s.Path)
|
|
||||||
}
|
|
||||||
allArgs := []string{"-u", s.shares[0].As, executable}
|
|
||||||
allArgs = append(allArgs, args...)
|
|
||||||
cmd := exec.Command("sudo", allArgs...)
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("stdout pipe: %w", err)
|
|
||||||
}
|
|
||||||
defer stdout.Close()
|
|
||||||
stderr, err := cmd.StderrPipe()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("stderr pipe: %w", err)
|
|
||||||
}
|
|
||||||
defer stderr.Close()
|
|
||||||
|
|
||||||
err = cmd.Start()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("start: %w", err)
|
|
||||||
}
|
|
||||||
s.mu.Lock()
|
|
||||||
s.cmd = cmd
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
// read address
|
|
||||||
stdoutScanner := bufio.NewScanner(stdout)
|
|
||||||
stdoutScanner.Scan()
|
|
||||||
if stdoutScanner.Err() != nil {
|
|
||||||
return fmt.Errorf("read addr: %w", stdoutScanner.Err())
|
|
||||||
}
|
|
||||||
addr := stdoutScanner.Text()
|
|
||||||
// send the rest of stdout and stderr to logger to avoid blocking
|
|
||||||
go func() {
|
|
||||||
for stdoutScanner.Scan() {
|
|
||||||
s.logf("tailscaled serve-tailfs stdout: %v", stdoutScanner.Text())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
stderrScanner := bufio.NewScanner(stderr)
|
|
||||||
go func() {
|
|
||||||
for stderrScanner.Scan() {
|
|
||||||
s.logf("tailscaled serve-tailfs stderr: %v", stderrScanner.Text())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
s.mu.Lock()
|
|
||||||
s.addr = strings.TrimSpace(addr)
|
|
||||||
s.mu.Unlock()
|
|
||||||
return cmd.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
var writeMethods = map[string]bool{
|
|
||||||
"PUT": true,
|
|
||||||
"POST": true,
|
|
||||||
"COPY": true,
|
|
||||||
"LOCK": true,
|
|
||||||
"UNLOCK": true,
|
|
||||||
"MKCOL": true,
|
|
||||||
"MOVE": true,
|
|
||||||
"PROPPATCH": true,
|
|
||||||
}
|
}
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
// Package tailfs provides a filesystem that allows sharing folders between
|
|
||||||
// Tailscale nodes using WebDAV.
|
|
||||||
package tailfs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// statCacheTTL causes the local WebDAV proxy to cache file metadata to
|
|
||||||
// avoid excessive network roundtrips. This is similar to the
|
|
||||||
// DirectoryCacheLifetime setting of Windows' built-in SMB client,
|
|
||||||
// see https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-7/ff686200(v=ws.10)
|
|
||||||
statCacheTTL = 10 * time.Second
|
|
||||||
)
|
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
package tailfs
|
package tailfsimpl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
//go:build windows || darwin
|
//go:build windows || darwin
|
||||||
|
|
||||||
package tailfs
|
package tailfsimpl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
@ -15,7 +15,7 @@
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tailscale/xnet/webdav"
|
"github.com/tailscale/xnet/webdav"
|
||||||
"tailscale.com/tailfs/shared"
|
"tailscale.com/tailfs/tailfsimpl/shared"
|
||||||
"tailscale.com/tstime"
|
"tailscale.com/tstime"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
)
|
)
|
@ -15,7 +15,7 @@
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tailscale/xnet/webdav"
|
"github.com/tailscale/xnet/webdav"
|
||||||
"tailscale.com/tailfs/shared"
|
"tailscale.com/tailfs/tailfsimpl/shared"
|
||||||
"tailscale.com/tstest"
|
"tailscale.com/tstest"
|
||||||
)
|
)
|
||||||
|
|
@ -7,7 +7,7 @@
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"tailscale.com/tailfs/shared"
|
"tailscale.com/tailfs/tailfsimpl/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mkdir implements webdav.Filesystem. The root of this file system is
|
// Mkdir implements webdav.Filesystem. The root of this file system is
|
@ -9,7 +9,7 @@
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/tailscale/xnet/webdav"
|
"github.com/tailscale/xnet/webdav"
|
||||||
"tailscale.com/tailfs/shared"
|
"tailscale.com/tailfs/tailfsimpl/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OpenFile implements interface webdav.Filesystem.
|
// OpenFile implements interface webdav.Filesystem.
|
@ -7,7 +7,7 @@
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"tailscale.com/tailfs/shared"
|
"tailscale.com/tailfs/tailfsimpl/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RemoveAll implements webdav.File. The root of this file system is read-only,
|
// RemoveAll implements webdav.File. The root of this file system is read-only,
|
@ -7,7 +7,7 @@
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"tailscale.com/tailfs/shared"
|
"tailscale.com/tailfs/tailfsimpl/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Rename implements interface webdav.FileSystem. The root of this file system
|
// Rename implements interface webdav.FileSystem. The root of this file system
|
@ -7,7 +7,7 @@
|
|||||||
"context"
|
"context"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
|
||||||
"tailscale.com/tailfs/shared"
|
"tailscale.com/tailfs/tailfsimpl/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Stat implements webdav.FileSystem.
|
// Stat implements webdav.FileSystem.
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
package tailfs
|
package tailfsimpl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
package tailfs
|
package tailfsimpl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
package tailfs
|
package tailfsimpl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
@ -9,11 +9,11 @@
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/tailscale/xnet/webdav"
|
"github.com/tailscale/xnet/webdav"
|
||||||
"tailscale.com/tailfs/shared"
|
"tailscale.com/tailfs/tailfsimpl/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FileServer is a standalone WebDAV server that dynamically serves up shares.
|
// FileServer is a standalone WebDAV server that dynamically serves up shares.
|
||||||
// It's typically used in a separate process from the actual Tailfs server to
|
// It's typically used in a separate process from the actual TailFS server to
|
||||||
// serve up files as an unprivileged user.
|
// serve up files as an unprivileged user.
|
||||||
type FileServer struct {
|
type FileServer struct {
|
||||||
l net.Listener
|
l net.Listener
|
103
tailfs/tailfsimpl/local_impl.go
Normal file
103
tailfs/tailfsimpl/local_impl.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// Package tailfsimpl provides an implementation of package tailfs.
|
||||||
|
package tailfsimpl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tailscale/xnet/webdav"
|
||||||
|
"tailscale.com/tailfs"
|
||||||
|
"tailscale.com/tailfs/tailfsimpl/compositefs"
|
||||||
|
"tailscale.com/tailfs/tailfsimpl/webdavfs"
|
||||||
|
"tailscale.com/types/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// statCacheTTL causes the local WebDAV proxy to cache file metadata to
|
||||||
|
// avoid excessive network roundtrips. This is similar to the
|
||||||
|
// DirectoryCacheLifetime setting of Windows' built-in SMB client,
|
||||||
|
// see https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-7/ff686200(v=ws.10)
|
||||||
|
statCacheTTL = 10 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewFileSystemForLocal starts serving a filesystem for local clients.
|
||||||
|
// Inbound connections must be handed to HandleConn.
|
||||||
|
func NewFileSystemForLocal(logf logger.Logf) *FileSystemForLocal {
|
||||||
|
if logf == nil {
|
||||||
|
logf = log.Printf
|
||||||
|
}
|
||||||
|
fs := &FileSystemForLocal{
|
||||||
|
logf: logf,
|
||||||
|
cfs: compositefs.New(compositefs.Options{Logf: logf}),
|
||||||
|
listener: newConnListener(),
|
||||||
|
}
|
||||||
|
fs.startServing()
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileSystemForLocal is the TailFS filesystem exposed to local clients. It
|
||||||
|
// provides a unified WebDAV interface to remote TailFS shares on other nodes.
|
||||||
|
type FileSystemForLocal struct {
|
||||||
|
logf logger.Logf
|
||||||
|
cfs *compositefs.CompositeFileSystem
|
||||||
|
listener *connListener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FileSystemForLocal) startServing() {
|
||||||
|
hs := &http.Server{
|
||||||
|
Handler: &webdav.Handler{
|
||||||
|
FileSystem: s.cfs,
|
||||||
|
LockSystem: webdav.NewMemLS(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
err := hs.Serve(s.listener)
|
||||||
|
if err != nil {
|
||||||
|
// TODO(oxtoacart): should we panic or something different here?
|
||||||
|
log.Printf("serve: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleConn handles connections from local WebDAV clients
|
||||||
|
func (s *FileSystemForLocal) HandleConn(conn net.Conn, remoteAddr net.Addr) error {
|
||||||
|
return s.listener.HandleConn(conn, remoteAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRemotes sets the complete set of remotes on the given tailnet domain
|
||||||
|
// using a map of name -> url. If transport is specified, that transport
|
||||||
|
// will be used to connect to these remotes.
|
||||||
|
func (s *FileSystemForLocal) SetRemotes(domain string, remotes []*tailfs.Remote, transport http.RoundTripper) {
|
||||||
|
children := make([]*compositefs.Child, 0, len(remotes))
|
||||||
|
for _, remote := range remotes {
|
||||||
|
opts := webdavfs.Options{
|
||||||
|
URL: remote.URL,
|
||||||
|
Transport: transport,
|
||||||
|
StatCacheTTL: statCacheTTL,
|
||||||
|
Logf: s.logf,
|
||||||
|
}
|
||||||
|
children = append(children, &compositefs.Child{
|
||||||
|
Name: remote.Name,
|
||||||
|
FS: webdavfs.New(opts),
|
||||||
|
Available: remote.Available,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
domainChild, found := s.cfs.GetChild(domain)
|
||||||
|
if !found {
|
||||||
|
domainChild = compositefs.New(compositefs.Options{Logf: s.logf})
|
||||||
|
s.cfs.SetChildren(&compositefs.Child{Name: domain, FS: domainChild})
|
||||||
|
}
|
||||||
|
domainChild.(*compositefs.CompositeFileSystem).SetChildren(children...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close() stops serving the WebDAV content
|
||||||
|
func (s *FileSystemForLocal) Close() error {
|
||||||
|
s.cfs.Close()
|
||||||
|
return s.listener.Close()
|
||||||
|
}
|
359
tailfs/tailfsimpl/remote_impl.go
Normal file
359
tailfs/tailfsimpl/remote_impl.go
Normal file
@ -0,0 +1,359 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package tailfsimpl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tailscale/xnet/webdav"
|
||||||
|
"tailscale.com/safesocket"
|
||||||
|
"tailscale.com/tailfs"
|
||||||
|
"tailscale.com/tailfs/tailfsimpl/compositefs"
|
||||||
|
"tailscale.com/tailfs/tailfsimpl/shared"
|
||||||
|
"tailscale.com/tailfs/tailfsimpl/webdavfs"
|
||||||
|
"tailscale.com/types/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewFileSystemForRemote(logf logger.Logf) *FileSystemForRemote {
|
||||||
|
if logf == nil {
|
||||||
|
logf = log.Printf
|
||||||
|
}
|
||||||
|
fs := &FileSystemForRemote{
|
||||||
|
logf: logf,
|
||||||
|
lockSystem: webdav.NewMemLS(),
|
||||||
|
fileSystems: make(map[string]webdav.FileSystem),
|
||||||
|
userServers: make(map[string]*userServer),
|
||||||
|
}
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileSystemForRemote implements tailfs.FileSystemForRemote.
|
||||||
|
type FileSystemForRemote struct {
|
||||||
|
logf logger.Logf
|
||||||
|
lockSystem webdav.LockSystem
|
||||||
|
|
||||||
|
// mu guards the below values. Acquire a write lock before updating any of
|
||||||
|
// them, acquire a read lock before reading any of them.
|
||||||
|
mu sync.RWMutex
|
||||||
|
fileServerAddr string
|
||||||
|
shares map[string]*tailfs.Share
|
||||||
|
fileSystems map[string]webdav.FileSystem
|
||||||
|
userServers map[string]*userServer
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFileServerAddr implements tailfs.FileSystemForRemote.
|
||||||
|
func (s *FileSystemForRemote) SetFileServerAddr(addr string) {
|
||||||
|
s.mu.Lock()
|
||||||
|
s.fileServerAddr = addr
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetShares implements tailfs.FileSystemForRemote.
|
||||||
|
func (s *FileSystemForRemote) SetShares(shares map[string]*tailfs.Share) {
|
||||||
|
userServers := make(map[string]*userServer)
|
||||||
|
if tailfs.AllowShareAs() {
|
||||||
|
// set up per-user server
|
||||||
|
for _, share := range shares {
|
||||||
|
p, found := userServers[share.As]
|
||||||
|
if !found {
|
||||||
|
p = &userServer{
|
||||||
|
logf: s.logf,
|
||||||
|
}
|
||||||
|
userServers[share.As] = p
|
||||||
|
}
|
||||||
|
p.shares = append(p.shares, share)
|
||||||
|
}
|
||||||
|
for _, p := range userServers {
|
||||||
|
go p.runLoop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSystems := make(map[string]webdav.FileSystem, len(shares))
|
||||||
|
for _, share := range shares {
|
||||||
|
fileSystems[share.Name] = s.buildWebDAVFS(share)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.mu.Lock()
|
||||||
|
s.shares = shares
|
||||||
|
oldFileSystems := s.fileSystems
|
||||||
|
oldUserServers := s.userServers
|
||||||
|
s.fileSystems = fileSystems
|
||||||
|
s.userServers = userServers
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
s.stopUserServers(oldUserServers)
|
||||||
|
s.closeFileSystems(oldFileSystems)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FileSystemForRemote) buildWebDAVFS(share *tailfs.Share) webdav.FileSystem {
|
||||||
|
return webdavfs.New(webdavfs.Options{
|
||||||
|
Logf: s.logf,
|
||||||
|
URL: fmt.Sprintf("http://%v/%v", hex.EncodeToString([]byte(share.Name)), share.Name),
|
||||||
|
Transport: &http.Transport{
|
||||||
|
Dial: func(_, shareAddr string) (net.Conn, error) {
|
||||||
|
shareNameHex, _, err := net.SplitHostPort(shareAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to parse share address %v: %w", shareAddr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We had to encode the share name in hex to make sure it's a valid hostname
|
||||||
|
shareNameBytes, err := hex.DecodeString(shareNameHex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to decode share name from host %v: %v", shareNameHex, err)
|
||||||
|
}
|
||||||
|
shareName := string(shareNameBytes)
|
||||||
|
|
||||||
|
s.mu.RLock()
|
||||||
|
share, shareFound := s.shares[shareName]
|
||||||
|
userServers := s.userServers
|
||||||
|
fileServerAddr := s.fileServerAddr
|
||||||
|
s.mu.RUnlock()
|
||||||
|
|
||||||
|
if !shareFound {
|
||||||
|
return nil, fmt.Errorf("unknown share %v", shareName)
|
||||||
|
}
|
||||||
|
|
||||||
|
var addr string
|
||||||
|
if !tailfs.AllowShareAs() {
|
||||||
|
addr = fileServerAddr
|
||||||
|
} else {
|
||||||
|
userServer, found := userServers[share.As]
|
||||||
|
if found {
|
||||||
|
userServer.mu.RLock()
|
||||||
|
addr = userServer.addr
|
||||||
|
userServer.mu.RUnlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr == "" {
|
||||||
|
return nil, fmt.Errorf("unable to determine address for share %v", shareName)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = netip.ParseAddrPort(addr)
|
||||||
|
if err == nil {
|
||||||
|
// this is a regular network address, dial normally
|
||||||
|
return net.Dial("tcp", addr)
|
||||||
|
}
|
||||||
|
// assume this is a safesocket address
|
||||||
|
return safesocket.Connect(addr)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
StatRoot: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTPWithPerms implements tailfs.FileSystemForRemote.
|
||||||
|
func (s *FileSystemForRemote) ServeHTTPWithPerms(permissions tailfs.Permissions, w http.ResponseWriter, r *http.Request) {
|
||||||
|
isWrite := writeMethods[r.Method]
|
||||||
|
if isWrite {
|
||||||
|
share := shared.CleanAndSplit(r.URL.Path)[0]
|
||||||
|
switch permissions.For(share) {
|
||||||
|
case tailfs.PermissionNone:
|
||||||
|
// If we have no permissions to this share, treat it as not found
|
||||||
|
// to avoid leaking any information about the share's existence.
|
||||||
|
http.Error(w, "not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
case tailfs.PermissionReadOnly:
|
||||||
|
http.Error(w, "permission denied", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.mu.RLock()
|
||||||
|
fileSystems := s.fileSystems
|
||||||
|
s.mu.RUnlock()
|
||||||
|
|
||||||
|
children := make([]*compositefs.Child, 0, len(fileSystems))
|
||||||
|
// filter out shares to which the connecting principal has no access
|
||||||
|
for name, fs := range fileSystems {
|
||||||
|
if permissions.For(name) == tailfs.PermissionNone {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
children = append(children, &compositefs.Child{Name: name, FS: fs})
|
||||||
|
}
|
||||||
|
|
||||||
|
cfs := compositefs.New(
|
||||||
|
compositefs.Options{
|
||||||
|
Logf: s.logf,
|
||||||
|
StatChildren: true,
|
||||||
|
})
|
||||||
|
cfs.SetChildren(children...)
|
||||||
|
h := webdav.Handler{
|
||||||
|
FileSystem: cfs,
|
||||||
|
LockSystem: s.lockSystem,
|
||||||
|
}
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FileSystemForRemote) stopUserServers(userServers map[string]*userServer) {
|
||||||
|
for _, server := range userServers {
|
||||||
|
if err := server.Close(); err != nil {
|
||||||
|
s.logf("error closing tailfs user server: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FileSystemForRemote) closeFileSystems(fileSystems map[string]webdav.FileSystem) {
|
||||||
|
for _, fs := range fileSystems {
|
||||||
|
closer, ok := fs.(interface{ Close() error })
|
||||||
|
if ok {
|
||||||
|
if err := closer.Close(); err != nil {
|
||||||
|
s.logf("error closing tailfs filesystem: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close() implements tailfs.FileSystemForRemote.
|
||||||
|
func (s *FileSystemForRemote) Close() error {
|
||||||
|
s.mu.Lock()
|
||||||
|
userServers := s.userServers
|
||||||
|
fileSystems := s.fileSystems
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
s.stopUserServers(userServers)
|
||||||
|
s.closeFileSystems(fileSystems)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// userServer runs tailscaled serve-tailfs to serve webdav content for the
|
||||||
|
// given Shares. All Shares are assumed to have the same Share.As, and the
|
||||||
|
// content is served as that Share.As user.
|
||||||
|
type userServer struct {
|
||||||
|
logf logger.Logf
|
||||||
|
shares []*tailfs.Share
|
||||||
|
|
||||||
|
// mu guards the below values. Acquire a write lock before updating any of
|
||||||
|
// them, acquire a read lock before reading any of them.
|
||||||
|
mu sync.RWMutex
|
||||||
|
cmd *exec.Cmd
|
||||||
|
addr string
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *userServer) Close() error {
|
||||||
|
s.mu.Lock()
|
||||||
|
cmd := s.cmd
|
||||||
|
s.closed = true
|
||||||
|
s.mu.Unlock()
|
||||||
|
if cmd != nil && cmd.Process != nil {
|
||||||
|
return cmd.Process.Kill()
|
||||||
|
}
|
||||||
|
// not running, that's okay
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *userServer) runLoop() {
|
||||||
|
executable, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
s.logf("can't find executable: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
maxSleepTime := 30 * time.Second
|
||||||
|
consecutiveFailures := float64(0)
|
||||||
|
var timeOfLastFailure time.Time
|
||||||
|
for {
|
||||||
|
s.mu.RLock()
|
||||||
|
closed := s.closed
|
||||||
|
s.mu.RUnlock()
|
||||||
|
if closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.run(executable)
|
||||||
|
now := time.Now()
|
||||||
|
timeSinceLastFailure := now.Sub(timeOfLastFailure)
|
||||||
|
timeOfLastFailure = now
|
||||||
|
if timeSinceLastFailure < maxSleepTime {
|
||||||
|
consecutiveFailures++
|
||||||
|
} else {
|
||||||
|
consecutiveFailures = 1
|
||||||
|
}
|
||||||
|
sleepTime := time.Duration(math.Pow(2, consecutiveFailures)) * time.Millisecond
|
||||||
|
if sleepTime > maxSleepTime {
|
||||||
|
sleepTime = maxSleepTime
|
||||||
|
}
|
||||||
|
s.logf("user server % v stopped with error %v, will try again in %v", executable, err, sleepTime)
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs the executable (tailscaled). This function only works on UNIX systems,
|
||||||
|
// but those are the only ones on which we use userServers anyway.
|
||||||
|
func (s *userServer) run(executable string) error {
|
||||||
|
// set up the command
|
||||||
|
args := []string{"serve-tailfs"}
|
||||||
|
for _, s := range s.shares {
|
||||||
|
args = append(args, s.Name, s.Path)
|
||||||
|
}
|
||||||
|
allArgs := []string{"-u", s.shares[0].As, executable}
|
||||||
|
allArgs = append(allArgs, args...)
|
||||||
|
cmd := exec.Command("sudo", allArgs...)
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stdout pipe: %w", err)
|
||||||
|
}
|
||||||
|
defer stdout.Close()
|
||||||
|
stderr, err := cmd.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("stderr pipe: %w", err)
|
||||||
|
}
|
||||||
|
defer stderr.Close()
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("start: %w", err)
|
||||||
|
}
|
||||||
|
s.mu.Lock()
|
||||||
|
s.cmd = cmd
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
// read address
|
||||||
|
stdoutScanner := bufio.NewScanner(stdout)
|
||||||
|
stdoutScanner.Scan()
|
||||||
|
if stdoutScanner.Err() != nil {
|
||||||
|
return fmt.Errorf("read addr: %w", stdoutScanner.Err())
|
||||||
|
}
|
||||||
|
addr := stdoutScanner.Text()
|
||||||
|
// send the rest of stdout and stderr to logger to avoid blocking
|
||||||
|
go func() {
|
||||||
|
for stdoutScanner.Scan() {
|
||||||
|
s.logf("tailscaled serve-tailfs stdout: %v", stdoutScanner.Text())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
stderrScanner := bufio.NewScanner(stderr)
|
||||||
|
go func() {
|
||||||
|
for stderrScanner.Scan() {
|
||||||
|
s.logf("tailscaled serve-tailfs stderr: %v", stderrScanner.Text())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.mu.Lock()
|
||||||
|
s.addr = strings.TrimSpace(addr)
|
||||||
|
s.mu.Unlock()
|
||||||
|
return cmd.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
var writeMethods = map[string]bool{
|
||||||
|
"PUT": true,
|
||||||
|
"POST": true,
|
||||||
|
"COPY": true,
|
||||||
|
"LOCK": true,
|
||||||
|
"UNLOCK": true,
|
||||||
|
"MKCOL": true,
|
||||||
|
"MOVE": true,
|
||||||
|
"PROPPATCH": true,
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
package tailfs
|
package tailfsimpl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -20,8 +20,9 @@
|
|||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/tailscale/xnet/webdav"
|
"github.com/tailscale/xnet/webdav"
|
||||||
"tailscale.com/tailfs/shared"
|
"tailscale.com/tailfs"
|
||||||
"tailscale.com/tailfs/webdavfs"
|
"tailscale.com/tailfs/tailfsimpl/shared"
|
||||||
|
"tailscale.com/tailfs/tailfsimpl/webdavfs"
|
||||||
"tailscale.com/tstest"
|
"tailscale.com/tstest"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -38,10 +39,10 @@
|
|||||||
func init() {
|
func init() {
|
||||||
// set AllowShareAs() to false so that we don't try to use sub-processes
|
// set AllowShareAs() to false so that we don't try to use sub-processes
|
||||||
// for access files on disk.
|
// for access files on disk.
|
||||||
disallowShareAs = true
|
tailfs.DisallowShareAs = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// The tests in this file simulate real-life Tailfs scenarios, but without
|
// The tests in this file simulate real-life TailFS scenarios, but without
|
||||||
// going over the Tailscale network stack.
|
// going over the Tailscale network stack.
|
||||||
func TestDirectoryListing(t *testing.T) {
|
func TestDirectoryListing(t *testing.T) {
|
||||||
s := newSystem(t)
|
s := newSystem(t)
|
||||||
@ -51,9 +52,9 @@ func TestDirectoryListing(t *testing.T) {
|
|||||||
s.checkDirList("root directory should contain the one and only domain once a remote has been set", "/", domain)
|
s.checkDirList("root directory should contain the one and only domain once a remote has been set", "/", domain)
|
||||||
s.checkDirList("domain should contain its only remote", shared.Join(domain), remote1)
|
s.checkDirList("domain should contain its only remote", shared.Join(domain), remote1)
|
||||||
s.checkDirList("remote with no shares should be empty", shared.Join(domain, remote1))
|
s.checkDirList("remote with no shares should be empty", shared.Join(domain, remote1))
|
||||||
s.addShare(remote1, share11, PermissionReadWrite)
|
s.addShare(remote1, share11, tailfs.PermissionReadWrite)
|
||||||
s.checkDirList("remote with one share should contain that share", shared.Join(domain, remote1), share11)
|
s.checkDirList("remote with one share should contain that share", shared.Join(domain, remote1), share11)
|
||||||
s.addShare(remote1, share12, PermissionReadOnly)
|
s.addShare(remote1, share12, tailfs.PermissionReadOnly)
|
||||||
s.checkDirList("remote with two shares should contain both in lexicographical order", shared.Join(domain, remote1), share12, share11)
|
s.checkDirList("remote with two shares should contain both in lexicographical order", shared.Join(domain, remote1), share12, share11)
|
||||||
s.checkDirListIncremental("remote with two shares should contain both in lexicographical order even when reading directory incrementally", shared.Join(domain, remote1), share12, share11)
|
s.checkDirListIncremental("remote with two shares should contain both in lexicographical order even when reading directory incrementally", shared.Join(domain, remote1), share12, share11)
|
||||||
|
|
||||||
@ -73,12 +74,12 @@ func TestFileManipulation(t *testing.T) {
|
|||||||
defer s.stop()
|
defer s.stop()
|
||||||
|
|
||||||
s.addRemote(remote1)
|
s.addRemote(remote1)
|
||||||
s.addShare(remote1, share11, PermissionReadWrite)
|
s.addShare(remote1, share11, tailfs.PermissionReadWrite)
|
||||||
s.writeFile("writing file to read/write remote should succeed", remote1, share11, file111, "hello world", true)
|
s.writeFile("writing file to read/write remote should succeed", remote1, share11, file111, "hello world", true)
|
||||||
s.checkFileStatus(remote1, share11, file111)
|
s.checkFileStatus(remote1, share11, file111)
|
||||||
s.checkFileContents(remote1, share11, file111)
|
s.checkFileContents(remote1, share11, file111)
|
||||||
|
|
||||||
s.addShare(remote1, share12, PermissionReadOnly)
|
s.addShare(remote1, share12, tailfs.PermissionReadOnly)
|
||||||
s.writeFile("writing file to read-only remote should fail", remote1, share12, file111, "hello world", false)
|
s.writeFile("writing file to read-only remote should fail", remote1, share12, file111, "hello world", false)
|
||||||
|
|
||||||
s.writeFile("writing file to non-existent remote should fail", "non-existent", share11, file111, "hello world", false)
|
s.writeFile("writing file to non-existent remote should fail", "non-existent", share11, file111, "hello world", false)
|
||||||
@ -92,7 +93,7 @@ func TestFileOps(t *testing.T) {
|
|||||||
defer s.stop()
|
defer s.stop()
|
||||||
|
|
||||||
s.addRemote(remote1)
|
s.addRemote(remote1)
|
||||||
s.addShare(remote1, share11, PermissionReadWrite)
|
s.addShare(remote1, share11, tailfs.PermissionReadWrite)
|
||||||
s.writeFile("writing file to read/write remote should succeed", remote1, share11, file111, "hello world", true)
|
s.writeFile("writing file to read/write remote should succeed", remote1, share11, file111, "hello world", true)
|
||||||
fi, err := s.fs.Stat(context.Background(), pathTo(remote1, share11, file111))
|
fi, err := s.fs.Stat(context.Background(), pathTo(remote1, share11, file111))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -204,7 +205,7 @@ func TestFileRewind(t *testing.T) {
|
|||||||
defer s.stop()
|
defer s.stop()
|
||||||
|
|
||||||
s.addRemote(remote1)
|
s.addRemote(remote1)
|
||||||
s.addShare(remote1, share11, PermissionReadWrite)
|
s.addShare(remote1, share11, tailfs.PermissionReadWrite)
|
||||||
|
|
||||||
// Create a file slightly longer than our max rewind buffer of 512
|
// Create a file slightly longer than our max rewind buffer of 512
|
||||||
fileLength := webdavfs.MaxRewindBuffer + 1
|
fileLength := webdavfs.MaxRewindBuffer + 1
|
||||||
@ -267,7 +268,7 @@ type remote struct {
|
|||||||
fs *FileSystemForRemote
|
fs *FileSystemForRemote
|
||||||
fileServer *FileServer
|
fileServer *FileServer
|
||||||
shares map[string]string
|
shares map[string]string
|
||||||
permissions map[string]Permission
|
permissions map[string]tailfs.Permission
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,15 +344,15 @@ func (s *system) addRemote(name string) {
|
|||||||
fileServer: fileServer,
|
fileServer: fileServer,
|
||||||
fs: NewFileSystemForRemote(log.Printf),
|
fs: NewFileSystemForRemote(log.Printf),
|
||||||
shares: make(map[string]string),
|
shares: make(map[string]string),
|
||||||
permissions: make(map[string]Permission),
|
permissions: make(map[string]tailfs.Permission),
|
||||||
}
|
}
|
||||||
r.fs.SetFileServerAddr(fileServer.Addr())
|
r.fs.SetFileServerAddr(fileServer.Addr())
|
||||||
go http.Serve(l, r)
|
go http.Serve(l, r)
|
||||||
s.remotes[name] = r
|
s.remotes[name] = r
|
||||||
|
|
||||||
remotes := make([]*Remote, 0, len(s.remotes))
|
remotes := make([]*tailfs.Remote, 0, len(s.remotes))
|
||||||
for name, r := range s.remotes {
|
for name, r := range s.remotes {
|
||||||
remotes = append(remotes, &Remote{
|
remotes = append(remotes, &tailfs.Remote{
|
||||||
Name: name,
|
Name: name,
|
||||||
URL: fmt.Sprintf("http://%s", r.l.Addr()),
|
URL: fmt.Sprintf("http://%s", r.l.Addr()),
|
||||||
})
|
})
|
||||||
@ -359,7 +360,7 @@ func (s *system) addRemote(name string) {
|
|||||||
s.local.fs.SetRemotes(domain, remotes, &http.Transport{})
|
s.local.fs.SetRemotes(domain, remotes, &http.Transport{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *system) addShare(remoteName, shareName string, permission Permission) {
|
func (s *system) addShare(remoteName, shareName string, permission tailfs.Permission) {
|
||||||
r, ok := s.remotes[remoteName]
|
r, ok := s.remotes[remoteName]
|
||||||
if !ok {
|
if !ok {
|
||||||
s.t.Fatalf("unknown remote %q", remoteName)
|
s.t.Fatalf("unknown remote %q", remoteName)
|
||||||
@ -369,9 +370,9 @@ func (s *system) addShare(remoteName, shareName string, permission Permission) {
|
|||||||
r.shares[shareName] = f
|
r.shares[shareName] = f
|
||||||
r.permissions[shareName] = permission
|
r.permissions[shareName] = permission
|
||||||
|
|
||||||
shares := make(map[string]*Share, len(r.shares))
|
shares := make(map[string]*tailfs.Share, len(r.shares))
|
||||||
for shareName, folder := range r.shares {
|
for shareName, folder := range r.shares {
|
||||||
shares[shareName] = &Share{
|
shares[shareName] = &tailfs.Share{
|
||||||
Name: shareName,
|
Name: shareName,
|
||||||
Path: folder,
|
Path: folder,
|
||||||
}
|
}
|
@ -10,7 +10,7 @@
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tailscale.com/tailfs/shared"
|
"tailscale.com/tailfs/tailfsimpl/shared"
|
||||||
"tailscale.com/tstest"
|
"tailscale.com/tstest"
|
||||||
)
|
)
|
||||||
|
|
@ -19,7 +19,7 @@
|
|||||||
"github.com/tailscale/gowebdav"
|
"github.com/tailscale/gowebdav"
|
||||||
"github.com/tailscale/xnet/webdav"
|
"github.com/tailscale/xnet/webdav"
|
||||||
|
|
||||||
"tailscale.com/tailfs/shared"
|
"tailscale.com/tailfs/tailfsimpl/shared"
|
||||||
"tailscale.com/tstime"
|
"tailscale.com/tstime"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
)
|
)
|
@ -10,7 +10,7 @@
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"tailscale.com/tailfs/shared"
|
"tailscale.com/tailfs/tailfsimpl/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
type writeOnlyFile struct {
|
type writeOnlyFile struct {
|
29
tsd/tsd.go
29
tsd/tsd.go
@ -38,17 +38,18 @@
|
|||||||
|
|
||||||
// System contains all the subsystems of a Tailscale node (tailscaled, etc.)
|
// System contains all the subsystems of a Tailscale node (tailscaled, etc.)
|
||||||
type System struct {
|
type System struct {
|
||||||
Dialer SubSystem[*tsdial.Dialer]
|
Dialer SubSystem[*tsdial.Dialer]
|
||||||
DNSManager SubSystem[*dns.Manager] // can get its *resolver.Resolver from DNSManager.Resolver
|
DNSManager SubSystem[*dns.Manager] // can get its *resolver.Resolver from DNSManager.Resolver
|
||||||
Engine SubSystem[wgengine.Engine]
|
Engine SubSystem[wgengine.Engine]
|
||||||
NetMon SubSystem[*netmon.Monitor]
|
NetMon SubSystem[*netmon.Monitor]
|
||||||
MagicSock SubSystem[*magicsock.Conn]
|
MagicSock SubSystem[*magicsock.Conn]
|
||||||
NetstackRouter SubSystem[bool] // using Netstack at all (either entirely or at least for subnets)
|
NetstackRouter SubSystem[bool] // using Netstack at all (either entirely or at least for subnets)
|
||||||
Router SubSystem[router.Router]
|
Router SubSystem[router.Router]
|
||||||
Tun SubSystem[*tstun.Wrapper]
|
Tun SubSystem[*tstun.Wrapper]
|
||||||
StateStore SubSystem[ipn.StateStore]
|
StateStore SubSystem[ipn.StateStore]
|
||||||
Netstack SubSystem[NetstackImpl] // actually a *netstack.Impl
|
Netstack SubSystem[NetstackImpl] // actually a *netstack.Impl
|
||||||
TailfsForLocal SubSystem[*tailfs.FileSystemForLocal]
|
TailFSForLocal SubSystem[tailfs.FileSystemForLocal]
|
||||||
|
TailFSForRemote SubSystem[tailfs.FileSystemForRemote]
|
||||||
|
|
||||||
// InitialConfig is initial server config, if any.
|
// InitialConfig is initial server config, if any.
|
||||||
// It is nil if the node is not in declarative mode.
|
// It is nil if the node is not in declarative mode.
|
||||||
@ -100,8 +101,10 @@ type ft interface {
|
|||||||
s.StateStore.Set(v)
|
s.StateStore.Set(v)
|
||||||
case NetstackImpl:
|
case NetstackImpl:
|
||||||
s.Netstack.Set(v)
|
s.Netstack.Set(v)
|
||||||
case *tailfs.FileSystemForLocal:
|
case tailfs.FileSystemForLocal:
|
||||||
s.TailfsForLocal.Set(v)
|
s.TailFSForLocal.Set(v)
|
||||||
|
case tailfs.FileSystemForRemote:
|
||||||
|
s.TailFSForRemote.Set(v)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("unknown type %T", v))
|
panic(fmt.Sprintf("unknown type %T", v))
|
||||||
}
|
}
|
||||||
|
@ -530,7 +530,7 @@ func (s *Server) start() (reterr error) {
|
|||||||
closePool.add(s.dialer)
|
closePool.add(s.dialer)
|
||||||
sys.Set(eng)
|
sys.Set(eng)
|
||||||
|
|
||||||
// TODO(oxtoacart): do we need to support Tailfs on tsnet, and if so, how?
|
// TODO(oxtoacart): do we need to support TailFS on tsnet, and if so, how?
|
||||||
ns, err := netstack.Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), s.dialer, sys.DNSManager.Get(), sys.ProxyMapper(), nil)
|
ns, err := netstack.Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), s.dialer, sys.DNSManager.Get(), sys.ProxyMapper(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("netstack.Create: %w", err)
|
return fmt.Errorf("netstack.Create: %w", err)
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
_ "tailscale.com/ssh/tailssh"
|
_ "tailscale.com/ssh/tailssh"
|
||||||
_ "tailscale.com/syncs"
|
_ "tailscale.com/syncs"
|
||||||
_ "tailscale.com/tailcfg"
|
_ "tailscale.com/tailcfg"
|
||||||
_ "tailscale.com/tailfs"
|
_ "tailscale.com/tailfs/tailfsimpl"
|
||||||
_ "tailscale.com/tsd"
|
_ "tailscale.com/tsd"
|
||||||
_ "tailscale.com/tsweb/varz"
|
_ "tailscale.com/tsweb/varz"
|
||||||
_ "tailscale.com/types/flagtype"
|
_ "tailscale.com/types/flagtype"
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
_ "tailscale.com/ssh/tailssh"
|
_ "tailscale.com/ssh/tailssh"
|
||||||
_ "tailscale.com/syncs"
|
_ "tailscale.com/syncs"
|
||||||
_ "tailscale.com/tailcfg"
|
_ "tailscale.com/tailcfg"
|
||||||
_ "tailscale.com/tailfs"
|
_ "tailscale.com/tailfs/tailfsimpl"
|
||||||
_ "tailscale.com/tsd"
|
_ "tailscale.com/tsd"
|
||||||
_ "tailscale.com/tsweb/varz"
|
_ "tailscale.com/tsweb/varz"
|
||||||
_ "tailscale.com/types/flagtype"
|
_ "tailscale.com/types/flagtype"
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
_ "tailscale.com/ssh/tailssh"
|
_ "tailscale.com/ssh/tailssh"
|
||||||
_ "tailscale.com/syncs"
|
_ "tailscale.com/syncs"
|
||||||
_ "tailscale.com/tailcfg"
|
_ "tailscale.com/tailcfg"
|
||||||
_ "tailscale.com/tailfs"
|
_ "tailscale.com/tailfs/tailfsimpl"
|
||||||
_ "tailscale.com/tsd"
|
_ "tailscale.com/tsd"
|
||||||
_ "tailscale.com/tsweb/varz"
|
_ "tailscale.com/tsweb/varz"
|
||||||
_ "tailscale.com/types/flagtype"
|
_ "tailscale.com/types/flagtype"
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
_ "tailscale.com/ssh/tailssh"
|
_ "tailscale.com/ssh/tailssh"
|
||||||
_ "tailscale.com/syncs"
|
_ "tailscale.com/syncs"
|
||||||
_ "tailscale.com/tailcfg"
|
_ "tailscale.com/tailcfg"
|
||||||
_ "tailscale.com/tailfs"
|
_ "tailscale.com/tailfs/tailfsimpl"
|
||||||
_ "tailscale.com/tsd"
|
_ "tailscale.com/tsd"
|
||||||
_ "tailscale.com/tsweb/varz"
|
_ "tailscale.com/tsweb/varz"
|
||||||
_ "tailscale.com/types/flagtype"
|
_ "tailscale.com/types/flagtype"
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
_ "tailscale.com/safesocket"
|
_ "tailscale.com/safesocket"
|
||||||
_ "tailscale.com/syncs"
|
_ "tailscale.com/syncs"
|
||||||
_ "tailscale.com/tailcfg"
|
_ "tailscale.com/tailcfg"
|
||||||
_ "tailscale.com/tailfs"
|
_ "tailscale.com/tailfs/tailfsimpl"
|
||||||
_ "tailscale.com/tsd"
|
_ "tailscale.com/tsd"
|
||||||
_ "tailscale.com/tsweb/varz"
|
_ "tailscale.com/tsweb/varz"
|
||||||
_ "tailscale.com/types/flagtype"
|
_ "tailscale.com/types/flagtype"
|
||||||
|
@ -133,7 +133,7 @@ type Impl struct {
|
|||||||
ctxCancel context.CancelFunc // called on Close
|
ctxCancel context.CancelFunc // called on Close
|
||||||
lb *ipnlocal.LocalBackend // or nil
|
lb *ipnlocal.LocalBackend // or nil
|
||||||
dns *dns.Manager
|
dns *dns.Manager
|
||||||
tailfsForLocal *tailfs.FileSystemForLocal // or nil
|
tailFSForLocal tailfs.FileSystemForLocal // or nil
|
||||||
|
|
||||||
peerapiPort4Atomic atomic.Uint32 // uint16 port number for IPv4 peerapi
|
peerapiPort4Atomic atomic.Uint32 // uint16 port number for IPv4 peerapi
|
||||||
peerapiPort6Atomic atomic.Uint32 // uint16 port number for IPv6 peerapi
|
peerapiPort6Atomic atomic.Uint32 // uint16 port number for IPv6 peerapi
|
||||||
@ -161,7 +161,7 @@ type Impl struct {
|
|||||||
const maxUDPPacketSize = tstun.MaxPacketSize
|
const maxUDPPacketSize = tstun.MaxPacketSize
|
||||||
|
|
||||||
// Create creates and populates a new Impl.
|
// Create creates and populates a new Impl.
|
||||||
func Create(logf logger.Logf, tundev *tstun.Wrapper, e wgengine.Engine, mc *magicsock.Conn, dialer *tsdial.Dialer, dns *dns.Manager, pm *proxymap.Mapper, tailfsForLocal *tailfs.FileSystemForLocal) (*Impl, error) {
|
func Create(logf logger.Logf, tundev *tstun.Wrapper, e wgengine.Engine, mc *magicsock.Conn, dialer *tsdial.Dialer, dns *dns.Manager, pm *proxymap.Mapper, tailFSForLocal tailfs.FileSystemForLocal) (*Impl, error) {
|
||||||
if mc == nil {
|
if mc == nil {
|
||||||
return nil, errors.New("nil magicsock.Conn")
|
return nil, errors.New("nil magicsock.Conn")
|
||||||
}
|
}
|
||||||
@ -241,7 +241,7 @@ func Create(logf logger.Logf, tundev *tstun.Wrapper, e wgengine.Engine, mc *magi
|
|||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
connsOpenBySubnetIP: make(map[netip.Addr]int),
|
connsOpenBySubnetIP: make(map[netip.Addr]int),
|
||||||
dns: dns,
|
dns: dns,
|
||||||
tailfsForLocal: tailfsForLocal,
|
tailFSForLocal: tailFSForLocal,
|
||||||
}
|
}
|
||||||
ns.ctx, ns.ctxCancel = context.WithCancel(context.Background())
|
ns.ctx, ns.ctxCancel = context.WithCancel(context.Background())
|
||||||
ns.atomicIsLocalIPFunc.Store(tsaddr.FalseContainsIPFunc())
|
ns.atomicIsLocalIPFunc.Store(tsaddr.FalseContainsIPFunc())
|
||||||
@ -443,7 +443,7 @@ func (ns *Impl) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper) filter.Re
|
|||||||
return filter.DropSilently
|
return filter.DropSilently
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's not traffic to the service IP (e.g. magicDNS or Tailfs) we don't
|
// If it's not traffic to the service IP (e.g. magicDNS or TailFS) we don't
|
||||||
// care; resume processing.
|
// care; resume processing.
|
||||||
if dst := p.Dst.Addr(); dst != serviceIP && dst != serviceIPv6 {
|
if dst := p.Dst.Addr(); dst != serviceIP && dst != serviceIPv6 {
|
||||||
return filter.Accept
|
return filter.Accept
|
||||||
@ -922,8 +922,8 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
|
|||||||
// Local DNS Service (DNS and WebDAV)
|
// Local DNS Service (DNS and WebDAV)
|
||||||
hittingServiceIP := dialIP == serviceIP || dialIP == serviceIPv6
|
hittingServiceIP := dialIP == serviceIP || dialIP == serviceIPv6
|
||||||
hittingDNS := hittingServiceIP && reqDetails.LocalPort == 53
|
hittingDNS := hittingServiceIP && reqDetails.LocalPort == 53
|
||||||
hittingTailfs := hittingServiceIP && ns.tailfsForLocal != nil && reqDetails.LocalPort == 8080
|
hittingTailFS := hittingServiceIP && ns.tailFSForLocal != nil && reqDetails.LocalPort == 8080
|
||||||
if hittingDNS || hittingTailfs {
|
if hittingDNS || hittingTailFS {
|
||||||
c := getConnOrReset()
|
c := getConnOrReset()
|
||||||
if c == nil {
|
if c == nil {
|
||||||
return
|
return
|
||||||
@ -931,8 +931,8 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
|
|||||||
addrPort := netip.AddrPortFrom(clientRemoteIP, reqDetails.RemotePort)
|
addrPort := netip.AddrPortFrom(clientRemoteIP, reqDetails.RemotePort)
|
||||||
if hittingDNS {
|
if hittingDNS {
|
||||||
go ns.dns.HandleTCPConn(c, addrPort)
|
go ns.dns.HandleTCPConn(c, addrPort)
|
||||||
} else if hittingTailfs {
|
} else if hittingTailFS {
|
||||||
err := ns.tailfsForLocal.HandleConn(c, net.TCPAddrFromAddrPort(addrPort))
|
err := ns.tailFSForLocal.HandleConn(c, net.TCPAddrFromAddrPort(addrPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ns.logf("netstack: tailfs.HandleConn: %v", err)
|
ns.logf("netstack: tailfs.HandleConn: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -203,9 +203,9 @@ type Config struct {
|
|||||||
// SetSubsystem, if non-nil, is called for each new subsystem created, just before a successful return.
|
// SetSubsystem, if non-nil, is called for each new subsystem created, just before a successful return.
|
||||||
SetSubsystem func(any)
|
SetSubsystem func(any)
|
||||||
|
|
||||||
// EnableTailfs, if true, will cause the engine to expose a Tailfs listener
|
// TailFSForLocal, if populated, will cause the engine to expose a TailFS
|
||||||
// at 100.100.100.100:8080
|
// listener at 100.100.100.100:8080.
|
||||||
EnableTailfs bool
|
TailFSForLocal tailfs.FileSystemForLocal
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFakeUserspaceEngine returns a new userspace engine for testing.
|
// NewFakeUserspaceEngine returns a new userspace engine for testing.
|
||||||
@ -451,8 +451,8 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
|||||||
conf.SetSubsystem(conf.Router)
|
conf.SetSubsystem(conf.Router)
|
||||||
conf.SetSubsystem(conf.Dialer)
|
conf.SetSubsystem(conf.Dialer)
|
||||||
conf.SetSubsystem(e.netMon)
|
conf.SetSubsystem(e.netMon)
|
||||||
if conf.EnableTailfs {
|
if conf.TailFSForLocal != nil {
|
||||||
conf.SetSubsystem(tailfs.NewFileSystemForLocal(e.logf))
|
conf.SetSubsystem(conf.TailFSForLocal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user