mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-09-10 13:24:08 +08:00
Compare commits
21 Commits
0102ac830d
...
d03b4eaf0a
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d03b4eaf0a | ||
![]() |
cc3e2fb8fb | ||
![]() |
7cb1cacd89 | ||
![]() |
aa47149382 | ||
![]() |
57c6c2b40e | ||
![]() |
ea822eedfc | ||
![]() |
2284fc465c | ||
![]() |
b4d4a38cd4 | ||
![]() |
feeace6ee2 | ||
![]() |
af2446c150 | ||
![]() |
6010e6cb67 | ||
![]() |
f3cc2fdb73 | ||
![]() |
71f7c7ec43 | ||
![]() |
9e33df85ab | ||
![]() |
e2065120d4 | ||
![]() |
fecafd3cb1 | ||
![]() |
c5f6e22ddb | ||
![]() |
cd7cabcbae | ||
![]() |
7b9b6642d6 | ||
![]() |
331ba48a6a | ||
![]() |
1612e98bc6 |
@ -57,7 +57,6 @@ type InboundContext struct {
|
|||||||
Domain string
|
Domain string
|
||||||
Client string
|
Client string
|
||||||
SniffContext any
|
SniffContext any
|
||||||
SnifferNames []string
|
|
||||||
SniffError error
|
SniffError error
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
@ -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.Unwrap(),
|
Source: metadata.Source,
|
||||||
Destination: metadata.Destination.Unwrap(),
|
Destination: metadata.Destination,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,40 +88,42 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
|
|
||||||
if networkManager != nil {
|
if networkManager != nil {
|
||||||
defaultOptions := networkManager.DefaultOptions()
|
defaultOptions := networkManager.DefaultOptions()
|
||||||
if defaultOptions.BindInterface != "" {
|
if !disableDefaultBind {
|
||||||
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
if defaultOptions.BindInterface != "" {
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
||||||
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)
|
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() {
|
||||||
|
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 {
|
if networkManager != nil {
|
||||||
|
@ -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.ClientChromium)
|
require.Equal(t, metadata.Client, C.ClientQUICGo)
|
||||||
require.Equal(t, metadata.Domain, "www.google.com")
|
require.Equal(t, metadata.Domain, "www.google.com")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
146
dns/client.go
146
dns/client.go
@ -2,14 +2,12 @@ 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"
|
||||||
@ -19,7 +17,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"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
dns "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -32,18 +30,16 @@ var (
|
|||||||
var _ adapter.DNSClient = (*Client)(nil)
|
var _ adapter.DNSClient = (*Client)(nil)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
disableCache bool
|
disableCache bool
|
||||||
disableExpire bool
|
disableExpire bool
|
||||||
independentCache bool
|
independentCache bool
|
||||||
clientSubnet netip.Prefix
|
clientSubnet netip.Prefix
|
||||||
rdrc adapter.RDRCStore
|
rdrc adapter.RDRCStore
|
||||||
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 {
|
||||||
@ -100,15 +96,17 @@ 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))
|
||||||
}
|
}
|
||||||
return FixedResponseStatus(message, dns.RcodeFormatError), nil
|
responseMessage := dns.Msg{
|
||||||
|
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
|
||||||
@ -116,38 +114,12 @@ 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) == 1 &&
|
len(message.Extra) == 0 &&
|
||||||
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)
|
||||||
@ -155,14 +127,27 @@ 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 !disableCache && responseChecker != nil && c.rdrc != nil {
|
if 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
|
||||||
@ -172,12 +157,7 @@ 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
|
return nil, err
|
||||||
if errors.As(err, &rcodeError) {
|
|
||||||
response = FixedResponseStatus(message, int(rcodeError))
|
|
||||||
} else {
|
|
||||||
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
|
||||||
@ -216,14 +196,13 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
}*/
|
}*/
|
||||||
if responseChecker != nil {
|
if responseChecker != nil {
|
||||||
var rejected bool
|
var rejected bool
|
||||||
// TODO: add accept_any rule and support to check response instead of addresses
|
if !(response.Rcode == dns.RcodeSuccess || response.Rcode == dns.RcodeNameError) {
|
||||||
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 !disableCache && c.rdrc != nil {
|
if 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)
|
||||||
@ -326,7 +305,8 @@ 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -340,36 +320,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 {
|
||||||
addresses, err := c.questionCache(dns.Question{
|
response, 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 addresses, true
|
return response, true
|
||||||
}
|
}
|
||||||
} else if strategy == C.DomainStrategyIPv6Only {
|
} else if strategy == C.DomainStrategyIPv6Only {
|
||||||
addresses, err := c.questionCache(dns.Question{
|
response, 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 addresses, true
|
return response, true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
response4, _ := c.loadResponse(dns.Question{
|
response4, _ := c.questionCache(dns.Question{
|
||||||
Name: dnsName,
|
Name: dnsName,
|
||||||
Qtype: dns.TypeA,
|
Qtype: dns.TypeA,
|
||||||
Qclass: dns.ClassINET,
|
Qclass: dns.ClassINET,
|
||||||
}, nil)
|
}, nil)
|
||||||
response6, _ := c.loadResponse(dns.Question{
|
response6, _ := c.questionCache(dns.Question{
|
||||||
Name: dnsName,
|
Name: dnsName,
|
||||||
Qtype: dns.TypeAAAA,
|
Qtype: dns.TypeAAAA,
|
||||||
Qclass: dns.ClassINET,
|
Qclass: dns.ClassINET,
|
||||||
}, nil)
|
}, nil)
|
||||||
if response4 != nil || response6 != nil {
|
if len(response4) > 0 || len(response6) > 0 {
|
||||||
return sortAddresses(MessageToAddresses(response4), MessageToAddresses(response6), strategy), true
|
return sortAddresses(response4, response6, strategy), true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
@ -410,15 +390,15 @@ func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Questio
|
|||||||
transportTag: transport.Tag(),
|
transportTag: transport.Tag(),
|
||||||
}, message)
|
}, message)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !c.independentCache {
|
||||||
|
c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive))
|
||||||
} else {
|
} else {
|
||||||
if !c.independentCache {
|
c.transportCache.AddWithLifetime(transportCacheKey{
|
||||||
c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive))
|
Question: question,
|
||||||
} else {
|
transportTag: transport.Tag(),
|
||||||
c.transportCache.AddWithLifetime(transportCacheKey{
|
}, message, time.Second*time.Duration(timeToLive))
|
||||||
Question: question,
|
|
||||||
transportTag: transport.Tag(),
|
|
||||||
}, message, time.Second*time.Duration(timeToLive))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -537,9 +517,6 @@ 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) {
|
||||||
@ -584,12 +561,9 @@ func transportTagFromContext(ctx context.Context) (string, bool) {
|
|||||||
func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {
|
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,
|
||||||
Response: true,
|
Rcode: rcode,
|
||||||
Authoritative: true,
|
Response: true,
|
||||||
RecursionDesired: true,
|
|
||||||
RecursionAvailable: true,
|
|
||||||
Rcode: rcode,
|
|
||||||
},
|
},
|
||||||
Question: message.Question,
|
Question: message.Question,
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,11 @@ 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) {
|
||||||
@ -113,7 +118,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(buf.UDPBufferSize, false)
|
request.SetEdns0(maxDNSPacketSize, 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 {
|
||||||
|
@ -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(buf.UDPBufferSize, false)
|
request.SetEdns0(maxDNSPacketSize, 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 {
|
||||||
|
@ -10,6 +10,11 @@ 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{}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.13.0-alpha.9
|
#### 1.13.0-alpha.8
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
@ -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
6
go.mod
@ -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.8-0.20250906004629-421beb6473ea
|
github.com/sagernet/sing v0.7.6-0.20250826155514-8bdb5fee4568
|
||||||
github.com/sagernet/sing-mux v0.3.3
|
github.com/sagernet/sing-mux v0.3.3
|
||||||
github.com/sagernet/sing-quic v0.5.1
|
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.8.0-beta.1
|
github.com/sagernet/sing-tun v0.7.0-beta.1.0.20250827122908-b76e852f59b0
|
||||||
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
12
go.sum
@ -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.8-0.20250906004629-421beb6473ea h1:CDRl4q5Y2dM6MQE1MwukhrxbObfK/rj0QtK7vnJhST0=
|
github.com/sagernet/sing v0.7.6-0.20250826155514-8bdb5fee4568 h1:0bBD73wG4Rmn1ZyMYsvHwgoDz9tFnX8BzvjbAEPoavg=
|
||||||
github.com/sagernet/sing v0.7.8-0.20250906004629-421beb6473ea/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.7.6-0.20250826155514-8bdb5fee4568/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.1 h1:o+mX/schfy6fbbU2rnb6ouUYOL+iUBjA4jOZqyIvDsU=
|
github.com/sagernet/sing-quic v0.5.0 h1:jNLIyVk24lFPvu8A4x+ZNEnZdI+Tg1rp7eCJ6v0Csak=
|
||||||
github.com/sagernet/sing-quic v0.5.1/go.mod h1:evP1e++ZG8TJHVV5HudXV4vWeYzGfCdF4HwSJZcdqkI=
|
github.com/sagernet/sing-quic v0.5.0/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0=
|
||||||
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.8.0-beta.1 h1:k8DOTDMBBc42sUW0C91MBMOsqE5jAWtB0kmJq9TTBvU=
|
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/go.mod h1:LokZYuEV3crByjQc/XRohLgfNvybtXdx5qe/I4W6S7k=
|
github.com/sagernet/sing-tun v0.7.0-beta.1.0.20250827122908-b76e852f59b0/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=
|
||||||
|
@ -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.Unwrap()
|
metadata.Destination = destination
|
||||||
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)
|
||||||
|
@ -179,7 +179,26 @@ 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)
|
||||||
}
|
}
|
||||||
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, len(destinationAddresses) > 0 && destinationAddresses[0].Is6(), nil, nil, nil, h.fallbackDelay)
|
var domainStrategy C.DomainStrategy
|
||||||
|
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) {
|
||||||
@ -200,7 +219,26 @@ 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)
|
||||||
}
|
}
|
||||||
return dialer.DialParallelNetwork(ctx, h.dialer, network, destination, destinationAddresses, len(destinationAddresses) > 0 && destinationAddresses[0].Is6(), networkStrategy, networkType, fallbackNetworkType, fallbackDelay)
|
var domainStrategy C.DomainStrategy
|
||||||
|
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) {
|
||||||
|
@ -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).Unwrap()
|
destination := M.ParseSocksaddr(hostPort)
|
||||||
|
|
||||||
if hijacker, isHijacker := writer.(http.Hijacker); isHijacker {
|
if hijacker, isHijacker := writer.(http.Hijacker); isHijacker {
|
||||||
conn, _, err := hijacker.Hijack()
|
conn, _, err := hijacker.Hijack()
|
||||||
|
104
route/route.go
104
route/route.go
@ -28,8 +28,6 @@ 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.
|
||||||
@ -382,16 +380,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, nil)
|
}, inputConn, inputPacketConn, 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{
|
||||||
@ -490,16 +488,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, packetBuffers)
|
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers)
|
||||||
|
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
|
||||||
@ -525,7 +523,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, inputPacketBuffers []*N.PacketBuffer,
|
inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer,
|
||||||
) (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")
|
||||||
@ -537,7 +535,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 slices.Equal(metadata.SnifferNames, action.SnifferNames) && metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
|
} else if 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
|
||||||
}
|
}
|
||||||
@ -564,7 +562,6 @@ 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
|
||||||
@ -590,13 +587,10 @@ 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 slices.Equal(metadata.SnifferNames, action.SnifferNames) && metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
|
} else if 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
|
||||||
@ -611,37 +605,12 @@ 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
|
||||||
@ -667,7 +636,7 @@ func (r *Router) actionSniff(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if quicMoreData() {
|
if len(packetBuffers) > 0 || metadata.SniffError != nil {
|
||||||
err = sniff.PeekPacket(
|
err = sniff.PeekPacket(
|
||||||
ctx,
|
ctx,
|
||||||
metadata,
|
metadata,
|
||||||
@ -687,34 +656,32 @@ 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
|
//goland:noinspection GoDeprecation
|
||||||
}
|
if action.OverrideDestination && M.IsDomainName(metadata.Domain) {
|
||||||
finally:
|
metadata.Destination = M.Socksaddr{
|
||||||
if err == nil {
|
Fqdn: metadata.Domain,
|
||||||
//goland:noinspection GoDeprecation
|
Port: metadata.Destination.Port,
|
||||||
if action.OverrideDestination && M.IsDomainName(metadata.Domain) {
|
}
|
||||||
metadata.Destination = M.Socksaddr{
|
}
|
||||||
Fqdn: metadata.Domain,
|
if metadata.Domain != "" && metadata.Client != "" {
|
||||||
Port: metadata.Destination.Port,
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if metadata.Domain != "" && metadata.Client != "" {
|
break
|
||||||
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
|
return
|
||||||
@ -742,6 +709,11 @@ 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
|
||||||
}
|
}
|
||||||
|
@ -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(), ")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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, err
|
return destination.Unwrap(), 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 {
|
||||||
|
@ -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.SocksaddrFromNet(addr).Unwrap().AddrPort())
|
eps[0] = remoteEndpoint(M.AddrPortFromNet(addr))
|
||||||
count = 1
|
count = 1
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user