Compare commits

..

6 Commits

Author SHA1 Message Date
世界
c67692085d
Fix multiple sniff 2025-09-02 21:38:08 +08:00
世界
0ef7e8eca2
Fix route.default_interface not taking effect 2025-09-02 18:00:02 +08:00
世界
1a18e43a88
Fix linux icmp routes 2025-09-02 17:55:48 +08:00
世界
6849288d6d
Fix typo in TestSniffUQUICChrome115 2025-09-02 17:55:26 +08:00
世界
2edfed7d91
Improve DHCP DNS server 2025-09-02 17:55:26 +08:00
世界
30c069f5b7
Fix local DNS server on legacy windows 2025-09-02 17:55:26 +08:00
6 changed files with 100 additions and 69 deletions

View File

@ -57,6 +57,7 @@ type InboundContext struct {
Domain string
Client string
SniffContext any
SnifferNames []string
SniffError error
// cache

View File

@ -88,43 +88,41 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
if networkManager != nil {
defaultOptions := networkManager.DefaultOptions()
if !disableDefaultBind {
if defaultOptions.BindInterface != "" {
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
if defaultOptions.BindInterface != "" {
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if networkManager.AutoDetectInterface() && !disableDefaultBind {
if platformInterface != nil {
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
networkStrategy = defaultOptions.NetworkStrategy
networkType = defaultOptions.NetworkType
fallbackNetworkType = defaultOptions.FallbackNetworkType
}
networkFallbackDelay = time.Duration(options.FallbackDelay)
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
networkFallbackDelay = defaultOptions.FallbackDelay
}
if networkStrategy == nil {
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
defaultNetworkStrategy = true
}
bindFunc := networkManager.ProtectFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else {
bindFunc := networkManager.AutoDetectInterfaceFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if networkManager.AutoDetectInterface() {
if platformInterface != nil {
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
networkStrategy = defaultOptions.NetworkStrategy
networkType = defaultOptions.NetworkType
fallbackNetworkType = defaultOptions.FallbackNetworkType
}
networkFallbackDelay = time.Duration(options.FallbackDelay)
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
networkFallbackDelay = defaultOptions.FallbackDelay
}
if networkStrategy == nil {
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
defaultNetworkStrategy = true
}
bindFunc := networkManager.ProtectFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else {
bindFunc := networkManager.AutoDetectInterfaceFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
}
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
}
}
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
}
}
if networkManager != nil {
markFunc := networkManager.AutoRedirectOutputMarkFunc()

4
go.mod
View File

@ -27,13 +27,13 @@ require (
github.com/sagernet/gomobile v0.1.8
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
github.com/sagernet/quic-go v0.52.0-beta.1
github.com/sagernet/sing v0.7.6-0.20250902093152-bd5790e3b4db
github.com/sagernet/sing v0.7.6
github.com/sagernet/sing-mux v0.3.3
github.com/sagernet/sing-quic v0.5.0
github.com/sagernet/sing-shadowsocks v0.2.8
github.com/sagernet/sing-shadowsocks2 v0.2.1
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
github.com/sagernet/sing-vmess v0.2.7
github.com/sagernet/smux v1.5.34-mod.2
github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.1

8
go.sum
View File

@ -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/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.7.6-0.20250902093152-bd5790e3b4db h1:D+56hVUg42b+nbXVYMwDNyFaN6vTviSXqF96dvBqRiM=
github.com/sagernet/sing v0.7.6-0.20250902093152-bd5790e3b4db/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.7.6 h1:6LBfDH+aI/26J3r9UHlaxTNjJeMhBpU/wrk0JKDZYI4=
github.com/sagernet/sing v0.7.6/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/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
github.com/sagernet/sing-quic v0.5.0 h1:jNLIyVk24lFPvu8A4x+ZNEnZdI+Tg1rp7eCJ6v0Csak=
@ -179,8 +179,8 @@ 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-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-tun v0.7.0-beta.1 h1:mBIFXYAnGO5ey/HcCYanqnBx61E7yF8zTFGRZonGYmY=
github.com/sagernet/sing-tun v0.7.0-beta.1/go.mod h1:AHJuRrLbNRJuivuFZ2VhXwDj4ViYp14szG5EkkKAqRQ=
github.com/sagernet/sing-tun v0.7.0 h1:zda+KYbxVyeWKdE9k73Ax02jg7cmPZ7/4ZTVCloFBYo=
github.com/sagernet/sing-tun v0.7.0/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM=
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/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=

View File

@ -27,6 +27,8 @@ import (
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/uot"
"golang.org/x/exp/slices"
)
// Deprecated: use RouteConnectionEx instead.
@ -501,7 +503,7 @@ func (r *Router) actionSniff(
if inputConn != nil {
if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 {
return
} else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
} else if slices.Equal(metadata.SnifferNames, action.SnifferNames) && metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
return
}
@ -528,6 +530,7 @@ func (r *Router) actionSniff(
action.Timeout,
streamSniffers...,
)
metadata.SnifferNames = action.SnifferNames
metadata.SniffError = err
if err == nil {
//goland:noinspection GoDeprecation
@ -553,10 +556,13 @@ func (r *Router) actionSniff(
} else if inputPacketConn != nil {
if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 {
return
} else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
} else if slices.Equal(metadata.SnifferNames, action.SnifferNames) && metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
return
}
quicMoreData := func() bool {
return slices.Equal(metadata.SnifferNames, action.SnifferNames) && errors.Is(metadata.SniffError, sniff.ErrNeedMoreData)
}
var packetSniffers []sniff.PacketSniffer
if len(action.PacketSniffers) > 0 {
packetSniffers = action.PacketSniffers
@ -571,12 +577,36 @@ func (r *Router) actionSniff(
sniff.NTP,
}
}
var err error
for _, packetBuffer := range packetBuffers {
if quicMoreData() {
err = sniff.PeekPacket(
ctx,
metadata,
packetBuffer.Buffer.Bytes(),
sniff.QUICClientHello,
)
} else {
err = sniff.PeekPacket(
ctx, metadata,
packetBuffer.Buffer.Bytes(),
packetSniffers...,
)
}
metadata.SnifferNames = action.SnifferNames
metadata.SniffError = err
if errors.Is(err, sniff.ErrNeedMoreData) {
// TODO: replace with generic message when there are more multi-packet protocols
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
continue
}
goto finally
}
for {
var (
sniffBuffer = buf.NewPacket()
destination M.Socksaddr
done = make(chan struct{})
err error
)
go func() {
sniffTimeout := C.ReadPayloadTimeout
@ -602,7 +632,7 @@ func (r *Router) actionSniff(
return
}
} else {
if len(packetBuffers) > 0 || metadata.SniffError != nil {
if quicMoreData() {
err = sniff.PeekPacket(
ctx,
metadata,
@ -622,32 +652,34 @@ func (r *Router) actionSniff(
Destination: destination,
}
packetBuffers = append(packetBuffers, packetBuffer)
metadata.SnifferNames = action.SnifferNames
metadata.SniffError = err
if errors.Is(err, sniff.ErrNeedMoreData) {
// TODO: replace with generic message when there are more multi-packet protocols
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
continue
}
if metadata.Protocol != "" {
//goland:noinspection GoDeprecation
if action.OverrideDestination && M.IsDomainName(metadata.Domain) {
metadata.Destination = M.Socksaddr{
Fqdn: metadata.Domain,
Port: metadata.Destination.Port,
}
}
if metadata.Domain != "" && metadata.Client != "" {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client)
} else if metadata.Domain != "" {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain)
} else if metadata.Client != "" {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", client: ", metadata.Client)
} else {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol)
}
}
goto finally
}
finally:
if err == nil {
//goland:noinspection GoDeprecation
if action.OverrideDestination && M.IsDomainName(metadata.Domain) {
metadata.Destination = M.Socksaddr{
Fqdn: metadata.Domain,
Port: metadata.Destination.Port,
}
}
break
if metadata.Domain != "" && metadata.Client != "" {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain, ", client: ", metadata.Client)
} else if metadata.Domain != "" {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", domain: ", metadata.Domain)
} else if metadata.Client != "" {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol, ", client: ", metadata.Client)
} else {
r.logger.DebugContext(ctx, "sniffed packet protocol: ", metadata.Protocol)
}
}
}
return

