mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-11 13:18:53 +00:00
tailfs: initial implementation
Add a WebDAV-based folder sharing mechanism that is exposed to local clients at 100.100.100.100:8080 and to remote peers via a new peerapi endpoint at /v0/tailfs. Add the ability to manage folder sharing via the new 'share' CLI sub-command. Updates tailscale/corp#16827 Signed-off-by: Percy Wegmann <percy@tailscale.com>
This commit is contained in:

committed by
Percy Wegmann

parent
2e404b769d
commit
993acf4475
@@ -48,6 +48,7 @@ import (
|
||||
"tailscale.com/proxymap"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tailfs"
|
||||
"tailscale.com/types/ipproto"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/netmap"
|
||||
@@ -63,8 +64,8 @@ const debugPackets = false
|
||||
var debugNetstack = envknob.RegisterBool("TS_DEBUG_NETSTACK")
|
||||
|
||||
var (
|
||||
magicDNSIP = tsaddr.TailscaleServiceIP()
|
||||
magicDNSIPv6 = tsaddr.TailscaleServiceIPv6()
|
||||
serviceIP = tsaddr.TailscaleServiceIP()
|
||||
serviceIPv6 = tsaddr.TailscaleServiceIPv6()
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -120,18 +121,19 @@ type Impl struct {
|
||||
// It can only be set before calling Start.
|
||||
ProcessSubnets bool
|
||||
|
||||
ipstack *stack.Stack
|
||||
linkEP *channel.Endpoint
|
||||
tundev *tstun.Wrapper
|
||||
e wgengine.Engine
|
||||
pm *proxymap.Mapper
|
||||
mc *magicsock.Conn
|
||||
logf logger.Logf
|
||||
dialer *tsdial.Dialer
|
||||
ctx context.Context // alive until Close
|
||||
ctxCancel context.CancelFunc // called on Close
|
||||
lb *ipnlocal.LocalBackend // or nil
|
||||
dns *dns.Manager
|
||||
ipstack *stack.Stack
|
||||
linkEP *channel.Endpoint
|
||||
tundev *tstun.Wrapper
|
||||
e wgengine.Engine
|
||||
pm *proxymap.Mapper
|
||||
mc *magicsock.Conn
|
||||
logf logger.Logf
|
||||
dialer *tsdial.Dialer
|
||||
ctx context.Context // alive until Close
|
||||
ctxCancel context.CancelFunc // called on Close
|
||||
lb *ipnlocal.LocalBackend // or nil
|
||||
dns *dns.Manager
|
||||
tailfsForLocal *tailfs.FileSystemForLocal // or nil
|
||||
|
||||
peerapiPort4Atomic atomic.Uint32 // uint16 port number for IPv4 peerapi
|
||||
peerapiPort6Atomic atomic.Uint32 // uint16 port number for IPv6 peerapi
|
||||
@@ -159,7 +161,7 @@ const nicID = 1
|
||||
const maxUDPPacketSize = tstun.MaxPacketSize
|
||||
|
||||
// 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) (*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 {
|
||||
return nil, errors.New("nil magicsock.Conn")
|
||||
}
|
||||
@@ -239,6 +241,7 @@ func Create(logf logger.Logf, tundev *tstun.Wrapper, e wgengine.Engine, mc *magi
|
||||
dialer: dialer,
|
||||
connsOpenBySubnetIP: make(map[netip.Addr]int),
|
||||
dns: dns,
|
||||
tailfsForLocal: tailfsForLocal,
|
||||
}
|
||||
ns.ctx, ns.ctxCancel = context.WithCancel(context.Background())
|
||||
ns.atomicIsLocalIPFunc.Store(tsaddr.FalseContainsIPFunc())
|
||||
@@ -440,16 +443,16 @@ func (ns *Impl) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper) filter.Re
|
||||
return filter.DropSilently
|
||||
}
|
||||
|
||||
// If it's not traffic to the service IP (i.e. magicDNS) we don't
|
||||
// If it's not traffic to the service IP (e.g. magicDNS or Tailfs) we don't
|
||||
// care; resume processing.
|
||||
if dst := p.Dst.Addr(); dst != magicDNSIP && dst != magicDNSIPv6 {
|
||||
if dst := p.Dst.Addr(); dst != serviceIP && dst != serviceIPv6 {
|
||||
return filter.Accept
|
||||
}
|
||||
// Of traffic to the service IP, we only care about UDP 53, and TCP
|
||||
// on port 80 & 53.
|
||||
// on port 53, 80, and 8080.
|
||||
switch p.IPProto {
|
||||
case ipproto.TCP:
|
||||
if port := p.Dst.Port(); port != 53 && port != 80 {
|
||||
if port := p.Dst.Port(); port != 53 && port != 80 && port != 8080 {
|
||||
return filter.Accept
|
||||
}
|
||||
case ipproto.UDP:
|
||||
@@ -546,12 +549,12 @@ func (ns *Impl) inject() {
|
||||
if b := pkt.NetworkHeader().Slice(); len(b) >= 20 { // min ipv4 header
|
||||
switch b[0] >> 4 { // ip proto field
|
||||
case 4:
|
||||
if srcIP := netaddr.IPv4(b[12], b[13], b[14], b[15]); magicDNSIP == srcIP {
|
||||
if srcIP := netaddr.IPv4(b[12], b[13], b[14], b[15]); serviceIP == srcIP {
|
||||
sendToHost = true
|
||||
}
|
||||
case 6:
|
||||
if len(b) >= 40 { // min ipv6 header
|
||||
if srcIP, ok := netip.AddrFromSlice(net.IP(b[8:24])); ok && magicDNSIPv6 == srcIP {
|
||||
if srcIP, ok := netip.AddrFromSlice(net.IP(b[8:24])); ok && serviceIPv6 == srcIP {
|
||||
sendToHost = true
|
||||
}
|
||||
}
|
||||
@@ -916,13 +919,24 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
|
||||
return gonet.NewTCPConn(&wq, ep)
|
||||
}
|
||||
|
||||
// DNS
|
||||
if reqDetails.LocalPort == 53 && (dialIP == magicDNSIP || dialIP == magicDNSIPv6) {
|
||||
// Local DNS Service (DNS and WebDAV)
|
||||
hittingServiceIP := dialIP == serviceIP || dialIP == serviceIPv6
|
||||
hittingDNS := hittingServiceIP && reqDetails.LocalPort == 53
|
||||
hittingTailfs := hittingServiceIP && ns.tailfsForLocal != nil && reqDetails.LocalPort == 8080
|
||||
if hittingDNS || hittingTailfs {
|
||||
c := getConnOrReset()
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
go ns.dns.HandleTCPConn(c, netip.AddrPortFrom(clientRemoteIP, reqDetails.RemotePort))
|
||||
addrPort := netip.AddrPortFrom(clientRemoteIP, reqDetails.RemotePort)
|
||||
if hittingDNS {
|
||||
go ns.dns.HandleTCPConn(c, addrPort)
|
||||
} else if hittingTailfs {
|
||||
err := ns.tailfsForLocal.HandleConn(c, net.TCPAddrFromAddrPort(addrPort))
|
||||
if err != nil {
|
||||
ns.logf("netstack: tailfs.HandleConn: %v", err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1056,7 +1070,7 @@ func (ns *Impl) acceptUDP(r *udp.ForwarderRequest) {
|
||||
}
|
||||
|
||||
// Handle magicDNS traffic (via UDP) here.
|
||||
if dst := dstAddr.Addr(); dst == magicDNSIP || dst == magicDNSIPv6 {
|
||||
if dst := dstAddr.Addr(); dst == serviceIP || dst == serviceIPv6 {
|
||||
if dstAddr.Port() != 53 {
|
||||
ep.Close()
|
||||
return // Only MagicDNS traffic runs on the service IPs for now.
|
||||
|
@@ -53,7 +53,7 @@ func TestInjectInboundLeak(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ns, err := Create(logf, tunWrap, eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get(), sys.ProxyMapper())
|
||||
ns, err := Create(logf, tunWrap, eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get(), sys.ProxyMapper(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -102,7 +102,7 @@ func makeNetstack(t *testing.T, config func(*Impl)) *Impl {
|
||||
t.Cleanup(func() { eng.Close() })
|
||||
sys.Set(eng)
|
||||
|
||||
ns, err := Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get(), sys.ProxyMapper())
|
||||
ns, err := Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get(), sys.ProxyMapper(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@@ -34,6 +34,7 @@ import (
|
||||
"tailscale.com/net/tstun"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tailfs"
|
||||
"tailscale.com/tstime/mono"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/ipproto"
|
||||
@@ -201,6 +202,10 @@ type Config struct {
|
||||
|
||||
// SetSubsystem, if non-nil, is called for each new subsystem created, just before a successful return.
|
||||
SetSubsystem func(any)
|
||||
|
||||
// EnableTailfs, if true, will cause the engine to expose a Tailfs listener
|
||||
// at 100.100.100.100:8080
|
||||
EnableTailfs bool
|
||||
}
|
||||
|
||||
// NewFakeUserspaceEngine returns a new userspace engine for testing.
|
||||
@@ -446,6 +451,9 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
||||
conf.SetSubsystem(conf.Router)
|
||||
conf.SetSubsystem(conf.Dialer)
|
||||
conf.SetSubsystem(e.netMon)
|
||||
if conf.EnableTailfs {
|
||||
conf.SetSubsystem(tailfs.NewFileSystemForLocal(e.logf))
|
||||
}
|
||||
}
|
||||
|
||||
e.logf("Engine created.")
|
||||
|
Reference in New Issue
Block a user