tailscale: Add system interface support

This commit is contained in:
世界
2026-01-07 14:37:58 +08:00
parent bd9935eebb
commit f196b7a583
8 changed files with 458 additions and 14 deletions

View File

@@ -22,8 +22,6 @@ import (
"github.com/sagernet/gvisor/pkg/tcpip/header"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/common/dialer"
@@ -49,8 +47,10 @@ import (
tsDNS "github.com/sagernet/tailscale/net/dns"
"github.com/sagernet/tailscale/net/netmon"
"github.com/sagernet/tailscale/net/tsaddr"
tsTUN "github.com/sagernet/tailscale/net/tstun"
"github.com/sagernet/tailscale/tsnet"
"github.com/sagernet/tailscale/types/ipproto"
"github.com/sagernet/tailscale/types/nettype"
"github.com/sagernet/tailscale/version"
"github.com/sagernet/tailscale/wgengine"
"github.com/sagernet/tailscale/wgengine/filter"
@@ -101,6 +101,51 @@ type Endpoint struct {
relayServerStaticEndpoints []netip.AddrPort
udpTimeout time.Duration
systemInterface bool
systemInterfaceName string
systemInterfaceMTU uint32
systemTun tun.Tun
fallbackTCPCloser func()
}
func (t *Endpoint) registerNetstackHandlers() {
netstack := t.server.ExportNetstack()
if netstack == nil {
return
}
previousTCP := netstack.GetTCPHandlerForFlow
netstack.GetTCPHandlerForFlow = func(src, dst netip.AddrPort) (handler func(net.Conn), intercept bool) {
if previousTCP != nil {
handler, intercept = previousTCP(src, dst)
if handler != nil || !intercept {
return handler, intercept
}
}
return func(conn net.Conn) {
ctx := log.ContextWithNewID(t.ctx)
source := M.SocksaddrFrom(src.Addr(), src.Port())
destination := M.SocksaddrFrom(dst.Addr(), dst.Port())
t.NewConnectionEx(ctx, conn, source, destination, nil)
}, true
}
previousUDP := netstack.GetUDPHandlerForFlow
netstack.GetUDPHandlerForFlow = func(src, dst netip.AddrPort) (handler func(nettype.ConnPacketConn), intercept bool) {
if previousUDP != nil {
handler, intercept = previousUDP(src, dst)
if handler != nil || !intercept {
return handler, intercept
}
}
return func(conn nettype.ConnPacketConn) {
ctx := log.ContextWithNewID(t.ctx)
source := M.SocksaddrFrom(src.Addr(), src.Port())
destination := M.SocksaddrFrom(dst.Addr(), dst.Port())
packetConn := bufio.NewPacketConn(conn)
t.NewPacketConnectionEx(ctx, packetConn, source, destination, nil)
}, true
}
}
func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TailscaleEndpointOptions) (adapter.Endpoint, error) {
@@ -202,6 +247,9 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
relayServerPort: options.RelayServerPort,
relayServerStaticEndpoints: options.RelayServerStaticEndpoints,
udpTimeout: udpTimeout,
systemInterface: options.SystemInterface,
systemInterfaceName: options.SystemInterfaceName,
systemInterfaceMTU: options.SystemInterfaceMTU,
}, nil
}
@@ -237,10 +285,59 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
setAndroidProtectFunc(t.platformInterface)
}
}
if t.systemInterface {
mtu := t.systemInterfaceMTU
if mtu == 0 {
mtu = uint32(tsTUN.DefaultTUNMTU())
}
tunName := t.systemInterfaceName
if tunName == "" {
tunName = tun.CalculateInterfaceName("tailscale")
}
tunOptions := tun.Options{
Name: tunName,
MTU: mtu,
GSO: true,
InterfaceScope: true,
InterfaceMonitor: t.network.InterfaceMonitor(),
InterfaceFinder: t.network.InterfaceFinder(),
Logger: t.logger,
EXP_ExternalConfiguration: true,
}
systemTun, err := tun.New(tunOptions)
if err != nil {
return err
}
err = systemTun.Start()
if err != nil {
_ = systemTun.Close()
return err
}
wgTunDevice, err := newTunDeviceAdapter(systemTun, int(mtu), t.logger)
if err != nil {
_ = systemTun.Close()
return err
}
t.systemTun = systemTun
t.server.TunDevice = wgTunDevice
}
err := t.server.Start()
if err != nil {
if t.systemTun != nil {
_ = t.systemTun.Close()
}
return err
}
if t.fallbackTCPCloser == nil {
t.fallbackTCPCloser = t.server.RegisterFallbackTCPHandler(func(src, dst netip.AddrPort) (handler func(net.Conn), intercept bool) {
return func(conn net.Conn) {
ctx := log.ContextWithNewID(t.ctx)
source := M.SocksaddrFrom(src.Addr(), src.Port())
destination := M.SocksaddrFrom(dst.Addr(), dst.Port())
t.NewConnectionEx(ctx, conn, source, destination, nil)
}, true
})
}
t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine).SetOnReconfigListener(t.onReconfig)
ipStack := t.server.ExportNetstack().ExportIPStack()
@@ -252,13 +349,12 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
if gErr != nil {
return gonet.TranslateNetstackError(gErr)
}
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(t.ctx, ipStack, t).HandlePacket)
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(t.ctx, ipStack, t, t.udpTimeout).HandlePacket)
icmpForwarder := tun.NewICMPForwarder(t.ctx, ipStack, t, t.udpTimeout)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
t.stack = ipStack
t.icmpForwarder = icmpForwarder
t.registerNetstackHandlers()
localBackend := t.server.ExportLocalBackend()
perfs := &ipn.MaskedPrefs{
@@ -355,6 +451,10 @@ func (t *Endpoint) Close() error {
if runtime.GOOS == "android" {
setAndroidProtectFunc(nil)
}
if t.fallbackTCPCloser != nil {
t.fallbackTCPCloser()
t.fallbackTCPCloser = nil
}
return common.Close(common.PtrOrNil(t.server))
}

View File

@@ -0,0 +1,156 @@
//go:build !windows
package tailscale
import (
"encoding/hex"
"errors"
"io"
"os"
"sync"
"sync/atomic"
singTun "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/logger"
wgTun "github.com/sagernet/wireguard-go/tun"
)
type tunDeviceAdapter struct {
tun singTun.Tun
linuxTUN singTun.LinuxTUN
events chan wgTun.Event
mtu int
logger logger.ContextLogger
debugTun bool
readCount atomic.Uint32
writeCount atomic.Uint32
closeOnce sync.Once
}
func newTunDeviceAdapter(tun singTun.Tun, mtu int, logger logger.ContextLogger) (wgTun.Device, error) {
if tun == nil {
return nil, os.ErrInvalid
}
if mtu == 0 {
mtu = 1500
}
adapter := &tunDeviceAdapter{
tun: tun,
events: make(chan wgTun.Event, 1),
mtu: mtu,
logger: logger,
debugTun: os.Getenv("SINGBOX_TS_TUN_DEBUG") != "",
}
if linuxTUN, ok := tun.(singTun.LinuxTUN); ok {
adapter.linuxTUN = linuxTUN
}
adapter.events <- wgTun.EventUp
return adapter, nil
}
func (a *tunDeviceAdapter) File() *os.File {
return nil
}
func (a *tunDeviceAdapter) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) {
if a.linuxTUN != nil {
n, err := a.linuxTUN.BatchRead(bufs, offset-singTun.PacketOffset, sizes)
if err == nil {
for i := 0; i < n; i++ {
a.debugPacket("read", bufs[i][offset:offset+sizes[i]])
}
}
return n, err
}
if offset < singTun.PacketOffset {
return 0, io.ErrShortBuffer
}
readBuf := bufs[0][offset-singTun.PacketOffset:]
n, err := a.tun.Read(readBuf)
if err == nil {
if n < singTun.PacketOffset {
return 0, io.ErrUnexpectedEOF
}
sizes[0] = n - singTun.PacketOffset
a.debugPacket("read", readBuf[singTun.PacketOffset:n])
return 1, nil
}
if errors.Is(err, singTun.ErrTooManySegments) {
err = wgTun.ErrTooManySegments
}
return 0, err
}
func (a *tunDeviceAdapter) Write(bufs [][]byte, offset int) (count int, err error) {
if a.linuxTUN != nil {
for i := range bufs {
a.debugPacket("write", bufs[i][offset:])
}
return a.linuxTUN.BatchWrite(bufs, offset)
}
for _, packet := range bufs {
a.debugPacket("write", packet[offset:])
if singTun.PacketOffset > 0 {
common.ClearArray(packet[offset-singTun.PacketOffset : offset])
singTun.PacketFillHeader(packet[offset-singTun.PacketOffset:], singTun.PacketIPVersion(packet[offset:]))
}
_, err = a.tun.Write(packet[offset-singTun.PacketOffset:])
if err != nil {
return 0, err
}
}
// WireGuard will not read count.
return 0, nil
}
func (a *tunDeviceAdapter) MTU() (int, error) {
return a.mtu, nil
}
func (a *tunDeviceAdapter) Name() (string, error) {
return a.tun.Name()
}
func (a *tunDeviceAdapter) Events() <-chan wgTun.Event {
return a.events
}
func (a *tunDeviceAdapter) Close() error {
var err error
a.closeOnce.Do(func() {
close(a.events)
err = a.tun.Close()
})
return err
}
func (a *tunDeviceAdapter) BatchSize() int {
if a.linuxTUN != nil {
return a.linuxTUN.BatchSize()
}
return 1
}
func (a *tunDeviceAdapter) debugPacket(direction string, packet []byte) {
if !a.debugTun || a.logger == nil {
return
}
var counter *atomic.Uint32
switch direction {
case "read":
counter = &a.readCount
case "write":
counter = &a.writeCount
default:
return
}
if counter.Add(1) > 8 {
return
}
sample := packet
if len(sample) > 64 {
sample = sample[:64]
}
a.logger.Trace("tailscale tun ", direction, " len=", len(packet), " head=", hex.EncodeToString(sample))
}

