Compare commits

..

32 Commits

Author SHA1 Message Date
世界
0102ac830d
documentation: Bump version 2025-09-06 09:07:18 +08:00
世界
c3964f8cbc
Add proxy support for ICMP echo request 2025-09-06 09:06:44 +08:00
世界
109a614817
Fix resolve using resolved 2025-09-06 09:04:46 +08:00
世界
0ab00a40cb
documentation: Update behavior of local DNS server on darwin 2025-09-06 09:03:42 +08:00
世界
822d914941
Stop using DHCP on iOS and tvOS
We do not have the `com.apple.developer.networking.multicast` entitlement and are unable to obtain it for non-technical reasons.
2025-09-06 09:03:42 +08:00
世界
3c764499d2
Remove use of ldflags -checklinkname=0 on darwin 2025-09-06 09:03:42 +08:00
世界
1a1f13c7f3
Fix local DNS server on darwin
We mistakenly believed that `libresolv`'s `search` function worked correctly in NetworkExtension, but it seems only `getaddrinfo` does.

This commit changes the behavior of the `local` DNS server in NetworkExtension to prefer DHCP, falling back to `getaddrinfo` if DHCP servers are unavailable.

It's worth noting that `prefer_go` does not disable DHCP since it respects Dial Fields, but `getaddrinfo` does the opposite. The new behavior only applies to NetworkExtension, not to all scenarios (primarily command-line binaries) as it did previously.

In addition, this commit also improves the DHCP DNS server to use the same robust query logic as `local`.
2025-09-06 09:03:36 +08:00
世界
00afb50510
Fix legacy DNS config 2025-09-06 09:03:27 +08:00
世界
6ebd711a27
Fix rule-set format 2025-09-06 09:03:27 +08:00
世界
9e743dab66
documentation: Remove outdated icons 2025-09-06 09:03:27 +08:00
世界
6a280a182d
documentation: Improve local DNS server 2025-09-06 09:03:27 +08:00
世界
43464368af
Use libresolv in local DNS server on darwin 2025-09-06 09:03:27 +08:00
世界
2606cc771b
Use resolved in local DNS server if available 2025-09-06 09:03:27 +08:00
xchacha20-poly1305
65c1842d5d
Fix rule set version 2025-09-06 08:52:23 +08:00
世界
26dea6c0df
documentation: Add preferred_by route rule item 2025-09-06 08:52:23 +08:00
世界
2f57365248
Add preferred_by route rule item 2025-09-06 08:52:23 +08:00
世界
9283c29147
documentation: Add interface address rule items 2025-09-06 08:47:04 +08:00
世界
7bf5d29e42
Add interface address rule items 2025-09-06 08:47:04 +08:00
neletor
a5d3ce5efb
Add support for ech retry configs 2025-09-06 08:47:04 +08:00
Zephyruso
07240af171
Add /dns/flush-clash meta api 2025-09-06 08:47:04 +08:00
世界
d8191a1ee6
Treat requests with OPT extra but no options as simple requests 2025-09-05 16:28:29 +08:00
世界
c6396b8a59
Fix DNS packet size 2025-09-05 16:17:49 +08:00
世界
d78cdac640
Fix DNS client 2025-09-05 15:49:06 +08:00
世界
531beeeed7
Fix DNS cache 2025-09-05 15:20:49 +08:00
世界
f352f84483
Fix read address 2025-09-05 15:16:14 +08:00
世界
cbf48e9b8c
Fix multiple sniff 2025-09-03 20:09:05 +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
世界
649163cb7b
Fix domain strategy not taking effect 2025-09-02 17:35:27 +08:00
20 changed files with 214 additions and 209 deletions

View File

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

View File

@ -78,8 +78,8 @@ func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
// Deprecated: removed // Deprecated: removed
func UpstreamMetadata(metadata InboundContext) M.Metadata { func UpstreamMetadata(metadata InboundContext) M.Metadata {
return M.Metadata{ return M.Metadata{
Source: metadata.Source, Source: metadata.Source.Unwrap(),
Destination: metadata.Destination, Destination: metadata.Destination.Unwrap(),
} }
} }

