mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-07-08 20:58:44 +00:00
Add tun support
This commit is contained in:
+296
@@ -0,0 +1,296 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-tun"
|
||||
sing_common "github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/ranges"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/singbridge"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
features_tun "github.com/xtls/xray-core/features/tun"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
)
|
||||
|
||||
//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
|
||||
|
||||
func init() {
|
||||
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, cfg interface{}) (interface{}, error) {
|
||||
return New(ctx, cfg.(*Config))
|
||||
}))
|
||||
}
|
||||
|
||||
var TunInitializer features_tun.Interface = (*Tun)(nil)
|
||||
|
||||
type Tun struct {
|
||||
ctx context.Context
|
||||
dispatcher routing.Dispatcher
|
||||
logger logger.ContextLogger
|
||||
tunOptions tun.Options
|
||||
stack string
|
||||
endpointIndependentNat bool
|
||||
udpTimeout int64
|
||||
tunIf tun.Tun
|
||||
tunStack tun.Stack
|
||||
networkMonitor tun.NetworkUpdateMonitor
|
||||
interfaceMonitor tun.DefaultInterfaceMonitor
|
||||
packageManager tun.PackageManager
|
||||
interfaceFinder *myInterfaceFinder
|
||||
}
|
||||
|
||||
func New(ctx context.Context, config *Config) (*Tun, error) {
|
||||
instance := core.MustFromContext(ctx)
|
||||
tunInterface := &Tun{
|
||||
ctx: ctx,
|
||||
dispatcher: instance.GetFeature(routing.DispatcherType()).(routing.Dispatcher),
|
||||
logger: singbridge.NewLogger(newError),
|
||||
stack: config.Stack,
|
||||
endpointIndependentNat: config.EndpointIndependentNat,
|
||||
udpTimeout: int64(5 * time.Minute.Seconds()),
|
||||
interfaceFinder: new(myInterfaceFinder),
|
||||
}
|
||||
networkUpdateMonitor, err := tun.NewNetworkUpdateMonitor(tunInterface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defaultInterfaceMonitor, err := tun.NewDefaultInterfaceMonitor(networkUpdateMonitor, tun.DefaultInterfaceMonitorOptions{
|
||||
OverrideAndroidVPN: config.OverrideAndroidVpn,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defaultInterfaceMonitor.RegisterCallback(tunInterface.notifyNetworkUpdate)
|
||||
if config.AutoDetectInterface {
|
||||
networkUpdateMonitor.RegisterCallback(tunInterface.interfaceFinder.update)
|
||||
const useInterfaceName = runtime.GOOS == "linux" || runtime.GOOS == "android"
|
||||
bindFunc := control.BindToInterfaceFunc(tunInterface.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int) {
|
||||
remoteAddr := M.ParseSocksaddr(address).Addr
|
||||
if useInterfaceName {
|
||||
return defaultInterfaceMonitor.DefaultInterfaceName(remoteAddr), -1
|
||||
} else {
|
||||
return "", defaultInterfaceMonitor.DefaultInterfaceIndex(remoteAddr)
|
||||
}
|
||||
})
|
||||
internet.UseAlternativeSystemDialer(nil)
|
||||
internet.RegisterDialerController(bindFunc)
|
||||
internet.RegisterListenerController(bindFunc)
|
||||
}
|
||||
if runtime.GOOS == "android" {
|
||||
packageManage, err := tun.NewPackageManager(tunInterface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tunInterface.packageManager = packageManage
|
||||
}
|
||||
tunInterface.networkMonitor = networkUpdateMonitor
|
||||
tunInterface.interfaceMonitor = defaultInterfaceMonitor
|
||||
tunName := config.InterfaceName
|
||||
if tunName == "" {
|
||||
tunName = tun.CalculateInterfaceName("")
|
||||
}
|
||||
tunMTU := config.Mtu
|
||||
if tunMTU == 0 {
|
||||
tunMTU = 9000
|
||||
}
|
||||
includeUID := uidToRange(config.IncludeUid)
|
||||
if len(config.IncludeUidRange) > 0 {
|
||||
var err error
|
||||
includeUID, err = parseRange(includeUID, config.IncludeUidRange)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse include_uid_range")
|
||||
}
|
||||
}
|
||||
excludeUID := uidToRange(config.ExcludeUid)
|
||||
if len(config.ExcludeUidRange) > 0 {
|
||||
var err error
|
||||
excludeUID, err = parseRange(excludeUID, config.ExcludeUidRange)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse exclude_uid_range")
|
||||
}
|
||||
}
|
||||
if config.UdpTimeout != 0 {
|
||||
tunInterface.udpTimeout = config.UdpTimeout
|
||||
}
|
||||
tunInterface.tunOptions = tun.Options{
|
||||
Name: tunName,
|
||||
Inet4Address: sing_common.Map(config.Inet4Address, netip.MustParsePrefix),
|
||||
Inet6Address: sing_common.Map(config.Inet6Address, netip.MustParsePrefix),
|
||||
MTU: tunMTU,
|
||||
AutoRoute: config.AutoRoute,
|
||||
StrictRoute: config.StrictRoute,
|
||||
Inet4RouteAddress: sing_common.Map(config.Inet4RouteAddress, netip.MustParsePrefix),
|
||||
Inet6RouteAddress: sing_common.Map(config.Inet6RouteAddress, netip.MustParsePrefix),
|
||||
IncludeUID: includeUID,
|
||||
ExcludeUID: excludeUID,
|
||||
IncludeAndroidUser: sing_common.Map(config.IncludeAndroidUser, func(it int32) int {
|
||||
return int(it)
|
||||
}),
|
||||
IncludePackage: config.IncludePackage,
|
||||
ExcludePackage: config.ExcludePackage,
|
||||
InterfaceMonitor: defaultInterfaceMonitor,
|
||||
TableIndex: 2022,
|
||||
}
|
||||
return tunInterface, nil
|
||||
}
|
||||
|
||||
func (t *Tun) Type() interface{} {
|
||||
return features_tun.InterfaceType()
|
||||
}
|
||||
|
||||
func (t *Tun) Start() error {
|
||||
err := t.interfaceMonitor.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.networkMonitor.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if runtime.GOOS == "android" {
|
||||
err = t.packageManager.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.tunOptions.BuildAndroidRules(t.packageManager, t)
|
||||
}
|
||||
tunIf, err := tun.New(t.tunOptions)
|
||||
if err != nil {
|
||||
return E.Cause(err, "configure tun interface")
|
||||
}
|
||||
t.tunIf = tunIf
|
||||
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
|
||||
Context: t.ctx,
|
||||
Tun: tunIf,
|
||||
MTU: t.tunOptions.MTU,
|
||||
Name: t.tunOptions.Name,
|
||||
Inet4Address: t.tunOptions.Inet4Address,
|
||||
Inet6Address: t.tunOptions.Inet6Address,
|
||||
EndpointIndependentNat: t.endpointIndependentNat,
|
||||
UDPTimeout: t.udpTimeout,
|
||||
Handler: t,
|
||||
Logger: t.logger,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = t.tunStack.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.logger.Info("tun started at ", t.tunOptions.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
sid := session.NewID()
|
||||
ctx = session.ContextWithID(ctx, sid)
|
||||
t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||
t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||
ctx = session.ContextWithInbound(ctx, &session.Inbound{
|
||||
Source: net.DestinationFromAddr(metadata.Source.TCPAddr()),
|
||||
Conn: conn,
|
||||
})
|
||||
wConn := singbridge.NewConn(conn)
|
||||
_ = t.dispatcher.DispatchLink(ctx, singbridge.ToDestination(metadata.Destination, net.Network_TCP), &transport.Link{
|
||||
Reader: wConn,
|
||||
Writer: wConn,
|
||||
})
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
sid := session.NewID()
|
||||
ctx = session.ContextWithID(ctx, sid)
|
||||
t.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source)
|
||||
t.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
|
||||
ctx = session.ContextWithInbound(ctx, &session.Inbound{
|
||||
Source: net.DestinationFromAddr(metadata.Source.UDPAddr()),
|
||||
})
|
||||
pc := &PacketConn{conn}
|
||||
_ = t.dispatcher.DispatchLink(ctx, singbridge.ToDestination(metadata.Destination, net.Network_UDP), &transport.Link{
|
||||
Reader: pc,
|
||||
Writer: pc,
|
||||
})
|
||||
conn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tun) Close() error {
|
||||
return sing_common.Close(
|
||||
t.packageManager,
|
||||
t.interfaceMonitor,
|
||||
t.networkMonitor,
|
||||
t.tunStack,
|
||||
t.tunIf,
|
||||
)
|
||||
}
|
||||
|
||||
func (t *Tun) OnPackagesUpdated(packages int, sharedUsers int) {
|
||||
t.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users")
|
||||
}
|
||||
|
||||
func (t *Tun) NewError(ctx context.Context, err error) {
|
||||
}
|
||||
|
||||
func (t *Tun) notifyNetworkUpdate(int) error {
|
||||
if runtime.GOOS == "android" {
|
||||
var vpnStatus string
|
||||
if t.interfaceMonitor.AndroidVPNEnabled() {
|
||||
vpnStatus = "enabled"
|
||||
} else {
|
||||
vpnStatus = "disabled"
|
||||
}
|
||||
t.logger.Info("updated default interface ", t.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", t.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()), ", vpn ", vpnStatus)
|
||||
} else {
|
||||
t.logger.Info("updated default interface ", t.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", t.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func uidToRange(uidList []uint32) []ranges.Range[uint32] {
|
||||
return sing_common.Map(uidList, func(uid uint32) ranges.Range[uint32] {
|
||||
return ranges.NewSingle(uint32(uid))
|
||||
})
|
||||
}
|
||||
|
||||
func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.Range[uint32], error) {
|
||||
for _, uidRange := range rangeList {
|
||||
if !strings.Contains(uidRange, ":") {
|
||||
return nil, E.New("missing ':' in range: ", uidRange)
|
||||
}
|
||||
subIndex := strings.Index(uidRange, ":")
|
||||
if subIndex == 0 {
|
||||
return nil, E.New("missing range start: ", uidRange)
|
||||
} else if subIndex == len(uidRange)-1 {
|
||||
return nil, E.New("missing range end: ", uidRange)
|
||||
}
|
||||
var start, end uint64
|
||||
var err error
|
||||
start, err = strconv.ParseUint(uidRange[:subIndex], 10, 32)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse range start")
|
||||
}
|
||||
end, err = strconv.ParseUint(uidRange[subIndex+1:], 10, 32)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse range end")
|
||||
}
|
||||
uidRanges = append(uidRanges, ranges.New(uint32(start), uint32(end)))
|
||||
}
|
||||
return uidRanges, nil
|
||||
}
|
||||
Reference in New Issue
Block a user