mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-09-10 05:14:07 +08:00
Compare commits
22 Commits
3145f8c54c
...
2978c4aee1
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2978c4aee1 | ||
![]() |
0636a87db0 | ||
![]() |
fb4de45bbe | ||
![]() |
f9c5da2e57 | ||
![]() |
438b1b383b | ||
![]() |
8c0e899e37 | ||
![]() |
addc385f83 | ||
![]() |
8a7cd246bc | ||
![]() |
bcd0cd5d94 | ||
![]() |
caf39fa141 | ||
![]() |
a9f9ce92bb | ||
![]() |
06e588d9b8 | ||
![]() |
5442e05bba | ||
![]() |
49bf5fac99 | ||
![]() |
b5ba2ed1f0 | ||
![]() |
f6bee821e3 | ||
![]() |
a514f203a3 | ||
![]() |
345341fc05 | ||
![]() |
97113801e4 | ||
![]() |
c994065f74 | ||
![]() |
3328a20a9b | ||
![]() |
031f25c1c1 |
2
Makefile
2
Makefile
@ -6,7 +6,7 @@ GOHOSTOS = $(shell go env GOHOSTOS)
|
|||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
|
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
|
||||||
|
|
||||||
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid= -checklinkname=0"
|
||||||
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
|
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
PREFIX ?= $(shell go env GOPATH)
|
PREFIX ?= $(shell go env GOPATH)
|
||||||
|
@ -2,9 +2,12 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -18,6 +21,17 @@ type Outbound interface {
|
|||||||
N.Dialer
|
N.Dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OutboundWithPreferredRoutes interface {
|
||||||
|
Outbound
|
||||||
|
PreferredDomain(domain string) bool
|
||||||
|
PreferredAddress(address netip.Addr) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type DirectRouteOutbound interface {
|
||||||
|
Outbound
|
||||||
|
NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
|
||||||
|
}
|
||||||
|
|
||||||
type OutboundRegistry interface {
|
type OutboundRegistry interface {
|
||||||
option.OutboundOptionsRegistry
|
option.OutboundOptionsRegistry
|
||||||
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
||||||
|
@ -6,8 +6,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
@ -19,7 +21,7 @@ import (
|
|||||||
type Router interface {
|
type Router interface {
|
||||||
Lifecycle
|
Lifecycle
|
||||||
ConnectionRouter
|
ConnectionRouter
|
||||||
PreMatch(metadata InboundContext) error
|
PreMatch(metadata InboundContext, context tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
|
||||||
ConnectionRouterEx
|
ConnectionRouterEx
|
||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
NeedWIFIState() bool
|
NeedWIFIState() bool
|
||||||
|
7
box.go
7
box.go
@ -323,13 +323,14 @@ func New(options Options) (*Box, error) {
|
|||||||
option.DirectOutboundOptions{},
|
option.DirectOutboundOptions{},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
dnsTransportManager.Initialize(common.Must1(
|
dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) {
|
||||||
local.NewTransport(
|
return local.NewTransport(
|
||||||
ctx,
|
ctx,
|
||||||
logFactory.NewLogger("dns/local"),
|
logFactory.NewLogger("dns/local"),
|
||||||
"local",
|
"local",
|
||||||
option.LocalDNSServerOptions{},
|
option.LocalDNSServerOptions{},
|
||||||
)))
|
)
|
||||||
|
})
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
err = platformInterface.Initialize(networkManager)
|
err = platformInterface.Initialize(networkManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -46,7 +46,7 @@ var (
|
|||||||
sharedFlags []string
|
sharedFlags []string
|
||||||
debugFlags []string
|
debugFlags []string
|
||||||
sharedTags []string
|
sharedTags []string
|
||||||
darwinTags []string
|
macOSTags []string
|
||||||
memcTags []string
|
memcTags []string
|
||||||
notMemcTags []string
|
notMemcTags []string
|
||||||
debugTags []string
|
debugTags []string
|
||||||
@ -63,7 +63,7 @@ func init() {
|
|||||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||||
|
|
||||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
|
||||||
darwinTags = append(darwinTags, "with_dhcp")
|
macOSTags = append(macOSTags, "with_dhcp")
|
||||||
memcTags = append(memcTags, "with_tailscale")
|
memcTags = append(memcTags, "with_tailscale")
|
||||||
notMemcTags = append(notMemcTags, "with_low_memory")
|
notMemcTags = append(notMemcTags, "with_low_memory")
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
@ -160,7 +160,9 @@ func buildApple() {
|
|||||||
"-tags-not-macos=with_low_memory",
|
"-tags-not-macos=with_low_memory",
|
||||||
}
|
}
|
||||||
if !withTailscale {
|
if !withTailscale {
|
||||||
args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
|
args = append(args, "-tags-macos="+strings.Join(append(macOSTags, memcTags...), ","))
|
||||||
|
} else {
|
||||||
|
args = append(args, "-tags-macos="+strings.Join(macOSTags, ","))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
@ -169,7 +171,7 @@ func buildApple() {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := append(sharedTags, darwinTags...)
|
tags := sharedTags
|
||||||
if withTailscale {
|
if withTailscale {
|
||||||
tags = append(tags, memcTags...)
|
tags = append(tags, memcTags...)
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -69,7 +71,7 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = srs.Write(outputFile, plainRuleSet.Options, plainRuleSet.Version)
|
err = srs.Write(outputFile, plainRuleSet.Options, downgradeRuleSetVersion(plainRuleSet.Version, plainRuleSet.Options))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
os.Remove(outputPath)
|
os.Remove(outputPath)
|
||||||
@ -78,3 +80,18 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func downgradeRuleSetVersion(version uint8, options option.PlainRuleSet) uint8 {
|
||||||
|
if version == C.RuleSetVersion4 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
||||||
|
return rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 ||
|
||||||
|
len(rule.DefaultInterfaceAddress) > 0
|
||||||
|
}) {
|
||||||
|
version = C.RuleSetVersion3
|
||||||
|
}
|
||||||
|
if version == C.RuleSetVersion3 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
||||||
|
return len(rule.NetworkType) > 0 || rule.NetworkIsExpensive || rule.NetworkIsConstrained
|
||||||
|
}) {
|
||||||
|
version = C.RuleSetVersion2
|
||||||
|
}
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@ -43,7 +42,7 @@ type DefaultDialer struct {
|
|||||||
networkType []C.InterfaceType
|
networkType []C.InterfaceType
|
||||||
fallbackNetworkType []C.InterfaceType
|
fallbackNetworkType []C.InterfaceType
|
||||||
networkFallbackDelay time.Duration
|
networkFallbackDelay time.Duration
|
||||||
networkLastFallback atomic.TypedValue[time.Time]
|
networkLastFallback common.TypedValue[time.Time]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
||||||
@ -318,6 +317,14 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DefaultDialer) DialerForICMPDestination(destination netip.Addr) net.Dialer {
|
||||||
|
if !destination.Is6() {
|
||||||
|
return dialerFromTCPDialer(d.dialer6)
|
||||||
|
} else {
|
||||||
|
return dialerFromTCPDialer(d.dialer4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||||
if strategy == nil {
|
if strategy == nil {
|
||||||
strategy = d.networkStrategy
|
strategy = d.networkStrategy
|
||||||
|
@ -8,10 +8,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
@ -12,6 +12,8 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/domain"
|
"github.com/sagernet/sing/common/domain"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
|
|
||||||
"go4.org/netipx"
|
"go4.org/netipx"
|
||||||
@ -41,6 +43,8 @@ const (
|
|||||||
ruleItemNetworkType
|
ruleItemNetworkType
|
||||||
ruleItemNetworkIsExpensive
|
ruleItemNetworkIsExpensive
|
||||||
ruleItemNetworkIsConstrained
|
ruleItemNetworkIsConstrained
|
||||||
|
ruleItemNetworkInterfaceAddress
|
||||||
|
ruleItemDefaultInterfaceAddress
|
||||||
ruleItemFinal uint8 = 0xFF
|
ruleItemFinal uint8 = 0xFF
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -230,6 +234,51 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
rule.NetworkIsExpensive = true
|
rule.NetworkIsExpensive = true
|
||||||
case ruleItemNetworkIsConstrained:
|
case ruleItemNetworkIsConstrained:
|
||||||
rule.NetworkIsConstrained = true
|
rule.NetworkIsConstrained = true
|
||||||
|
case ruleItemNetworkInterfaceAddress:
|
||||||
|
rule.NetworkInterfaceAddress = new(badjson.TypedMap[option.InterfaceType, badoption.Listable[*badoption.Prefixable]])
|
||||||
|
var size uint64
|
||||||
|
size, err = binary.ReadUvarint(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := uint64(0); i < size; i++ {
|
||||||
|
var key uint8
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &key)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var value []*badoption.Prefixable
|
||||||
|
var prefixCount uint64
|
||||||
|
prefixCount, err = binary.ReadUvarint(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for j := uint64(0); j < prefixCount; j++ {
|
||||||
|
var prefix netip.Prefix
|
||||||
|
prefix, err = readPrefix(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value = append(value, common.Ptr(badoption.Prefixable(prefix)))
|
||||||
|
}
|
||||||
|
rule.NetworkInterfaceAddress.Put(option.InterfaceType(key), value)
|
||||||
|
}
|
||||||
|
case ruleItemDefaultInterfaceAddress:
|
||||||
|
var value []*badoption.Prefixable
|
||||||
|
var prefixCount uint64
|
||||||
|
prefixCount, err = binary.ReadUvarint(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for j := uint64(0); j < prefixCount; j++ {
|
||||||
|
var prefix netip.Prefix
|
||||||
|
prefix, err = readPrefix(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value = append(value, common.Ptr(badoption.Prefixable(prefix)))
|
||||||
|
}
|
||||||
|
rule.DefaultInterfaceAddress = value
|
||||||
case ruleItemFinal:
|
case ruleItemFinal:
|
||||||
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
|
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
|
||||||
return
|
return
|
||||||
@ -346,7 +395,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
}
|
}
|
||||||
if len(rule.NetworkType) > 0 {
|
if len(rule.NetworkType) > 0 {
|
||||||
if generateVersion < C.RuleSetVersion3 {
|
if generateVersion < C.RuleSetVersion3 {
|
||||||
return E.New("network_type rule item is only supported in version 3 or later")
|
return E.New("`network_type` rule item is only supported in version 3 or later")
|
||||||
}
|
}
|
||||||
err = writeRuleItemUint8(writer, ruleItemNetworkType, rule.NetworkType)
|
err = writeRuleItemUint8(writer, ruleItemNetworkType, rule.NetworkType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -354,17 +403,71 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rule.NetworkIsExpensive {
|
if rule.NetworkIsExpensive {
|
||||||
|
if generateVersion < C.RuleSetVersion3 {
|
||||||
|
return E.New("`network_is_expensive` rule item is only supported in version 3 or later")
|
||||||
|
}
|
||||||
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive)
|
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rule.NetworkIsConstrained {
|
if rule.NetworkIsConstrained {
|
||||||
|
if generateVersion < C.RuleSetVersion3 {
|
||||||
|
return E.New("`network_is_constrained` rule item is only supported in version 3 or later")
|
||||||
|
}
|
||||||
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained)
|
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 {
|
||||||
|
if generateVersion < C.RuleSetVersion4 {
|
||||||
|
return E.New("`network_interface_address` rule item is only supported in version 4 or later")
|
||||||
|
}
|
||||||
|
err = writer.WriteByte(ruleItemNetworkInterfaceAddress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = varbin.WriteUvarint(writer, uint64(rule.NetworkInterfaceAddress.Size()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, entry := range rule.NetworkInterfaceAddress.Entries() {
|
||||||
|
err = binary.Write(writer, binary.BigEndian, uint8(entry.Key.Build()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = varbin.WriteUvarint(writer, uint64(len(entry.Value)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, rawPrefix := range entry.Value {
|
||||||
|
err = writePrefix(writer, rawPrefix.Build(netip.Prefix{}))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(rule.DefaultInterfaceAddress) > 0 {
|
||||||
|
if generateVersion < C.RuleSetVersion4 {
|
||||||
|
return E.New("`default_interface_address` rule item is only supported in version 4 or later")
|
||||||
|
}
|
||||||
|
err = writer.WriteByte(ruleItemDefaultInterfaceAddress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = varbin.WriteUvarint(writer, uint64(len(rule.DefaultInterfaceAddress)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, rawPrefix := range rule.DefaultInterfaceAddress {
|
||||||
|
err = writePrefix(writer, rawPrefix.Build(netip.Prefix{}))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if len(rule.WIFISSID) > 0 {
|
if len(rule.WIFISSID) > 0 {
|
||||||
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
|
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
33
common/srs/ip_cidr.go
Normal file
33
common/srs/ip_cidr.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package srs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
"github.com/sagernet/sing/common/varbin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readPrefix(reader varbin.Reader) (netip.Prefix, error) {
|
||||||
|
addrSlice, err := varbin.ReadValue[[]byte](reader, binary.BigEndian)
|
||||||
|
if err != nil {
|
||||||
|
return netip.Prefix{}, err
|
||||||
|
}
|
||||||
|
prefixBits, err := varbin.ReadValue[uint8](reader, binary.BigEndian)
|
||||||
|
if err != nil {
|
||||||
|
return netip.Prefix{}, err
|
||||||
|
}
|
||||||
|
return netip.PrefixFrom(M.AddrFromIP(addrSlice), int(prefixBits)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePrefix(writer varbin.Writer, prefix netip.Prefix) error {
|
||||||
|
err := varbin.Write(writer, binary.BigEndian, prefix.Addr().AsSlice())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, uint8(prefix.Bits()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -2,10 +2,11 @@ package tls
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/badtls"
|
"github.com/sagernet/sing-box/common/badtls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@ -14,7 +15,7 @@ import (
|
|||||||
aTLS "github.com/sagernet/sing/common/tls"
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDialerFromOptions(ctx context.Context, router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
func NewDialerFromOptions(ctx context.Context, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return dialer, nil
|
return dialer, nil
|
||||||
}
|
}
|
||||||
@ -53,26 +54,57 @@ func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, e
|
|||||||
return tlsConn, nil
|
return tlsConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Dialer struct {
|
type Dialer interface {
|
||||||
|
N.Dialer
|
||||||
|
DialTLSContext(ctx context.Context, destination M.Socksaddr) (Conn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultDialer struct {
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
config Config
|
config Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDialer(dialer N.Dialer, config Config) N.Dialer {
|
func NewDialer(dialer N.Dialer, config Config) Dialer {
|
||||||
return &Dialer{dialer, config}
|
return &defaultDialer{dialer, config}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (d *defaultDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
if network != N.NetworkTCP {
|
if N.NetworkName(network) != N.NetworkTCP {
|
||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
conn, err := d.dialer.DialContext(ctx, network, destination)
|
return d.DialTLSContext(ctx, destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *defaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *defaultDialer) DialTLSContext(ctx context.Context, destination M.Socksaddr) (Conn, error) {
|
||||||
|
return d.dialContext(ctx, destination, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *defaultDialer) dialContext(ctx context.Context, destination M.Socksaddr, echRetry bool) (Conn, error) {
|
||||||
|
conn, err := d.dialer.DialContext(ctx, N.NetworkTCP, destination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return ClientHandshake(ctx, conn, d.config)
|
tlsConn, err := ClientHandshake(ctx, conn, d.config)
|
||||||
|
if err == nil {
|
||||||
|
return tlsConn, nil
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
if echRetry {
|
||||||
|
var echErr *tls.ECHRejectionError
|
||||||
|
if errors.As(err, &echErr) && len(echErr.RetryConfigList) > 0 {
|
||||||
|
if echConfig, isECH := d.config.(ECHCapableConfig); isECH {
|
||||||
|
echConfig.SetECHConfigList(echErr.RetryConfigList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return d.dialContext(ctx, destination, false)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *defaultDialer) Upstream() any {
|
||||||
return nil, os.ErrInvalid
|
return d.dialer
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,8 @@ const (
|
|||||||
RuleSetVersion1 = 1 + iota
|
RuleSetVersion1 = 1 + iota
|
||||||
RuleSetVersion2
|
RuleSetVersion2
|
||||||
RuleSetVersion3
|
RuleSetVersion3
|
||||||
RuleSetVersionCurrent = RuleSetVersion3
|
RuleSetVersion4
|
||||||
|
RuleSetVersionCurrent = RuleSetVersion4
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -9,10 +9,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/dns/transport"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
@ -29,6 +27,7 @@ import (
|
|||||||
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterTransport(registry *dns.TransportRegistry) {
|
func RegisterTransport(registry *dns.TransportRegistry) {
|
||||||
@ -45,9 +44,12 @@ type Transport struct {
|
|||||||
networkManager adapter.NetworkManager
|
networkManager adapter.NetworkManager
|
||||||
interfaceName string
|
interfaceName string
|
||||||
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
|
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
|
||||||
transports []adapter.DNSTransport
|
transportLock sync.RWMutex
|
||||||
updateAccess sync.Mutex
|
|
||||||
updatedAt time.Time
|
updatedAt time.Time
|
||||||
|
servers []M.Socksaddr
|
||||||
|
search []string
|
||||||
|
ndots int
|
||||||
|
attempts int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.DHCPDNSServerOptions) (adapter.DNSTransport, error) {
|
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.DHCPDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
@ -62,27 +64,40 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
networkManager: service.FromContext[adapter.NetworkManager](ctx),
|
networkManager: service.FromContext[adapter.NetworkManager](ctx),
|
||||||
interfaceName: options.Interface,
|
interfaceName: options.Interface,
|
||||||
|
ndots: 1,
|
||||||
|
attempts: 2,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewRawTransport(transportAdapter dns.TransportAdapter, ctx context.Context, dialer N.Dialer, logger log.ContextLogger) *Transport {
|
||||||
|
return &Transport{
|
||||||
|
TransportAdapter: transportAdapter,
|
||||||
|
ctx: ctx,
|
||||||
|
dialer: dialer,
|
||||||
|
logger: logger,
|
||||||
|
networkManager: service.FromContext[adapter.NetworkManager](ctx),
|
||||||
|
ndots: 1,
|
||||||
|
attempts: 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Transport) Start(stage adapter.StartStage) error {
|
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||||
if stage != adapter.StartStateStart {
|
if stage != adapter.StartStateStart {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
err := t.fetchServers()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if t.interfaceName == "" {
|
if t.interfaceName == "" {
|
||||||
t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated)
|
t.interfaceCallback = t.networkManager.InterfaceMonitor().RegisterCallback(t.interfaceUpdated)
|
||||||
}
|
}
|
||||||
|
go func() {
|
||||||
|
_, err := t.Fetch()
|
||||||
|
if err != nil {
|
||||||
|
t.logger.Error(E.Cause(err, "fetch DNS servers"))
|
||||||
|
}
|
||||||
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Close() error {
|
func (t *Transport) Close() error {
|
||||||
for _, transport := range t.transports {
|
|
||||||
transport.Close()
|
|
||||||
}
|
|
||||||
if t.interfaceCallback != nil {
|
if t.interfaceCallback != nil {
|
||||||
t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)
|
t.networkManager.InterfaceMonitor().UnregisterCallback(t.interfaceCallback)
|
||||||
}
|
}
|
||||||
@ -90,23 +105,44 @@ func (t *Transport) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
err := t.fetchServers()
|
servers, err := t.Fetch()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(servers) == 0 {
|
||||||
if len(t.transports) == 0 {
|
|
||||||
return nil, E.New("dhcp: empty DNS servers from response")
|
return nil, E.New("dhcp: empty DNS servers from response")
|
||||||
}
|
}
|
||||||
|
return t.Exchange0(ctx, message, servers)
|
||||||
|
}
|
||||||
|
|
||||||
var response *mDNS.Msg
|
func (t *Transport) Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error) {
|
||||||
for _, transport := range t.transports {
|
question := message.Question[0]
|
||||||
response, err = transport.Exchange(ctx, message)
|
domain := dns.FqdnToDomain(question.Name)
|
||||||
if err == nil {
|
if len(servers) == 1 || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
|
||||||
return response, nil
|
return t.exchangeSingleRequest(ctx, servers, message, domain)
|
||||||
}
|
} else {
|
||||||
|
return t.exchangeParallel(ctx, servers, message, domain)
|
||||||
}
|
}
|
||||||
return nil, err
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Fetch() ([]M.Socksaddr, error) {
|
||||||
|
t.transportLock.RLock()
|
||||||
|
updatedAt := t.updatedAt
|
||||||
|
servers := t.servers
|
||||||
|
t.transportLock.RUnlock()
|
||||||
|
if time.Since(updatedAt) < C.DHCPTTL {
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
|
t.transportLock.Lock()
|
||||||
|
defer t.transportLock.Unlock()
|
||||||
|
if time.Since(t.updatedAt) < C.DHCPTTL {
|
||||||
|
return t.servers, nil
|
||||||
|
}
|
||||||
|
err := t.updateServers()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return t.servers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) fetchInterface() (*control.Interface, error) {
|
func (t *Transport) fetchInterface() (*control.Interface, error) {
|
||||||
@ -124,18 +160,6 @@ func (t *Transport) fetchInterface() (*control.Interface, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) fetchServers() error {
|
|
||||||
if time.Since(t.updatedAt) < C.DHCPTTL {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
t.updateAccess.Lock()
|
|
||||||
defer t.updateAccess.Unlock()
|
|
||||||
if time.Since(t.updatedAt) < C.DHCPTTL {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return t.updateServers()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) updateServers() error {
|
func (t *Transport) updateServers() error {
|
||||||
iface, err := t.fetchInterface()
|
iface, err := t.fetchInterface()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -148,7 +172,7 @@ func (t *Transport) updateServers() error {
|
|||||||
cancel()
|
cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if len(t.transports) == 0 {
|
} else if len(t.servers) == 0 {
|
||||||
return E.New("dhcp: empty DNS servers response")
|
return E.New("dhcp: empty DNS servers response")
|
||||||
} else {
|
} else {
|
||||||
t.updatedAt = time.Now()
|
t.updatedAt = time.Now()
|
||||||
@ -177,7 +201,11 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *control.Interface)
|
|||||||
}
|
}
|
||||||
defer packetConn.Close()
|
defer packetConn.Close()
|
||||||
|
|
||||||
discovery, err := dhcpv4.NewDiscovery(iface.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(dhcpv4.OptionDomainNameServer))
|
discovery, err := dhcpv4.NewDiscovery(iface.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(
|
||||||
|
dhcpv4.OptionDomainName,
|
||||||
|
dhcpv4.OptionDomainNameServer,
|
||||||
|
dhcpv4.OptionDNSDomainSearchList,
|
||||||
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -223,31 +251,23 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
dns := dhcpPacket.DNS()
|
return t.recreateServers(iface, dhcpPacket)
|
||||||
if len(dns) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return t.recreateServers(iface, common.Map(dns, func(it net.IP) M.Socksaddr {
|
|
||||||
return M.SocksaddrFrom(M.AddrFromIP(it), 53)
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) recreateServers(iface *control.Interface, serverAddrs []M.Socksaddr) error {
|
func (t *Transport) recreateServers(iface *control.Interface, dhcpPacket *dhcpv4.DHCPv4) error {
|
||||||
if len(serverAddrs) > 0 {
|
searchList := dhcpPacket.DomainSearch()
|
||||||
t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "]")
|
if searchList != nil && len(searchList.Labels) > 0 {
|
||||||
|
t.search = searchList.Labels
|
||||||
|
} else if dhcpPacket.DomainName() != "" {
|
||||||
|
t.search = []string{dhcpPacket.DomainName()}
|
||||||
}
|
}
|
||||||
serverDialer := common.Must1(dialer.NewDefault(t.ctx, option.DialerOptions{
|
serverAddrs := common.Map(dhcpPacket.DNS(), func(it net.IP) M.Socksaddr {
|
||||||
BindInterface: iface.Name,
|
return M.SocksaddrFrom(M.AddrFromIP(it), 53)
|
||||||
UDPFragmentDefault: true,
|
})
|
||||||
}))
|
if len(serverAddrs) > 0 && !slices.Equal(t.servers, serverAddrs) {
|
||||||
var transports []adapter.DNSTransport
|
t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "], search: [", strings.Join(t.search, ","), "]")
|
||||||
for _, serverAddr := range serverAddrs {
|
|
||||||
transports = append(transports, transport.NewUDPRaw(t.logger, t.TransportAdapter, serverDialer, serverAddr))
|
|
||||||
}
|
}
|
||||||
for _, transport := range t.transports {
|
t.servers = serverAddrs
|
||||||
transport.Close()
|
|
||||||
}
|
|
||||||
t.transports = transports
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
202
dns/transport/dhcp/dhcp_shared.go
Normal file
202
dns/transport/dhcp/dhcp_shared.go
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
package dhcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/rand"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// net.maxDNSPacketSize
|
||||||
|
maxDNSPacketSize = 1232
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *Transport) exchangeSingleRequest(ctx context.Context, servers []M.Socksaddr, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
||||||
|
var lastErr error
|
||||||
|
for _, fqdn := range t.nameList(domain) {
|
||||||
|
response, err := t.tryOneName(ctx, servers, fqdn, message)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
return nil, lastErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) exchangeParallel(ctx context.Context, servers []M.Socksaddr, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
||||||
|
returned := make(chan struct{})
|
||||||
|
defer close(returned)
|
||||||
|
type queryResult struct {
|
||||||
|
response *mDNS.Msg
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
results := make(chan queryResult)
|
||||||
|
startRacer := func(ctx context.Context, fqdn string) {
|
||||||
|
response, err := t.tryOneName(ctx, servers, fqdn, message)
|
||||||
|
if err == nil {
|
||||||
|
if response.Rcode != mDNS.RcodeSuccess {
|
||||||
|
err = dns.RcodeError(response.Rcode)
|
||||||
|
} else if len(dns.MessageToAddresses(response)) == 0 {
|
||||||
|
err = E.New(fqdn, ": empty result")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case results <- queryResult{response, err}:
|
||||||
|
case <-returned:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queryCtx, queryCancel := context.WithCancel(ctx)
|
||||||
|
defer queryCancel()
|
||||||
|
var nameCount int
|
||||||
|
for _, fqdn := range t.nameList(domain) {
|
||||||
|
nameCount++
|
||||||
|
go startRacer(queryCtx, fqdn)
|
||||||
|
}
|
||||||
|
var errors []error
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case result := <-results:
|
||||||
|
if result.err == nil {
|
||||||
|
return result.response, nil
|
||||||
|
}
|
||||||
|
errors = append(errors, result.err)
|
||||||
|
if len(errors) == nameCount {
|
||||||
|
return nil, E.Errors(errors...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) tryOneName(ctx context.Context, servers []M.Socksaddr, fqdn string, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
sLen := len(servers)
|
||||||
|
var lastErr error
|
||||||
|
for i := 0; i < t.attempts; i++ {
|
||||||
|
for j := 0; j < sLen; j++ {
|
||||||
|
server := servers[j]
|
||||||
|
question := message.Question[0]
|
||||||
|
question.Name = fqdn
|
||||||
|
response, err := t.exchangeOne(ctx, server, question, C.DNSTimeout, false, true)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, E.Cause(lastErr, fqdn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) {
|
||||||
|
if server.Port == 0 {
|
||||||
|
server.Port = 53
|
||||||
|
}
|
||||||
|
var networks []string
|
||||||
|
if useTCP {
|
||||||
|
networks = []string{N.NetworkTCP}
|
||||||
|
} else {
|
||||||
|
networks = []string{N.NetworkUDP, N.NetworkTCP}
|
||||||
|
}
|
||||||
|
request := &mDNS.Msg{
|
||||||
|
MsgHdr: mDNS.MsgHdr{
|
||||||
|
Id: uint16(rand.Uint32()),
|
||||||
|
RecursionDesired: true,
|
||||||
|
AuthenticatedData: ad,
|
||||||
|
},
|
||||||
|
Question: []mDNS.Question{question},
|
||||||
|
Compress: true,
|
||||||
|
}
|
||||||
|
request.SetEdns0(maxDNSPacketSize, false)
|
||||||
|
buffer := buf.Get(buf.UDPBufferSize)
|
||||||
|
defer buf.Put(buffer)
|
||||||
|
for _, network := range networks {
|
||||||
|
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
|
||||||
|
defer cancel()
|
||||||
|
conn, err := t.dialer.DialContext(ctx, network, server)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
||||||
|
conn.SetDeadline(deadline)
|
||||||
|
}
|
||||||
|
rawMessage, err := request.PackBuffer(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "pack request")
|
||||||
|
}
|
||||||
|
_, err = conn.Write(rawMessage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "write request")
|
||||||
|
}
|
||||||
|
n, err := conn.Read(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read response")
|
||||||
|
}
|
||||||
|
var response mDNS.Msg
|
||||||
|
err = response.Unpack(buffer[:n])
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "unpack response")
|
||||||
|
}
|
||||||
|
if response.Truncated && network == N.NetworkUDP {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
panic("unexpected")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) nameList(name string) []string {
|
||||||
|
l := len(name)
|
||||||
|
rooted := l > 0 && name[l-1] == '.'
|
||||||
|
if l > 254 || l == 254 && !rooted {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if rooted {
|
||||||
|
if avoidDNS(name) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return []string{name}
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNdots := strings.Count(name, ".") >= t.ndots
|
||||||
|
name += "."
|
||||||
|
// l++
|
||||||
|
|
||||||
|
names := make([]string, 0, 1+len(t.search))
|
||||||
|
if hasNdots && !avoidDNS(name) {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
for _, suffix := range t.search {
|
||||||
|
fqdn := name + suffix
|
||||||
|
if !avoidDNS(fqdn) && len(fqdn) <= 254 {
|
||||||
|
names = append(names, fqdn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasNdots && !avoidDNS(name) {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
func avoidDNS(name string) bool {
|
||||||
|
if name == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if name[len(name)-1] == '.' {
|
||||||
|
name = name[:len(name)-1]
|
||||||
|
}
|
||||||
|
return strings.HasSuffix(name, ".onion")
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
|
//go:build !darwin
|
||||||
|
|
||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
@ -11,21 +11,27 @@ import (
|
|||||||
"github.com/sagernet/sing-box/dns/transport/hosts"
|
"github.com/sagernet/sing-box/dns/transport/hosts"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
"github.com/sagernet/sing/common/logger"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func RegisterTransport(registry *dns.TransportRegistry) {
|
||||||
|
dns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewTransport)
|
||||||
|
}
|
||||||
|
|
||||||
var _ adapter.DNSTransport = (*Transport)(nil)
|
var _ adapter.DNSTransport = (*Transport)(nil)
|
||||||
|
|
||||||
type Transport struct {
|
type Transport struct {
|
||||||
dns.TransportAdapter
|
dns.TransportAdapter
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
hosts *hosts.File
|
logger logger.ContextLogger
|
||||||
dialer N.Dialer
|
hosts *hosts.File
|
||||||
|
dialer N.Dialer
|
||||||
|
preferGo bool
|
||||||
|
resolved ResolvedResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
|
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
@ -36,20 +42,45 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
|||||||
return &Transport{
|
return &Transport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
logger: logger,
|
||||||
hosts: hosts.NewFile(hosts.DefaultPath),
|
hosts: hosts.NewFile(hosts.DefaultPath),
|
||||||
dialer: transportDialer,
|
dialer: transportDialer,
|
||||||
|
preferGo: options.PreferGo,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Start(stage adapter.StartStage) error {
|
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||||
|
switch stage {
|
||||||
|
case adapter.StartStateInitialize:
|
||||||
|
if !t.preferGo {
|
||||||
|
resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger)
|
||||||
|
if err == nil {
|
||||||
|
err = resolvedResolver.Start()
|
||||||
|
if err == nil {
|
||||||
|
t.resolved = resolvedResolver
|
||||||
|
} else {
|
||||||
|
t.logger.Warn(E.Cause(err, "initialize resolved resolver"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Close() error {
|
func (t *Transport) Close() error {
|
||||||
|
if t.resolved != nil {
|
||||||
|
return t.resolved.Close()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
if t.resolved != nil {
|
||||||
|
resolverObject := t.resolved.Object()
|
||||||
|
if resolverObject != nil {
|
||||||
|
return t.resolved.Exchange(resolverObject, ctx, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
domain := dns.FqdnToDomain(question.Name)
|
domain := dns.FqdnToDomain(question.Name)
|
||||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
@ -58,147 +89,5 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
|||||||
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
systemConfig := getSystemDNSConfig(t.ctx)
|
return t.exchange(ctx, message, domain)
|
||||||
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
|
|
||||||
return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
|
|
||||||
} else {
|
|
||||||
return t.exchangeParallel(ctx, systemConfig, message, domain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) exchangeSingleRequest(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
|
||||||
var lastErr error
|
|
||||||
for _, fqdn := range systemConfig.nameList(domain) {
|
|
||||||
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
|
||||||
if err != nil {
|
|
||||||
lastErr = err
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
return nil, lastErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
|
||||||
returned := make(chan struct{})
|
|
||||||
defer close(returned)
|
|
||||||
type queryResult struct {
|
|
||||||
response *mDNS.Msg
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
results := make(chan queryResult)
|
|
||||||
startRacer := func(ctx context.Context, fqdn string) {
|
|
||||||
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
|
||||||
if err == nil {
|
|
||||||
if response.Rcode != mDNS.RcodeSuccess {
|
|
||||||
err = dns.RcodeError(response.Rcode)
|
|
||||||
} else if len(dns.MessageToAddresses(response)) == 0 {
|
|
||||||
err = E.New(fqdn, ": empty result")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case results <- queryResult{response, err}:
|
|
||||||
case <-returned:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
queryCtx, queryCancel := context.WithCancel(ctx)
|
|
||||||
defer queryCancel()
|
|
||||||
var nameCount int
|
|
||||||
for _, fqdn := range systemConfig.nameList(domain) {
|
|
||||||
nameCount++
|
|
||||||
go startRacer(queryCtx, fqdn)
|
|
||||||
}
|
|
||||||
var errors []error
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
case result := <-results:
|
|
||||||
if result.err == nil {
|
|
||||||
return result.response, nil
|
|
||||||
}
|
|
||||||
errors = append(errors, result.err)
|
|
||||||
if len(errors) == nameCount {
|
|
||||||
return nil, E.Errors(errors...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) tryOneName(ctx context.Context, config *dnsConfig, fqdn string, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
serverOffset := config.serverOffset()
|
|
||||||
sLen := uint32(len(config.servers))
|
|
||||||
var lastErr error
|
|
||||||
for i := 0; i < config.attempts; i++ {
|
|
||||||
for j := uint32(0); j < sLen; j++ {
|
|
||||||
server := config.servers[(serverOffset+j)%sLen]
|
|
||||||
question := message.Question[0]
|
|
||||||
question.Name = fqdn
|
|
||||||
response, err := t.exchangeOne(ctx, M.ParseSocksaddr(server), question, config.timeout, config.useTCP, config.trustAD)
|
|
||||||
if err != nil {
|
|
||||||
lastErr = err
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, E.Cause(lastErr, fqdn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) {
|
|
||||||
if server.Port == 0 {
|
|
||||||
server.Port = 53
|
|
||||||
}
|
|
||||||
var networks []string
|
|
||||||
if useTCP {
|
|
||||||
networks = []string{N.NetworkTCP}
|
|
||||||
} else {
|
|
||||||
networks = []string{N.NetworkUDP, N.NetworkTCP}
|
|
||||||
}
|
|
||||||
request := &mDNS.Msg{
|
|
||||||
MsgHdr: mDNS.MsgHdr{
|
|
||||||
Id: uint16(rand.Uint32()),
|
|
||||||
RecursionDesired: true,
|
|
||||||
AuthenticatedData: ad,
|
|
||||||
},
|
|
||||||
Question: []mDNS.Question{question},
|
|
||||||
Compress: true,
|
|
||||||
}
|
|
||||||
request.SetEdns0(maxDNSPacketSize, false)
|
|
||||||
buffer := buf.Get(buf.UDPBufferSize)
|
|
||||||
defer buf.Put(buffer)
|
|
||||||
for _, network := range networks {
|
|
||||||
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
|
|
||||||
defer cancel()
|
|
||||||
conn, err := t.dialer.DialContext(ctx, network, server)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
|
||||||
conn.SetDeadline(deadline)
|
|
||||||
}
|
|
||||||
rawMessage, err := request.PackBuffer(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "pack request")
|
|
||||||
}
|
|
||||||
_, err = conn.Write(rawMessage)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "write request")
|
|
||||||
}
|
|
||||||
n, err := conn.Read(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "read response")
|
|
||||||
}
|
|
||||||
var response mDNS.Msg
|
|
||||||
err = response.Unpack(buffer[:n])
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "unpack response")
|
|
||||||
}
|
|
||||||
if response.Truncated && network == N.NetworkUDP {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return &response, nil
|
|
||||||
}
|
|
||||||
panic("unexpected")
|
|
||||||
}
|
}
|
||||||
|
143
dns/transport/local/local_darwin.go
Normal file
143
dns/transport/local/local_darwin.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/hosts"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
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/service"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterTransport(registry *dns.TransportRegistry) {
|
||||||
|
dns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewTransport)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.DNSTransport = (*Transport)(nil)
|
||||||
|
|
||||||
|
type Transport struct {
|
||||||
|
dns.TransportAdapter
|
||||||
|
ctx context.Context
|
||||||
|
logger logger.ContextLogger
|
||||||
|
hosts *hosts.File
|
||||||
|
dialer N.Dialer
|
||||||
|
preferGo bool
|
||||||
|
fallback bool
|
||||||
|
dhcpTransport dhcpTransport
|
||||||
|
resolver net.Resolver
|
||||||
|
}
|
||||||
|
|
||||||
|
type dhcpTransport interface {
|
||||||
|
adapter.DNSTransport
|
||||||
|
Fetch() ([]M.Socksaddr, error)
|
||||||
|
Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
|
transportDialer, err := dns.NewLocalDialer(ctx, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
transportAdapter := dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options)
|
||||||
|
return &Transport{
|
||||||
|
TransportAdapter: transportAdapter,
|
||||||
|
ctx: ctx,
|
||||||
|
logger: logger,
|
||||||
|
hosts: hosts.NewFile(hosts.DefaultPath),
|
||||||
|
dialer: transportDialer,
|
||||||
|
preferGo: options.PreferGo,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
inboundManager := service.FromContext[adapter.InboundManager](t.ctx)
|
||||||
|
for _, inbound := range inboundManager.Inbounds() {
|
||||||
|
if inbound.Type() == C.TypeTun {
|
||||||
|
t.fallback = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !C.IsIos {
|
||||||
|
if t.fallback {
|
||||||
|
t.dhcpTransport = newDHCPTransport(t.TransportAdapter, log.ContextWithOverrideLevel(t.ctx, log.LevelDebug), t.dialer, t.logger)
|
||||||
|
if t.dhcpTransport != nil {
|
||||||
|
err := t.dhcpTransport.Start(stage)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Close() error {
|
||||||
|
return common.Close(
|
||||||
|
t.dhcpTransport,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
question := message.Question[0]
|
||||||
|
domain := dns.FqdnToDomain(question.Name)
|
||||||
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
|
addresses := t.hosts.Lookup(domain)
|
||||||
|
if len(addresses) > 0 {
|
||||||
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !t.fallback {
|
||||||
|
return t.exchange(ctx, message, domain)
|
||||||
|
}
|
||||||
|
if !C.IsIos {
|
||||||
|
if t.dhcpTransport != nil {
|
||||||
|
dhcpTransports, _ := t.dhcpTransport.Fetch()
|
||||||
|
if len(dhcpTransports) > 0 {
|
||||||
|
return t.dhcpTransport.Exchange0(ctx, message, dhcpTransports)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t.preferGo {
|
||||||
|
// Assuming the user knows what they are doing, we still execute the query which will fail.
|
||||||
|
return t.exchange(ctx, message, domain)
|
||||||
|
}
|
||||||
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
|
var network string
|
||||||
|
if question.Qtype == mDNS.TypeA {
|
||||||
|
network = "ip4"
|
||||||
|
} else {
|
||||||
|
network = "ip6"
|
||||||
|
}
|
||||||
|
addresses, err := t.resolver.LookupNetIP(ctx, network, domain)
|
||||||
|
if err != nil {
|
||||||
|
var dnsError *net.DNSError
|
||||||
|
if errors.As(err, &dnsError) && dnsError.IsNotFound {
|
||||||
|
return nil, dns.RcodeRefused
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
|
}
|
||||||
|
if C.IsIos {
|
||||||
|
return nil, E.New("only A and AAAA queries are supported on iOS and tvOS when using NetworkExtension.")
|
||||||
|
} else {
|
||||||
|
return nil, E.New("only A and AAAA queries are supported on macOS when using NetworkExtension and DHCP unavailable.")
|
||||||
|
}
|
||||||
|
}
|
16
dns/transport/local/local_darwin_dhcp.go
Normal file
16
dns/transport/local/local_darwin_dhcp.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
//go:build darwin && with_dhcp
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/dhcp"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newDHCPTransport(transportAdapter dns.TransportAdapter, ctx context.Context, dialer N.Dialer, logger log.ContextLogger) dhcpTransport {
|
||||||
|
return dhcp.NewRawTransport(transportAdapter, ctx, dialer, logger)
|
||||||
|
}
|
15
dns/transport/local/local_darwin_nodhcp.go
Normal file
15
dns/transport/local/local_darwin_nodhcp.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
//go:build darwin && !with_dhcp
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newDHCPTransport(transportAdapter dns.TransportAdapter, ctx context.Context, dialer N.Dialer, logger log.ContextLogger) dhcpTransport {
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,205 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/dns"
|
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterTransport(registry *dns.TransportRegistry) {
|
|
||||||
dns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewFallbackTransport)
|
|
||||||
}
|
|
||||||
|
|
||||||
type FallbackTransport struct {
|
|
||||||
adapter.DNSTransport
|
|
||||||
ctx context.Context
|
|
||||||
fallback bool
|
|
||||||
resolver net.Resolver
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFallbackTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
|
|
||||||
transport, err := NewTransport(ctx, logger, tag, options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &FallbackTransport{
|
|
||||||
DNSTransport: transport,
|
|
||||||
ctx: ctx,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FallbackTransport) Start(stage adapter.StartStage) error {
|
|
||||||
if stage != adapter.StartStateStart {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
platformInterface := service.FromContext[platform.Interface](f.ctx)
|
|
||||||
if platformInterface == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
inboundManager := service.FromContext[adapter.InboundManager](f.ctx)
|
|
||||||
for _, inbound := range inboundManager.Inbounds() {
|
|
||||||
if inbound.Type() == C.TypeTun {
|
|
||||||
// platform tun hijacks DNS, so we can only use cgo resolver here
|
|
||||||
f.fallback = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FallbackTransport) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FallbackTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
if !f.fallback {
|
|
||||||
return f.DNSTransport.Exchange(ctx, message)
|
|
||||||
}
|
|
||||||
question := message.Question[0]
|
|
||||||
domain := dns.FqdnToDomain(question.Name)
|
|
||||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
|
||||||
var network string
|
|
||||||
if question.Qtype == mDNS.TypeA {
|
|
||||||
network = "ip4"
|
|
||||||
} else {
|
|
||||||
network = "ip6"
|
|
||||||
}
|
|
||||||
addresses, err := f.resolver.LookupNetIP(ctx, network, domain)
|
|
||||||
if err != nil {
|
|
||||||
var dnsError *net.DNSError
|
|
||||||
if errors.As(err, &dnsError) && dnsError.IsNotFound {
|
|
||||||
return nil, dns.RcodeRefused
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
|
||||||
} else if question.Qtype == mDNS.TypeNS {
|
|
||||||
records, err := f.resolver.LookupNS(ctx, domain)
|
|
||||||
if err != nil {
|
|
||||||
var dnsError *net.DNSError
|
|
||||||
if errors.As(err, &dnsError) && dnsError.IsNotFound {
|
|
||||||
return nil, dns.RcodeRefused
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
response := &mDNS.Msg{
|
|
||||||
MsgHdr: mDNS.MsgHdr{
|
|
||||||
Id: message.Id,
|
|
||||||
Rcode: mDNS.RcodeSuccess,
|
|
||||||
Response: true,
|
|
||||||
},
|
|
||||||
Question: []mDNS.Question{question},
|
|
||||||
}
|
|
||||||
for _, record := range records {
|
|
||||||
response.Answer = append(response.Answer, &mDNS.NS{
|
|
||||||
Hdr: mDNS.RR_Header{
|
|
||||||
Name: question.Name,
|
|
||||||
Rrtype: mDNS.TypeNS,
|
|
||||||
Class: mDNS.ClassINET,
|
|
||||||
Ttl: C.DefaultDNSTTL,
|
|
||||||
},
|
|
||||||
Ns: record.Host,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
} else if question.Qtype == mDNS.TypeCNAME {
|
|
||||||
cname, err := f.resolver.LookupCNAME(ctx, domain)
|
|
||||||
if err != nil {
|
|
||||||
var dnsError *net.DNSError
|
|
||||||
if errors.As(err, &dnsError) && dnsError.IsNotFound {
|
|
||||||
return nil, dns.RcodeRefused
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &mDNS.Msg{
|
|
||||||
MsgHdr: mDNS.MsgHdr{
|
|
||||||
Id: message.Id,
|
|
||||||
Rcode: mDNS.RcodeSuccess,
|
|
||||||
Response: true,
|
|
||||||
},
|
|
||||||
Question: []mDNS.Question{question},
|
|
||||||
Answer: []mDNS.RR{
|
|
||||||
&mDNS.CNAME{
|
|
||||||
Hdr: mDNS.RR_Header{
|
|
||||||
Name: question.Name,
|
|
||||||
Rrtype: mDNS.TypeCNAME,
|
|
||||||
Class: mDNS.ClassINET,
|
|
||||||
Ttl: C.DefaultDNSTTL,
|
|
||||||
},
|
|
||||||
Target: cname,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
} else if question.Qtype == mDNS.TypeTXT {
|
|
||||||
records, err := f.resolver.LookupTXT(ctx, domain)
|
|
||||||
if err != nil {
|
|
||||||
var dnsError *net.DNSError
|
|
||||||
if errors.As(err, &dnsError) && dnsError.IsNotFound {
|
|
||||||
return nil, dns.RcodeRefused
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &mDNS.Msg{
|
|
||||||
MsgHdr: mDNS.MsgHdr{
|
|
||||||
Id: message.Id,
|
|
||||||
Rcode: mDNS.RcodeSuccess,
|
|
||||||
Response: true,
|
|
||||||
},
|
|
||||||
Question: []mDNS.Question{question},
|
|
||||||
Answer: []mDNS.RR{
|
|
||||||
&mDNS.TXT{
|
|
||||||
Hdr: mDNS.RR_Header{
|
|
||||||
Name: question.Name,
|
|
||||||
Rrtype: mDNS.TypeCNAME,
|
|
||||||
Class: mDNS.ClassINET,
|
|
||||||
Ttl: C.DefaultDNSTTL,
|
|
||||||
},
|
|
||||||
Txt: records,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
} else if question.Qtype == mDNS.TypeMX {
|
|
||||||
records, err := f.resolver.LookupMX(ctx, domain)
|
|
||||||
if err != nil {
|
|
||||||
var dnsError *net.DNSError
|
|
||||||
if errors.As(err, &dnsError) && dnsError.IsNotFound {
|
|
||||||
return nil, dns.RcodeRefused
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
response := &mDNS.Msg{
|
|
||||||
MsgHdr: mDNS.MsgHdr{
|
|
||||||
Id: message.Id,
|
|
||||||
Rcode: mDNS.RcodeSuccess,
|
|
||||||
Response: true,
|
|
||||||
},
|
|
||||||
Question: []mDNS.Question{question},
|
|
||||||
}
|
|
||||||
for _, record := range records {
|
|
||||||
response.Answer = append(response.Answer, &mDNS.MX{
|
|
||||||
Hdr: mDNS.RR_Header{
|
|
||||||
Name: question.Name,
|
|
||||||
Rrtype: mDNS.TypeA,
|
|
||||||
Class: mDNS.ClassINET,
|
|
||||||
Ttl: C.DefaultDNSTTL,
|
|
||||||
},
|
|
||||||
Preference: record.Pref,
|
|
||||||
Mx: record.Host,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
} else {
|
|
||||||
return nil, E.New("only A, AAAA, NS, CNAME, TXT, MX queries are supported on current platform when using TUN, please switch to a fixed DNS server.")
|
|
||||||
}
|
|
||||||
}
|
|
14
dns/transport/local/local_resolved.go
Normal file
14
dns/transport/local/local_resolved.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResolvedResolver interface {
|
||||||
|
Start() error
|
||||||
|
Close() error
|
||||||
|
Object() any
|
||||||
|
Exchange(object any, ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error)
|
||||||
|
}
|
229
dns/transport/local/local_resolved_linux.go
Normal file
229
dns/transport/local/local_resolved_linux.go
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/service/resolved"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBusResolvedResolver struct {
|
||||||
|
ctx context.Context
|
||||||
|
logger logger.ContextLogger
|
||||||
|
interfaceMonitor tun.DefaultInterfaceMonitor
|
||||||
|
interfaceCallback *list.Element[tun.DefaultInterfaceUpdateCallback]
|
||||||
|
systemBus *dbus.Conn
|
||||||
|
resoledObject atomic.Pointer[ResolvedObject]
|
||||||
|
closeOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResolvedObject struct {
|
||||||
|
dbus.BusObject
|
||||||
|
InterfaceIndex int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) {
|
||||||
|
interfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor()
|
||||||
|
if interfaceMonitor == nil {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
systemBus, err := dbus.SystemBus()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &DBusResolvedResolver{
|
||||||
|
ctx: ctx,
|
||||||
|
logger: logger,
|
||||||
|
interfaceMonitor: interfaceMonitor,
|
||||||
|
systemBus: systemBus,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DBusResolvedResolver) Start() error {
|
||||||
|
t.updateStatus()
|
||||||
|
t.interfaceCallback = t.interfaceMonitor.RegisterCallback(t.updateDefaultInterface)
|
||||||
|
err := t.systemBus.BusObject().AddMatchSignal(
|
||||||
|
"org.freedesktop.DBus",
|
||||||
|
"NameOwnerChanged",
|
||||||
|
dbus.WithMatchSender("org.freedesktop.DBus"),
|
||||||
|
dbus.WithMatchArg(0, "org.freedesktop.resolve1.Manager"),
|
||||||
|
).Err
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "configure resolved restart listener")
|
||||||
|
}
|
||||||
|
go t.loopUpdateStatus()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DBusResolvedResolver) Close() error {
|
||||||
|
t.closeOnce.Do(func() {
|
||||||
|
if t.interfaceCallback != nil {
|
||||||
|
t.interfaceMonitor.UnregisterCallback(t.interfaceCallback)
|
||||||
|
}
|
||||||
|
if t.systemBus != nil {
|
||||||
|
_ = t.systemBus.Close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DBusResolvedResolver) Object() any {
|
||||||
|
return t.resoledObject.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DBusResolvedResolver) Exchange(object any, ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
question := message.Question[0]
|
||||||
|
resolvedObject := object.(*ResolvedObject)
|
||||||
|
call := resolvedObject.CallWithContext(
|
||||||
|
ctx,
|
||||||
|
"org.freedesktop.resolve1.Manager.ResolveRecord",
|
||||||
|
0,
|
||||||
|
resolvedObject.InterfaceIndex,
|
||||||
|
question.Name,
|
||||||
|
question.Qclass,
|
||||||
|
question.Qtype,
|
||||||
|
uint64(0),
|
||||||
|
)
|
||||||
|
if call.Err != nil {
|
||||||
|
var dbusError dbus.Error
|
||||||
|
if errors.As(call.Err, &dbusError) && dbusError.Name == "org.freedesktop.resolve1.NoNameServers" {
|
||||||
|
t.updateStatus()
|
||||||
|
}
|
||||||
|
return nil, E.Cause(call.Err, " resolve record via resolved")
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
records []resolved.ResourceRecord
|
||||||
|
outflags uint64
|
||||||
|
)
|
||||||
|
err := call.Store(&records, &outflags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response := &mDNS.Msg{
|
||||||
|
MsgHdr: mDNS.MsgHdr{
|
||||||
|
Id: message.Id,
|
||||||
|
Response: true,
|
||||||
|
Authoritative: true,
|
||||||
|
RecursionDesired: true,
|
||||||
|
RecursionAvailable: true,
|
||||||
|
Rcode: mDNS.RcodeSuccess,
|
||||||
|
},
|
||||||
|
Question: []mDNS.Question{question},
|
||||||
|
}
|
||||||
|
for _, record := range records {
|
||||||
|
var rr mDNS.RR
|
||||||
|
rr, _, err = mDNS.UnpackRR(record.Data, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "unpack resource record")
|
||||||
|
}
|
||||||
|
response.Answer = append(response.Answer, rr)
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DBusResolvedResolver) loopUpdateStatus() {
|
||||||
|
signalChan := make(chan *dbus.Signal, 1)
|
||||||
|
t.systemBus.Signal(signalChan)
|
||||||
|
for signal := range signalChan {
|
||||||
|
var restarted bool
|
||||||
|
if signal.Name == "org.freedesktop.DBus.NameOwnerChanged" {
|
||||||
|
if len(signal.Body) != 3 || signal.Body[2].(string) == "" {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
restarted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if restarted {
|
||||||
|
t.updateStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DBusResolvedResolver) updateStatus() {
|
||||||
|
dbusObject, err := t.checkResolved(context.Background())
|
||||||
|
oldValue := t.resoledObject.Swap(dbusObject)
|
||||||
|
if err != nil {
|
||||||
|
var dbusErr dbus.Error
|
||||||
|
if !errors.As(err, &dbusErr) || dbusErr.Name != "org.freedesktop.DBus.Error.NameHasNoOwnerCould" {
|
||||||
|
t.logger.Debug(E.Cause(err, "systemd-resolved service unavailable"))
|
||||||
|
}
|
||||||
|
if oldValue != nil {
|
||||||
|
t.logger.Debug("systemd-resolved service is gone")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
} else if oldValue == nil {
|
||||||
|
t.logger.Debug("using systemd-resolved service as resolver")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DBusResolvedResolver) checkResolved(ctx context.Context) (*ResolvedObject, error) {
|
||||||
|
dbusObject := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1")
|
||||||
|
err := dbusObject.Call("org.freedesktop.DBus.Peer.Ping", 0).Err
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defaultInterface := t.interfaceMonitor.DefaultInterface()
|
||||||
|
if defaultInterface == nil {
|
||||||
|
return nil, E.New("missing default interface")
|
||||||
|
}
|
||||||
|
call := dbusObject.(*dbus.Object).CallWithContext(
|
||||||
|
ctx,
|
||||||
|
"org.freedesktop.resolve1.Manager.GetLink",
|
||||||
|
0,
|
||||||
|
int32(defaultInterface.Index),
|
||||||
|
)
|
||||||
|
if call.Err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var linkPath dbus.ObjectPath
|
||||||
|
err = call.Store(&linkPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
linkObject := t.systemBus.Object("org.freedesktop.resolve1", linkPath)
|
||||||
|
if linkObject == nil {
|
||||||
|
return nil, E.New("missing link object for default interface")
|
||||||
|
}
|
||||||
|
dnsProp, err := linkObject.GetProperty("org.freedesktop.resolve1.Link.DNS")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var linkDNS []resolved.LinkDNS
|
||||||
|
err = dnsProp.Store(&linkDNS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(linkDNS) == 0 {
|
||||||
|
for _, inbound := range service.FromContext[adapter.InboundManager](t.ctx).Inbounds() {
|
||||||
|
if inbound.Type() == C.TypeTun {
|
||||||
|
return nil, E.New("No appropriate name servers or networks for name found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &ResolvedObject{
|
||||||
|
BusObject: dbusObject,
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
return &ResolvedObject{
|
||||||
|
BusObject: dbusObject,
|
||||||
|
InterfaceIndex: int32(defaultInterface.Index),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *DBusResolvedResolver) updateDefaultInterface(defaultInterface *control.Interface, flags int) {
|
||||||
|
t.updateStatus()
|
||||||
|
}
|
14
dns/transport/local/local_resolved_stub.go
Normal file
14
dns/transport/local/local_resolved_stub.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
161
dns/transport/local/local_shared.go
Normal file
161
dns/transport/local/local_shared.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
||||||
|
systemConfig := getSystemDNSConfig(t.ctx)
|
||||||
|
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
|
||||||
|
return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
|
||||||
|
} else {
|
||||||
|
return t.exchangeParallel(ctx, systemConfig, message, domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) exchangeSingleRequest(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
||||||
|
var lastErr error
|
||||||
|
for _, fqdn := range systemConfig.nameList(domain) {
|
||||||
|
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
return nil, lastErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
||||||
|
returned := make(chan struct{})
|
||||||
|
defer close(returned)
|
||||||
|
type queryResult struct {
|
||||||
|
response *mDNS.Msg
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
results := make(chan queryResult)
|
||||||
|
startRacer := func(ctx context.Context, fqdn string) {
|
||||||
|
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
||||||
|
if err == nil {
|
||||||
|
if response.Rcode != mDNS.RcodeSuccess {
|
||||||
|
err = dns.RcodeError(response.Rcode)
|
||||||
|
} else if len(dns.MessageToAddresses(response)) == 0 {
|
||||||
|
err = E.New(fqdn, ": empty result")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case results <- queryResult{response, err}:
|
||||||
|
case <-returned:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queryCtx, queryCancel := context.WithCancel(ctx)
|
||||||
|
defer queryCancel()
|
||||||
|
var nameCount int
|
||||||
|
for _, fqdn := range systemConfig.nameList(domain) {
|
||||||
|
nameCount++
|
||||||
|
go startRacer(queryCtx, fqdn)
|
||||||
|
}
|
||||||
|
var errors []error
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case result := <-results:
|
||||||
|
if result.err == nil {
|
||||||
|
return result.response, nil
|
||||||
|
}
|
||||||
|
errors = append(errors, result.err)
|
||||||
|
if len(errors) == nameCount {
|
||||||
|
return nil, E.Errors(errors...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) tryOneName(ctx context.Context, config *dnsConfig, fqdn string, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
serverOffset := config.serverOffset()
|
||||||
|
sLen := uint32(len(config.servers))
|
||||||
|
var lastErr error
|
||||||
|
for i := 0; i < config.attempts; i++ {
|
||||||
|
for j := uint32(0); j < sLen; j++ {
|
||||||
|
server := config.servers[(serverOffset+j)%sLen]
|
||||||
|
question := message.Question[0]
|
||||||
|
question.Name = fqdn
|
||||||
|
response, err := t.exchangeOne(ctx, M.ParseSocksaddr(server), question, config.timeout, config.useTCP, config.trustAD)
|
||||||
|
if err != nil {
|
||||||
|
lastErr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, E.Cause(lastErr, fqdn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) {
|
||||||
|
if server.Port == 0 {
|
||||||
|
server.Port = 53
|
||||||
|
}
|
||||||
|
var networks []string
|
||||||
|
if useTCP {
|
||||||
|
networks = []string{N.NetworkTCP}
|
||||||
|
} else {
|
||||||
|
networks = []string{N.NetworkUDP, N.NetworkTCP}
|
||||||
|
}
|
||||||
|
request := &mDNS.Msg{
|
||||||
|
MsgHdr: mDNS.MsgHdr{
|
||||||
|
Id: uint16(rand.Uint32()),
|
||||||
|
RecursionDesired: true,
|
||||||
|
AuthenticatedData: ad,
|
||||||
|
},
|
||||||
|
Question: []mDNS.Question{question},
|
||||||
|
Compress: true,
|
||||||
|
}
|
||||||
|
request.SetEdns0(maxDNSPacketSize, false)
|
||||||
|
buffer := buf.Get(buf.UDPBufferSize)
|
||||||
|
defer buf.Put(buffer)
|
||||||
|
for _, network := range networks {
|
||||||
|
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
|
||||||
|
defer cancel()
|
||||||
|
conn, err := t.dialer.DialContext(ctx, network, server)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
|
||||||
|
conn.SetDeadline(deadline)
|
||||||
|
}
|
||||||
|
rawMessage, err := request.PackBuffer(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "pack request")
|
||||||
|
}
|
||||||
|
_, err = conn.Write(rawMessage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "write request")
|
||||||
|
}
|
||||||
|
n, err := conn.Read(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read response")
|
||||||
|
}
|
||||||
|
var response mDNS.Msg
|
||||||
|
err = response.Unpack(buffer[:n])
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "unpack response")
|
||||||
|
}
|
||||||
|
if response.Truncated && network == N.NetworkUDP {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
panic("unexpected")
|
||||||
|
}
|
@ -1,55 +0,0 @@
|
|||||||
//go:build darwin && cgo
|
|
||||||
|
|
||||||
package local
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <resolv.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
|
||||||
var state C.struct___res_state
|
|
||||||
if C.res_ninit(&state) != 0 {
|
|
||||||
return &dnsConfig{
|
|
||||||
servers: defaultNS,
|
|
||||||
search: dnsDefaultSearch(),
|
|
||||||
ndots: 1,
|
|
||||||
timeout: 5 * time.Second,
|
|
||||||
attempts: 2,
|
|
||||||
err: E.New("libresolv initialization failed"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conf := &dnsConfig{
|
|
||||||
ndots: 1,
|
|
||||||
timeout: 5 * time.Second,
|
|
||||||
attempts: int(state.retry),
|
|
||||||
}
|
|
||||||
for i := 0; i < int(state.nscount); i++ {
|
|
||||||
ns := state.nsaddr_list[i]
|
|
||||||
addr := C.inet_ntoa(ns.sin_addr)
|
|
||||||
if addr == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
conf.servers = append(conf.servers, C.GoString(addr))
|
|
||||||
}
|
|
||||||
for i := 0; ; i++ {
|
|
||||||
search := state.dnsrch[i]
|
|
||||||
if search == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
conf.search = append(conf.search, dns.Fqdn(C.GoString(search)))
|
|
||||||
}
|
|
||||||
return conf
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
//go:build !windows && !(darwin && cgo)
|
//go:build !windows
|
||||||
|
|
||||||
package local
|
package local
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ func RegisterTLS(registry *dns.TransportRegistry) {
|
|||||||
type TLSTransport struct {
|
type TLSTransport struct {
|
||||||
dns.TransportAdapter
|
dns.TransportAdapter
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
dialer N.Dialer
|
dialer tls.Dialer
|
||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
tlsConfig tls.Config
|
tlsConfig tls.Config
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
@ -67,7 +67,7 @@ func NewTLSRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer
|
|||||||
return &TLSTransport{
|
return &TLSTransport{
|
||||||
TransportAdapter: adapter,
|
TransportAdapter: adapter,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
dialer: dialer,
|
dialer: tls.NewDialer(dialer, tlsConfig),
|
||||||
serverAddr: serverAddr,
|
serverAddr: serverAddr,
|
||||||
tlsConfig: tlsConfig,
|
tlsConfig: tlsConfig,
|
||||||
}
|
}
|
||||||
@ -100,15 +100,10 @@ func (t *TLSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tcpConn, err := t.dialer.DialContext(ctx, N.NetworkTCP, t.serverAddr)
|
tlsConn, err := t.dialer.DialTLSContext(ctx, t.serverAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConn, err := tls.ClientHandshake(ctx, tcpConn, t.tlsConfig)
|
|
||||||
if err != nil {
|
|
||||||
tcpConn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return t.exchange(message, &tlsDNSConn{Conn: tlsConn})
|
return t.exchange(message, &tlsDNSConn{Conn: tlsConn})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ type TransportManager struct {
|
|||||||
transportByTag map[string]adapter.DNSTransport
|
transportByTag map[string]adapter.DNSTransport
|
||||||
dependByTag map[string][]string
|
dependByTag map[string][]string
|
||||||
defaultTransport adapter.DNSTransport
|
defaultTransport adapter.DNSTransport
|
||||||
defaultTransportFallback adapter.DNSTransport
|
defaultTransportFallback func() (adapter.DNSTransport, error)
|
||||||
fakeIPTransport adapter.FakeIPTransport
|
fakeIPTransport adapter.FakeIPTransport
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ func NewTransportManager(logger logger.ContextLogger, registry adapter.DNSTransp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *TransportManager) Initialize(defaultTransportFallback adapter.DNSTransport) {
|
func (m *TransportManager) Initialize(defaultTransportFallback func() (adapter.DNSTransport, error)) {
|
||||||
m.defaultTransportFallback = defaultTransportFallback
|
m.defaultTransportFallback = defaultTransportFallback
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,14 +56,27 @@ func (m *TransportManager) Start(stage adapter.StartStage) error {
|
|||||||
}
|
}
|
||||||
m.started = true
|
m.started = true
|
||||||
m.stage = stage
|
m.stage = stage
|
||||||
transports := m.transports
|
|
||||||
m.access.Unlock()
|
|
||||||
if stage == adapter.StartStateStart {
|
if stage == adapter.StartStateStart {
|
||||||
if m.defaultTag != "" && m.defaultTransport == nil {
|
if m.defaultTag != "" && m.defaultTransport == nil {
|
||||||
|
m.access.Unlock()
|
||||||
return E.New("default DNS server not found: ", m.defaultTag)
|
return E.New("default DNS server not found: ", m.defaultTag)
|
||||||
}
|
}
|
||||||
return m.startTransports(m.transports)
|
if m.defaultTransport == nil {
|
||||||
|
defaultTransport, err := m.defaultTransportFallback()
|
||||||
|
if err != nil {
|
||||||
|
m.access.Unlock()
|
||||||
|
return E.Cause(err, "default DNS server fallback")
|
||||||
|
}
|
||||||
|
m.transports = append(m.transports, defaultTransport)
|
||||||
|
m.transportByTag[defaultTransport.Tag()] = defaultTransport
|
||||||
|
m.defaultTransport = defaultTransport
|
||||||
|
}
|
||||||
|
transports := m.transports
|
||||||
|
m.access.Unlock()
|
||||||
|
return m.startTransports(transports)
|
||||||
} else {
|
} else {
|
||||||
|
transports := m.transports
|
||||||
|
m.access.Unlock()
|
||||||
for _, outbound := range transports {
|
for _, outbound := range transports {
|
||||||
err := adapter.LegacyStart(outbound, stage)
|
err := adapter.LegacyStart(outbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -172,11 +185,7 @@ func (m *TransportManager) Transport(tag string) (adapter.DNSTransport, bool) {
|
|||||||
func (m *TransportManager) Default() adapter.DNSTransport {
|
func (m *TransportManager) Default() adapter.DNSTransport {
|
||||||
m.access.RLock()
|
m.access.RLock()
|
||||||
defer m.access.RUnlock()
|
defer m.access.RUnlock()
|
||||||
if m.defaultTransport != nil {
|
return m.defaultTransport
|
||||||
return m.defaultTransport
|
|
||||||
} else {
|
|
||||||
return m.defaultTransportFallback
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *TransportManager) FakeIP() adapter.FakeIPTransport {
|
func (m *TransportManager) FakeIP() adapter.FakeIPTransport {
|
||||||
|
@ -2,19 +2,63 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
#### 1.13.0-alpha.5
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.12.3
|
#### 1.12.3
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.13.0-alpha.4
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.12.2
|
#### 1.12.2
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.13.0-alpha.3
|
||||||
|
|
||||||
|
* Improve `local` DNS server **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
On Apple platforms, Windows, and Linux (when using systemd-resolved),
|
||||||
|
`local` DNS server now works with Tun inbound which overrides system DNS servers.
|
||||||
|
|
||||||
|
See [Local DNS Server](/configuration/dns/server/local/).
|
||||||
|
|
||||||
|
#### 1.13.0-alpha.2
|
||||||
|
|
||||||
|
* Add `preferred_by` rule item **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
The new `preferred_by` routing rule item allows you to
|
||||||
|
match preferred domains and addresses for specific outbounds.
|
||||||
|
|
||||||
|
See [Route Rule](/configuration/route/rule/#preferred_by).
|
||||||
|
|
||||||
|
#### 1.13.0-alpha.1
|
||||||
|
|
||||||
|
* Add interface address rule items **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
New interface address rules allow you to dynamically adjust rules based on your network environment.
|
||||||
|
|
||||||
|
See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/)
|
||||||
|
and [Headless Rule](/configuration/rule-set/headless-rule/).
|
||||||
|
|
||||||
#### 1.12.1
|
#### 1.12.1
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.12.0
|
### 1.12.0
|
||||||
|
|
||||||
* Refactor DNS servers **1**
|
* Refactor DNS servers **1**
|
||||||
* Add domain resolver options**2**
|
* Add domain resolver options**2**
|
||||||
@ -165,7 +209,7 @@ We continue to experience issues updating our sing-box apps on the App Store and
|
|||||||
Until we rewrite and resubmit the apps, they are considered irrecoverable.
|
Until we rewrite and resubmit the apps, they are considered irrecoverable.
|
||||||
Therefore, after this release, we will not be repeating this notice unless there is new information.
|
Therefore, after this release, we will not be repeating this notice unless there is new information.
|
||||||
|
|
||||||
### 1.11.15
|
#### 1.11.15
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -181,7 +225,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
|
|
||||||
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
|
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
|
||||||
|
|
||||||
### 1.11.14
|
#### 1.11.14
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -231,7 +275,7 @@ You can now choose what the DERP home page shows, just like with derper's `-home
|
|||||||
|
|
||||||
See [DERP](/configuration/service/derp/#home).
|
See [DERP](/configuration/service/derp/#home).
|
||||||
|
|
||||||
### 1.11.13
|
#### 1.11.13
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -269,7 +313,7 @@ SSM API service is a RESTful API server for managing Shadowsocks servers.
|
|||||||
|
|
||||||
See [SSM API Service](/configuration/service/ssm-api/).
|
See [SSM API Service](/configuration/service/ssm-api/).
|
||||||
|
|
||||||
### 1.11.11
|
#### 1.11.11
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -301,7 +345,7 @@ You can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fiel
|
|||||||
|
|
||||||
See [Listen Fields](/configuration/shared/listen/).
|
See [Listen Fields](/configuration/shared/listen/).
|
||||||
|
|
||||||
### 1.11.10
|
#### 1.11.10
|
||||||
|
|
||||||
* Undeprecate the `block` outbound **1**
|
* Undeprecate the `block` outbound **1**
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
@ -319,7 +363,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
* Update quic-go to v0.51.0
|
* Update quic-go to v0.51.0
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
### 1.11.9
|
#### 1.11.9
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -330,7 +374,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
### 1.11.8
|
#### 1.11.8
|
||||||
|
|
||||||
* Improve `auto_redirect` **1**
|
* Improve `auto_redirect` **1**
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
@ -347,7 +391,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
### 1.11.7
|
#### 1.11.7
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -363,7 +407,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,
|
Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,
|
||||||
see [Tun](/configuration/inbound/tun/#auto_redirect).
|
see [Tun](/configuration/inbound/tun/#auto_redirect).
|
||||||
|
|
||||||
### 1.11.6
|
#### 1.11.6
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -404,7 +448,7 @@ See [Protocol Sniff](/configuration/route/sniff/).
|
|||||||
|
|
||||||
See [Dial Fields](/configuration/shared/dial/#domain_resolver).
|
See [Dial Fields](/configuration/shared/dial/#domain_resolver).
|
||||||
|
|
||||||
### 1.11.5
|
#### 1.11.5
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -420,7 +464,7 @@ violated the rules (TestFlight users are not affected)._
|
|||||||
|
|
||||||
See [DNS Rule Action](/configuration/dns/rule_action/#predefined).
|
See [DNS Rule Action](/configuration/dns/rule_action/#predefined).
|
||||||
|
|
||||||
### 1.11.4
|
#### 1.11.4
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -476,7 +520,7 @@ Due to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to co
|
|||||||
|
|
||||||
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
||||||
|
|
||||||
### 1.11.3
|
#### 1.11.3
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -487,7 +531,7 @@ process._
|
|||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
### 1.11.1
|
#### 1.11.1
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -666,7 +710,7 @@ See [Hysteria2](/configuration/outbound/hysteria2/).
|
|||||||
|
|
||||||
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
|
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
|
||||||
|
|
||||||
### 1.10.7
|
#### 1.10.7
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
@ -761,7 +805,7 @@ and the old outbound will be removed in sing-box 1.13.0.
|
|||||||
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
|
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
|
||||||
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
|
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
|
||||||
|
|
||||||
### 1.10.2
|
#### 1.10.2
|
||||||
|
|
||||||
* Add deprecated warnings
|
* Add deprecated warnings
|
||||||
* Fix proxying websocket connections in HTTP/mixed inbounds
|
* Fix proxying websocket connections in HTTP/mixed inbounds
|
||||||
@ -898,7 +942,7 @@ See [Rule Action](/configuration/route/rule_action/).
|
|||||||
* Update quic-go to v0.48.0
|
* Update quic-go to v0.48.0
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
### 1.10.1
|
#### 1.10.1
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
@ -2,6 +2,12 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
|
:material-plus: [interface_address](#interface_address)
|
||||||
|
:material-plus: [network_interface_address](#network_interface_address)
|
||||||
|
:material-plus: [default_interface_address](#default_interface_address)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [ip_accept_any](#ip_accept_any)
|
:material-plus: [ip_accept_any](#ip_accept_any)
|
||||||
@ -130,6 +136,19 @@ icon: material/alert-decagram
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
|
"interface_address": {
|
||||||
|
"en0": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"network_interface_address": {
|
||||||
|
"wifi": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_interface_address": [
|
||||||
|
"2000::/3"
|
||||||
|
],
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@ -359,6 +378,36 @@ such as Cellular or a Personal Hotspot (on Apple platforms).
|
|||||||
|
|
||||||
Match if network is in Low Data Mode.
|
Match if network is in Low Data Mode.
|
||||||
|
|
||||||
|
#### interface_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Match interface address.
|
||||||
|
|
||||||
|
#### network_interface_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported in graphical clients on Android and Apple platforms.
|
||||||
|
|
||||||
|
Matches network interface (same values as `network_type`) address.
|
||||||
|
|
||||||
|
#### default_interface_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Match default interface address.
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
@ -2,6 +2,12 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: [interface_address](#interface_address)
|
||||||
|
:material-plus: [network_interface_address](#network_interface_address)
|
||||||
|
:material-plus: [default_interface_address](#default_interface_address)
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [ip_accept_any](#ip_accept_any)
|
:material-plus: [ip_accept_any](#ip_accept_any)
|
||||||
@ -130,6 +136,19 @@ icon: material/alert-decagram
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
|
"interface_address": {
|
||||||
|
"en0": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"network_interface_address": {
|
||||||
|
"wifi": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_interface_address": [
|
||||||
|
"2000::/3"
|
||||||
|
],
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@ -358,6 +377,36 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
匹配如果网络在低数据模式下。
|
匹配如果网络在低数据模式下。
|
||||||
|
|
||||||
|
#### interface_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
|
匹配接口地址。
|
||||||
|
|
||||||
|
#### network_interface_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||||
|
|
||||||
|
匹配网络接口(可用值同 `network_type`)地址。
|
||||||
|
|
||||||
|
#### default_interface_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
|
匹配默认接口地址。
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
|
:material-plus: [prefer_go](#prefer_go)
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
!!! question "Since sing-box 1.12.0"
|
||||||
|
|
||||||
# Local
|
# Local
|
||||||
@ -15,6 +19,7 @@ icon: material/new-box
|
|||||||
{
|
{
|
||||||
"type": "local",
|
"type": "local",
|
||||||
"tag": "",
|
"tag": "",
|
||||||
|
"prefer_go": false
|
||||||
|
|
||||||
// Dial Fields
|
// Dial Fields
|
||||||
}
|
}
|
||||||
@ -28,6 +33,29 @@ icon: material/new-box
|
|||||||
* The old legacy local server only handles IP requests; the new one handles all types of requests and supports concurrent for IP requests.
|
* The old legacy local server only handles IP requests; the new one handles all types of requests and supports concurrent for IP requests.
|
||||||
* The old local server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
|
* The old local server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### prefer_go
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
When enabled, `local` DNS server will resolve DNS by dialing itself whenever possible.
|
||||||
|
|
||||||
|
Specifically, it disables following behaviors which was added as features in sing-box 1.13.0:
|
||||||
|
|
||||||
|
1. On Apple platforms: Attempt to resolve A/AAAA requests using `getaddrinfo` in NetworkExtension.
|
||||||
|
2. On Linux: Resolve through `systemd-resolvd`'s DBus interface when available.
|
||||||
|
|
||||||
|
As a sole exception, it cannot disable the following behavior:
|
||||||
|
|
||||||
|
1. In the Android graphical client,
|
||||||
|
`local` will always resolve DNS through the platform interface,
|
||||||
|
as there is no other way to obtain upstream DNS servers;
|
||||||
|
On devices running Android versions lower than 10, this interface can only resolve A/AAAA requests.
|
||||||
|
|
||||||
|
2. On macOS, `local` will try DHCP first in Network Extension, since DHCP respects DIal Fields,
|
||||||
|
it will not be disabled by `prefer_go`.
|
||||||
|
|
||||||
### Dial Fields
|
### Dial Fields
|
||||||
|
|
||||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.11.0"
|
!!! question "Since sing-box 1.11.0"
|
||||||
|
|
||||||
# Endpoint
|
# Endpoint
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.11.0 起"
|
!!! question "自 sing-box 1.11.0 起"
|
||||||
|
|
||||||
# 端点
|
# 端点
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.11.0"
|
!!! question "Since sing-box 1.11.0"
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.11.0 起"
|
!!! question "自 sing-box 1.11.0 起"
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [server_ports](#server_ports)
|
:material-plus: [server_ports](#server_ports)
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [server_ports](#server_ports)
|
:material-plus: [server_ports](#server_ports)
|
||||||
|
@ -2,6 +2,13 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
|
:material-plus: [interface_address](#interface_address)
|
||||||
|
:material-plus: [network_interface_address](#network_interface_address)
|
||||||
|
:material-plus: [default_interface_address](#default_interface_address)
|
||||||
|
:material-plus: [preferred_by](#preferred_by)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [action](#action)
|
:material-plus: [action](#action)
|
||||||
@ -128,12 +135,29 @@ icon: material/new-box
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
|
"interface_address": {
|
||||||
|
"en0": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"network_interface_address": {
|
||||||
|
"wifi": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_interface_address": [
|
||||||
|
"2000::/3"
|
||||||
|
],
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
"wifi_bssid": [
|
"wifi_bssid": [
|
||||||
"00:00:00:00:00:00"
|
"00:00:00:00:00:00"
|
||||||
],
|
],
|
||||||
|
"preferred_by": [
|
||||||
|
"tailscale",
|
||||||
|
"wireguard"
|
||||||
|
],
|
||||||
"rule_set": [
|
"rule_set": [
|
||||||
"geoip-cn",
|
"geoip-cn",
|
||||||
"geosite-cn"
|
"geosite-cn"
|
||||||
@ -363,6 +387,36 @@ such as Cellular or a Personal Hotspot (on Apple platforms).
|
|||||||
|
|
||||||
Match if network is in Low Data Mode.
|
Match if network is in Low Data Mode.
|
||||||
|
|
||||||
|
#### interface_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Match interface address.
|
||||||
|
|
||||||
|
#### network_interface_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported in graphical clients on Android and Apple platforms.
|
||||||
|
|
||||||
|
Matches network interface (same values as `network_type`) address.
|
||||||
|
|
||||||
|
#### default_interface_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Match default interface address.
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@ -379,6 +433,17 @@ Match WiFi SSID.
|
|||||||
|
|
||||||
Match WiFi BSSID.
|
Match WiFi BSSID.
|
||||||
|
|
||||||
|
#### preferred_by
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
Match specified outbounds' preferred routes.
|
||||||
|
|
||||||
|
| Type | Match |
|
||||||
|
|-------------|-----------------------------------------------|
|
||||||
|
| `tailscale` | Match MagicDNS domains and peers' allowed IPs |
|
||||||
|
| `wireguard` | Match peers's allowed IPs |
|
||||||
|
|
||||||
#### rule_set
|
#### rule_set
|
||||||
|
|
||||||
!!! question "Since sing-box 1.8.0"
|
!!! question "Since sing-box 1.8.0"
|
||||||
|
@ -2,6 +2,13 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: [interface_address](#interface_address)
|
||||||
|
:material-plus: [network_interface_address](#network_interface_address)
|
||||||
|
:material-plus: [default_interface_address](#default_interface_address)
|
||||||
|
:material-plus: [preferred_by](#preferred_by)
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [action](#action)
|
:material-plus: [action](#action)
|
||||||
@ -125,12 +132,29 @@ icon: material/new-box
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
|
"interface_address": {
|
||||||
|
"en0": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"network_interface_address": {
|
||||||
|
"wifi": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_interface_address": [
|
||||||
|
"2000::/3"
|
||||||
|
],
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
"wifi_bssid": [
|
"wifi_bssid": [
|
||||||
"00:00:00:00:00:00"
|
"00:00:00:00:00:00"
|
||||||
],
|
],
|
||||||
|
"preferred_by": [
|
||||||
|
"tailscale",
|
||||||
|
"wireguard"
|
||||||
|
],
|
||||||
"rule_set": [
|
"rule_set": [
|
||||||
"geoip-cn",
|
"geoip-cn",
|
||||||
"geosite-cn"
|
"geosite-cn"
|
||||||
@ -337,7 +361,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
匹配网络类型。
|
匹配网络类型。
|
||||||
|
|
||||||
Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
可用值: `wifi`, `cellular`, `ethernet` and `other`.
|
||||||
|
|
||||||
#### network_is_expensive
|
#### network_is_expensive
|
||||||
|
|
||||||
@ -360,6 +384,36 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
匹配如果网络在低数据模式下。
|
匹配如果网络在低数据模式下。
|
||||||
|
|
||||||
|
#### interface_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
|
匹配接口地址。
|
||||||
|
|
||||||
|
#### network_interface_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||||
|
|
||||||
|
匹配网络接口(可用值同 `network_type`)地址。
|
||||||
|
|
||||||
|
#### default_interface_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
|
匹配默认接口地址。
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@ -376,6 +430,17 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
匹配 WiFi BSSID。
|
匹配 WiFi BSSID。
|
||||||
|
|
||||||
|
#### preferred_by
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
匹配制定出站的首选路由。
|
||||||
|
|
||||||
|
| 类型 | 匹配 |
|
||||||
|
|-------------|--------------------------------|
|
||||||
|
| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs |
|
||||||
|
| `wireguard` | 匹配对端的 allowed IPs |
|
||||||
|
|
||||||
#### rule_set
|
#### rule_set
|
||||||
|
|
||||||
!!! question "自 sing-box 1.8.0 起"
|
!!! question "自 sing-box 1.8.0 起"
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
|
:material-plus: [network_interface_address](#network_interface_address)
|
||||||
|
:material-plus: [default_interface_address](#default_interface_address)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [network_type](#network_type)
|
:material-plus: [network_type](#network_type)
|
||||||
@ -78,6 +83,14 @@ icon: material/new-box
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
|
"network_interface_address": {
|
||||||
|
"wifi": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_interface_address": [
|
||||||
|
"2000::/3"
|
||||||
|
],
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@ -225,6 +238,26 @@ such as Cellular or a Personal Hotspot (on Apple platforms).
|
|||||||
|
|
||||||
Match if network is in Low Data Mode.
|
Match if network is in Low Data Mode.
|
||||||
|
|
||||||
|
#### network_interface_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported in graphical clients on Android and Apple platforms.
|
||||||
|
|
||||||
|
Matches network interface (same values as `network_type`) address.
|
||||||
|
|
||||||
|
#### default_interface_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.13.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux, Windows, and macOS.
|
||||||
|
|
||||||
|
Match default interface address.
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: [network_interface_address](#network_interface_address)
|
||||||
|
:material-plus: [default_interface_address](#default_interface_address)
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [network_type](#network_type)
|
:material-plus: [network_type](#network_type)
|
||||||
@ -78,6 +83,14 @@ icon: material/new-box
|
|||||||
],
|
],
|
||||||
"network_is_expensive": false,
|
"network_is_expensive": false,
|
||||||
"network_is_constrained": false,
|
"network_is_constrained": false,
|
||||||
|
"network_interface_address": {
|
||||||
|
"wifi": [
|
||||||
|
"2000::/3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_interface_address": [
|
||||||
|
"2000::/3"
|
||||||
|
],
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@ -221,6 +234,26 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
匹配如果网络在低数据模式下。
|
匹配如果网络在低数据模式下。
|
||||||
|
|
||||||
|
#### network_interface_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||||
|
|
||||||
|
匹配网络接口(可用值同 `network_type`)地址。
|
||||||
|
|
||||||
|
#### default_interface_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.13.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux、Windows 和 macOS.
|
||||||
|
|
||||||
|
匹配默认接口地址。
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
|
:material-plus: version `4`
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: version `3`
|
:material-plus: version `3`
|
||||||
@ -36,6 +40,7 @@ Version of rule-set.
|
|||||||
* 1: sing-box 1.8.0: Initial rule-set version.
|
* 1: sing-box 1.8.0: Initial rule-set version.
|
||||||
* 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets.
|
* 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets.
|
||||||
* 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items.
|
* 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items.
|
||||||
|
* 4: sing-box 1.13.0: Added `network_interface_address` and `default_interface_address` rule items.
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: version `4`
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: version `3`
|
:material-plus: version `3`
|
||||||
@ -36,6 +40,7 @@ icon: material/new-box
|
|||||||
* 1: sing-box 1.8.0: 初始规则集版本。
|
* 1: sing-box 1.8.0: 初始规则集版本。
|
||||||
* 2: sing-box 1.10.0: 优化了二进制规则集中 `domain_suffix` 规则的内存使用。
|
* 2: sing-box 1.10.0: 优化了二进制规则集中 `domain_suffix` 规则的内存使用。
|
||||||
* 3: sing-box 1.11.0: 添加了 `network_type`、 `network_is_expensive` 和 `network_is_constrainted` 规则项。
|
* 3: sing-box 1.11.0: 添加了 `network_type`、 `network_is_expensive` 和 `network_is_constrainted` 规则项。
|
||||||
|
* 4: sing-box 1.13.0: 添加了 `network_interface_address` 和 `default_interface_address` 规则项。
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
func cacheRouter(ctx context.Context) http.Handler {
|
func cacheRouter(ctx context.Context) http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Post("/fakeip/flush", flushFakeip(ctx))
|
r.Post("/fakeip/flush", flushFakeip(ctx))
|
||||||
|
r.Post("/dns/flush", flushDNS(ctx))
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,3 +32,13 @@ func flushFakeip(ctx context.Context) func(w http.ResponseWriter, r *http.Reques
|
|||||||
render.NoContent(w, r)
|
render.NoContent(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func flushDNS(ctx context.Context) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
dnsRouter := service.FromContext[adapter.DNSRouter](ctx)
|
||||||
|
if dnsRouter != nil {
|
||||||
|
dnsRouter.ClearCache()
|
||||||
|
}
|
||||||
|
render.NoContent(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,12 +3,12 @@ package trafficontrol
|
|||||||
import (
|
import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi/compatible"
|
"github.com/sagernet/sing-box/experimental/clashapi/compatible"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
|
||||||
|
@ -2,11 +2,11 @@ package trafficontrol
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
@ -7,11 +7,11 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
8
go.mod
8
go.mod
@ -25,18 +25,18 @@ require (
|
|||||||
github.com/sagernet/cors v1.2.1
|
github.com/sagernet/cors v1.2.1
|
||||||
github.com/sagernet/fswatch v0.1.1
|
github.com/sagernet/fswatch v0.1.1
|
||||||
github.com/sagernet/gomobile v0.1.8
|
github.com/sagernet/gomobile v0.1.8
|
||||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
|
github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237
|
||||||
github.com/sagernet/quic-go v0.52.0-beta.1
|
github.com/sagernet/quic-go v0.52.0-beta.1
|
||||||
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f
|
github.com/sagernet/sing v0.7.6-0.20250825141840-811aa328e57b
|
||||||
github.com/sagernet/sing-mux v0.3.3
|
github.com/sagernet/sing-mux v0.3.3
|
||||||
github.com/sagernet/sing-quic v0.5.0
|
github.com/sagernet/sing-quic v0.5.0
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
||||||
github.com/sagernet/sing-tun v0.7.0-beta.1
|
github.com/sagernet/sing-tun v0.7.0-beta.1.0.20250825142028-0386fcb66a72
|
||||||
github.com/sagernet/sing-vmess v0.2.7
|
github.com/sagernet/sing-vmess v0.2.7
|
||||||
github.com/sagernet/smux v1.5.34-mod.2
|
github.com/sagernet/smux v1.5.34-mod.2
|
||||||
github.com/sagernet/tailscale v1.80.3-mod.5
|
github.com/sagernet/tailscale v1.80.3-mod.6
|
||||||
github.com/sagernet/wireguard-go v0.0.1-beta.7
|
github.com/sagernet/wireguard-go v0.0.1-beta.7
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
|
16
go.sum
16
go.sum
@ -158,8 +158,8 @@ github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQ
|
|||||||
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
||||||
github.com/sagernet/gomobile v0.1.8 h1:vXgoN0pjsMONAaYCTdsKBX2T1kxuS7sbT/mZ7PElGoo=
|
github.com/sagernet/gomobile v0.1.8 h1:vXgoN0pjsMONAaYCTdsKBX2T1kxuS7sbT/mZ7PElGoo=
|
||||||
github.com/sagernet/gomobile v0.1.8/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY=
|
github.com/sagernet/gomobile v0.1.8/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY=
|
||||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb h1:pprQtDqNgqXkRsXn+0E8ikKOemzmum8bODjSfDene38=
|
github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 h1:SUPFNB+vSP4RBPrSEgNII+HkfqC8hKMpYLodom4o4EU=
|
||||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4=
|
github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4=
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
||||||
@ -167,8 +167,8 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l
|
|||||||
github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs=
|
github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs=
|
||||||
github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
|
github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
|
||||||
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f h1:HIBo8l+tsS3wLwuI1E56uRTQw46QytXSUpZTP3vwG/U=
|
github.com/sagernet/sing v0.7.6-0.20250825141840-811aa328e57b h1:RCfo1Q6VDAXfumNupRyqTomKzDODhASswkxVCqM8l2M=
|
||||||
github.com/sagernet/sing v0.7.6-0.20250815070458-d33ece7a184f/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.7.6-0.20250825141840-811aa328e57b/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw=
|
github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw=
|
||||||
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
||||||
github.com/sagernet/sing-quic v0.5.0 h1:jNLIyVk24lFPvu8A4x+ZNEnZdI+Tg1rp7eCJ6v0Csak=
|
github.com/sagernet/sing-quic v0.5.0 h1:jNLIyVk24lFPvu8A4x+ZNEnZdI+Tg1rp7eCJ6v0Csak=
|
||||||
@ -179,14 +179,14 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq
|
|||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||||
github.com/sagernet/sing-tun v0.7.0-beta.1 h1:mBIFXYAnGO5ey/HcCYanqnBx61E7yF8zTFGRZonGYmY=
|
github.com/sagernet/sing-tun v0.7.0-beta.1.0.20250825142028-0386fcb66a72 h1:8UFMs1xVFIq+z+HVwe4NUjxqFfhbmomorH08SS1JjqE=
|
||||||
github.com/sagernet/sing-tun v0.7.0-beta.1/go.mod h1:AHJuRrLbNRJuivuFZ2VhXwDj4ViYp14szG5EkkKAqRQ=
|
github.com/sagernet/sing-tun v0.7.0-beta.1.0.20250825142028-0386fcb66a72/go.mod h1:1fyZW3WhK6rg0REkeJ2LIuK/v+Q7X/gQU/FyQMza7LM=
|
||||||
github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk=
|
github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk=
|
||||||
github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs=
|
github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs=
|
||||||
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
||||||
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
|
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
|
||||||
github.com/sagernet/tailscale v1.80.3-mod.5 h1:7V7z+p2C//TGtff20pPnDCt3qP6uFyY62peJoKF9z/A=
|
github.com/sagernet/tailscale v1.80.3-mod.6 h1:oJs0jpRNS/12+mPf3r9maxWl9dWy1RanugLNmsF74Gs=
|
||||||
github.com/sagernet/tailscale v1.80.3-mod.5/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI=
|
github.com/sagernet/tailscale v1.80.3-mod.6/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI=
|
||||||
github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI=
|
github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI=
|
||||||
github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
|
github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||||
|
@ -190,7 +190,7 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
remoteOptions := RemoteDNSServerOptions{
|
remoteOptions := RemoteDNSServerOptions{
|
||||||
LocalDNSServerOptions: LocalDNSServerOptions{
|
RawLocalDNSServerOptions: RawLocalDNSServerOptions{
|
||||||
DialerOptions: DialerOptions{
|
DialerOptions: DialerOptions{
|
||||||
Detour: options.Detour,
|
Detour: options.Detour,
|
||||||
DomainResolver: &DomainResolveOptions{
|
DomainResolver: &DomainResolveOptions{
|
||||||
@ -211,7 +211,9 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
|||||||
switch serverType {
|
switch serverType {
|
||||||
case C.DNSTypeLocal:
|
case C.DNSTypeLocal:
|
||||||
o.Type = C.DNSTypeLocal
|
o.Type = C.DNSTypeLocal
|
||||||
o.Options = &remoteOptions.LocalDNSServerOptions
|
o.Options = &LocalDNSServerOptions{
|
||||||
|
RawLocalDNSServerOptions: remoteOptions.RawLocalDNSServerOptions,
|
||||||
|
}
|
||||||
case C.DNSTypeUDP:
|
case C.DNSTypeUDP:
|
||||||
o.Type = C.DNSTypeUDP
|
o.Type = C.DNSTypeUDP
|
||||||
o.Options = &remoteOptions
|
o.Options = &remoteOptions
|
||||||
@ -363,7 +365,7 @@ type HostsDNSServerOptions struct {
|
|||||||
Predefined *badjson.TypedMap[string, badoption.Listable[netip.Addr]] `json:"predefined,omitempty"`
|
Predefined *badjson.TypedMap[string, badoption.Listable[netip.Addr]] `json:"predefined,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LocalDNSServerOptions struct {
|
type RawLocalDNSServerOptions struct {
|
||||||
DialerOptions
|
DialerOptions
|
||||||
Legacy bool `json:"-"`
|
Legacy bool `json:"-"`
|
||||||
LegacyStrategy DomainStrategy `json:"-"`
|
LegacyStrategy DomainStrategy `json:"-"`
|
||||||
@ -371,8 +373,13 @@ type LocalDNSServerOptions struct {
|
|||||||
LegacyClientSubnet netip.Prefix `json:"-"`
|
LegacyClientSubnet netip.Prefix `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LocalDNSServerOptions struct {
|
||||||
|
RawLocalDNSServerOptions
|
||||||
|
PreferGo bool `json:"prefer_go,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type RemoteDNSServerOptions struct {
|
type RemoteDNSServerOptions struct {
|
||||||
LocalDNSServerOptions
|
RawLocalDNSServerOptions
|
||||||
DNSServerAddressOptions
|
DNSServerAddressOptions
|
||||||
LegacyAddressResolver string `json:"-"`
|
LegacyAddressResolver string `json:"-"`
|
||||||
LegacyAddressStrategy DomainStrategy `json:"-"`
|
LegacyAddressStrategy DomainStrategy `json:"-"`
|
||||||
|
@ -67,42 +67,46 @@ func (r Rule) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RawDefaultRule struct {
|
type RawDefaultRule struct {
|
||||||
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
|
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
Network badoption.Listable[string] `json:"network,omitempty"`
|
Network badoption.Listable[string] `json:"network,omitempty"`
|
||||||
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
|
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
|
||||||
Protocol badoption.Listable[string] `json:"protocol,omitempty"`
|
Protocol badoption.Listable[string] `json:"protocol,omitempty"`
|
||||||
Client badoption.Listable[string] `json:"client,omitempty"`
|
Client badoption.Listable[string] `json:"client,omitempty"`
|
||||||
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
||||||
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
||||||
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
|
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
|
||||||
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
|
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
|
||||||
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
|
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
|
||||||
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
||||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
||||||
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
||||||
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
||||||
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
||||||
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
||||||
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
||||||
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
||||||
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
||||||
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
||||||
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
||||||
User badoption.Listable[string] `json:"user,omitempty"`
|
User badoption.Listable[string] `json:"user,omitempty"`
|
||||||
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
|
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
|
||||||
ClashMode string `json:"clash_mode,omitempty"`
|
ClashMode string `json:"clash_mode,omitempty"`
|
||||||
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
||||||
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
||||||
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
||||||
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
||||||
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
||||||
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
InterfaceAddress *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]] `json:"interface_address,omitempty"`
|
||||||
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
||||||
Invert bool `json:"invert,omitempty"`
|
DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"`
|
||||||
|
PreferredBy badoption.Listable[string] `json:"preferred_by,omitempty"`
|
||||||
|
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
||||||
|
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
|
||||||
// Deprecated: renamed to rule_set_ip_cidr_match_source
|
// Deprecated: renamed to rule_set_ip_cidr_match_source
|
||||||
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
|
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
|
||||||
|
@ -68,45 +68,48 @@ func (r DNSRule) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RawDefaultDNSRule struct {
|
type RawDefaultDNSRule struct {
|
||||||
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
|
Inbound badoption.Listable[string] `json:"inbound,omitempty"`
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
|
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
|
||||||
Network badoption.Listable[string] `json:"network,omitempty"`
|
Network badoption.Listable[string] `json:"network,omitempty"`
|
||||||
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
|
AuthUser badoption.Listable[string] `json:"auth_user,omitempty"`
|
||||||
Protocol badoption.Listable[string] `json:"protocol,omitempty"`
|
Protocol badoption.Listable[string] `json:"protocol,omitempty"`
|
||||||
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
||||||
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
||||||
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
|
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
|
||||||
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
|
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
|
||||||
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
|
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
|
||||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
||||||
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
||||||
IPAcceptAny bool `json:"ip_accept_any,omitempty"`
|
IPAcceptAny bool `json:"ip_accept_any,omitempty"`
|
||||||
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
||||||
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
||||||
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
||||||
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
||||||
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
||||||
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
||||||
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
||||||
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
||||||
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
||||||
User badoption.Listable[string] `json:"user,omitempty"`
|
User badoption.Listable[string] `json:"user,omitempty"`
|
||||||
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
|
UserID badoption.Listable[int32] `json:"user_id,omitempty"`
|
||||||
Outbound badoption.Listable[string] `json:"outbound,omitempty"`
|
Outbound badoption.Listable[string] `json:"outbound,omitempty"`
|
||||||
ClashMode string `json:"clash_mode,omitempty"`
|
ClashMode string `json:"clash_mode,omitempty"`
|
||||||
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
||||||
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
||||||
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
||||||
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
||||||
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
||||||
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
InterfaceAddress *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]] `json:"interface_address,omitempty"`
|
||||||
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
||||||
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"`
|
DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"`
|
||||||
Invert bool `json:"invert,omitempty"`
|
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
||||||
|
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
||||||
|
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
|
||||||
// Deprecated: renamed to rule_set_ip_cidr_match_source
|
// Deprecated: renamed to rule_set_ip_cidr_match_source
|
||||||
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
|
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
|
||||||
|
@ -182,28 +182,31 @@ func (r HeadlessRule) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DefaultHeadlessRule struct {
|
type DefaultHeadlessRule struct {
|
||||||
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
|
QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
|
||||||
Network badoption.Listable[string] `json:"network,omitempty"`
|
Network badoption.Listable[string] `json:"network,omitempty"`
|
||||||
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
||||||
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
||||||
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
||||||
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
||||||
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
||||||
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
||||||
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
||||||
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
||||||
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
||||||
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
||||||
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
PackageName badoption.Listable[string] `json:"package_name,omitempty"`
|
||||||
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
|
||||||
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
|
||||||
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
|
||||||
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
|
||||||
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
|
||||||
Invert bool `json:"invert,omitempty"`
|
NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"`
|
||||||
|
DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"`
|
||||||
|
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
|
||||||
DomainMatcher *domain.Matcher `json:"-"`
|
DomainMatcher *domain.Matcher `json:"-"`
|
||||||
SourceIPSet *netipx.IPSet `json:"-"`
|
SourceIPSet *netipx.IPSet `json:"-"`
|
||||||
@ -240,7 +243,7 @@ type PlainRuleSetCompat _PlainRuleSetCompat
|
|||||||
func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) {
|
func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) {
|
||||||
var v any
|
var v any
|
||||||
switch r.Version {
|
switch r.Version {
|
||||||
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
|
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:
|
||||||
v = r.Options
|
v = r.Options
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown rule-set version: ", r.Version)
|
return nil, E.New("unknown rule-set version: ", r.Version)
|
||||||
@ -255,7 +258,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
|
|||||||
}
|
}
|
||||||
var v any
|
var v any
|
||||||
switch r.Version {
|
switch r.Version {
|
||||||
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
|
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:
|
||||||
v = &r.Options
|
v = &r.Options
|
||||||
case 0:
|
case 0:
|
||||||
return E.New("missing rule-set version")
|
return E.New("missing rule-set version")
|
||||||
@ -272,7 +275,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
|
|||||||
|
|
||||||
func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) {
|
func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) {
|
||||||
switch r.Version {
|
switch r.Version {
|
||||||
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3:
|
case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:
|
||||||
default:
|
default:
|
||||||
return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version))
|
return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version))
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ func RegisterOutbound(registry *outbound.Registry) {
|
|||||||
|
|
||||||
type Outbound struct {
|
type Outbound struct {
|
||||||
outbound.Adapter
|
outbound.Adapter
|
||||||
dialer N.Dialer
|
dialer tls.Dialer
|
||||||
server M.Socksaddr
|
server M.Socksaddr
|
||||||
tlsConfig tls.Config
|
tlsConfig tls.Config
|
||||||
client *anytls.Client
|
client *anytls.Client
|
||||||
@ -58,7 +58,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
outbound.dialer = outboundDialer
|
|
||||||
|
outbound.dialer = tls.NewDialer(outboundDialer, tlsConfig)
|
||||||
|
|
||||||
client, err := anytls.NewClient(ctx, anytls.ClientConfig{
|
client, err := anytls.NewClient(ctx, anytls.ClientConfig{
|
||||||
Password: options.Password,
|
Password: options.Password,
|
||||||
@ -91,16 +92,7 @@ func (d anytlsDialer) ListenPacket(ctx context.Context, destination M.Socksaddr)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *Outbound) dialOut(ctx context.Context) (net.Conn, error) {
|
func (h *Outbound) dialOut(ctx context.Context) (net.Conn, error) {
|
||||||
conn, err := h.dialer.DialContext(ctx, N.NetworkTCP, h.server)
|
return h.dialer.DialTLSContext(ctx, h.server)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tlsConn, err := tls.ClientHandshake(ctx, conn, h.tlsConfig)
|
|
||||||
if err != nil {
|
|
||||||
common.Close(tlsConn, conn)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tlsConn, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
@ -13,6 +13,8 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing-tun/ping"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@ -29,10 +31,12 @@ var (
|
|||||||
_ N.ParallelDialer = (*Outbound)(nil)
|
_ N.ParallelDialer = (*Outbound)(nil)
|
||||||
_ dialer.ParallelNetworkDialer = (*Outbound)(nil)
|
_ dialer.ParallelNetworkDialer = (*Outbound)(nil)
|
||||||
_ dialer.DirectDialer = (*Outbound)(nil)
|
_ dialer.DirectDialer = (*Outbound)(nil)
|
||||||
|
_ adapter.DirectRouteOutbound = (*Outbound)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Outbound struct {
|
type Outbound struct {
|
||||||
outbound.Adapter
|
outbound.Adapter
|
||||||
|
ctx context.Context
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
dialer dialer.ParallelInterfaceDialer
|
dialer dialer.ParallelInterfaceDialer
|
||||||
domainStrategy C.DomainStrategy
|
domainStrategy C.DomainStrategy
|
||||||
@ -58,7 +62,8 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
outbound := &Outbound{
|
outbound := &Outbound{
|
||||||
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions),
|
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions),
|
||||||
|
ctx: ctx,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
domainStrategy: C.DomainStrategy(options.DomainStrategy),
|
domainStrategy: C.DomainStrategy(options.DomainStrategy),
|
||||||
@ -146,6 +151,16 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||||
|
ctx := log.ContextWithNewID(h.ctx)
|
||||||
|
destination, err := ping.ConnectDestination(ctx, h.logger, common.MustCast[*dialer.DefaultDialer](h.dialer).DialerForICMPDestination(metadata.Destination.Addr).Control, metadata.Destination.Addr, routeContext, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
h.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
|
||||||
|
return destination, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Outbound) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) {
|
func (h *Outbound) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) {
|
||||||
ctx, metadata := adapter.ExtendContext(ctx)
|
ctx, metadata := adapter.ExtendContext(ctx)
|
||||||
metadata.Outbound = h.Tag()
|
metadata.Outbound = h.Tag()
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@ -37,7 +37,7 @@ type Selector struct {
|
|||||||
tags []string
|
tags []string
|
||||||
defaultTag string
|
defaultTag string
|
||||||
outbounds map[string]adapter.Outbound
|
outbounds map[string]adapter.Outbound
|
||||||
selected atomic.TypedValue[adapter.Outbound]
|
selected common.TypedValue[adapter.Outbound]
|
||||||
interruptGroup *interrupt.Group
|
interruptGroup *interrupt.Group
|
||||||
interruptExternalConnections bool
|
interruptExternalConnections bool
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@ -14,7 +15,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/batch"
|
"github.com/sagernet/sing/common/batch"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@ -192,7 +192,7 @@ type URLTestGroup struct {
|
|||||||
ticker *time.Ticker
|
ticker *time.Ticker
|
||||||
close chan struct{}
|
close chan struct{}
|
||||||
started bool
|
started bool
|
||||||
lastActive atomic.TypedValue[time.Time]
|
lastActive common.TypedValue[time.Time]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool) (*URLTestGroup, error) {
|
func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool) (*URLTestGroup, error) {
|
||||||
|
@ -34,7 +34,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
detour, err := tls.NewDialerFromOptions(ctx, router, outboundDialer, options.Server, common.PtrValueOrDefault(options.TLS))
|
detour, err := tls.NewDialerFromOptions(ctx, outboundDialer, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -47,8 +46,6 @@ type DNSTransport struct {
|
|||||||
acceptDefaultResolvers bool
|
acceptDefaultResolvers bool
|
||||||
dnsRouter adapter.DNSRouter
|
dnsRouter adapter.DNSRouter
|
||||||
endpointManager adapter.EndpointManager
|
endpointManager adapter.EndpointManager
|
||||||
cfg *wgcfg.Config
|
|
||||||
dnsCfg *nDNS.Config
|
|
||||||
endpoint *Endpoint
|
endpoint *Endpoint
|
||||||
routePrefixes []netip.Prefix
|
routePrefixes []netip.Prefix
|
||||||
routes map[string][]adapter.DNSTransport
|
routes map[string][]adapter.DNSTransport
|
||||||
@ -83,10 +80,10 @@ func (t *DNSTransport) Start(stage adapter.StartStage) error {
|
|||||||
if !isTailscale {
|
if !isTailscale {
|
||||||
return E.New("endpoint is not Tailscale: ", t.endpointTag)
|
return E.New("endpoint is not Tailscale: ", t.endpointTag)
|
||||||
}
|
}
|
||||||
if ep.onReconfig != nil {
|
if ep.onReconfigHook != nil {
|
||||||
return E.New("only one Tailscale DNS server is allowed for single endpoint")
|
return E.New("only one Tailscale DNS server is allowed for single endpoint")
|
||||||
}
|
}
|
||||||
ep.onReconfig = t.onReconfig
|
ep.onReconfigHook = t.onReconfig
|
||||||
t.endpoint = ep
|
t.endpoint = ep
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -95,14 +92,6 @@ func (t *DNSTransport) Reset() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *DNSTransport) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *nDNS.Config) {
|
func (t *DNSTransport) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *nDNS.Config) {
|
||||||
if cfg == nil || dnsCfg == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.cfg = cfg
|
|
||||||
t.dnsCfg = dnsCfg
|
|
||||||
err := t.updateDNSServers(routerCfg, dnsCfg)
|
err := t.updateDNSServers(routerCfg, dnsCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.logger.Error(E.Cause(err, "update DNS servers"))
|
t.logger.Error(E.Cause(err, "update DNS servers"))
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@ -20,6 +21,7 @@ import (
|
|||||||
"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet"
|
"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet"
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
"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/tcp"
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
|
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@ -29,7 +31,9 @@ import (
|
|||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing-tun/ping"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
@ -49,6 +53,15 @@ import (
|
|||||||
"github.com/sagernet/tailscale/version"
|
"github.com/sagernet/tailscale/version"
|
||||||
"github.com/sagernet/tailscale/wgengine"
|
"github.com/sagernet/tailscale/wgengine"
|
||||||
"github.com/sagernet/tailscale/wgengine/filter"
|
"github.com/sagernet/tailscale/wgengine/filter"
|
||||||
|
"github.com/sagernet/tailscale/wgengine/router"
|
||||||
|
"github.com/sagernet/tailscale/wgengine/wgcfg"
|
||||||
|
|
||||||
|
"go4.org/netipx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil)
|
||||||
|
_ adapter.DirectRouteOutbound = (*Endpoint)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -69,8 +82,14 @@ type Endpoint struct {
|
|||||||
platformInterface platform.Interface
|
platformInterface platform.Interface
|
||||||
server *tsnet.Server
|
server *tsnet.Server
|
||||||
stack *stack.Stack
|
stack *stack.Stack
|
||||||
|
icmpForwarder *tun.ICMPForwarder
|
||||||
filter *atomic.Pointer[filter.Filter]
|
filter *atomic.Pointer[filter.Filter]
|
||||||
onReconfig wgengine.ReconfigListener
|
onReconfigHook wgengine.ReconfigListener
|
||||||
|
|
||||||
|
cfg *wgcfg.Config
|
||||||
|
dnsCfg *tsDNS.Config
|
||||||
|
routeDomains common.TypedValue[map[string]bool]
|
||||||
|
routePrefixes atomic.Pointer[netipx.IPSet]
|
||||||
|
|
||||||
acceptRoutes bool
|
acceptRoutes bool
|
||||||
exitNode string
|
exitNode string
|
||||||
@ -163,7 +182,7 @@ func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
return &Endpoint{
|
return &Endpoint{
|
||||||
Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP}, nil),
|
Adapter: endpoint.NewAdapter(C.TypeTailscale, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, nil),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
@ -216,9 +235,7 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if t.onReconfig != nil {
|
t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine).SetOnReconfigListener(t.onReconfig)
|
||||||
t.server.ExportLocalBackend().ExportEngine().(wgengine.ExportedUserspaceEngine).SetOnReconfigListener(t.onReconfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
ipStack := t.server.ExportNetstack().ExportIPStack()
|
ipStack := t.server.ExportNetstack().ExportIPStack()
|
||||||
gErr := ipStack.SetSpoofing(tun.DefaultNIC, true)
|
gErr := ipStack.SetSpoofing(tun.DefaultNIC, true)
|
||||||
@ -230,9 +247,12 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
|
|||||||
return gonet.TranslateNetstackError(gErr)
|
return gonet.TranslateNetstackError(gErr)
|
||||||
}
|
}
|
||||||
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(t.ctx, ipStack, t).HandlePacket)
|
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(t.ctx, ipStack, t).HandlePacket)
|
||||||
udpForwarder := tun.NewUDPForwarder(t.ctx, ipStack, t, t.udpTimeout)
|
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(t.ctx, ipStack, t, t.udpTimeout).HandlePacket)
|
||||||
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.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.stack = ipStack
|
||||||
|
t.icmpForwarder = icmpForwarder
|
||||||
|
|
||||||
localBackend := t.server.ExportLocalBackend()
|
localBackend := t.server.ExportLocalBackend()
|
||||||
perfs := &ipn.MaskedPrefs{
|
perfs := &ipn.MaskedPrefs{
|
||||||
@ -254,7 +274,6 @@ func (t *Endpoint) Start(stage adapter.StartStage) error {
|
|||||||
return E.Cause(err, "update prefs")
|
return E.Cause(err, "update prefs")
|
||||||
}
|
}
|
||||||
t.filter = localBackend.ExportFilter()
|
t.filter = localBackend.ExportFilter()
|
||||||
|
|
||||||
go t.watchState()
|
go t.watchState()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -406,7 +425,7 @@ func (t *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
|||||||
return udpConn, nil
|
return udpConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error {
|
func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||||
tsFilter := t.filter.Load()
|
tsFilter := t.filter.Load()
|
||||||
if tsFilter != nil {
|
if tsFilter != nil {
|
||||||
var ipProto ipproto.Proto
|
var ipProto ipproto.Proto
|
||||||
@ -415,22 +434,41 @@ func (t *Endpoint) PrepareConnection(network string, source M.Socksaddr, destina
|
|||||||
ipProto = ipproto.TCP
|
ipProto = ipproto.TCP
|
||||||
case N.NetworkUDP:
|
case N.NetworkUDP:
|
||||||
ipProto = ipproto.UDP
|
ipProto = ipproto.UDP
|
||||||
|
case N.NetworkICMP:
|
||||||
|
if !destination.IsIPv6() {
|
||||||
|
ipProto = ipproto.ICMPv4
|
||||||
|
} else {
|
||||||
|
ipProto = ipproto.ICMPv6
|
||||||
|
}
|
||||||
}
|
}
|
||||||
response := tsFilter.Check(source.Addr, destination.Addr, destination.Port, ipProto)
|
response := tsFilter.Check(source.Addr, destination.Addr, destination.Port, ipProto)
|
||||||
switch response {
|
switch response {
|
||||||
case filter.Drop:
|
case filter.Drop:
|
||||||
return syscall.ECONNRESET
|
return nil, syscall.ECONNREFUSED
|
||||||
case filter.DropSilently:
|
case filter.DropSilently:
|
||||||
return tun.ErrDrop
|
return nil, tun.ErrDrop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return t.router.PreMatch(adapter.InboundContext{
|
var ipVersion uint8
|
||||||
|
if !destination.IsIPv6() {
|
||||||
|
ipVersion = 4
|
||||||
|
} else {
|
||||||
|
ipVersion = 6
|
||||||
|
}
|
||||||
|
routeDestination, err := t.router.PreMatch(adapter.InboundContext{
|
||||||
Inbound: t.Tag(),
|
Inbound: t.Tag(),
|
||||||
InboundType: t.Type(),
|
InboundType: t.Type(),
|
||||||
|
IPVersion: ipVersion,
|
||||||
Network: network,
|
Network: network,
|
||||||
Source: source,
|
Source: source,
|
||||||
Destination: destination,
|
Destination: destination,
|
||||||
})
|
}, routeContext, timeout)
|
||||||
|
if err != nil {
|
||||||
|
if !rule.IsRejected(err) {
|
||||||
|
t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return routeDestination, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (t *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
@ -473,10 +511,88 @@ func (t *Endpoint) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
|||||||
t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||||
|
inet4Address, inet6Address := t.server.TailscaleIPs()
|
||||||
|
if metadata.Destination.Addr.Is4() && !inet4Address.IsValid() || metadata.Destination.Addr.Is6() && !inet6Address.IsValid() {
|
||||||
|
return nil, E.New("Tailscale is not ready yet")
|
||||||
|
}
|
||||||
|
ctx := log.ContextWithNewID(t.ctx)
|
||||||
|
destination, err := ping.ConnectGVisor(
|
||||||
|
ctx, t.logger,
|
||||||
|
metadata.Source.Addr, metadata.Destination.Addr,
|
||||||
|
routeContext,
|
||||||
|
t.stack,
|
||||||
|
inet4Address, inet6Address,
|
||||||
|
timeout,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
|
||||||
|
return destination, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Endpoint) PreferredDomain(domain string) bool {
|
||||||
|
routeDomains := t.routeDomains.Load()
|
||||||
|
if routeDomains == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return routeDomains[strings.ToLower(domain)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Endpoint) PreferredAddress(address netip.Addr) bool {
|
||||||
|
routePrefixes := t.routePrefixes.Load()
|
||||||
|
if routePrefixes == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return routePrefixes.Contains(address)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Endpoint) Server() *tsnet.Server {
|
func (t *Endpoint) Server() *tsnet.Server {
|
||||||
return t.server
|
return t.server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Endpoint) onReconfig(cfg *wgcfg.Config, routerCfg *router.Config, dnsCfg *tsDNS.Config) {
|
||||||
|
if cfg == nil || dnsCfg == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (t.cfg != nil && reflect.DeepEqual(t.cfg, cfg)) && (t.dnsCfg != nil && reflect.DeepEqual(t.dnsCfg, dnsCfg)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var inet4Address, inet6Address netip.Addr
|
||||||
|
for _, address := range cfg.Addresses {
|
||||||
|
if address.Addr().Is4() {
|
||||||
|
inet4Address = address.Addr()
|
||||||
|
} else if address.Addr().Is6() {
|
||||||
|
inet6Address = address.Addr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.icmpForwarder.SetLocalAddresses(inet4Address, inet6Address)
|
||||||
|
t.cfg = cfg
|
||||||
|
t.dnsCfg = dnsCfg
|
||||||
|
|
||||||
|
routeDomains := make(map[string]bool)
|
||||||
|
for fqdn := range dnsCfg.Routes {
|
||||||
|
routeDomains[fqdn.WithoutTrailingDot()] = true
|
||||||
|
}
|
||||||
|
for _, fqdn := range dnsCfg.SearchDomains {
|
||||||
|
routeDomains[fqdn.WithoutTrailingDot()] = true
|
||||||
|
}
|
||||||
|
t.routeDomains.Store(routeDomains)
|
||||||
|
|
||||||
|
var builder netipx.IPSetBuilder
|
||||||
|
for _, peer := range cfg.Peers {
|
||||||
|
for _, allowedIP := range peer.AllowedIPs {
|
||||||
|
builder.AddPrefix(allowedIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.routePrefixes.Store(common.Must1(builder.IPSet()))
|
||||||
|
|
||||||
|
if t.onReconfigHook != nil {
|
||||||
|
t.onReconfigHook(cfg, routerCfg, dnsCfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func addressFromAddr(destination netip.Addr) tcpip.Address {
|
func addressFromAddr(destination netip.Addr) tcpip.Address {
|
||||||
if destination.Is6() {
|
if destination.Is6() {
|
||||||
return tcpip.AddrFrom16(destination.As16())
|
return tcpip.AddrFrom16(destination.As16())
|
||||||
|
@ -34,6 +34,7 @@ type Outbound struct {
|
|||||||
key [56]byte
|
key [56]byte
|
||||||
multiplexDialer *mux.Client
|
multiplexDialer *mux.Client
|
||||||
tlsConfig tls.Config
|
tlsConfig tls.Config
|
||||||
|
tlsDialer tls.Dialer
|
||||||
transport adapter.V2RayClientTransport
|
transport adapter.V2RayClientTransport
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,6 +55,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
outbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig)
|
||||||
}
|
}
|
||||||
if options.Transport != nil {
|
if options.Transport != nil {
|
||||||
outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
|
outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
|
||||||
@ -121,11 +123,10 @@ func (h *trojanDialer) DialContext(ctx context.Context, network string, destinat
|
|||||||
var err error
|
var err error
|
||||||
if h.transport != nil {
|
if h.transport != nil {
|
||||||
conn, err = h.transport.DialContext(ctx)
|
conn, err = h.transport.DialContext(ctx)
|
||||||
|
} else if h.tlsDialer != nil {
|
||||||
|
conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)
|
||||||
} else {
|
} else {
|
||||||
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
||||||
if err == nil && h.tlsConfig != nil {
|
|
||||||
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.Close(conn)
|
common.Close(conn)
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@ -454,15 +455,28 @@ func (t *Inbound) Close() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error {
|
func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||||
return t.router.PreMatch(adapter.InboundContext{
|
var ipVersion uint8
|
||||||
|
if !destination.IsIPv6() {
|
||||||
|
ipVersion = 4
|
||||||
|
} else {
|
||||||
|
ipVersion = 6
|
||||||
|
}
|
||||||
|
routeDestination, err := t.router.PreMatch(adapter.InboundContext{
|
||||||
Inbound: t.tag,
|
Inbound: t.tag,
|
||||||
InboundType: C.TypeTun,
|
InboundType: C.TypeTun,
|
||||||
|
IPVersion: ipVersion,
|
||||||
Network: network,
|
Network: network,
|
||||||
Source: source,
|
Source: source,
|
||||||
Destination: destination,
|
Destination: destination,
|
||||||
InboundOptions: t.inboundOptions,
|
InboundOptions: t.inboundOptions,
|
||||||
})
|
}, routeContext, timeout)
|
||||||
|
if err != nil {
|
||||||
|
if !rule.IsRejected(err) {
|
||||||
|
t.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return routeDestination, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (t *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
|
@ -35,6 +35,7 @@ type Outbound struct {
|
|||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
multiplexDialer *mux.Client
|
multiplexDialer *mux.Client
|
||||||
tlsConfig tls.Config
|
tlsConfig tls.Config
|
||||||
|
tlsDialer tls.Dialer
|
||||||
transport adapter.V2RayClientTransport
|
transport adapter.V2RayClientTransport
|
||||||
packetAddr bool
|
packetAddr bool
|
||||||
xudp bool
|
xudp bool
|
||||||
@ -56,6 +57,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
outbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig)
|
||||||
}
|
}
|
||||||
if options.Transport != nil {
|
if options.Transport != nil {
|
||||||
outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
|
outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
|
||||||
@ -140,11 +142,10 @@ func (h *vlessDialer) DialContext(ctx context.Context, network string, destinati
|
|||||||
var err error
|
var err error
|
||||||
if h.transport != nil {
|
if h.transport != nil {
|
||||||
conn, err = h.transport.DialContext(ctx)
|
conn, err = h.transport.DialContext(ctx)
|
||||||
|
} else if h.tlsDialer != nil {
|
||||||
|
conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)
|
||||||
} else {
|
} else {
|
||||||
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
||||||
if err == nil && h.tlsConfig != nil {
|
|
||||||
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -183,11 +184,10 @@ func (h *vlessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr)
|
|||||||
var err error
|
var err error
|
||||||
if h.transport != nil {
|
if h.transport != nil {
|
||||||
conn, err = h.transport.DialContext(ctx)
|
conn, err = h.transport.DialContext(ctx)
|
||||||
|
} else if h.tlsDialer != nil {
|
||||||
|
conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)
|
||||||
} else {
|
} else {
|
||||||
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
||||||
if err == nil && h.tlsConfig != nil {
|
|
||||||
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.Close(conn)
|
common.Close(conn)
|
||||||
|
@ -35,6 +35,7 @@ type Outbound struct {
|
|||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
multiplexDialer *mux.Client
|
multiplexDialer *mux.Client
|
||||||
tlsConfig tls.Config
|
tlsConfig tls.Config
|
||||||
|
tlsDialer tls.Dialer
|
||||||
transport adapter.V2RayClientTransport
|
transport adapter.V2RayClientTransport
|
||||||
packetAddr bool
|
packetAddr bool
|
||||||
xudp bool
|
xudp bool
|
||||||
@ -56,6 +57,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
outbound.tlsDialer = tls.NewDialer(outboundDialer, outbound.tlsConfig)
|
||||||
}
|
}
|
||||||
if options.Transport != nil {
|
if options.Transport != nil {
|
||||||
outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
|
outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig)
|
||||||
@ -154,11 +156,10 @@ func (h *vmessDialer) DialContext(ctx context.Context, network string, destinati
|
|||||||
var err error
|
var err error
|
||||||
if h.transport != nil {
|
if h.transport != nil {
|
||||||
conn, err = h.transport.DialContext(ctx)
|
conn, err = h.transport.DialContext(ctx)
|
||||||
|
} else if h.tlsDialer != nil {
|
||||||
|
conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)
|
||||||
} else {
|
} else {
|
||||||
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
||||||
if err == nil && h.tlsConfig != nil {
|
|
||||||
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.Close(conn)
|
common.Close(conn)
|
||||||
@ -182,11 +183,10 @@ func (h *vmessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr)
|
|||||||
var err error
|
var err error
|
||||||
if h.transport != nil {
|
if h.transport != nil {
|
||||||
conn, err = h.transport.DialContext(ctx)
|
conn, err = h.transport.DialContext(ctx)
|
||||||
|
} else if h.tlsDialer != nil {
|
||||||
|
conn, err = h.tlsDialer.DialTLSContext(ctx, h.serverAddr)
|
||||||
} else {
|
} else {
|
||||||
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
|
||||||
if err == nil && h.tlsConfig != nil {
|
|
||||||
conn, err = tls.ClientHandshake(ctx, conn, h.tlsConfig)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -12,7 +12,9 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing-box/transport/wireguard"
|
"github.com/sagernet/sing-box/transport/wireguard"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@ -22,6 +24,8 @@ import (
|
|||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ adapter.OutboundWithPreferredRoutes = (*Endpoint)(nil)
|
||||||
|
|
||||||
func RegisterEndpoint(registry *endpoint.Registry) {
|
func RegisterEndpoint(registry *endpoint.Registry) {
|
||||||
endpoint.Register[option.WireGuardEndpointOptions](registry, C.TypeWireGuard, NewEndpoint)
|
endpoint.Register[option.WireGuardEndpointOptions](registry, C.TypeWireGuard, NewEndpoint)
|
||||||
}
|
}
|
||||||
@ -38,7 +42,7 @@ type Endpoint struct {
|
|||||||
|
|
||||||
func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardEndpointOptions) (adapter.Endpoint, error) {
|
func NewEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardEndpointOptions) (adapter.Endpoint, error) {
|
||||||
ep := &Endpoint{
|
ep := &Endpoint{
|
||||||
Adapter: endpoint.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions),
|
Adapter: endpoint.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
router: router,
|
router: router,
|
||||||
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
||||||
@ -122,14 +126,27 @@ func (w *Endpoint) Close() error {
|
|||||||
return w.endpoint.Close()
|
return w.endpoint.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error {
|
func (w *Endpoint) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||||
return w.router.PreMatch(adapter.InboundContext{
|
var ipVersion uint8
|
||||||
|
if !destination.IsIPv6() {
|
||||||
|
ipVersion = 4
|
||||||
|
} else {
|
||||||
|
ipVersion = 6
|
||||||
|
}
|
||||||
|
routeDestination, err := w.router.PreMatch(adapter.InboundContext{
|
||||||
Inbound: w.Tag(),
|
Inbound: w.Tag(),
|
||||||
InboundType: w.Type(),
|
InboundType: w.Type(),
|
||||||
|
IPVersion: ipVersion,
|
||||||
Network: network,
|
Network: network,
|
||||||
Source: source,
|
Source: source,
|
||||||
Destination: destination,
|
Destination: destination,
|
||||||
})
|
}, routeContext, timeout)
|
||||||
|
if err != nil {
|
||||||
|
if !rule.IsRejected(err) {
|
||||||
|
w.logger.Warn(E.Cause(err, "link ", network, " connection from ", source.AddrString(), " to ", destination.AddrString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return routeDestination, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (w *Endpoint) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
@ -210,3 +227,15 @@ func (w *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
|||||||
}
|
}
|
||||||
return w.endpoint.ListenPacket(ctx, destination)
|
return w.endpoint.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *Endpoint) PreferredDomain(domain string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Endpoint) PreferredAddress(address netip.Addr) bool {
|
||||||
|
return w.endpoint.Lookup(address) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||||
|
return w.endpoint.NewDirectRouteConnection(metadata, routeContext, timeout)
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
@ -13,6 +14,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/transport/wireguard"
|
"github.com/sagernet/sing-box/transport/wireguard"
|
||||||
|
tun "github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
@ -21,6 +23,8 @@ import (
|
|||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ adapter.OutboundWithPreferredRoutes = (*Outbound)(nil)
|
||||||
|
|
||||||
func RegisterOutbound(registry *outbound.Registry) {
|
func RegisterOutbound(registry *outbound.Registry) {
|
||||||
outbound.Register[option.LegacyWireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound)
|
outbound.Register[option.LegacyWireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound)
|
||||||
}
|
}
|
||||||
@ -40,7 +44,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
deprecated.Report(ctx, deprecated.OptionWireGuardGSO)
|
deprecated.Report(ctx, deprecated.OptionWireGuardGSO)
|
||||||
}
|
}
|
||||||
outbound := &Outbound{
|
outbound := &Outbound{
|
||||||
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions),
|
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeWireGuard, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
@ -158,3 +162,15 @@ func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
|||||||
}
|
}
|
||||||
return o.endpoint.ListenPacket(ctx, destination)
|
return o.endpoint.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) PreferredDomain(domain string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) PreferredAddress(address netip.Addr) bool {
|
||||||
|
return o.endpoint.Lookup(address) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||||
|
return o.endpoint.NewDirectRouteConnection(metadata, routeContext, timeout)
|
||||||
|
}
|
||||||
|
@ -19,7 +19,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
@ -37,7 +36,7 @@ var _ adapter.NetworkManager = (*NetworkManager)(nil)
|
|||||||
type NetworkManager struct {
|
type NetworkManager struct {
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
interfaceFinder *control.DefaultInterfaceFinder
|
interfaceFinder *control.DefaultInterfaceFinder
|
||||||
networkInterfaces atomic.TypedValue[[]adapter.NetworkInterface]
|
networkInterfaces common.TypedValue[[]adapter.NetworkInterface]
|
||||||
|
|
||||||
autoDetectInterface bool
|
autoDetectInterface bool
|
||||||
defaultOptions adapter.NetworkOptions
|
defaultOptions adapter.NetworkOptions
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
R "github.com/sagernet/sing-box/route/rule"
|
R "github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing-mux"
|
"github.com/sagernet/sing-mux"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing-vmess"
|
"github.com/sagernet/sing-vmess"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
@ -258,19 +259,37 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) PreMatch(metadata adapter.InboundContext) error {
|
func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||||
selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil)
|
selectedRule, _, _, _, err := r.matchRule(r.ctx, &metadata, true, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
if selectedRule == nil {
|
if selectedRule != nil {
|
||||||
return nil
|
switch action := selectedRule.Action().(type) {
|
||||||
|
case *R.RuleActionReject:
|
||||||
|
return nil, action.Error(context.Background())
|
||||||
|
case *R.RuleActionRoute:
|
||||||
|
if routeContext == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
outbound, loaded := r.outbound.Outbound(action.Outbound)
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("outbound not found: ", action.Outbound)
|
||||||
|
}
|
||||||
|
if !common.Contains(outbound.Network(), metadata.Network) {
|
||||||
|
return nil, E.New(metadata.Network, " is not supported by outbound: ", action.Outbound)
|
||||||
|
}
|
||||||
|
return outbound.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rejectAction, isReject := selectedRule.Action().(*R.RuleActionReject)
|
if selectedRule != nil || metadata.Network != N.NetworkICMP {
|
||||||
if !isReject {
|
return nil, nil
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return rejectAction.Error(context.Background())
|
defaultOutbound := r.outbound.Default()
|
||||||
|
if !common.Contains(defaultOutbound.Network(), metadata.Network) {
|
||||||
|
return nil, E.New(metadata.Network, " is not supported by default outbound: ", defaultOutbound.Tag())
|
||||||
|
}
|
||||||
|
return defaultOutbound.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) matchRule(
|
func (r *Router) matchRule(
|
||||||
|
@ -117,7 +117,7 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
|
|||||||
if len(options.DomainRegex) > 0 {
|
if len(options.DomainRegex) > 0 {
|
||||||
item, err := NewDomainRegexItem(options.DomainRegex)
|
item, err := NewDomainRegexItem(options.DomainRegex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "domain_regex")
|
return nil, err
|
||||||
}
|
}
|
||||||
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
@ -246,6 +246,26 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
|
|||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
|
if options.InterfaceAddress != nil && options.InterfaceAddress.Size() > 0 {
|
||||||
|
item := NewInterfaceAddressItem(networkManager, options.InterfaceAddress)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 {
|
||||||
|
item := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.DefaultInterfaceAddress) > 0 {
|
||||||
|
item := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.PreferredBy) > 0 {
|
||||||
|
item := NewPreferredByItem(ctx, options.PreferredBy)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
if len(options.RuleSet) > 0 {
|
if len(options.RuleSet) > 0 {
|
||||||
var matchSource bool
|
var matchSource bool
|
||||||
if options.RuleSetIPCIDRMatchSource {
|
if options.RuleSetIPCIDRMatchSource {
|
||||||
|
56
route/rule/rule_default_interface_address.go
Normal file
56
route/rule/rule_default_interface_address.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RuleItem = (*DefaultInterfaceAddressItem)(nil)
|
||||||
|
|
||||||
|
type DefaultInterfaceAddressItem struct {
|
||||||
|
interfaceMonitor tun.DefaultInterfaceMonitor
|
||||||
|
interfaceAddresses []netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses badoption.Listable[*badoption.Prefixable]) *DefaultInterfaceAddressItem {
|
||||||
|
item := &DefaultInterfaceAddressItem{
|
||||||
|
interfaceMonitor: networkManager.InterfaceMonitor(),
|
||||||
|
interfaceAddresses: make([]netip.Prefix, 0, len(interfaceAddresses)),
|
||||||
|
}
|
||||||
|
for _, prefixable := range interfaceAddresses {
|
||||||
|
item.interfaceAddresses = append(item.interfaceAddresses, prefixable.Build(netip.Prefix{}))
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultInterfaceAddressItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
defaultInterface := r.interfaceMonitor.DefaultInterface()
|
||||||
|
if defaultInterface == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, address := range r.interfaceAddresses {
|
||||||
|
if common.All(defaultInterface.Addresses, func(it netip.Prefix) bool {
|
||||||
|
return !address.Overlaps(it)
|
||||||
|
}) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultInterfaceAddressItem) String() string {
|
||||||
|
addressLen := len(r.interfaceAddresses)
|
||||||
|
switch {
|
||||||
|
case addressLen == 1:
|
||||||
|
return "default_interface_address=" + r.interfaceAddresses[0].String()
|
||||||
|
case addressLen > 3:
|
||||||
|
return "default_interface_address=[" + strings.Join(common.Map(r.interfaceAddresses[:3], netip.Prefix.String), " ") + "...]"
|
||||||
|
default:
|
||||||
|
return "default_interface_address=[" + strings.Join(common.Map(r.interfaceAddresses, netip.Prefix.String), " ") + "]"
|
||||||
|
}
|
||||||
|
}
|
@ -247,6 +247,21 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
|
|||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
|
if options.InterfaceAddress != nil && options.InterfaceAddress.Size() > 0 {
|
||||||
|
item := NewInterfaceAddressItem(networkManager, options.InterfaceAddress)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 {
|
||||||
|
item := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.DefaultInterfaceAddress) > 0 {
|
||||||
|
item := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
if len(options.RuleSet) > 0 {
|
if len(options.RuleSet) > 0 {
|
||||||
var matchSource bool
|
var matchSource bool
|
||||||
if options.RuleSetIPCIDRMatchSource {
|
if options.RuleSetIPCIDRMatchSource {
|
||||||
|
@ -164,13 +164,21 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR
|
|||||||
item := NewWIFISSIDItem(networkManager, options.WIFISSID)
|
item := NewWIFISSIDItem(networkManager, options.WIFISSID)
|
||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
|
||||||
}
|
}
|
||||||
if len(options.WIFIBSSID) > 0 {
|
if len(options.WIFIBSSID) > 0 {
|
||||||
item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID)
|
item := NewWIFIBSSIDItem(networkManager, options.WIFIBSSID)
|
||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if options.NetworkInterfaceAddress != nil && options.NetworkInterfaceAddress.Size() > 0 {
|
||||||
|
item := NewNetworkInterfaceAddressItem(networkManager, options.NetworkInterfaceAddress)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.DefaultInterfaceAddress) > 0 {
|
||||||
|
item := NewDefaultInterfaceAddressItem(networkManager, options.DefaultInterfaceAddress)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(options.AdGuardDomain) > 0 {
|
if len(options.AdGuardDomain) > 0 {
|
||||||
|
62
route/rule/rule_interface_address.go
Normal file
62
route/rule/rule_interface_address.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RuleItem = (*InterfaceAddressItem)(nil)
|
||||||
|
|
||||||
|
type InterfaceAddressItem struct {
|
||||||
|
networkManager adapter.NetworkManager
|
||||||
|
interfaceAddresses map[string][]netip.Prefix
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[string, badoption.Listable[*badoption.Prefixable]]) *InterfaceAddressItem {
|
||||||
|
item := &InterfaceAddressItem{
|
||||||
|
networkManager: networkManager,
|
||||||
|
interfaceAddresses: make(map[string][]netip.Prefix, interfaceAddresses.Size()),
|
||||||
|
}
|
||||||
|
var entryDescriptions []string
|
||||||
|
for _, entry := range interfaceAddresses.Entries() {
|
||||||
|
prefixes := make([]netip.Prefix, 0, len(entry.Value))
|
||||||
|
for _, prefixable := range entry.Value {
|
||||||
|
prefixes = append(prefixes, prefixable.Build(netip.Prefix{}))
|
||||||
|
}
|
||||||
|
item.interfaceAddresses[entry.Key] = prefixes
|
||||||
|
entryDescriptions = append(entryDescriptions, entry.Key+"="+strings.Join(common.Map(prefixes, netip.Prefix.String), ","))
|
||||||
|
}
|
||||||
|
item.description = "interface_address=[" + strings.Join(entryDescriptions, " ") + "]"
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InterfaceAddressItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
interfaces := r.networkManager.InterfaceFinder().Interfaces()
|
||||||
|
for ifName, addresses := range r.interfaceAddresses {
|
||||||
|
iface := common.Find(interfaces, func(it control.Interface) bool {
|
||||||
|
return it.Name == ifName
|
||||||
|
})
|
||||||
|
if iface.Name == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if common.All(addresses, func(address netip.Prefix) bool {
|
||||||
|
return common.All(iface.Addresses, func(it netip.Prefix) bool {
|
||||||
|
return !address.Overlaps(it)
|
||||||
|
})
|
||||||
|
}) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InterfaceAddressItem) String() string {
|
||||||
|
return r.description
|
||||||
|
}
|
86
route/rule/rule_item_preferred_by.go
Normal file
86
route/rule/rule_item_preferred_by.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RuleItem = (*PreferredByItem)(nil)
|
||||||
|
|
||||||
|
type PreferredByItem struct {
|
||||||
|
ctx context.Context
|
||||||
|
outboundTags []string
|
||||||
|
outbounds []adapter.OutboundWithPreferredRoutes
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPreferredByItem(ctx context.Context, outboundTags []string) *PreferredByItem {
|
||||||
|
return &PreferredByItem{
|
||||||
|
ctx: ctx,
|
||||||
|
outboundTags: outboundTags,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PreferredByItem) Start() error {
|
||||||
|
outboundManager := service.FromContext[adapter.OutboundManager](r.ctx)
|
||||||
|
for _, outboundTag := range r.outboundTags {
|
||||||
|
rawOutbound, loaded := outboundManager.Outbound(outboundTag)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("outbound not found: ", outboundTag)
|
||||||
|
}
|
||||||
|
outboundWithPreferredRoutes, withRoutes := rawOutbound.(adapter.OutboundWithPreferredRoutes)
|
||||||
|
if !withRoutes {
|
||||||
|
return E.New("outbound type does not support preferred routes: ", rawOutbound.Type())
|
||||||
|
}
|
||||||
|
r.outbounds = append(r.outbounds, outboundWithPreferredRoutes)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PreferredByItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
var domainHost string
|
||||||
|
if metadata.Domain != "" {
|
||||||
|
domainHost = metadata.Domain
|
||||||
|
} else {
|
||||||
|
domainHost = metadata.Destination.Fqdn
|
||||||
|
}
|
||||||
|
if domainHost != "" {
|
||||||
|
for _, outbound := range r.outbounds {
|
||||||
|
if outbound.PreferredDomain(domainHost) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsIP() {
|
||||||
|
for _, outbound := range r.outbounds {
|
||||||
|
if outbound.PreferredAddress(metadata.Destination.Addr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(metadata.DestinationAddresses) > 0 {
|
||||||
|
for _, address := range metadata.DestinationAddresses {
|
||||||
|
for _, outbound := range r.outbounds {
|
||||||
|
if outbound.PreferredAddress(address) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PreferredByItem) String() string {
|
||||||
|
description := "preferred_by="
|
||||||
|
pLen := len(r.outboundTags)
|
||||||
|
if pLen == 1 {
|
||||||
|
description += F.ToString(r.outboundTags[0])
|
||||||
|
} else {
|
||||||
|
description += "[" + strings.Join(F.MapToString(r.outboundTags), " ") + "]"
|
||||||
|
}
|
||||||
|
return description
|
||||||
|
}
|
64
route/rule/rule_network_interface_address.go
Normal file
64
route/rule/rule_network_interface_address.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RuleItem = (*NetworkInterfaceAddressItem)(nil)
|
||||||
|
|
||||||
|
type NetworkInterfaceAddressItem struct {
|
||||||
|
networkManager adapter.NetworkManager
|
||||||
|
interfaceAddresses map[C.InterfaceType][]netip.Prefix
|
||||||
|
description string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNetworkInterfaceAddressItem(networkManager adapter.NetworkManager, interfaceAddresses *badjson.TypedMap[option.InterfaceType, badoption.Listable[*badoption.Prefixable]]) *NetworkInterfaceAddressItem {
|
||||||
|
item := &NetworkInterfaceAddressItem{
|
||||||
|
networkManager: networkManager,
|
||||||
|
interfaceAddresses: make(map[C.InterfaceType][]netip.Prefix, interfaceAddresses.Size()),
|
||||||
|
}
|
||||||
|
var entryDescriptions []string
|
||||||
|
for _, entry := range interfaceAddresses.Entries() {
|
||||||
|
prefixes := make([]netip.Prefix, 0, len(entry.Value))
|
||||||
|
for _, prefixable := range entry.Value {
|
||||||
|
prefixes = append(prefixes, prefixable.Build(netip.Prefix{}))
|
||||||
|
}
|
||||||
|
item.interfaceAddresses[entry.Key.Build()] = prefixes
|
||||||
|
entryDescriptions = append(entryDescriptions, entry.Key.Build().String()+"="+strings.Join(common.Map(prefixes, netip.Prefix.String), ","))
|
||||||
|
}
|
||||||
|
item.description = "network_interface_address=[" + strings.Join(entryDescriptions, " ") + "]"
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *NetworkInterfaceAddressItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
interfaces := r.networkManager.NetworkInterfaces()
|
||||||
|
match:
|
||||||
|
for ifType, addresses := range r.interfaceAddresses {
|
||||||
|
for _, networkInterface := range interfaces {
|
||||||
|
if networkInterface.Type != ifType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if common.Any(networkInterface.Addresses, func(it netip.Prefix) bool {
|
||||||
|
return common.Any(addresses, func(prefix netip.Prefix) bool {
|
||||||
|
return prefix.Overlaps(it)
|
||||||
|
})
|
||||||
|
}) {
|
||||||
|
continue match
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *NetworkInterfaceAddressItem) String() string {
|
||||||
|
return r.description
|
||||||
|
}
|
@ -42,7 +42,7 @@ func extractIPSetFromRule(rawRule adapter.HeadlessRule) []*netipx.IPSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {
|
func HasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
switch rule.Type {
|
switch rule.Type {
|
||||||
case C.RuleTypeDefault:
|
case C.RuleTypeDefault:
|
||||||
@ -50,7 +50,7 @@ func hasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultH
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case C.RuleTypeLogical:
|
case C.RuleTypeLogical:
|
||||||
if hasHeadlessRule(rule.LogicalOptions.Rules, cond) {
|
if HasHeadlessRule(rule.LogicalOptions.Rules, cond) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/sagernet/fswatch"
|
"github.com/sagernet/fswatch"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@ -13,7 +14,6 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@ -138,9 +138,9 @@ func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var metadata adapter.RuleSetMetadata
|
var metadata adapter.RuleSetMetadata
|
||||||
metadata.ContainsProcessRule = hasHeadlessRule(headlessRules, isProcessHeadlessRule)
|
metadata.ContainsProcessRule = HasHeadlessRule(headlessRules, isProcessHeadlessRule)
|
||||||
metadata.ContainsWIFIRule = hasHeadlessRule(headlessRules, isWIFIHeadlessRule)
|
metadata.ContainsWIFIRule = HasHeadlessRule(headlessRules, isWIFIHeadlessRule)
|
||||||
metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
|
metadata.ContainsIPCIDRRule = HasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
|
||||||
s.rules = rules
|
s.rules = rules
|
||||||
s.metadata = metadata
|
s.metadata = metadata
|
||||||
s.callbackAccess.Lock()
|
s.callbackAccess.Lock()
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@ -17,7 +18,6 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@ -185,9 +185,9 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
|
|||||||
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
s.metadata.ContainsProcessRule = HasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
||||||
s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
s.metadata.ContainsWIFIRule = HasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
||||||
s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
s.metadata.ContainsIPCIDRRule = HasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
||||||
s.rules = rules
|
s.rules = rules
|
||||||
s.callbackAccess.Lock()
|
s.callbackAccess.Lock()
|
||||||
callbacks := s.callbacks.Array()
|
callbacks := s.callbacks.Array()
|
||||||
|
@ -5,8 +5,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
"github.com/sagernet/sing/service/filemanager"
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
|
@ -3,9 +3,9 @@ package ssmapi
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
@ -29,7 +29,6 @@ var defaultClientHeader = http.Header{
|
|||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
dialer N.Dialer
|
|
||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
transport *http2.Transport
|
transport *http2.Transport
|
||||||
options option.V2RayGRPCOptions
|
options option.V2RayGRPCOptions
|
||||||
@ -46,7 +45,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
}
|
}
|
||||||
client := &Client{
|
client := &Client{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
dialer: dialer,
|
|
||||||
serverAddr: serverAddr,
|
serverAddr: serverAddr,
|
||||||
options: options,
|
options: options,
|
||||||
transport: &http2.Transport{
|
transport: &http2.Transport{
|
||||||
@ -62,7 +60,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
},
|
},
|
||||||
host: host,
|
host: host,
|
||||||
}
|
}
|
||||||
|
|
||||||
if tlsConfig == nil {
|
if tlsConfig == nil {
|
||||||
client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
|
client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
|
||||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
@ -71,12 +68,9 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
if len(tlsConfig.NextProtos()) == 0 {
|
if len(tlsConfig.NextProtos()) == 0 {
|
||||||
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
|
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
|
||||||
}
|
}
|
||||||
|
tlsDialer := tls.NewDialer(dialer, tlsConfig)
|
||||||
client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
|
client.transport.DialTLSContext = func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
|
||||||
conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
return tlsDialer.DialTLSContext(ctx, M.ParseSocksaddr(addr))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tls.ClientHandshake(ctx, conn, tlsConfig)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,15 +47,12 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
if len(tlsConfig.NextProtos()) == 0 {
|
if len(tlsConfig.NextProtos()) == 0 {
|
||||||
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
|
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
|
||||||
}
|
}
|
||||||
|
tlsDialer := tls.NewDialer(dialer, tlsConfig)
|
||||||
transport = &http2.Transport{
|
transport = &http2.Transport{
|
||||||
ReadIdleTimeout: time.Duration(options.IdleTimeout),
|
ReadIdleTimeout: time.Duration(options.IdleTimeout),
|
||||||
PingTimeout: time.Duration(options.PingTimeout),
|
PingTimeout: time.Duration(options.PingTimeout),
|
||||||
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
|
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
|
||||||
conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
return tlsDialer.DialTLSContext(ctx, M.ParseSocksaddr(addr))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return tls.ClientHandshake(ctx, conn, tlsConfig)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@ var _ adapter.V2RayClientTransport = (*Client)(nil)
|
|||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
tlsConfig tls.Config
|
|
||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
requestURL url.URL
|
requestURL url.URL
|
||||||
headers http.Header
|
headers http.Header
|
||||||
@ -35,6 +34,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
if len(tlsConfig.NextProtos()) == 0 {
|
if len(tlsConfig.NextProtos()) == 0 {
|
||||||
tlsConfig.SetNextProtos([]string{"http/1.1"})
|
tlsConfig.SetNextProtos([]string{"http/1.1"})
|
||||||
}
|
}
|
||||||
|
dialer = tls.NewDialer(dialer, tlsConfig)
|
||||||
}
|
}
|
||||||
var host string
|
var host string
|
||||||
if options.Host != "" {
|
if options.Host != "" {
|
||||||
@ -65,7 +65,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
}
|
}
|
||||||
return &Client{
|
return &Client{
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
tlsConfig: tlsConfig,
|
|
||||||
serverAddr: serverAddr,
|
serverAddr: serverAddr,
|
||||||
requestURL: requestURL,
|
requestURL: requestURL,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
@ -78,12 +77,6 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if c.tlsConfig != nil {
|
|
||||||
conn, err = tls.ClientHandshake(ctx, conn, c.tlsConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
request := &http.Request{
|
request := &http.Request{
|
||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
URL: &c.requestURL,
|
URL: &c.requestURL,
|
||||||
|
@ -15,7 +15,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-quic"
|
"github.com/sagernet/sing-quic"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@ -30,7 +29,7 @@ type Client struct {
|
|||||||
tlsConfig tls.Config
|
tlsConfig tls.Config
|
||||||
quicConfig *quic.Config
|
quicConfig *quic.Config
|
||||||
connAccess sync.Mutex
|
connAccess sync.Mutex
|
||||||
conn atomic.TypedValue[quic.Connection]
|
conn common.TypedValue[quic.Connection]
|
||||||
rawConn net.Conn
|
rawConn net.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ var _ adapter.V2RayClientTransport = (*Client)(nil)
|
|||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
tlsConfig tls.Config
|
|
||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
requestURL url.URL
|
requestURL url.URL
|
||||||
headers http.Header
|
headers http.Header
|
||||||
@ -39,6 +38,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
if len(tlsConfig.NextProtos()) == 0 {
|
if len(tlsConfig.NextProtos()) == 0 {
|
||||||
tlsConfig.SetNextProtos([]string{"http/1.1"})
|
tlsConfig.SetNextProtos([]string{"http/1.1"})
|
||||||
}
|
}
|
||||||
|
dialer = tls.NewDialer(dialer, tlsConfig)
|
||||||
}
|
}
|
||||||
var requestURL url.URL
|
var requestURL url.URL
|
||||||
if tlsConfig == nil {
|
if tlsConfig == nil {
|
||||||
@ -65,7 +65,6 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
}
|
}
|
||||||
return &Client{
|
return &Client{
|
||||||
dialer,
|
dialer,
|
||||||
tlsConfig,
|
|
||||||
serverAddr,
|
serverAddr,
|
||||||
requestURL,
|
requestURL,
|
||||||
headers,
|
headers,
|
||||||
@ -79,12 +78,6 @@ func (c *Client) dialContext(ctx context.Context, requestURL *url.URL, headers h
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if c.tlsConfig != nil {
|
|
||||||
conn, err = tls.ClientHandshake(ctx, conn, c.tlsConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var deadlineConn net.Conn
|
var deadlineConn net.Conn
|
||||||
if deadline.NeedAdditionalReadDeadline(conn) {
|
if deadline.NeedAdditionalReadDeadline(conn) {
|
||||||
deadlineConn = deadline.NewConn(conn)
|
deadlineConn = deadline.NewConn(conn)
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@ -17,6 +18,8 @@ type Device interface {
|
|||||||
N.Dialer
|
N.Dialer
|
||||||
Start() error
|
Start() error
|
||||||
SetDevice(device *device.Device)
|
SetDevice(device *device.Device)
|
||||||
|
Inet4Address() netip.Addr
|
||||||
|
Inet6Address() netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeviceOptions struct {
|
type DeviceOptions struct {
|
||||||
@ -35,9 +38,14 @@ type DeviceOptions struct {
|
|||||||
func NewDevice(options DeviceOptions) (Device, error) {
|
func NewDevice(options DeviceOptions) (Device, error) {
|
||||||
if !options.System {
|
if !options.System {
|
||||||
return newStackDevice(options)
|
return newStackDevice(options)
|
||||||
} else if options.Handler == nil {
|
} else if !tun.WithGVisor {
|
||||||
return newSystemDevice(options)
|
return newSystemDevice(options)
|
||||||
} else {
|
} else {
|
||||||
return newSystemStackDevice(options)
|
return newSystemStackDevice(options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NatDevice interface {
|
||||||
|
Device
|
||||||
|
CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
|
||||||
|
}
|
||||||
|
103
transport/wireguard/device_nat.go
Normal file
103
transport/wireguard/device_nat.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package wireguard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing-tun/ping"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Device = (*natDeviceWrapper)(nil)
|
||||||
|
|
||||||
|
type natDeviceWrapper struct {
|
||||||
|
Device
|
||||||
|
ctx context.Context
|
||||||
|
logger logger.ContextLogger
|
||||||
|
packetOutbound chan *buf.Buffer
|
||||||
|
rewriter *ping.Rewriter
|
||||||
|
buffer [][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNATDevice(ctx context.Context, logger logger.ContextLogger, upstream Device) NatDevice {
|
||||||
|
wrapper := &natDeviceWrapper{
|
||||||
|
Device: upstream,
|
||||||
|
ctx: ctx,
|
||||||
|
logger: logger,
|
||||||
|
packetOutbound: make(chan *buf.Buffer, 256),
|
||||||
|
rewriter: ping.NewRewriter(ctx, logger, upstream.Inet4Address(), upstream.Inet6Address()),
|
||||||
|
}
|
||||||
|
return wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDeviceWrapper) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) {
|
||||||
|
select {
|
||||||
|
case packet := <-d.packetOutbound:
|
||||||
|
defer packet.Release()
|
||||||
|
sizes[0] = copy(bufs[0][offset:], packet.Bytes())
|
||||||
|
return 1, nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return d.Device.Read(bufs, sizes, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDeviceWrapper) Write(bufs [][]byte, offset int) (int, error) {
|
||||||
|
for _, buffer := range bufs {
|
||||||
|
handled, err := d.rewriter.WriteBack(buffer[offset:])
|
||||||
|
if handled {
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d.buffer = append(d.buffer, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(d.buffer) > 0 {
|
||||||
|
_, err := d.Device.Write(d.buffer, offset)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
d.buffer = d.buffer[:0]
|
||||||
|
}
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDeviceWrapper) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||||
|
ctx := log.ContextWithNewID(d.ctx)
|
||||||
|
session := tun.DirectRouteSession{
|
||||||
|
Source: metadata.Source.Addr,
|
||||||
|
Destination: metadata.Destination.Addr,
|
||||||
|
}
|
||||||
|
d.rewriter.CreateSession(session, routeContext)
|
||||||
|
d.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
|
||||||
|
return &natDestination{device: d, session: session}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ tun.DirectRouteDestination = (*natDestination)(nil)
|
||||||
|
|
||||||
|
type natDestination struct {
|
||||||
|
device *natDeviceWrapper
|
||||||
|
session tun.DirectRouteSession
|
||||||
|
closed atomic.Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDestination) WritePacket(buffer *buf.Buffer) error {
|
||||||
|
d.device.rewriter.RewritePacket(buffer.Bytes())
|
||||||
|
d.device.packetOutbound <- buffer
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDestination) Close() error {
|
||||||
|
d.closed.Store(true)
|
||||||
|
d.device.rewriter.DeleteSession(d.session)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDestination) IsClosed() bool {
|
||||||
|
return d.closed.Load()
|
||||||
|
}
|
@ -5,7 +5,9 @@ package wireguard
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/gvisor/pkg/buffer"
|
"github.com/sagernet/gvisor/pkg/buffer"
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||||
@ -14,9 +16,14 @@ import (
|
|||||||
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv4"
|
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv4"
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv6"
|
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv6"
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
"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/tcp"
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
|
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing-tun/ping"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@ -24,30 +31,40 @@ import (
|
|||||||
wgTun "github.com/sagernet/wireguard-go/tun"
|
wgTun "github.com/sagernet/wireguard-go/tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Device = (*stackDevice)(nil)
|
var _ NatDevice = (*stackDevice)(nil)
|
||||||
|
|
||||||
type stackDevice struct {
|
type stackDevice struct {
|
||||||
stack *stack.Stack
|
ctx context.Context
|
||||||
mtu uint32
|
logger log.ContextLogger
|
||||||
events chan wgTun.Event
|
stack *stack.Stack
|
||||||
outbound chan *stack.PacketBuffer
|
mtu uint32
|
||||||
done chan struct{}
|
events chan wgTun.Event
|
||||||
dispatcher stack.NetworkDispatcher
|
outbound chan *stack.PacketBuffer
|
||||||
addr4 tcpip.Address
|
packetOutbound chan *buf.Buffer
|
||||||
addr6 tcpip.Address
|
done chan struct{}
|
||||||
|
dispatcher stack.NetworkDispatcher
|
||||||
|
inet4Address netip.Addr
|
||||||
|
inet6Address netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStackDevice(options DeviceOptions) (*stackDevice, error) {
|
func newStackDevice(options DeviceOptions) (*stackDevice, error) {
|
||||||
tunDevice := &stackDevice{
|
tunDevice := &stackDevice{
|
||||||
mtu: options.MTU,
|
ctx: options.Context,
|
||||||
events: make(chan wgTun.Event, 1),
|
logger: options.Logger,
|
||||||
outbound: make(chan *stack.PacketBuffer, 256),
|
mtu: options.MTU,
|
||||||
done: make(chan struct{}),
|
events: make(chan wgTun.Event, 1),
|
||||||
|
outbound: make(chan *stack.PacketBuffer, 256),
|
||||||
|
packetOutbound: make(chan *buf.Buffer, 256),
|
||||||
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
ipStack, err := tun.NewGVisorStack((*wireEndpoint)(tunDevice))
|
ipStack, err := tun.NewGVisorStackWithOptions((*wireEndpoint)(tunDevice), stack.NICOptions{}, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
var (
|
||||||
|
inet4Address netip.Addr
|
||||||
|
inet6Address netip.Addr
|
||||||
|
)
|
||||||
for _, prefix := range options.Address {
|
for _, prefix := range options.Address {
|
||||||
addr := tun.AddressFromAddr(prefix.Addr())
|
addr := tun.AddressFromAddr(prefix.Addr())
|
||||||
protoAddr := tcpip.ProtocolAddress{
|
protoAddr := tcpip.ProtocolAddress{
|
||||||
@ -57,10 +74,12 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
if prefix.Addr().Is4() {
|
if prefix.Addr().Is4() {
|
||||||
tunDevice.addr4 = addr
|
inet4Address = prefix.Addr()
|
||||||
|
tunDevice.inet4Address = inet4Address
|
||||||
protoAddr.Protocol = ipv4.ProtocolNumber
|
protoAddr.Protocol = ipv4.ProtocolNumber
|
||||||
} else {
|
} else {
|
||||||
tunDevice.addr6 = addr
|
inet6Address = prefix.Addr()
|
||||||
|
tunDevice.inet6Address = inet6Address
|
||||||
protoAddr.Protocol = ipv6.ProtocolNumber
|
protoAddr.Protocol = ipv6.ProtocolNumber
|
||||||
}
|
}
|
||||||
gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{})
|
gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{})
|
||||||
@ -72,6 +91,10 @@ func newStackDevice(options DeviceOptions) (*stackDevice, error) {
|
|||||||
if options.Handler != nil {
|
if options.Handler != nil {
|
||||||
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)
|
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)
|
||||||
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket)
|
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket)
|
||||||
|
icmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout)
|
||||||
|
icmpForwarder.SetLocalAddresses(inet4Address, inet6Address)
|
||||||
|
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
|
||||||
|
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
|
||||||
}
|
}
|
||||||
return tunDevice, nil
|
return tunDevice, nil
|
||||||
}
|
}
|
||||||
@ -88,10 +111,10 @@ func (w *stackDevice) DialContext(ctx context.Context, network string, destinati
|
|||||||
var networkProtocol tcpip.NetworkProtocolNumber
|
var networkProtocol tcpip.NetworkProtocolNumber
|
||||||
if destination.IsIPv4() {
|
if destination.IsIPv4() {
|
||||||
networkProtocol = header.IPv4ProtocolNumber
|
networkProtocol = header.IPv4ProtocolNumber
|
||||||
bind.Addr = w.addr4
|
bind.Addr = tun.AddressFromAddr(w.inet4Address)
|
||||||
} else {
|
} else {
|
||||||
networkProtocol = header.IPv6ProtocolNumber
|
networkProtocol = header.IPv6ProtocolNumber
|
||||||
bind.Addr = w.addr6
|
bind.Addr = tun.AddressFromAddr(w.inet4Address)
|
||||||
}
|
}
|
||||||
switch N.NetworkName(network) {
|
switch N.NetworkName(network) {
|
||||||
case N.NetworkTCP:
|
case N.NetworkTCP:
|
||||||
@ -118,10 +141,10 @@ func (w *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr)
|
|||||||
var networkProtocol tcpip.NetworkProtocolNumber
|
var networkProtocol tcpip.NetworkProtocolNumber
|
||||||
if destination.IsIPv4() {
|
if destination.IsIPv4() {
|
||||||
networkProtocol = header.IPv4ProtocolNumber
|
networkProtocol = header.IPv4ProtocolNumber
|
||||||
bind.Addr = w.addr4
|
bind.Addr = tun.AddressFromAddr(w.inet4Address)
|
||||||
} else {
|
} else {
|
||||||
networkProtocol = header.IPv6ProtocolNumber
|
networkProtocol = header.IPv6ProtocolNumber
|
||||||
bind.Addr = w.addr6
|
bind.Addr = tun.AddressFromAddr(w.inet4Address)
|
||||||
}
|
}
|
||||||
udpConn, err := gonet.DialUDP(w.stack, &bind, nil, networkProtocol)
|
udpConn, err := gonet.DialUDP(w.stack, &bind, nil, networkProtocol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -130,6 +153,14 @@ func (w *stackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr)
|
|||||||
return udpConn, nil
|
return udpConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *stackDevice) Inet4Address() netip.Addr {
|
||||||
|
return w.inet4Address
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *stackDevice) Inet6Address() netip.Addr {
|
||||||
|
return w.inet6Address
|
||||||
|
}
|
||||||
|
|
||||||
func (w *stackDevice) SetDevice(device *device.Device) {
|
func (w *stackDevice) SetDevice(device *device.Device) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,20 +175,24 @@ func (w *stackDevice) File() *os.File {
|
|||||||
|
|
||||||
func (w *stackDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) {
|
func (w *stackDevice) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) {
|
||||||
select {
|
select {
|
||||||
case packetBuffer, ok := <-w.outbound:
|
case packet, ok := <-w.outbound:
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, os.ErrClosed
|
return 0, os.ErrClosed
|
||||||
}
|
}
|
||||||
defer packetBuffer.DecRef()
|
defer packet.DecRef()
|
||||||
p := bufs[0]
|
var copyN int
|
||||||
p = p[offset:]
|
/*rangeIterate(packet.Data().AsRange(), func(view *buffer.View) {
|
||||||
n := 0
|
copyN += copy(bufs[0][offset+copyN:], view.AsSlice())
|
||||||
for _, slice := range packetBuffer.AsSlices() {
|
})*/
|
||||||
n += copy(p[n:], slice)
|
for _, view := range packet.AsSlices() {
|
||||||
|
copyN += copy(bufs[0][offset+copyN:], view)
|
||||||
}
|
}
|
||||||
sizes[0] = n
|
sizes[0] = copyN
|
||||||
count = 1
|
return 1, nil
|
||||||
return
|
case packet := <-w.packetOutbound:
|
||||||
|
defer packet.Release()
|
||||||
|
sizes[0] = copy(bufs[0][offset:], packet.Bytes())
|
||||||
|
return 1, nil
|
||||||
case <-w.done:
|
case <-w.done:
|
||||||
return 0, os.ErrClosed
|
return 0, os.ErrClosed
|
||||||
}
|
}
|
||||||
@ -217,6 +252,23 @@ func (w *stackDevice) BatchSize() int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *stackDevice) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||||
|
ctx := log.ContextWithNewID(w.ctx)
|
||||||
|
destination, err := ping.ConnectGVisor(
|
||||||
|
ctx, w.logger,
|
||||||
|
metadata.Source.Addr, metadata.Destination.Addr,
|
||||||
|
routeContext,
|
||||||
|
w.stack,
|
||||||
|
w.inet4Address, w.inet6Address,
|
||||||
|
timeout,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
|
||||||
|
return destination, nil
|
||||||
|
}
|
||||||
|
|
||||||
var _ stack.LinkEndpoint = (*wireEndpoint)(nil)
|
var _ stack.LinkEndpoint = (*wireEndpoint)(nil)
|
||||||
|
|
||||||
type wireEndpoint stackDevice
|
type wireEndpoint stackDevice
|
||||||
|
@ -22,22 +22,42 @@ import (
|
|||||||
var _ Device = (*systemDevice)(nil)
|
var _ Device = (*systemDevice)(nil)
|
||||||
|
|
||||||
type systemDevice struct {
|
type systemDevice struct {
|
||||||
options DeviceOptions
|
options DeviceOptions
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
device tun.Tun
|
device tun.Tun
|
||||||
batchDevice tun.LinuxTUN
|
batchDevice tun.LinuxTUN
|
||||||
events chan wgTun.Event
|
events chan wgTun.Event
|
||||||
closeOnce sync.Once
|
closeOnce sync.Once
|
||||||
|
inet4Address netip.Addr
|
||||||
|
inet6Address netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSystemDevice(options DeviceOptions) (*systemDevice, error) {
|
func newSystemDevice(options DeviceOptions) (*systemDevice, error) {
|
||||||
if options.Name == "" {
|
if options.Name == "" {
|
||||||
options.Name = tun.CalculateInterfaceName("wg")
|
options.Name = tun.CalculateInterfaceName("wg")
|
||||||
}
|
}
|
||||||
|
var inet4Address netip.Addr
|
||||||
|
var inet6Address netip.Addr
|
||||||
|
if len(options.Address) > 0 {
|
||||||
|
if prefix := common.Find(options.Address, func(it netip.Prefix) bool {
|
||||||
|
return it.Addr().Is4()
|
||||||
|
}); prefix.IsValid() {
|
||||||
|
inet4Address = prefix.Addr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(options.Address) > 0 {
|
||||||
|
if prefix := common.Find(options.Address, func(it netip.Prefix) bool {
|
||||||
|
return it.Addr().Is6()
|
||||||
|
}); prefix.IsValid() {
|
||||||
|
inet6Address = prefix.Addr()
|
||||||
|
}
|
||||||
|
}
|
||||||
return &systemDevice{
|
return &systemDevice{
|
||||||
options: options,
|
options: options,
|
||||||
dialer: options.CreateDialer(options.Name),
|
dialer: options.CreateDialer(options.Name),
|
||||||
events: make(chan wgTun.Event, 1),
|
events: make(chan wgTun.Event, 1),
|
||||||
|
inet4Address: inet4Address,
|
||||||
|
inet6Address: inet6Address,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +69,14 @@ func (w *systemDevice) ListenPacket(ctx context.Context, destination M.Socksaddr
|
|||||||
return w.dialer.ListenPacket(ctx, destination)
|
return w.dialer.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *systemDevice) Inet4Address() netip.Addr {
|
||||||
|
return w.inet4Address
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *systemDevice) Inet6Address() netip.Addr {
|
||||||
|
return w.inet6Address
|
||||||
|
}
|
||||||
|
|
||||||
func (w *systemDevice) SetDevice(device *device.Device) {
|
func (w *systemDevice) SetDevice(device *device.Device) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,16 +3,26 @@
|
|||||||
package wireguard
|
package wireguard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/gvisor/pkg/buffer"
|
"github.com/sagernet/gvisor/pkg/buffer"
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||||
|
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv4"
|
||||||
|
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv6"
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
"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/tcp"
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
|
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing-tun/ping"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
"github.com/sagernet/wireguard-go/device"
|
"github.com/sagernet/wireguard-go/device"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,6 +30,8 @@ var _ Device = (*systemStackDevice)(nil)
|
|||||||
|
|
||||||
type systemStackDevice struct {
|
type systemStackDevice struct {
|
||||||
*systemDevice
|
*systemDevice
|
||||||
|
ctx context.Context
|
||||||
|
logger logger.ContextLogger
|
||||||
stack *stack.Stack
|
stack *stack.Stack
|
||||||
endpoint *deviceEndpoint
|
endpoint *deviceEndpoint
|
||||||
writeBufs [][]byte
|
writeBufs [][]byte
|
||||||
@ -34,13 +46,45 @@ func newSystemStackDevice(options DeviceOptions) (*systemStackDevice, error) {
|
|||||||
mtu: options.MTU,
|
mtu: options.MTU,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}
|
}
|
||||||
ipStack, err := tun.NewGVisorStack(endpoint)
|
ipStack, err := tun.NewGVisorStackWithOptions(endpoint, stack.NICOptions{}, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)
|
var (
|
||||||
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket)
|
inet4Address netip.Addr
|
||||||
|
inet6Address netip.Addr
|
||||||
|
)
|
||||||
|
for _, prefix := range options.Address {
|
||||||
|
addr := tun.AddressFromAddr(prefix.Addr())
|
||||||
|
protoAddr := tcpip.ProtocolAddress{
|
||||||
|
AddressWithPrefix: tcpip.AddressWithPrefix{
|
||||||
|
Address: addr,
|
||||||
|
PrefixLen: prefix.Bits(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if prefix.Addr().Is4() {
|
||||||
|
inet4Address = prefix.Addr()
|
||||||
|
protoAddr.Protocol = ipv4.ProtocolNumber
|
||||||
|
} else {
|
||||||
|
inet6Address = prefix.Addr()
|
||||||
|
protoAddr.Protocol = ipv6.ProtocolNumber
|
||||||
|
}
|
||||||
|
gErr := ipStack.AddProtocolAddress(tun.DefaultNIC, protoAddr, stack.AddressProperties{})
|
||||||
|
if gErr != nil {
|
||||||
|
return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", gErr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.Handler != nil {
|
||||||
|
ipStack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(options.Context, ipStack, options.Handler).HandlePacket)
|
||||||
|
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout).HandlePacket)
|
||||||
|
icmpForwarder := tun.NewICMPForwarder(options.Context, ipStack, options.Handler, options.UDPTimeout)
|
||||||
|
icmpForwarder.SetLocalAddresses(inet4Address, inet6Address)
|
||||||
|
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
|
||||||
|
ipStack.SetTransportProtocolHandler(icmp.ProtocolNumber6, icmpForwarder.HandlePacket)
|
||||||
|
}
|
||||||
return &systemStackDevice{
|
return &systemStackDevice{
|
||||||
|
ctx: options.Context,
|
||||||
|
logger: options.Logger,
|
||||||
systemDevice: system,
|
systemDevice: system,
|
||||||
stack: ipStack,
|
stack: ipStack,
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
@ -116,6 +160,23 @@ func (w *systemStackDevice) writeStack(packet []byte) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *systemStackDevice) CreateDestination(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||||
|
ctx := log.ContextWithNewID(w.ctx)
|
||||||
|
destination, err := ping.ConnectGVisor(
|
||||||
|
ctx, w.logger,
|
||||||
|
metadata.Source.Addr, metadata.Destination.Addr,
|
||||||
|
routeContext,
|
||||||
|
w.stack,
|
||||||
|
w.inet4Address, w.inet6Address,
|
||||||
|
timeout,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection from ", metadata.Source.AddrString(), " to ", metadata.Destination.AddrString())
|
||||||
|
return destination, nil
|
||||||
|
}
|
||||||
|
|
||||||
type deviceEndpoint struct {
|
type deviceEndpoint struct {
|
||||||
mtu uint32
|
mtu uint32
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
|
@ -8,8 +8,13 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
@ -29,7 +34,9 @@ type Endpoint struct {
|
|||||||
ipcConf string
|
ipcConf string
|
||||||
allowedAddress []netip.Prefix
|
allowedAddress []netip.Prefix
|
||||||
tunDevice Device
|
tunDevice Device
|
||||||
|
natDevice NatDevice
|
||||||
device *device.Device
|
device *device.Device
|
||||||
|
allowedIPs *device.AllowedIPs
|
||||||
pause pause.Manager
|
pause pause.Manager
|
||||||
pauseCallback *list.Element[pause.Callback]
|
pauseCallback *list.Element[pause.Callback]
|
||||||
}
|
}
|
||||||
@ -111,12 +118,17 @@ func NewEndpoint(options EndpointOptions) (*Endpoint, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create WireGuard device")
|
return nil, E.Cause(err, "create WireGuard device")
|
||||||
}
|
}
|
||||||
|
natDevice, isNatDevice := tunDevice.(NatDevice)
|
||||||
|
if !isNatDevice {
|
||||||
|
natDevice = NewNATDevice(options.Context, options.Logger, tunDevice)
|
||||||
|
}
|
||||||
return &Endpoint{
|
return &Endpoint{
|
||||||
options: options,
|
options: options,
|
||||||
peers: peers,
|
peers: peers,
|
||||||
ipcConf: ipcConf,
|
ipcConf: ipcConf,
|
||||||
allowedAddress: allowedAddresses,
|
allowedAddress: allowedAddresses,
|
||||||
tunDevice: tunDevice,
|
tunDevice: tunDevice,
|
||||||
|
natDevice: natDevice,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +188,13 @@ func (e *Endpoint) Start(resolve bool) error {
|
|||||||
e.options.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...))
|
e.options.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
wgDevice := device.NewDevice(e.options.Context, e.tunDevice, bind, logger, e.options.Workers)
|
var deviceInput Device
|
||||||
|
if e.natDevice != nil {
|
||||||
|
deviceInput = e.natDevice
|
||||||
|
} else {
|
||||||
|
deviceInput = e.tunDevice
|
||||||
|
}
|
||||||
|
wgDevice := device.NewDevice(e.options.Context, deviceInput, bind, logger, e.options.Workers)
|
||||||
e.tunDevice.SetDevice(wgDevice)
|
e.tunDevice.SetDevice(wgDevice)
|
||||||
ipcConf := e.ipcConf
|
ipcConf := e.ipcConf
|
||||||
for _, peer := range e.peers {
|
for _, peer := range e.peers {
|
||||||
@ -191,6 +209,7 @@ func (e *Endpoint) Start(resolve bool) error {
|
|||||||
if e.pause != nil {
|
if e.pause != nil {
|
||||||
e.pauseCallback = e.pause.RegisterCallback(e.onPauseUpdated)
|
e.pauseCallback = e.pause.RegisterCallback(e.onPauseUpdated)
|
||||||
}
|
}
|
||||||
|
e.allowedIPs = (*device.AllowedIPs)(unsafe.Pointer(reflect.Indirect(reflect.ValueOf(wgDevice)).FieldByName("allowedips").UnsafeAddr()))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,6 +237,20 @@ func (e *Endpoint) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Endpoint) Lookup(address netip.Addr) *device.Peer {
|
||||||
|
if e.allowedIPs == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e.allowedIPs.Lookup(address.AsSlice())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Endpoint) NewDirectRouteConnection(metadata adapter.InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
|
||||||
|
if e.natDevice == nil {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
return e.natDevice.CreateDestination(metadata, routeContext, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Endpoint) onPauseUpdated(event int) {
|
func (e *Endpoint) onPauseUpdated(event int) {
|
||||||
switch event {
|
switch event {
|
||||||
case pause.EventDevicePaused, pause.EventNetworkPause:
|
case pause.EventDevicePaused, pause.EventNetworkPause:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user