View File

@ -87,7 +87,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
return &RuleActionHijackDNS{}, nil
case C.RuleActionTypeSniff:
sniffAction := &RuleActionSniff{
snifferNames: action.SniffOptions.Sniffer,
SnifferNames: action.SniffOptions.Sniffer,
Timeout: time.Duration(action.SniffOptions.Timeout),
}
return sniffAction, sniffAction.build()
@ -361,7 +361,7 @@ func (r *RuleActionHijackDNS) String() string {
}
type RuleActionSniff struct {
snifferNames []string
SnifferNames []string
StreamSniffers []sniff.StreamSniffer
PacketSniffers []sniff.PacketSniffer
Timeout time.Duration
@ -374,7 +374,7 @@ func (r *RuleActionSniff) Type() string {
}
func (r *RuleActionSniff) build() error {
for _, name := range r.snifferNames {
for _, name := range r.SnifferNames {
switch name {
case C.ProtocolTLS:
r.StreamSniffers = append(r.StreamSniffers, sniff.TLSClientHello)
@ -407,14 +407,14 @@ func (r *RuleActionSniff) build() error {
}
func (r *RuleActionSniff) String() string {
if len(r.snifferNames) == 0 && r.Timeout == 0 {
if len(r.SnifferNames) == 0 && r.Timeout == 0 {
return "sniff"
} else if len(r.snifferNames) > 0 && r.Timeout == 0 {
return F.ToString("sniff(", strings.Join(r.snifferNames, ","), ")")
} else if len(r.snifferNames) == 0 && r.Timeout > 0 {
} else if len(r.SnifferNames) > 0 && r.Timeout == 0 {
return F.ToString("sniff(", strings.Join(r.SnifferNames, ","), ")")
} else if len(r.SnifferNames) == 0 && r.Timeout > 0 {
return F.ToString("sniff(", r.Timeout.String(), ")")
} else {
return F.ToString("sniff(", strings.Join(r.snifferNames, ","), ",", r.Timeout.String(), ")")
return F.ToString("sniff(", strings.Join(r.SnifferNames, ","), ",", r.Timeout.String(), ")")
}
}