View File

@@ -0,0 +1,117 @@
//go:build windows
package tailscale
import (
"errors"
"os"
"sync"
"sync/atomic"
singTun "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/logger"
wgTun "github.com/sagernet/wireguard-go/tun"
)
type tunDeviceAdapter struct {
tun singTun.WinTun
nativeTun *singTun.NativeTun
events chan wgTun.Event
mtu atomic.Int64
closeOnce sync.Once
}
func newTunDeviceAdapter(tun singTun.Tun, mtu int, _ logger.ContextLogger) (wgTun.Device, error) {
winTun, ok := tun.(singTun.WinTun)
if !ok {
return nil, errors.New("not a windows tun device")
}
nativeTun, ok := winTun.(*singTun.NativeTun)
if !ok {
return nil, errors.New("unsupported windows tun device")
}
if mtu == 0 {
mtu = 1500
}
adapter := &tunDeviceAdapter{
tun: winTun,
nativeTun: nativeTun,
events: make(chan wgTun.Event, 1),
}
adapter.mtu.Store(int64(mtu))
adapter.events <- wgTun.EventUp
return adapter, nil
}
func (a *tunDeviceAdapter) File() *os.File {
return nil
}
func (a *tunDeviceAdapter) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) {
packet, release, err := a.tun.ReadPacket()
if err != nil {
return 0, err
}
defer release()
sizes[0] = copy(bufs[0][offset-singTun.PacketOffset:], packet)
return 1, nil
}
func (a *tunDeviceAdapter) Write(bufs [][]byte, offset int) (count int, err error) {
for _, packet := range bufs {
if singTun.PacketOffset > 0 {
singTun.PacketFillHeader(packet[offset-singTun.PacketOffset:], singTun.PacketIPVersion(packet[offset:]))
}
_, err = a.tun.Write(packet[offset-singTun.PacketOffset:])
if err != nil {
return 0, err
}
}
return 0, nil
}
func (a *tunDeviceAdapter) MTU() (int, error) {
return int(a.mtu.Load()), nil
}
func (a *tunDeviceAdapter) ForceMTU(mtu int) {
if mtu <= 0 {
return
}
update := int(a.mtu.Load()) != mtu
a.mtu.Store(int64(mtu))
if update {
select {
case a.events <- wgTun.EventMTUUpdate:
default:
}
}
}
func (a *tunDeviceAdapter) LUID() uint64 {
if a.nativeTun == nil {
return 0
}
return a.nativeTun.LUID()
}
func (a *tunDeviceAdapter) Name() (string, error) {
return a.tun.Name()
}
func (a *tunDeviceAdapter) Events() <-chan wgTun.Event {
return a.events
}
func (a *tunDeviceAdapter) Close() error {
var err error
a.closeOnce.Do(func() {
close(a.events)
err = a.tun.Close()
})
return err
}
func (a *tunDeviceAdapter) BatchSize() int {
return 1
}