View File

@ -88,12 +88,11 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
if networkManager != nil { if networkManager != nil {
defaultOptions := networkManager.DefaultOptions() defaultOptions := networkManager.DefaultOptions()
if !disableDefaultBind {
if defaultOptions.BindInterface != "" { if defaultOptions.BindInterface != "" {
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1) bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc) dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc)
} else if networkManager.AutoDetectInterface() { } else if networkManager.AutoDetectInterface() && !disableDefaultBind {
if platformInterface != nil { if platformInterface != nil {
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy) networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
networkType = common.Map(options.NetworkType, option.InterfaceType.Build) networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
@ -125,7 +124,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true)) listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
} }
} }
}
if networkManager != nil { if networkManager != nil {
markFunc := networkManager.AutoRedirectOutputMarkFunc() markFunc := networkManager.AutoRedirectOutputMarkFunc()
dialer.Control = control.Append(dialer.Control, markFunc) dialer.Control = control.Append(dialer.Control, markFunc)

View File

@ -56,7 +56,7 @@ func TestSniffUQUICChrome115(t *testing.T) {
err = sniff.QUICClientHello(context.Background(), &metadata, pkt) err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, metadata.Protocol, C.ProtocolQUIC) require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
require.Equal(t, metadata.Client, C.ClientQUICGo) require.Equal(t, metadata.Client, C.ClientChromium)
require.Equal(t, metadata.Domain, "www.google.com") require.Equal(t, metadata.Domain, "www.google.com")
} }

View File

