|
@@ -3,6 +3,9 @@ package inbound
|
|
|
import (
|
|
|
"context"
|
|
|
"net"
|
|
|
+ "net/netip"
|
|
|
+ "os"
|
|
|
+ "runtime"
|
|
|
"strconv"
|
|
|
"strings"
|
|
|
"time"
|
|
@@ -19,27 +22,91 @@ import (
|
|
|
M "github.com/sagernet/sing/common/metadata"
|
|
|
N "github.com/sagernet/sing/common/network"
|
|
|
"github.com/sagernet/sing/common/ranges"
|
|
|
+ "github.com/sagernet/sing/common/x/list"
|
|
|
+
|
|
|
+ "go4.org/netipx"
|
|
|
)
|
|
|
|
|
|
var _ adapter.Inbound = (*Tun)(nil)
|
|
|
|
|
|
type Tun struct {
|
|
|
- tag string
|
|
|
- ctx context.Context
|
|
|
- router adapter.Router
|
|
|
- logger log.ContextLogger
|
|
|
- inboundOptions option.InboundOptions
|
|
|
- tunOptions tun.Options
|
|
|
- endpointIndependentNat bool
|
|
|
- udpTimeout int64
|
|
|
- stack string
|
|
|
- tunIf tun.Tun
|
|
|
- tunStack tun.Stack
|
|
|
- platformInterface platform.Interface
|
|
|
- platformOptions option.TunPlatformOptions
|
|
|
+ tag string
|
|
|
+ ctx context.Context
|
|
|
+ router adapter.Router
|
|
|
+ logger log.ContextLogger
|
|
|
+ inboundOptions option.InboundOptions
|
|
|
+ tunOptions tun.Options
|
|
|
+ endpointIndependentNat bool
|
|
|
+ udpTimeout int64
|
|
|
+ stack string
|
|
|
+ tunIf tun.Tun
|
|
|
+ tunStack tun.Stack
|
|
|
+ platformInterface platform.Interface
|
|
|
+ platformOptions option.TunPlatformOptions
|
|
|
+ autoRedirect tun.AutoRedirect
|
|
|
+ routeRuleSet []adapter.RuleSet
|
|
|
+ routeRuleSetCallback []*list.Element[adapter.RuleSetUpdateCallback]
|
|
|
+ routeExcludeRuleSet []adapter.RuleSet
|
|
|
+ routeExcludeRuleSetCallback []*list.Element[adapter.RuleSetUpdateCallback]
|
|
|
+ routeAddressSet []*netipx.IPSet
|
|
|
+ routeExcludeAddressSet []*netipx.IPSet
|
|
|
}
|
|
|
|
|
|
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
|
|
|
+ address := options.Address
|
|
|
+ //nolint:staticcheck
|
|
|
+ //goland:noinspection GoDeprecation
|
|
|
+ if len(options.Inet4Address) > 0 {
|
|
|
+ address = append(address, options.Inet4Address...)
|
|
|
+ }
|
|
|
+ //nolint:staticcheck
|
|
|
+ //goland:noinspection GoDeprecation
|
|
|
+ if len(options.Inet6Address) > 0 {
|
|
|
+ address = append(address, options.Inet6Address...)
|
|
|
+ }
|
|
|
+ inet4Address := common.Filter(address, func(it netip.Prefix) bool {
|
|
|
+ return it.Addr().Is4()
|
|
|
+ })
|
|
|
+ inet6Address := common.Filter(address, func(it netip.Prefix) bool {
|
|
|
+ return it.Addr().Is6()
|
|
|
+ })
|
|
|
+
|
|
|
+ routeAddress := options.RouteAddress
|
|
|
+ //nolint:staticcheck
|
|
|
+ //goland:noinspection GoDeprecation
|
|
|
+ if len(options.Inet4RouteAddress) > 0 {
|
|
|
+ routeAddress = append(routeAddress, options.Inet4RouteAddress...)
|
|
|
+ }
|
|
|
+ //nolint:staticcheck
|
|
|
+ //goland:noinspection GoDeprecation
|
|
|
+ if len(options.Inet6RouteAddress) > 0 {
|
|
|
+ routeAddress = append(routeAddress, options.Inet6RouteAddress...)
|
|
|
+ }
|
|
|
+ inet4RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool {
|
|
|
+ return it.Addr().Is4()
|
|
|
+ })
|
|
|
+ inet6RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool {
|
|
|
+ return it.Addr().Is6()
|
|
|
+ })
|
|
|
+
|
|
|
+ routeExcludeAddress := options.RouteExcludeAddress
|
|
|
+ //nolint:staticcheck
|
|
|
+ //goland:noinspection GoDeprecation
|
|
|
+ if len(options.Inet4RouteExcludeAddress) > 0 {
|
|
|
+ routeExcludeAddress = append(routeExcludeAddress, options.Inet4RouteExcludeAddress...)
|
|
|
+ }
|
|
|
+ //nolint:staticcheck
|
|
|
+ //goland:noinspection GoDeprecation
|
|
|
+ if len(options.Inet6RouteExcludeAddress) > 0 {
|
|
|
+ routeExcludeAddress = append(routeExcludeAddress, options.Inet6RouteExcludeAddress...)
|
|
|
+ }
|
|
|
+ inet4RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool {
|
|
|
+ return it.Addr().Is4()
|
|
|
+ })
|
|
|
+ inet6RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool {
|
|
|
+ return it.Addr().Is6()
|
|
|
+ })
|
|
|
+
|
|
|
tunMTU := options.MTU
|
|
|
if tunMTU == 0 {
|
|
|
tunMTU = 9000
|
|
@@ -50,9 +117,9 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
|
|
} else {
|
|
|
udpTimeout = C.UDPTimeout
|
|
|
}
|
|
|
+ var err error
|
|
|
includeUID := uidToRange(options.IncludeUID)
|
|
|
if len(options.IncludeUIDRange) > 0 {
|
|
|
- var err error
|
|
|
includeUID, err = parseRange(includeUID, options.IncludeUIDRange)
|
|
|
if err != nil {
|
|
|
return nil, E.Cause(err, "parse include_uid_range")
|
|
@@ -60,13 +127,30 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
|
|
}
|
|
|
excludeUID := uidToRange(options.ExcludeUID)
|
|
|
if len(options.ExcludeUIDRange) > 0 {
|
|
|
- var err error
|
|
|
excludeUID, err = parseRange(excludeUID, options.ExcludeUIDRange)
|
|
|
if err != nil {
|
|
|
return nil, E.Cause(err, "parse exclude_uid_range")
|
|
|
}
|
|
|
}
|
|
|
- return &Tun{
|
|
|
+
|
|
|
+ tableIndex := options.IPRoute2TableIndex
|
|
|
+ if tableIndex == 0 {
|
|
|
+ tableIndex = tun.DefaultIPRoute2TableIndex
|
|
|
+ }
|
|
|
+ ruleIndex := options.IPRoute2RuleIndex
|
|
|
+ if ruleIndex == 0 {
|
|
|
+ ruleIndex = tun.DefaultIPRoute2RuleIndex
|
|
|
+ }
|
|
|
+ inputMark := options.AutoRedirectInputMark
|
|
|
+ if inputMark == 0 {
|
|
|
+ inputMark = tun.DefaultAutoRedirectInputMark
|
|
|
+ }
|
|
|
+ outputMark := options.AutoRedirectOutputMark
|
|
|
+ if outputMark == 0 {
|
|
|
+ outputMark = tun.DefaultAutoRedirectOutputMark
|
|
|
+ }
|
|
|
+
|
|
|
+ inbound := &Tun{
|
|
|
tag: tag,
|
|
|
ctx: ctx,
|
|
|
router: router,
|
|
@@ -76,30 +160,83 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
|
|
Name: options.InterfaceName,
|
|
|
MTU: tunMTU,
|
|
|
GSO: options.GSO,
|
|
|
- Inet4Address: options.Inet4Address,
|
|
|
- Inet6Address: options.Inet6Address,
|
|
|
+ Inet4Address: inet4Address,
|
|
|
+ Inet6Address: inet6Address,
|
|
|
AutoRoute: options.AutoRoute,
|
|
|
+ IPRoute2TableIndex: tableIndex,
|
|
|
+ IPRoute2RuleIndex: ruleIndex,
|
|
|
+ AutoRedirectInputMark: inputMark,
|
|
|
+ AutoRedirectOutputMark: outputMark,
|
|
|
StrictRoute: options.StrictRoute,
|
|
|
IncludeInterface: options.IncludeInterface,
|
|
|
ExcludeInterface: options.ExcludeInterface,
|
|
|
- Inet4RouteAddress: options.Inet4RouteAddress,
|
|
|
- Inet6RouteAddress: options.Inet6RouteAddress,
|
|
|
- Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress,
|
|
|
- Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress,
|
|
|
+ Inet4RouteAddress: inet4RouteAddress,
|
|
|
+ Inet6RouteAddress: inet6RouteAddress,
|
|
|
+ Inet4RouteExcludeAddress: inet4RouteExcludeAddress,
|
|
|
+ Inet6RouteExcludeAddress: inet6RouteExcludeAddress,
|
|
|
IncludeUID: includeUID,
|
|
|
ExcludeUID: excludeUID,
|
|
|
IncludeAndroidUser: options.IncludeAndroidUser,
|
|
|
IncludePackage: options.IncludePackage,
|
|
|
ExcludePackage: options.ExcludePackage,
|
|
|
InterfaceMonitor: router.InterfaceMonitor(),
|
|
|
- TableIndex: 2022,
|
|
|
},
|
|
|
endpointIndependentNat: options.EndpointIndependentNat,
|
|
|
udpTimeout: int64(udpTimeout.Seconds()),
|
|
|
stack: options.Stack,
|
|
|
platformInterface: platformInterface,
|
|
|
platformOptions: common.PtrValueOrDefault(options.Platform),
|
|
|
- }, nil
|
|
|
+ }
|
|
|
+ if options.AutoRedirect {
|
|
|
+ if !options.AutoRoute {
|
|
|
+ return nil, E.New("`auto_route` is required by `auto_redirect`")
|
|
|
+ }
|
|
|
+ disableNFTables, dErr := strconv.ParseBool(os.Getenv("DISABLE_NFTABLES"))
|
|
|
+ inbound.autoRedirect, err = tun.NewAutoRedirect(tun.AutoRedirectOptions{
|
|
|
+ TunOptions: &inbound.tunOptions,
|
|
|
+ Context: ctx,
|
|
|
+ Handler: inbound,
|
|
|
+ Logger: logger,
|
|
|
+ NetworkMonitor: router.NetworkMonitor(),
|
|
|
+ InterfaceFinder: router.InterfaceFinder(),
|
|
|
+ TableName: "sing-box",
|
|
|
+ DisableNFTables: dErr == nil && disableNFTables,
|
|
|
+ RouteAddressSet: &inbound.routeAddressSet,
|
|
|
+ RouteExcludeAddressSet: &inbound.routeExcludeAddressSet,
|
|
|
+ })
|
|
|
+ if err != nil {
|
|
|
+ return nil, E.Cause(err, "initialize auto-redirect")
|
|
|
+ }
|
|
|
+ if runtime.GOOS != "android" {
|
|
|
+ var markMode bool
|
|
|
+ for _, routeAddressSet := range options.RouteAddressSet {
|
|
|
+ ruleSet, loaded := router.RuleSet(routeAddressSet)
|
|
|
+ if !loaded {
|
|
|
+ return nil, E.New("parse route_address_set: rule-set not found: ", routeAddressSet)
|
|
|
+ }
|
|
|
+ ruleSet.IncRef()
|
|
|
+ inbound.routeRuleSet = append(inbound.routeRuleSet, ruleSet)
|
|
|
+ markMode = true
|
|
|
+ }
|
|
|
+ for _, routeExcludeAddressSet := range options.RouteExcludeAddressSet {
|
|
|
+ ruleSet, loaded := router.RuleSet(routeExcludeAddressSet)
|
|
|
+ if !loaded {
|
|
|
+ return nil, E.New("parse route_exclude_address_set: rule-set not found: ", routeExcludeAddressSet)
|
|
|
+ }
|
|
|
+ ruleSet.IncRef()
|
|
|
+ inbound.routeExcludeRuleSet = append(inbound.routeExcludeRuleSet, ruleSet)
|
|
|
+ markMode = true
|
|
|
+ }
|
|
|
+ if markMode {
|
|
|
+ inbound.tunOptions.AutoRedirectMarkMode = true
|
|
|
+ err = router.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return inbound, nil
|
|
|
}
|
|
|
|
|
|
func uidToRange(uidList option.Listable[uint32]) []ranges.Range[uint32] {
|
|
@@ -121,11 +258,11 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.
|
|
|
}
|
|
|
var start, end uint64
|
|
|
var err error
|
|
|
- start, err = strconv.ParseUint(uidRange[:subIndex], 10, 32)
|
|
|
+ start, err = strconv.ParseUint(uidRange[:subIndex], 0, 32)
|
|
|
if err != nil {
|
|
|
return nil, E.Cause(err, "parse range start")
|
|
|
}
|
|
|
- end, err = strconv.ParseUint(uidRange[subIndex+1:], 10, 32)
|
|
|
+ end, err = strconv.ParseUint(uidRange[subIndex+1:], 0, 32)
|
|
|
if err != nil {
|
|
|
return nil, E.Cause(err, "parse range end")
|
|
|
}
|
|
@@ -200,10 +337,58 @@ func (t *Tun) Start() error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+func (t *Tun) PostStart() error {
|
|
|
+ monitor := taskmonitor.New(t.logger, C.StartTimeout)
|
|
|
+ if t.autoRedirect != nil {
|
|
|
+ t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
|
|
|
+ for _, routeRuleSet := range t.routeRuleSet {
|
|
|
+ ipSets := routeRuleSet.ExtractIPSet()
|
|
|
+ if len(ipSets) == 0 {
|
|
|
+ t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name())
|
|
|
+ }
|
|
|
+ t.routeAddressSet = append(t.routeAddressSet, ipSets...)
|
|
|
+ }
|
|
|
+ t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
|
|
|
+ for _, routeExcludeRuleSet := range t.routeExcludeRuleSet {
|
|
|
+ ipSets := routeExcludeRuleSet.ExtractIPSet()
|
|
|
+ if len(ipSets) == 0 {
|
|
|
+ t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name())
|
|
|
+ }
|
|
|
+ t.routeExcludeAddressSet = append(t.routeExcludeAddressSet, ipSets...)
|
|
|
+ }
|
|
|
+ monitor.Start("initialize auto-redirect")
|
|
|
+ err := t.autoRedirect.Start()
|
|
|
+ monitor.Finish()
|
|
|
+ if err != nil {
|
|
|
+ return E.Cause(err, "auto-redirect")
|
|
|
+ }
|
|
|
+ for _, routeRuleSet := range t.routeRuleSet {
|
|
|
+ t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet))
|
|
|
+ routeRuleSet.DecRef()
|
|
|
+ }
|
|
|
+ for _, routeExcludeRuleSet := range t.routeExcludeRuleSet {
|
|
|
+ t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet))
|
|
|
+ routeExcludeRuleSet.DecRef()
|
|
|
+ }
|
|
|
+ t.routeAddressSet = nil
|
|
|
+ t.routeExcludeAddressSet = nil
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func (t *Tun) updateRouteAddressSet(it adapter.RuleSet) {
|
|
|
+ t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
|
|
|
+ t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
|
|
|
+ t.autoRedirect.UpdateRouteAddressSet()
|
|
|
+ t.routeAddressSet = nil
|
|
|
+ t.routeExcludeAddressSet = nil
|
|
|
+}
|
|
|
+
|
|
|
func (t *Tun) Close() error {
|
|
|
return common.Close(
|
|
|
t.tunStack,
|
|
|
t.tunIf,
|
|
|
+ t.autoRedirect,
|
|
|
)
|
|
|
}
|
|
|
|
|
@@ -215,7 +400,11 @@ func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata
|
|
|
metadata.Source = upstreamMetadata.Source
|
|
|
metadata.Destination = upstreamMetadata.Destination
|
|
|
metadata.InboundOptions = t.inboundOptions
|
|
|
- t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
|
|
+ if upstreamMetadata.Protocol != "" {
|
|
|
+ t.logger.InfoContext(ctx, "inbound ", upstreamMetadata.Protocol, " connection from ", metadata.Source)
|
|
|
+ } else {
|
|
|
+ t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
|
|
+ }
|
|
|
t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
|
|
err := t.router.RouteConnection(ctx, conn, metadata)
|
|
|
if err != nil {
|