@ -2,12 +2,14 @@ package dns
import ( import (
"context" "context"
"errors"
"net" "net"
"net/netip" "net/netip"
"strings" "strings"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/compatible"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@ -17,7 +19,7 @@ import (
"github.com/sagernet/sing/contrab/freelru" "github.com/sagernet/sing/contrab/freelru"
"github.com/sagernet/sing/contrab/maphash" "github.com/sagernet/sing/contrab/maphash"
dns "github.com/miekg/dns" "github.com/miekg/dns"
) )
var ( var (
@ -39,7 +41,9 @@ type Client struct {
initRDRCFunc func() adapter.RDRCStore initRDRCFunc func() adapter.RDRCStore
logger logger.ContextLogger logger logger.ContextLogger
cache freelru.Cache[dns.Question, *dns.Msg] cache freelru.Cache[dns.Question, *dns.Msg]
cacheLock compatible.Map[dns.Question, chan struct{}]
transportCache freelru.Cache[transportCacheKey, *dns.Msg] transportCache freelru.Cache[transportCacheKey, *dns.Msg]
transportCacheLock compatible.Map[dns.Question, chan struct{}]
} }
type ClientOptions struct { type ClientOptions struct {
@ -96,17 +100,15 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
if c.logger != nil { if c.logger != nil {
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question)) c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
} }
responseMessage := dns.Msg{ return FixedResponseStatus(message, dns.RcodeFormatError), nil
MsgHdr: dns.MsgHdr{
Id: message.Id,
Response: true,
Rcode: dns.RcodeFormatError,
},
Question: message.Question,
}
return &responseMessage, nil
} }
question := message.Question[0] question := message.Question[0]
if question.Qtype == dns.TypeA && options.Strategy == C.DomainStrategyIPv6Only || question.Qtype == dns.TypeAAAA && options.Strategy == C.DomainStrategyIPv4Only {
if c.logger != nil {
c.logger.DebugContext(ctx, "strategy rejected")
}
return FixedResponseStatus(message, dns.RcodeSuccess), nil
}
clientSubnet := options.ClientSubnet clientSubnet := options.ClientSubnet
if !clientSubnet.IsValid() { if !clientSubnet.IsValid() {
clientSubnet = c.clientSubnet clientSubnet = c.clientSubnet
@ -114,12 +116,38 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
if clientSubnet.IsValid() { if clientSubnet.IsValid() {
message = SetClientSubnet(message, clientSubnet) message = SetClientSubnet(message, clientSubnet)
} }
isSimpleRequest := len(message.Question) == 1 && isSimpleRequest := len(message.Question) == 1 &&
len(message.Ns) == 0 && len(message.Ns) == 0 &&
len(message.Extra) == 0 && (len(message.Extra) == 0 || len(message.Extra) == 1 &&
message.Extra[0].Header().Rrtype == dns.TypeOPT &&
message.Extra[0].Header().Class > 0 &&
message.Extra[0].Header().Ttl == 0 &&
len(message.Extra[0].(*dns.OPT).Option) == 0) &&
!options.ClientSubnet.IsValid() !options.ClientSubnet.IsValid()
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
if !disableCache { if !disableCache {
if c.cache != nil {
cond, loaded := c.cacheLock.LoadOrStore(question, make(chan struct{}))
if loaded {
<-cond
} else {
defer func() {
c.cacheLock.Delete(question)
close(cond)
}()
}
} else if c.transportCache != nil {
cond, loaded := c.transportCacheLock.LoadOrStore(question, make(chan struct{}))
if loaded {
<-cond
} else {
defer func() {
c.transportCacheLock.Delete(question)
close(cond)
}()
}
}
response, ttl := c.loadResponse(question, transport) response, ttl := c.loadResponse(question, transport)
if response != nil { if response != nil {
logCachedResponse(c.logger, ctx, response, ttl) logCachedResponse(c.logger, ctx, response, ttl)
@ -127,27 +155,14 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
return response, nil return response, nil
} }
} }
if question.Qtype == dns.TypeA && options.Strategy == C.DomainStrategyIPv6Only || question.Qtype == dns.TypeAAAA && options.Strategy == C.DomainStrategyIPv4Only {
responseMessage := dns.Msg{
MsgHdr: dns.MsgHdr{
Id: message.Id,
Response: true,
Rcode: dns.RcodeSuccess,
},
Question: []dns.Question{question},
}
if c.logger != nil {
c.logger.DebugContext(ctx, "strategy rejected")
}
return &responseMessage, nil
}
messageId := message.Id messageId := message.Id
contextTransport, clientSubnetLoaded := transportTagFromContext(ctx) contextTransport, clientSubnetLoaded := transportTagFromContext(ctx)
if clientSubnetLoaded && transport.Tag() == contextTransport { if clientSubnetLoaded && transport.Tag() == contextTransport {
return nil, E.New("DNS query loopback in transport[", contextTransport, "]") return nil, E.New("DNS query loopback in transport[", contextTransport, "]")
} }
ctx = contextWithTransportTag(ctx, transport.Tag()) ctx = contextWithTransportTag(ctx, transport.Tag())
if responseChecker != nil && c.rdrc != nil { if !disableCache && responseChecker != nil && c.rdrc != nil {
rejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype) rejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype)
if rejected { if rejected {
return nil, ErrResponseRejectedCached return nil, ErrResponseRejectedCached
@ -157,8 +172,13 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
response, err := transport.Exchange(ctx, message) response, err := transport.Exchange(ctx, message)
cancel() cancel()
if err != nil { if err != nil {
var rcodeError RcodeError
if errors.As(err, &rcodeError) {
response = FixedResponseStatus(message, int(rcodeError))
} else {
return nil, err return nil, err
} }
}
/*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA { /*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA {
validResponse := response validResponse := response
loop: loop:
@ -196,13 +216,14 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
}*/ }*/
if responseChecker != nil { if responseChecker != nil {
var rejected bool var rejected bool
if !(response.Rcode == dns.RcodeSuccess || response.Rcode == dns.RcodeNameError) { // TODO: add accept_any rule and support to check response instead of addresses
if response.Rcode != dns.RcodeSuccess || len(response.Answer) == 0 {
rejected = true rejected = true
} else { } else {
rejected = !responseChecker(MessageToAddresses(response)) rejected = !responseChecker(MessageToAddresses(response))
} }
if rejected { if rejected {
if c.rdrc != nil { if !disableCache && c.rdrc != nil {
c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger) c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger)
} }
logRejectedResponse(c.logger, ctx, response) logRejectedResponse(c.logger, ctx, response)
@ -305,8 +326,7 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
func (c *Client) ClearCache() { func (c *Client) ClearCache() {
if c.cache != nil { if c.cache != nil {
c.cache.Purge() c.cache.Purge()
} } else if c.transportCache != nil {
if c.transportCache != nil {
c.transportCache.Purge() c.transportCache.Purge()
} }
} }
@ -320,36 +340,36 @@ func (c *Client) LookupCache(domain string, strategy C.DomainStrategy) ([]netip.
} }
dnsName := dns.Fqdn(domain) dnsName := dns.Fqdn(domain)
if strategy == C.DomainStrategyIPv4Only { if strategy == C.DomainStrategyIPv4Only {
response, err := c.questionCache(dns.Question{ addresses, err := c.questionCache(dns.Question{
Name: dnsName, Name: dnsName,
Qtype: dns.TypeA, Qtype: dns.TypeA,
Qclass: dns.ClassINET, Qclass: dns.ClassINET,
}, nil) }, nil)
if err != ErrNotCached { if err != ErrNotCached {
return response, true return addresses, true
} }
} else if strategy == C.DomainStrategyIPv6Only { } else if strategy == C.DomainStrategyIPv6Only {
response, err := c.questionCache(dns.Question{ addresses, err := c.questionCache(dns.Question{
Name: dnsName, Name: dnsName,
Qtype: dns.TypeAAAA, Qtype: dns.TypeAAAA,
Qclass: dns.ClassINET, Qclass: dns.ClassINET,
}, nil) }, nil)
if err != ErrNotCached { if err != ErrNotCached {
return response, true return addresses, true
} }
} else { } else {
response4, _ := c.questionCache(dns.Question{ response4, _ := c.loadResponse(dns.Question{
Name: dnsName, Name: dnsName,
Qtype: dns.TypeA, Qtype: dns.TypeA,
Qclass: dns.ClassINET, Qclass: dns.ClassINET,
}, nil) }, nil)
response6, _ := c.questionCache(dns.Question{ response6, _ := c.loadResponse(dns.Question{
Name: dnsName, Name: dnsName,
Qtype: dns.TypeAAAA, Qtype: dns.TypeAAAA,
Qclass: dns.ClassINET, Qclass: dns.ClassINET,
}, nil) }, nil)
if len(response4) > 0 || len(response6) > 0 { if response4 != nil || response6 != nil {
return sortAddresses(response4, response6, strategy), true return sortAddresses(MessageToAddresses(response4), MessageToAddresses(response6), strategy), true
} }
} }
return nil, false return nil, false
@ -390,8 +410,7 @@ func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Questio
transportTag: transport.Tag(), transportTag: transport.Tag(),
}, message) }, message)
} }
return } else {
}
if !c.independentCache { if !c.independentCache {
c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive)) c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive))
} else { } else {
@ -401,6 +420,7 @@ func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Questio
}, message, time.Second*time.Duration(timeToLive)) }, message, time.Second*time.Duration(timeToLive))
} }
} }
}
func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name string, qType uint16, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) { func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name string, qType uint16, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
question := dns.Question{ question := dns.Question{
@ -517,6 +537,9 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
} }
func MessageToAddresses(response *dns.Msg) []netip.Addr { func MessageToAddresses(response *dns.Msg) []netip.Addr {
if response.Rcode != dns.RcodeSuccess {
return nil
}
addresses := make([]netip.Addr, 0, len(response.Answer)) addresses := make([]netip.Addr, 0, len(response.Answer))
for _, rawAnswer := range response.Answer { for _, rawAnswer := range response.Answer {
switch answer := rawAnswer.(type) { switch answer := rawAnswer.(type) {
@ -562,8 +585,11 @@ func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {
return &dns.Msg{ return &dns.Msg{
MsgHdr: dns.MsgHdr{ MsgHdr: dns.MsgHdr{
Id: message.Id, Id: message.Id,
Rcode: rcode,
Response: true, Response: true,
Authoritative: true,
RecursionDesired: true,
RecursionAvailable: true,
Rcode: rcode,
}, },
Question: message.Question, Question: message.Question,
} }

View File

@ -16,11 +16,6 @@ import (
mDNS "github.com/miekg/dns" 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) { func (t *Transport) exchangeSingleRequest(ctx context.Context, servers []M.Socksaddr, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
var lastErr error var lastErr error
for _, fqdn := range t.nameList(domain) { for _, fqdn := range t.nameList(domain) {
@ -118,7 +113,7 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio
Question: []mDNS.Question{question}, Question: []mDNS.Question{question},
Compress: true, Compress: true,
} }
request.SetEdns0(maxDNSPacketSize, false) request.SetEdns0(buf.UDPBufferSize, false)
buffer := buf.Get(buf.UDPBufferSize) buffer := buf.Get(buf.UDPBufferSize)
defer buf.Put(buffer) defer buf.Put(buffer)
for _, network := range networks { for _, network := range networks {

View File

@ -121,7 +121,7 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio
Question: []mDNS.Question{question}, Question: []mDNS.Question{question},
Compress: true, Compress: true,
} }
request.SetEdns0(maxDNSPacketSize, false) request.SetEdns0(buf.UDPBufferSize, false)
buffer := buf.Get(buf.UDPBufferSize) buffer := buf.Get(buf.UDPBufferSize)
defer buf.Put(buffer) defer buf.Put(buffer)
for _, network := range networks { for _, network := range networks {

View File

@ -10,11 +10,6 @@ import (
"time" "time"
) )
const (
// net.maxDNSPacketSize
maxDNSPacketSize = 1232
)
type resolverConfig struct { type resolverConfig struct {
initOnce sync.Once initOnce sync.Once
ch chan struct{} ch chan struct{}

View File

@ -2,7 +2,7 @@
icon: material/alert-decagram icon: material/alert-decagram
--- ---
#### 1.13.0-alpha.8 #### 1.13.0-alpha.9
* Fixes and improvements * Fixes and improvements

View File

@ -6,8 +6,8 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/sagernet/sing-box/common/compatible"
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/common" "github.com/sagernet/sing/common"
"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"

6
go.mod
View File

@ -27,13 +27,13 @@ require (
github.com/sagernet/gomobile v0.1.8 github.com/sagernet/gomobile v0.1.8
github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 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.20250826155514-8bdb5fee4568 github.com/sagernet/sing v0.7.8-0.20250906004629-421beb6473ea
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.1
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.0.20250827122908-b76e852f59b0 github.com/sagernet/sing-tun v0.8.0-beta.1
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-sing-box-1.13-mod.1 github.com/sagernet/tailscale v1.80.3-sing-box-1.13-mod.1

12
go.sum
View File

@ -167,20 +167,20 @@ 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.20250826155514-8bdb5fee4568 h1:0bBD73wG4Rmn1ZyMYsvHwgoDz9tFnX8BzvjbAEPoavg= github.com/sagernet/sing v0.7.8-0.20250906004629-421beb6473ea h1:CDRl4q5Y2dM6MQE1MwukhrxbObfK/rj0QtK7vnJhST0=
github.com/sagernet/sing v0.7.6-0.20250826155514-8bdb5fee4568/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.7.8-0.20250906004629-421beb6473ea/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.1 h1:o+mX/schfy6fbbU2rnb6ouUYOL+iUBjA4jOZqyIvDsU=
github.com/sagernet/sing-quic v0.5.0/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0= github.com/sagernet/sing-quic v0.5.1/go.mod h1:evP1e++ZG8TJHVV5HudXV4vWeYzGfCdF4HwSJZcdqkI=
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE= github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI= github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo= github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
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.0.20250827122908-b76e852f59b0 h1:Usid4HU1TKrtao2fv/wyubdOkBHpbHdwgU9KUzWXQMM= github.com/sagernet/sing-tun v0.8.0-beta.1 h1:k8DOTDMBBc42sUW0C91MBMOsqE5jAWtB0kmJq9TTBvU=
github.com/sagernet/sing-tun v0.7.0-beta.1.0.20250827122908-b76e852f59b0/go.mod h1:LokZYuEV3crByjQc/XRohLgfNvybtXdx5qe/I4W6S7k= github.com/sagernet/sing-tun v0.8.0-beta.1/go.mod h1:LokZYuEV3crByjQc/XRohLgfNvybtXdx5qe/I4W6S7k=
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=

View File

@ -124,7 +124,7 @@ func (h *inboundHandler) NewConnectionEx(ctx context.Context, conn net.Conn, sou
//nolint:staticcheck //nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
metadata.Source = source metadata.Source = source
metadata.Destination = destination metadata.Destination = destination.Unwrap()
if userName, _ := auth.UserFromContext[string](ctx); userName != "" { if userName, _ := auth.UserFromContext[string](ctx); userName != "" {
metadata.User = userName metadata.User = userName
h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination) h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination)

View File

@ -179,26 +179,7 @@ func (h *Outbound) DialParallel(ctx context.Context, network string, destination
case N.NetworkUDP: case N.NetworkUDP:
h.logger.InfoContext(ctx, "outbound packet connection to ", destination) h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
} }
var domainStrategy C.DomainStrategy return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, len(destinationAddresses) > 0 && destinationAddresses[0].Is6(), nil, nil, nil, h.fallbackDelay)
if h.domainStrategy != C.DomainStrategyAsIS {
domainStrategy = h.domainStrategy
} else {
//nolint:staticcheck
domainStrategy = C.DomainStrategy(metadata.InboundOptions.DomainStrategy)
}
switch domainStrategy {
case C.DomainStrategyIPv4Only:
destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is4)
if len(destinationAddresses) == 0 {
return nil, E.New("no IPv4 address available for ", destination)
}
case C.DomainStrategyIPv6Only:
destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is6)
if len(destinationAddresses) == 0 {
return nil, E.New("no IPv6 address available for ", destination)
}
}
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == C.DomainStrategyPreferIPv6, nil, nil, nil, h.fallbackDelay)
} }
func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
@ -219,26 +200,7 @@ func (h *Outbound) DialParallelNetwork(ctx context.Context, network string, dest
case N.NetworkUDP: case N.NetworkUDP:
h.logger.InfoContext(ctx, "outbound packet connection to ", destination) h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
} }
var domainStrategy C.DomainStrategy return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, len(destinationAddresses) > 0 && destinationAddresses[0].Is6(), networkStrategy, networkType, fallbackNetworkType, fallbackDelay)
if h.domainStrategy != C.DomainStrategyAsIS {
domainStrategy = h.domainStrategy
} else {
//nolint:staticcheck
domainStrategy = C.DomainStrategy(metadata.InboundOptions.DomainStrategy)
}
switch domainStrategy {
case C.DomainStrategyIPv4Only:
destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is4)
if len(destinationAddresses) == 0 {
return nil, E.New("no IPv4 address available for ", destination)
}
case C.DomainStrategyIPv6Only:
destinationAddresses = common.Filter(destinationAddresses, netip.Addr.Is6)
if len(destinationAddresses) == 0 {
return nil, E.New("no IPv6 address available for ", destination)
}
}
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == C.DomainStrategyPreferIPv6, networkStrategy, networkType, fallbackNetworkType, fallbackDelay)
} }
func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) { func (h *Outbound) ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, networkStrategy *C.NetworkStrategy, networkType []C.InterfaceType, fallbackNetworkType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {

View File

@ -170,7 +170,7 @@ func (n *Inbound) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
hostPort = request.Host hostPort = request.Host
} }
source := sHttp.SourceAddress(request) source := sHttp.SourceAddress(request)
destination := M.ParseSocksaddr(hostPort) destination := M.ParseSocksaddr(hostPort).Unwrap()
if hijacker, isHijacker := writer.(http.Hijacker); isHijacker { if hijacker, isHijacker := writer.(http.Hijacker); isHijacker {
conn, _, err := hijacker.Hijack() conn, _, err := hijacker.Hijack()

View File

@ -28,6 +28,8 @@ import (
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/uot" "github.com/sagernet/sing/common/uot"
"golang.org/x/exp/slices"
) )
// Deprecated: use RouteConnectionEx instead. // Deprecated: use RouteConnectionEx instead.
@ -380,16 +382,16 @@ func (r *Router) matchRule(
newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &R.RuleActionSniff{ newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &R.RuleActionSniff{
OverrideDestination: metadata.InboundOptions.SniffOverrideDestination, OverrideDestination: metadata.InboundOptions.SniffOverrideDestination,
Timeout: time.Duration(metadata.InboundOptions.SniffTimeout), Timeout: time.Duration(metadata.InboundOptions.SniffTimeout),
}, inputConn, inputPacketConn, nil) }, inputConn, inputPacketConn, nil, nil)
if newErr != nil {
fatalErr = newErr
return
}
if newBuffer != nil { if newBuffer != nil {
buffers = []*buf.Buffer{newBuffer} buffers = []*buf.Buffer{newBuffer}
} else if len(newPackerBuffers) > 0 { } else if len(newPackerBuffers) > 0 {
packetBuffers = newPackerBuffers packetBuffers = newPackerBuffers
} }
if newErr != nil {
fatalErr = newErr
return
}
} }
if C.DomainStrategy(metadata.InboundOptions.DomainStrategy) != C.DomainStrategyAsIS { if C.DomainStrategy(metadata.InboundOptions.DomainStrategy) != C.DomainStrategyAsIS {
fatalErr = r.actionResolve(ctx, metadata, &R.RuleActionResolve{ fatalErr = r.actionResolve(ctx, metadata, &R.RuleActionResolve{
@ -488,16 +490,16 @@ match:
switch action := currentRule.Action().(type) { switch action := currentRule.Action().(type) {
case *R.RuleActionSniff: case *R.RuleActionSniff:
if !preMatch { if !preMatch {
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers) newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers, packetBuffers)
if newErr != nil {
fatalErr = newErr
return
}
if newBuffer != nil { if newBuffer != nil {
buffers = append(buffers, newBuffer) buffers = append(buffers, newBuffer)
} else if len(newPacketBuffers) > 0 { } else if len(newPacketBuffers) > 0 {
packetBuffers = append(packetBuffers, newPacketBuffers...) packetBuffers = append(packetBuffers, newPacketBuffers...)
} }
if newErr != nil {
fatalErr = newErr
return
}
} else if metadata.Network != N.NetworkICMP { } else if metadata.Network != N.NetworkICMP {
selectedRule = currentRule selectedRule = currentRule
selectedRuleIndex = currentRuleIndex selectedRuleIndex = currentRuleIndex
@ -523,7 +525,7 @@ match:
func (r *Router) actionSniff( func (r *Router) actionSniff(
ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionSniff, ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionSniff,
inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer, inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer, inputPacketBuffers []*N.PacketBuffer,
) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) { ) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) {
if sniff.Skip(metadata) { if sniff.Skip(metadata) {
r.logger.DebugContext(ctx, "sniff skipped due to port considered as server-first") r.logger.DebugContext(ctx, "sniff skipped due to port considered as server-first")
@ -535,7 +537,7 @@ func (r *Router) actionSniff(
if inputConn != nil { if inputConn != nil {
if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 { if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 {
return 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) r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
return return
} }
@ -562,6 +564,7 @@ func (r *Router) actionSniff(
action.Timeout, action.Timeout,
streamSniffers..., streamSniffers...,
) )
metadata.SnifferNames = action.SnifferNames
metadata.SniffError = err metadata.SniffError = err
if err == nil { if err == nil {
//goland:noinspection GoDeprecation //goland:noinspection GoDeprecation
@ -587,10 +590,13 @@ func (r *Router) actionSniff(
} else if inputPacketConn != nil { } else if inputPacketConn != nil {
if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 { if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 {
return 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) r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
return return
} }
quicMoreData := func() bool {
return slices.Equal(metadata.SnifferNames, action.SnifferNames) && errors.Is(metadata.SniffError, sniff.ErrNeedMoreData)
}
var packetSniffers []sniff.PacketSniffer var packetSniffers []sniff.PacketSniffer
if len(action.PacketSniffers) > 0 { if len(action.PacketSniffers) > 0 {
packetSniffers = action.PacketSniffers packetSniffers = action.PacketSniffers
@ -605,12 +611,37 @@ func (r *Router) actionSniff(
sniff.NTP, sniff.NTP,
} }
} }
var err error
for _, packetBuffer := range inputPacketBuffers {
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
}
packetBuffers = inputPacketBuffers
for { for {
var ( var (
sniffBuffer = buf.NewPacket() sniffBuffer = buf.NewPacket()
destination M.Socksaddr destination M.Socksaddr
done = make(chan struct{}) done = make(chan struct{})
err error
) )
go func() { go func() {
sniffTimeout := C.ReadPayloadTimeout sniffTimeout := C.ReadPayloadTimeout
@ -636,7 +667,7 @@ func (r *Router) actionSniff(
return return
} }
} else { } else {
if len(packetBuffers) > 0 || metadata.SniffError != nil { if quicMoreData() {
err = sniff.PeekPacket( err = sniff.PeekPacket(
ctx, ctx,
metadata, metadata,
@ -656,13 +687,18 @@ func (r *Router) actionSniff(
Destination: destination, Destination: destination,
} }
packetBuffers = append(packetBuffers, packetBuffer) packetBuffers = append(packetBuffers, packetBuffer)
metadata.SnifferNames = action.SnifferNames
metadata.SniffError = err metadata.SniffError = err
if errors.Is(err, sniff.ErrNeedMoreData) { if errors.Is(err, sniff.ErrNeedMoreData) {
// TODO: replace with generic message when there are more multi-packet protocols // TODO: replace with generic message when there are more multi-packet protocols
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
continue continue
} }
if metadata.Protocol != "" { }
goto finally
}
finally:
if err == nil {
//goland:noinspection GoDeprecation //goland:noinspection GoDeprecation
if action.OverrideDestination && M.IsDomainName(metadata.Domain) { if action.OverrideDestination && M.IsDomainName(metadata.Domain) {
metadata.Destination = M.Socksaddr{ metadata.Destination = M.Socksaddr{
@ -681,9 +717,6 @@ func (r *Router) actionSniff(
} }
} }
} }
break
}
}
return return
} }
@ -709,11 +742,6 @@ func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundCon
} }
metadata.DestinationAddresses = addresses metadata.DestinationAddresses = addresses
r.logger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]") r.logger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]")
if metadata.Destination.IsIPv4() {
metadata.IPVersion = 4
} else if metadata.Destination.IsIPv6() {
metadata.IPVersion = 6
}
} }
return nil return nil
} }

View File

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

View File

@ -292,7 +292,7 @@ func ReadPacket(conn net.Conn, buffer *buf.Buffer) (M.Socksaddr, error) {
} }
_, err = buffer.ReadFullFrom(conn, int(length)) _, err = buffer.ReadFullFrom(conn, int(length))
return destination.Unwrap(), err return destination, err
} }
func WritePacket(conn net.Conn, buffer *buf.Buffer, destination M.Socksaddr) error { func WritePacket(conn net.Conn, buffer *buf.Buffer, destination M.Socksaddr) error {

View File

@ -138,7 +138,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint)
b := packets[0] b := packets[0]
common.ClearArray(b[1:4]) common.ClearArray(b[1:4])
} }
eps[0] = remoteEndpoint(M.AddrPortFromNet(addr)) eps[0] = remoteEndpoint(M.SocksaddrFromNet(addr).Unwrap().AddrPort())
count = 1 count = 1
return return
} }