mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-15 05:08:33 +10:00
Compare commits
9 Commits
testing
...
draft-wind
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1b6bf07ae | ||
|
|
fd09582c6a | ||
|
|
6c55bbd921 | ||
|
|
2e15cf82b2 | ||
|
|
6a7fe70ee8 | ||
|
|
a6e4184252 | ||
|
|
83b19121da | ||
|
|
ddf24c2154 | ||
|
|
ede12fa117 |
@@ -4,7 +4,6 @@
|
|||||||
--license GPL-3.0-or-later
|
--license GPL-3.0-or-later
|
||||||
--description "The universal proxy platform."
|
--description "The universal proxy platform."
|
||||||
--url "https://sing-box.sagernet.org/"
|
--url "https://sing-box.sagernet.org/"
|
||||||
--vendor SagerNet
|
|
||||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||||
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||||
--no-deb-generate-changes
|
--no-deb-generate-changes
|
||||||
|
|||||||
2
.github/CRONET_GO_VERSION
vendored
2
.github/CRONET_GO_VERSION
vendored
@@ -1 +1 @@
|
|||||||
e4926ba205fae5351e3d3eeafff7e7029654424a
|
ea7cd33752aed62603775af3df946c1b83f4b0b3
|
||||||
|
|||||||
2
.github/setup_go_for_macos1013.sh
vendored
2
.github/setup_go_for_macos1013.sh
vendored
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
VERSION="1.25.9"
|
VERSION="1.25.8"
|
||||||
PATCH_COMMITS=(
|
PATCH_COMMITS=(
|
||||||
"afe69d3cec1c6dcf0f1797b20546795730850070"
|
"afe69d3cec1c6dcf0f1797b20546795730850070"
|
||||||
"1ed289b0cf87dc5aae9c6fe1aa5f200a83412938"
|
"1ed289b0cf87dc5aae9c6fe1aa5f200a83412938"
|
||||||
|
|||||||
2
.github/setup_go_for_windows7.sh
vendored
2
.github/setup_go_for_windows7.sh
vendored
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
VERSION="1.25.9"
|
VERSION="1.25.8"
|
||||||
PATCH_COMMITS=(
|
PATCH_COMMITS=(
|
||||||
"466f6c7a29bc098b0d4c987b803c779222894a11"
|
"466f6c7a29bc098b0d4c987b803c779222894a11"
|
||||||
"1bdabae205052afe1dadb2ad6f1ba612cdbc532a"
|
"1bdabae205052afe1dadb2ad6f1ba612cdbc532a"
|
||||||
|
|||||||
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ~1.25.8
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -124,7 +124,7 @@ jobs:
|
|||||||
if: ${{ ! matrix.legacy_win7 }}
|
if: ${{ ! matrix.legacy_win7 }}
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ~1.25.8
|
||||||
- name: Cache Go for Windows 7
|
- name: Cache Go for Windows 7
|
||||||
if: matrix.legacy_win7
|
if: matrix.legacy_win7
|
||||||
id: cache-go-for-windows7
|
id: cache-go-for-windows7
|
||||||
@@ -641,7 +641,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ~1.25.8
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -731,7 +731,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ~1.25.8
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -830,7 +830,7 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ~1.25.8
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
|
|||||||
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -55,7 +55,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ~1.25.8
|
||||||
- name: Clone cronet-go
|
- name: Clone cronet-go
|
||||||
if: matrix.naive
|
if: matrix.naive
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
4
.github/workflows/linux.yml
vendored
4
.github/workflows/linux.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ~1.25.8
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -72,7 +72,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ~1.25.9
|
go-version: ~1.25.8
|
||||||
- name: Clone cronet-go
|
- name: Clone cronet-go
|
||||||
if: matrix.naive
|
if: matrix.naive
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package adapter
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -26,19 +25,18 @@ type DNSRouter interface {
|
|||||||
|
|
||||||
type DNSClient interface {
|
type DNSClient interface {
|
||||||
Start()
|
Start()
|
||||||
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(response *dns.Msg) bool) (*dns.Msg, error)
|
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error)
|
||||||
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error)
|
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)
|
||||||
ClearCache()
|
ClearCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSQueryOptions struct {
|
type DNSQueryOptions struct {
|
||||||
Transport DNSTransport
|
Transport DNSTransport
|
||||||
Strategy C.DomainStrategy
|
Strategy C.DomainStrategy
|
||||||
LookupStrategy C.DomainStrategy
|
LookupStrategy C.DomainStrategy
|
||||||
DisableCache bool
|
DisableCache bool
|
||||||
DisableOptimisticCache bool
|
RewriteTTL *uint32
|
||||||
RewriteTTL *uint32
|
ClientSubnet netip.Prefix
|
||||||
ClientSubnet netip.Prefix
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
|
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
|
||||||
@@ -51,12 +49,11 @@ func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptio
|
|||||||
return nil, E.New("domain resolver not found: " + options.Server)
|
return nil, E.New("domain resolver not found: " + options.Server)
|
||||||
}
|
}
|
||||||
return &DNSQueryOptions{
|
return &DNSQueryOptions{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
Strategy: C.DomainStrategy(options.Strategy),
|
Strategy: C.DomainStrategy(options.Strategy),
|
||||||
DisableCache: options.DisableCache,
|
DisableCache: options.DisableCache,
|
||||||
DisableOptimisticCache: options.DisableOptimisticCache,
|
RewriteTTL: options.RewriteTTL,
|
||||||
RewriteTTL: options.RewriteTTL,
|
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
||||||
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,13 +63,6 @@ type RDRCStore interface {
|
|||||||
SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger)
|
SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSCacheStore interface {
|
|
||||||
LoadDNSCache(transportName string, qName string, qType uint16) (rawMessage []byte, expireAt time.Time, loaded bool)
|
|
||||||
SaveDNSCache(transportName string, qName string, qType uint16, rawMessage []byte, expireAt time.Time) error
|
|
||||||
SaveDNSCacheAsync(transportName string, qName string, qType uint16, rawMessage []byte, expireAt time.Time, logger logger.Logger)
|
|
||||||
ClearDNSCache() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type DNSTransport interface {
|
type DNSTransport interface {
|
||||||
Lifecycle
|
Lifecycle
|
||||||
Type() string
|
Type() string
|
||||||
@@ -82,6 +72,11 @@ type DNSTransport interface {
|
|||||||
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LegacyDNSTransport interface {
|
||||||
|
LegacyStrategy() C.DomainStrategy
|
||||||
|
LegacyClientSubnet() netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
type DNSTransportRegistry interface {
|
type DNSTransportRegistry interface {
|
||||||
option.DNSTransportOptionsRegistry
|
option.DNSTransportOptionsRegistry
|
||||||
CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error)
|
CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error)
|
||||||
|
|||||||
@@ -47,12 +47,6 @@ type CacheFile interface {
|
|||||||
StoreRDRC() bool
|
StoreRDRC() bool
|
||||||
RDRCStore
|
RDRCStore
|
||||||
|
|
||||||
StoreDNS() bool
|
|
||||||
DNSCacheStore
|
|
||||||
|
|
||||||
SetDisableExpire(disableExpire bool)
|
|
||||||
SetOptimisticTimeout(timeout time.Duration)
|
|
||||||
|
|
||||||
LoadMode() string
|
LoadMode() string
|
||||||
StoreMode(mode string) error
|
StoreMode(mode string) error
|
||||||
LoadSelected(group string) string
|
LoadSelected(group string) string
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Inbound interface {
|
type Inbound interface {
|
||||||
@@ -81,16 +79,14 @@ type InboundContext struct {
|
|||||||
FallbackNetworkType []C.InterfaceType
|
FallbackNetworkType []C.InterfaceType
|
||||||
FallbackDelay time.Duration
|
FallbackDelay time.Duration
|
||||||
|
|
||||||
DestinationAddresses []netip.Addr
|
DestinationAddresses []netip.Addr
|
||||||
DNSResponse *dns.Msg
|
SourceGeoIPCode string
|
||||||
DestinationAddressMatchFromResponse bool
|
GeoIPCode string
|
||||||
SourceGeoIPCode string
|
ProcessInfo *ConnectionOwner
|
||||||
GeoIPCode string
|
SourceMACAddress net.HardwareAddr
|
||||||
ProcessInfo *ConnectionOwner
|
SourceHostname string
|
||||||
SourceMACAddress net.HardwareAddr
|
QueryType uint16
|
||||||
SourceHostname string
|
FakeIP bool
|
||||||
QueryType uint16
|
|
||||||
FakeIP bool
|
|
||||||
|
|
||||||
// rule cache
|
// rule cache
|
||||||
|
|
||||||
@@ -119,51 +115,6 @@ func (c *InboundContext) ResetRuleMatchCache() {
|
|||||||
c.DidMatch = false
|
c.DidMatch = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *InboundContext) DNSResponseAddressesForMatch() []netip.Addr {
|
|
||||||
return DNSResponseAddresses(c.DNSResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DNSResponseAddresses(response *dns.Msg) []netip.Addr {
|
|
||||||
if response == nil || response.Rcode != dns.RcodeSuccess {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
addresses := make([]netip.Addr, 0, len(response.Answer))
|
|
||||||
for _, rawRecord := range response.Answer {
|
|
||||||
switch record := rawRecord.(type) {
|
|
||||||
case *dns.A:
|
|
||||||
addr := M.AddrFromIP(record.A)
|
|
||||||
if addr.IsValid() {
|
|
||||||
addresses = append(addresses, addr)
|
|
||||||
}
|
|
||||||
case *dns.AAAA:
|
|
||||||
addr := M.AddrFromIP(record.AAAA)
|
|
||||||
if addr.IsValid() {
|
|
||||||
addresses = append(addresses, addr)
|
|
||||||
}
|
|
||||||
case *dns.HTTPS:
|
|
||||||
for _, value := range record.SVCB.Value {
|
|
||||||
switch hint := value.(type) {
|
|
||||||
case *dns.SVCBIPv4Hint:
|
|
||||||
for _, ip := range hint.Hint {
|
|
||||||
addr := M.AddrFromIP(ip).Unmap()
|
|
||||||
if addr.IsValid() {
|
|
||||||
addresses = append(addresses, addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *dns.SVCBIPv6Hint:
|
|
||||||
for _, ip := range hint.Hint {
|
|
||||||
addr := M.AddrFromIP(ip)
|
|
||||||
if addr.IsValid() {
|
|
||||||
addresses = append(addresses, addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return addresses
|
|
||||||
}
|
|
||||||
|
|
||||||
type inboundContextKey struct{}
|
type inboundContextKey struct{}
|
||||||
|
|
||||||
func WithContext(ctx context.Context, inboundContext *InboundContext) context.Context {
|
func WithContext(ctx context.Context, inboundContext *InboundContext) context.Context {
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDNSResponseAddressesUnmapsHTTPSIPv4Hints(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ipv4Hint := net.ParseIP("1.1.1.1")
|
|
||||||
require.NotNil(t, ipv4Hint)
|
|
||||||
|
|
||||||
response := &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{
|
|
||||||
Response: true,
|
|
||||||
Rcode: dns.RcodeSuccess,
|
|
||||||
},
|
|
||||||
Answer: []dns.RR{
|
|
||||||
&dns.HTTPS{
|
|
||||||
SVCB: dns.SVCB{
|
|
||||||
Hdr: dns.RR_Header{
|
|
||||||
Name: dns.Fqdn("example.com"),
|
|
||||||
Rrtype: dns.TypeHTTPS,
|
|
||||||
Class: dns.ClassINET,
|
|
||||||
Ttl: 60,
|
|
||||||
},
|
|
||||||
Priority: 1,
|
|
||||||
Target: ".",
|
|
||||||
Value: []dns.SVCBKeyValue{
|
|
||||||
&dns.SVCBIPv4Hint{Hint: []net.IP{ipv4Hint}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
addresses := DNSResponseAddresses(response)
|
|
||||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("1.1.1.1")}, addresses)
|
|
||||||
require.True(t, addresses[0].Is4())
|
|
||||||
}
|
|
||||||
@@ -66,16 +66,10 @@ type RuleSet interface {
|
|||||||
|
|
||||||
type RuleSetUpdateCallback func(it RuleSet)
|
type RuleSetUpdateCallback func(it RuleSet)
|
||||||
|
|
||||||
type DNSRuleSetUpdateValidator interface {
|
|
||||||
ValidateRuleSetMetadataUpdate(tag string, metadata RuleSetMetadata) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// ip_version is not a headless-rule item, so ContainsIPVersionRule is intentionally absent.
|
|
||||||
type RuleSetMetadata struct {
|
type RuleSetMetadata struct {
|
||||||
ContainsProcessRule bool
|
ContainsProcessRule bool
|
||||||
ContainsWIFIRule bool
|
ContainsWIFIRule bool
|
||||||
ContainsIPCIDRRule bool
|
ContainsIPCIDRRule bool
|
||||||
ContainsDNSQueryTypeRule bool
|
|
||||||
}
|
}
|
||||||
type HTTPStartContext struct {
|
type HTTPStartContext struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type HeadlessRule interface {
|
type HeadlessRule interface {
|
||||||
@@ -20,9 +18,8 @@ type Rule interface {
|
|||||||
|
|
||||||
type DNSRule interface {
|
type DNSRule interface {
|
||||||
Rule
|
Rule
|
||||||
LegacyPreMatch(metadata *InboundContext) bool
|
|
||||||
WithAddressLimit() bool
|
WithAddressLimit() bool
|
||||||
MatchAddressLimit(metadata *InboundContext, response *dns.Msg) bool
|
MatchAddressLimit(metadata *InboundContext) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuleAction interface {
|
type RuleAction interface {
|
||||||
@@ -32,7 +29,7 @@ type RuleAction interface {
|
|||||||
|
|
||||||
func IsFinalAction(action RuleAction) bool {
|
func IsFinalAction(action RuleAction) bool {
|
||||||
switch action.Type() {
|
switch action.Type() {
|
||||||
case C.RuleActionTypeSniff, C.RuleActionTypeResolve, C.RuleActionTypeEvaluate:
|
case C.RuleActionTypeSniff, C.RuleActionTypeResolve:
|
||||||
return false
|
return false
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import "context"
|
|
||||||
|
|
||||||
type TailscaleEndpoint interface {
|
|
||||||
SubscribeTailscaleStatus(ctx context.Context, fn func(*TailscaleEndpointStatus)) error
|
|
||||||
StartTailscalePing(ctx context.Context, peerIP string, fn func(*TailscalePingResult)) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type TailscalePingResult struct {
|
|
||||||
LatencyMs float64
|
|
||||||
IsDirect bool
|
|
||||||
Endpoint string
|
|
||||||
DERPRegionID int32
|
|
||||||
DERPRegionCode string
|
|
||||||
Error string
|
|
||||||
}
|
|
||||||
|
|
||||||
type TailscaleEndpointStatus struct {
|
|
||||||
BackendState string
|
|
||||||
AuthURL string
|
|
||||||
NetworkName string
|
|
||||||
MagicDNSSuffix string
|
|
||||||
Self *TailscalePeer
|
|
||||||
UserGroups []*TailscaleUserGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
type TailscaleUserGroup struct {
|
|
||||||
UserID int64
|
|
||||||
LoginName string
|
|
||||||
DisplayName string
|
|
||||||
ProfilePicURL string
|
|
||||||
Peers []*TailscalePeer
|
|
||||||
}
|
|
||||||
|
|
||||||
type TailscalePeer struct {
|
|
||||||
HostName string
|
|
||||||
DNSName string
|
|
||||||
OS string
|
|
||||||
TailscaleIPs []string
|
|
||||||
Online bool
|
|
||||||
ExitNode bool
|
|
||||||
ExitNodeOption bool
|
|
||||||
Active bool
|
|
||||||
RxBytes int64
|
|
||||||
TxBytes int64
|
|
||||||
UserID int64
|
|
||||||
KeyExpiry int64
|
|
||||||
}
|
|
||||||
20
box.go
20
box.go
@@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/local"
|
||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -196,12 +197,8 @@ func New(options Options) (*Box, error) {
|
|||||||
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
||||||
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
||||||
service.MustRegister[adapter.CertificateProviderManager](ctx, certificateProviderManager)
|
service.MustRegister[adapter.CertificateProviderManager](ctx, certificateProviderManager)
|
||||||
dnsRouter, err := dns.NewRouter(ctx, logFactory, dnsOptions)
|
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "initialize DNS router")
|
|
||||||
}
|
|
||||||
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||||
service.MustRegister[adapter.DNSRuleSetUpdateValidator](ctx, dnsRouter)
|
|
||||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize network manager")
|
return nil, E.Cause(err, "initialize network manager")
|
||||||
@@ -360,12 +357,11 @@ func New(options Options) (*Box, error) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) {
|
dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) {
|
||||||
return dnsTransportRegistry.CreateDNSTransport(
|
return local.NewTransport(
|
||||||
ctx,
|
ctx,
|
||||||
logFactory.NewLogger("dns/local"),
|
logFactory.NewLogger("dns/local"),
|
||||||
"local",
|
"local",
|
||||||
C.DNSTypeLocal,
|
option.LocalDNSServerOptions{},
|
||||||
&option.LocalDNSServerOptions{},
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
@@ -375,7 +371,7 @@ func New(options Options) (*Box, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if needCacheFile {
|
if needCacheFile {
|
||||||
cacheFile := cachefile.New(ctx, logFactory.NewLogger("cache-file"), common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
internalServices = append(internalServices, cacheFile)
|
internalServices = append(internalServices, cacheFile)
|
||||||
}
|
}
|
||||||
@@ -490,7 +486,7 @@ func (s *Box) preStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.network, s.connection, s.router, s.dnsRouter)
|
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -600,10 +596,6 @@ func (s *Box) Outbound() adapter.OutboundManager {
|
|||||||
return s.outbound
|
return s.outbound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) Endpoint() adapter.EndpointManager {
|
|
||||||
return s.endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Box) LogFactory() log.Factory {
|
func (s *Box) LogFactory() log.Factory {
|
||||||
return s.logFactory
|
return s.logFactory
|
||||||
}
|
}
|
||||||
|
|||||||
Submodule clients/android updated: ab09918615...834a5f7df0
Submodule clients/apple updated: ad7434d676...6b790c7a80
@@ -82,11 +82,6 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func downgradeRuleSetVersion(version uint8, options option.PlainRuleSet) uint8 {
|
func downgradeRuleSetVersion(version uint8, options option.PlainRuleSet) uint8 {
|
||||||
if version == C.RuleSetVersion5 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
|
||||||
return len(rule.PackageNameRegex) > 0
|
|
||||||
}) {
|
|
||||||
version = C.RuleSetVersion4
|
|
||||||
}
|
|
||||||
if version == C.RuleSetVersion4 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
if version == C.RuleSetVersion4 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
||||||
return rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 ||
|
return rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 ||
|
||||||
len(rule.DefaultInterfaceAddress) > 0
|
len(rule.DefaultInterfaceAddress) > 0
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/networkquality"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
commandNetworkQualityFlagConfigURL string
|
|
||||||
commandNetworkQualityFlagSerial bool
|
|
||||||
commandNetworkQualityFlagMaxRuntime int
|
|
||||||
commandNetworkQualityFlagHTTP3 bool
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandNetworkQuality = &cobra.Command{
|
|
||||||
Use: "networkquality",
|
|
||||||
Short: "Run a network quality test",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
err := runNetworkQuality()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commandNetworkQuality.Flags().StringVar(
|
|
||||||
&commandNetworkQualityFlagConfigURL,
|
|
||||||
"config-url", "",
|
|
||||||
"Network quality test config URL (default: Apple mensura)",
|
|
||||||
)
|
|
||||||
commandNetworkQuality.Flags().BoolVar(
|
|
||||||
&commandNetworkQualityFlagSerial,
|
|
||||||
"serial", false,
|
|
||||||
"Run download and upload tests sequentially instead of in parallel",
|
|
||||||
)
|
|
||||||
commandNetworkQuality.Flags().IntVar(
|
|
||||||
&commandNetworkQualityFlagMaxRuntime,
|
|
||||||
"max-runtime", int(networkquality.DefaultMaxRuntime/time.Second),
|
|
||||||
"Network quality maximum runtime in seconds",
|
|
||||||
)
|
|
||||||
commandNetworkQuality.Flags().BoolVar(
|
|
||||||
&commandNetworkQualityFlagHTTP3,
|
|
||||||
"http3", false,
|
|
||||||
"Use HTTP/3 (QUIC) for measurement traffic",
|
|
||||||
)
|
|
||||||
commandTools.AddCommand(commandNetworkQuality)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runNetworkQuality() error {
|
|
||||||
instance, err := createPreStartedClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer instance.Close()
|
|
||||||
|
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
httpClient := networkquality.NewHTTPClient(dialer)
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
|
|
||||||
measurementClientFactory, err := networkquality.NewOptionalHTTP3Factory(dialer, commandNetworkQualityFlagHTTP3)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr, "==== NETWORK QUALITY TEST ====")
|
|
||||||
|
|
||||||
result, err := networkquality.Run(networkquality.Options{
|
|
||||||
ConfigURL: commandNetworkQualityFlagConfigURL,
|
|
||||||
HTTPClient: httpClient,
|
|
||||||
NewMeasurementClient: measurementClientFactory,
|
|
||||||
Serial: commandNetworkQualityFlagSerial,
|
|
||||||
MaxRuntime: time.Duration(commandNetworkQualityFlagMaxRuntime) * time.Second,
|
|
||||||
Context: globalCtx,
|
|
||||||
OnProgress: func(p networkquality.Progress) {
|
|
||||||
if !commandNetworkQualityFlagSerial && p.Phase != networkquality.PhaseIdle {
|
|
||||||
fmt.Fprintf(os.Stderr, "\rDownload: %s RPM: %d Upload: %s RPM: %d",
|
|
||||||
networkquality.FormatBitrate(p.DownloadCapacity), p.DownloadRPM,
|
|
||||||
networkquality.FormatBitrate(p.UploadCapacity), p.UploadRPM)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch networkquality.Phase(p.Phase) {
|
|
||||||
case networkquality.PhaseIdle:
|
|
||||||
if p.IdleLatencyMs > 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "\rIdle Latency: %d ms", p.IdleLatencyMs)
|
|
||||||
} else {
|
|
||||||
fmt.Fprint(os.Stderr, "\rMeasuring idle latency...")
|
|
||||||
}
|
|
||||||
case networkquality.PhaseDownload:
|
|
||||||
fmt.Fprintf(os.Stderr, "\rDownload: %s RPM: %d",
|
|
||||||
networkquality.FormatBitrate(p.DownloadCapacity), p.DownloadRPM)
|
|
||||||
case networkquality.PhaseUpload:
|
|
||||||
fmt.Fprintf(os.Stderr, "\rUpload: %s RPM: %d",
|
|
||||||
networkquality.FormatBitrate(p.UploadCapacity), p.UploadRPM)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr)
|
|
||||||
fmt.Fprintln(os.Stderr, strings.Repeat("-", 40))
|
|
||||||
fmt.Fprintf(os.Stderr, "Idle Latency: %d ms\n", result.IdleLatencyMs)
|
|
||||||
fmt.Fprintf(os.Stderr, "Download Capacity: %-20s Accuracy: %s\n", networkquality.FormatBitrate(result.DownloadCapacity), result.DownloadCapacityAccuracy)
|
|
||||||
fmt.Fprintf(os.Stderr, "Upload Capacity: %-20s Accuracy: %s\n", networkquality.FormatBitrate(result.UploadCapacity), result.UploadCapacityAccuracy)
|
|
||||||
fmt.Fprintf(os.Stderr, "Download Responsiveness: %-20s Accuracy: %s\n", fmt.Sprintf("%d RPM", result.DownloadRPM), result.DownloadRPMAccuracy)
|
|
||||||
fmt.Fprintf(os.Stderr, "Upload Responsiveness: %-20s Accuracy: %s\n", fmt.Sprintf("%d RPM", result.UploadRPM), result.UploadRPMAccuracy)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/stun"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandSTUNFlagServer string
|
|
||||||
|
|
||||||
var commandSTUN = &cobra.Command{
|
|
||||||
Use: "stun",
|
|
||||||
Short: "Run a STUN test",
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
err := runSTUN()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commandSTUN.Flags().StringVarP(&commandSTUNFlagServer, "server", "s", stun.DefaultServer, "STUN server address")
|
|
||||||
commandTools.AddCommand(commandSTUN)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runSTUN() error {
|
|
||||||
instance, err := createPreStartedClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer instance.Close()
|
|
||||||
|
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr, "==== STUN TEST ====")
|
|
||||||
|
|
||||||
result, err := stun.Run(stun.Options{
|
|
||||||
Server: commandSTUNFlagServer,
|
|
||||||
Dialer: dialer,
|
|
||||||
Context: globalCtx,
|
|
||||||
OnProgress: func(p stun.Progress) {
|
|
||||||
switch p.Phase {
|
|
||||||
case stun.PhaseBinding:
|
|
||||||
if p.ExternalAddr != "" {
|
|
||||||
fmt.Fprintf(os.Stderr, "\rExternal Address: %s (%d ms)", p.ExternalAddr, p.LatencyMs)
|
|
||||||
} else {
|
|
||||||
fmt.Fprint(os.Stderr, "\rSending binding request...")
|
|
||||||
}
|
|
||||||
case stun.PhaseNATMapping:
|
|
||||||
fmt.Fprint(os.Stderr, "\rDetecting NAT mapping behavior...")
|
|
||||||
case stun.PhaseNATFiltering:
|
|
||||||
fmt.Fprint(os.Stderr, "\rDetecting NAT filtering behavior...")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr)
|
|
||||||
fmt.Fprintf(os.Stderr, "External Address: %s\n", result.ExternalAddr)
|
|
||||||
fmt.Fprintf(os.Stderr, "Latency: %d ms\n", result.LatencyMs)
|
|
||||||
if result.NATTypeSupported {
|
|
||||||
fmt.Fprintf(os.Stderr, "NAT Mapping: %s\n", result.NATMapping)
|
|
||||||
fmt.Fprintf(os.Stderr, "NAT Filtering: %s\n", result.NATFiltering)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintln(os.Stderr, "NAT Type Detection: not supported by server")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -149,10 +149,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
} else {
|
} else {
|
||||||
dialer.Timeout = C.TCPConnectTimeout
|
dialer.Timeout = C.TCPConnectTimeout
|
||||||
}
|
}
|
||||||
if options.DisableTCPKeepAlive {
|
if !options.DisableTCPKeepAlive {
|
||||||
dialer.KeepAlive = -1
|
|
||||||
dialer.KeepAliveConfig.Enable = false
|
|
||||||
} else {
|
|
||||||
keepIdle := time.Duration(options.TCPKeepAlive)
|
keepIdle := time.Duration(options.TCPKeepAlive)
|
||||||
if keepIdle == 0 {
|
if keepIdle == 0 {
|
||||||
keepIdle = C.TCPKeepAliveInitial
|
keepIdle = C.TCPKeepAliveInitial
|
||||||
|
|||||||
@@ -87,12 +87,11 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
|||||||
}
|
}
|
||||||
server = dialOptions.DomainResolver.Server
|
server = dialOptions.DomainResolver.Server
|
||||||
dnsQueryOptions = adapter.DNSQueryOptions{
|
dnsQueryOptions = adapter.DNSQueryOptions{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
Strategy: strategy,
|
Strategy: strategy,
|
||||||
DisableCache: dialOptions.DomainResolver.DisableCache,
|
DisableCache: dialOptions.DomainResolver.DisableCache,
|
||||||
DisableOptimisticCache: dialOptions.DomainResolver.DisableOptimisticCache,
|
RewriteTTL: dialOptions.DomainResolver.RewriteTTL,
|
||||||
RewriteTTL: dialOptions.DomainResolver.RewriteTTL,
|
ClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}),
|
||||||
ClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}),
|
|
||||||
}
|
}
|
||||||
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||||
} else if options.DirectResolver {
|
} else if options.DirectResolver {
|
||||||
|
|||||||
@@ -37,10 +37,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
|||||||
if l.listenOptions.ReuseAddr {
|
if l.listenOptions.ReuseAddr {
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
||||||
}
|
}
|
||||||
if l.listenOptions.DisableTCPKeepAlive {
|
if !l.listenOptions.DisableTCPKeepAlive {
|
||||||
listenConfig.KeepAlive = -1
|
|
||||||
listenConfig.KeepAliveConfig.Enable = false
|
|
||||||
} else {
|
|
||||||
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
||||||
if keepIdle == 0 {
|
if keepIdle == 0 {
|
||||||
keepIdle = C.TCPKeepAliveInitial
|
keepIdle = C.TCPKeepAliveInitial
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
package networkquality
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
sBufio "github.com/sagernet/sing/common/bufio"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func FormatBitrate(bps int64) string {
|
|
||||||
switch {
|
|
||||||
case bps >= 1_000_000_000:
|
|
||||||
return fmt.Sprintf("%.1f Gbps", float64(bps)/1_000_000_000)
|
|
||||||
case bps >= 1_000_000:
|
|
||||||
return fmt.Sprintf("%.1f Mbps", float64(bps)/1_000_000)
|
|
||||||
case bps >= 1_000:
|
|
||||||
return fmt.Sprintf("%.1f Kbps", float64(bps)/1_000)
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("%d bps", bps)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHTTPClient(dialer N.Dialer) *http.Client {
|
|
||||||
transport := &http.Transport{
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
TLSHandshakeTimeout: C.TCPTimeout,
|
|
||||||
}
|
|
||||||
if dialer != nil {
|
|
||||||
transport.DialContext = func(ctx context.Context, network string, addr string) (net.Conn, error) {
|
|
||||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &http.Client{Transport: transport}
|
|
||||||
}
|
|
||||||
|
|
||||||
func baseTransportFromClient(client *http.Client) (*http.Transport, error) {
|
|
||||||
if client == nil {
|
|
||||||
return nil, E.New("http client is nil")
|
|
||||||
}
|
|
||||||
if client.Transport == nil {
|
|
||||||
return http.DefaultTransport.(*http.Transport).Clone(), nil
|
|
||||||
}
|
|
||||||
transport, ok := client.Transport.(*http.Transport)
|
|
||||||
if !ok {
|
|
||||||
return nil, E.New("http client transport must be *http.Transport")
|
|
||||||
}
|
|
||||||
return transport.Clone(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMeasurementClient(
|
|
||||||
baseClient *http.Client,
|
|
||||||
connectEndpoint string,
|
|
||||||
singleConnection bool,
|
|
||||||
disableKeepAlives bool,
|
|
||||||
readCounters []N.CountFunc,
|
|
||||||
writeCounters []N.CountFunc,
|
|
||||||
) (*http.Client, error) {
|
|
||||||
transport, err := baseTransportFromClient(baseClient)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
transport.DisableCompression = true
|
|
||||||
transport.DisableKeepAlives = disableKeepAlives
|
|
||||||
if singleConnection {
|
|
||||||
transport.MaxConnsPerHost = 1
|
|
||||||
transport.MaxIdleConnsPerHost = 1
|
|
||||||
transport.MaxIdleConns = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
baseDialContext := transport.DialContext
|
|
||||||
if baseDialContext == nil {
|
|
||||||
dialer := &net.Dialer{}
|
|
||||||
baseDialContext = dialer.DialContext
|
|
||||||
}
|
|
||||||
transport.DialContext = func(ctx context.Context, network string, addr string) (net.Conn, error) {
|
|
||||||
dialAddr := addr
|
|
||||||
if connectEndpoint != "" {
|
|
||||||
dialAddr = rewriteDialAddress(addr, connectEndpoint)
|
|
||||||
}
|
|
||||||
conn, dialErr := baseDialContext(ctx, network, dialAddr)
|
|
||||||
if dialErr != nil {
|
|
||||||
return nil, dialErr
|
|
||||||
}
|
|
||||||
if len(readCounters) > 0 || len(writeCounters) > 0 {
|
|
||||||
return sBufio.NewCounterConn(conn, readCounters, writeCounters), nil
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &http.Client{
|
|
||||||
Transport: transport,
|
|
||||||
CheckRedirect: baseClient.CheckRedirect,
|
|
||||||
Jar: baseClient.Jar,
|
|
||||||
Timeout: baseClient.Timeout,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type MeasurementClientFactory func(
|
|
||||||
connectEndpoint string,
|
|
||||||
singleConnection bool,
|
|
||||||
disableKeepAlives bool,
|
|
||||||
readCounters []N.CountFunc,
|
|
||||||
writeCounters []N.CountFunc,
|
|
||||||
) (*http.Client, error)
|
|
||||||
|
|
||||||
func defaultMeasurementClientFactory(baseClient *http.Client) MeasurementClientFactory {
|
|
||||||
return func(connectEndpoint string, singleConnection, disableKeepAlives bool, readCounters, writeCounters []N.CountFunc) (*http.Client, error) {
|
|
||||||
return newMeasurementClient(baseClient, connectEndpoint, singleConnection, disableKeepAlives, readCounters, writeCounters)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOptionalHTTP3Factory(dialer N.Dialer, useHTTP3 bool) (MeasurementClientFactory, error) {
|
|
||||||
if !useHTTP3 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return NewHTTP3MeasurementClientFactory(dialer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func rewriteDialAddress(addr string, connectEndpoint string) string {
|
|
||||||
connectEndpoint = strings.TrimSpace(connectEndpoint)
|
|
||||||
host, port, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
endpointHost, endpointPort, err := net.SplitHostPort(connectEndpoint)
|
|
||||||
if err == nil {
|
|
||||||
host = endpointHost
|
|
||||||
if endpointPort != "" {
|
|
||||||
port = endpointPort
|
|
||||||
}
|
|
||||||
} else if connectEndpoint != "" {
|
|
||||||
host = connectEndpoint
|
|
||||||
}
|
|
||||||
return net.JoinHostPort(host, port)
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
//go:build with_quic
|
|
||||||
|
|
||||||
package networkquality
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/sagernet/quic-go"
|
|
||||||
"github.com/sagernet/quic-go/http3"
|
|
||||||
sBufio "github.com/sagernet/sing/common/bufio"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewHTTP3MeasurementClientFactory(dialer N.Dialer) (MeasurementClientFactory, error) {
|
|
||||||
// singleConnection and disableKeepAlives are not applied:
|
|
||||||
// HTTP/3 multiplexes streams over a single QUIC connection by default.
|
|
||||||
return func(connectEndpoint string, _, _ bool, readCounters, writeCounters []N.CountFunc) (*http.Client, error) {
|
|
||||||
transport := &http3.Transport{
|
|
||||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
|
|
||||||
dialAddr := addr
|
|
||||||
if connectEndpoint != "" {
|
|
||||||
dialAddr = rewriteDialAddress(addr, connectEndpoint)
|
|
||||||
}
|
|
||||||
destination := M.ParseSocksaddr(dialAddr)
|
|
||||||
var udpConn net.Conn
|
|
||||||
var dialErr error
|
|
||||||
if dialer != nil {
|
|
||||||
udpConn, dialErr = dialer.DialContext(ctx, N.NetworkUDP, destination)
|
|
||||||
} else {
|
|
||||||
var netDialer net.Dialer
|
|
||||||
udpConn, dialErr = netDialer.DialContext(ctx, N.NetworkUDP, destination.String())
|
|
||||||
}
|
|
||||||
if dialErr != nil {
|
|
||||||
return nil, dialErr
|
|
||||||
}
|
|
||||||
wrappedConn := udpConn
|
|
||||||
if len(readCounters) > 0 || len(writeCounters) > 0 {
|
|
||||||
wrappedConn = sBufio.NewCounterConn(udpConn, readCounters, writeCounters)
|
|
||||||
}
|
|
||||||
packetConn := sBufio.NewUnbindPacketConn(wrappedConn)
|
|
||||||
quicConn, dialErr := quic.DialEarly(ctx, packetConn, udpConn.RemoteAddr(), tlsCfg, cfg)
|
|
||||||
if dialErr != nil {
|
|
||||||
udpConn.Close()
|
|
||||||
return nil, dialErr
|
|
||||||
}
|
|
||||||
return quicConn, nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return &http.Client{Transport: transport}, nil
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
//go:build !with_quic
|
|
||||||
|
|
||||||
package networkquality
|
|
||||||
|
|
||||||
import (
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewHTTP3MeasurementClientFactory(dialer N.Dialer) (MeasurementClientFactory, error) {
|
|
||||||
return nil, C.ErrQUICNotIncluded
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -46,7 +46,6 @@ const (
|
|||||||
ruleItemNetworkIsConstrained
|
ruleItemNetworkIsConstrained
|
||||||
ruleItemNetworkInterfaceAddress
|
ruleItemNetworkInterfaceAddress
|
||||||
ruleItemDefaultInterfaceAddress
|
ruleItemDefaultInterfaceAddress
|
||||||
ruleItemPackageNameRegex
|
|
||||||
ruleItemFinal uint8 = 0xFF
|
ruleItemFinal uint8 = 0xFF
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -216,8 +215,6 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
rule.ProcessPathRegex, err = readRuleItemString(reader)
|
rule.ProcessPathRegex, err = readRuleItemString(reader)
|
||||||
case ruleItemPackageName:
|
case ruleItemPackageName:
|
||||||
rule.PackageName, err = readRuleItemString(reader)
|
rule.PackageName, err = readRuleItemString(reader)
|
||||||
case ruleItemPackageNameRegex:
|
|
||||||
rule.PackageNameRegex, err = readRuleItemString(reader)
|
|
||||||
case ruleItemWIFISSID:
|
case ruleItemWIFISSID:
|
||||||
rule.WIFISSID, err = readRuleItemString(reader)
|
rule.WIFISSID, err = readRuleItemString(reader)
|
||||||
case ruleItemWIFIBSSID:
|
case ruleItemWIFIBSSID:
|
||||||
@@ -397,15 +394,6 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(rule.PackageNameRegex) > 0 {
|
|
||||||
if generateVersion < C.RuleSetVersion5 {
|
|
||||||
return E.New("`package_name_regex` rule item is only supported in version 5 or later")
|
|
||||||
}
|
|
||||||
err = writeRuleItemString(writer, ruleItemPackageNameRegex, rule.PackageNameRegex)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(rule.NetworkType) > 0 {
|
if len(rule.NetworkType) > 0 {
|
||||||
if generateVersion < C.RuleSetVersion3 {
|
if generateVersion < C.RuleSetVersion3 {
|
||||||
return E.New("`network_type` rule item is only supported in version 3 or later")
|
return E.New("`network_type` rule item is only supported in version 3 or later")
|
||||||
|
|||||||
@@ -1,612 +0,0 @@
|
|||||||
package stun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
"github.com/sagernet/sing/common/bufio/deadline"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DefaultServer = "stun.voipgate.com:3478"
|
|
||||||
|
|
||||||
magicCookie = 0x2112A442
|
|
||||||
headerSize = 20
|
|
||||||
|
|
||||||
bindingRequest = 0x0001
|
|
||||||
bindingSuccessResponse = 0x0101
|
|
||||||
bindingErrorResponse = 0x0111
|
|
||||||
|
|
||||||
attrMappedAddress = 0x0001
|
|
||||||
attrChangeRequest = 0x0003
|
|
||||||
attrErrorCode = 0x0009
|
|
||||||
attrXORMappedAddress = 0x0020
|
|
||||||
attrOtherAddress = 0x802c
|
|
||||||
|
|
||||||
familyIPv4 = 0x01
|
|
||||||
familyIPv6 = 0x02
|
|
||||||
|
|
||||||
changeIP = 0x04
|
|
||||||
changePort = 0x02
|
|
||||||
|
|
||||||
defaultRTO = 500 * time.Millisecond
|
|
||||||
minRTO = 250 * time.Millisecond
|
|
||||||
maxRetransmit = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type Phase int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
PhaseBinding Phase = iota
|
|
||||||
PhaseNATMapping
|
|
||||||
PhaseNATFiltering
|
|
||||||
PhaseDone
|
|
||||||
)
|
|
||||||
|
|
||||||
type NATMapping int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
NATMappingUnknown NATMapping = iota
|
|
||||||
_ // reserved
|
|
||||||
NATMappingEndpointIndependent
|
|
||||||
NATMappingAddressDependent
|
|
||||||
NATMappingAddressAndPortDependent
|
|
||||||
)
|
|
||||||
|
|
||||||
func (m NATMapping) String() string {
|
|
||||||
switch m {
|
|
||||||
case NATMappingEndpointIndependent:
|
|
||||||
return "Endpoint Independent"
|
|
||||||
case NATMappingAddressDependent:
|
|
||||||
return "Address Dependent"
|
|
||||||
case NATMappingAddressAndPortDependent:
|
|
||||||
return "Address and Port Dependent"
|
|
||||||
default:
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type NATFiltering int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
NATFilteringUnknown NATFiltering = iota
|
|
||||||
NATFilteringEndpointIndependent
|
|
||||||
NATFilteringAddressDependent
|
|
||||||
NATFilteringAddressAndPortDependent
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f NATFiltering) String() string {
|
|
||||||
switch f {
|
|
||||||
case NATFilteringEndpointIndependent:
|
|
||||||
return "Endpoint Independent"
|
|
||||||
case NATFilteringAddressDependent:
|
|
||||||
return "Address Dependent"
|
|
||||||
case NATFilteringAddressAndPortDependent:
|
|
||||||
return "Address and Port Dependent"
|
|
||||||
default:
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type TransactionID [12]byte
|
|
||||||
|
|
||||||
type Options struct {
|
|
||||||
Server string
|
|
||||||
Dialer N.Dialer
|
|
||||||
Context context.Context
|
|
||||||
OnProgress func(Progress)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Progress struct {
|
|
||||||
Phase Phase
|
|
||||||
ExternalAddr string
|
|
||||||
LatencyMs int32
|
|
||||||
NATMapping NATMapping
|
|
||||||
NATFiltering NATFiltering
|
|
||||||
}
|
|
||||||
|
|
||||||
type Result struct {
|
|
||||||
ExternalAddr string
|
|
||||||
LatencyMs int32
|
|
||||||
NATMapping NATMapping
|
|
||||||
NATFiltering NATFiltering
|
|
||||||
NATTypeSupported bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type parsedResponse struct {
|
|
||||||
xorMappedAddr netip.AddrPort
|
|
||||||
mappedAddr netip.AddrPort
|
|
||||||
otherAddr netip.AddrPort
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *parsedResponse) externalAddr() (netip.AddrPort, bool) {
|
|
||||||
if r.xorMappedAddr.IsValid() {
|
|
||||||
return r.xorMappedAddr, true
|
|
||||||
}
|
|
||||||
if r.mappedAddr.IsValid() {
|
|
||||||
return r.mappedAddr, true
|
|
||||||
}
|
|
||||||
return netip.AddrPort{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
type stunAttribute struct {
|
|
||||||
typ uint16
|
|
||||||
value []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTransactionID() TransactionID {
|
|
||||||
var id TransactionID
|
|
||||||
_, _ = rand.Read(id[:])
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildBindingRequest(txID TransactionID, attrs ...stunAttribute) []byte {
|
|
||||||
attrLen := 0
|
|
||||||
for _, attr := range attrs {
|
|
||||||
attrLen += 4 + len(attr.value) + paddingLen(len(attr.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, headerSize+attrLen)
|
|
||||||
binary.BigEndian.PutUint16(buf[0:2], bindingRequest)
|
|
||||||
binary.BigEndian.PutUint16(buf[2:4], uint16(attrLen))
|
|
||||||
binary.BigEndian.PutUint32(buf[4:8], magicCookie)
|
|
||||||
copy(buf[8:20], txID[:])
|
|
||||||
|
|
||||||
offset := headerSize
|
|
||||||
for _, attr := range attrs {
|
|
||||||
binary.BigEndian.PutUint16(buf[offset:offset+2], attr.typ)
|
|
||||||
binary.BigEndian.PutUint16(buf[offset+2:offset+4], uint16(len(attr.value)))
|
|
||||||
copy(buf[offset+4:offset+4+len(attr.value)], attr.value)
|
|
||||||
offset += 4 + len(attr.value) + paddingLen(len(attr.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
func changeRequestAttr(flags byte) stunAttribute {
|
|
||||||
return stunAttribute{
|
|
||||||
typ: attrChangeRequest,
|
|
||||||
value: []byte{0, 0, 0, flags},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseResponse(data []byte, expectedTxID TransactionID) (*parsedResponse, error) {
|
|
||||||
if len(data) < headerSize {
|
|
||||||
return nil, E.New("response too short")
|
|
||||||
}
|
|
||||||
|
|
||||||
msgType := binary.BigEndian.Uint16(data[0:2])
|
|
||||||
if msgType&0xC000 != 0 {
|
|
||||||
return nil, E.New("invalid STUN message: top 2 bits not zero")
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie := binary.BigEndian.Uint32(data[4:8])
|
|
||||||
if cookie != magicCookie {
|
|
||||||
return nil, E.New("invalid magic cookie")
|
|
||||||
}
|
|
||||||
|
|
||||||
var txID TransactionID
|
|
||||||
copy(txID[:], data[8:20])
|
|
||||||
if txID != expectedTxID {
|
|
||||||
return nil, E.New("transaction ID mismatch")
|
|
||||||
}
|
|
||||||
|
|
||||||
msgLen := int(binary.BigEndian.Uint16(data[2:4]))
|
|
||||||
if msgLen > len(data)-headerSize {
|
|
||||||
return nil, E.New("message length exceeds data")
|
|
||||||
}
|
|
||||||
|
|
||||||
attrData := data[headerSize : headerSize+msgLen]
|
|
||||||
|
|
||||||
if msgType == bindingErrorResponse {
|
|
||||||
return nil, parseErrorResponse(attrData)
|
|
||||||
}
|
|
||||||
if msgType != bindingSuccessResponse {
|
|
||||||
return nil, E.New("unexpected message type: ", fmt.Sprintf("0x%04x", msgType))
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := &parsedResponse{}
|
|
||||||
offset := 0
|
|
||||||
for offset+4 <= len(attrData) {
|
|
||||||
attrType := binary.BigEndian.Uint16(attrData[offset : offset+2])
|
|
||||||
attrLen := int(binary.BigEndian.Uint16(attrData[offset+2 : offset+4]))
|
|
||||||
if offset+4+attrLen > len(attrData) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
attrValue := attrData[offset+4 : offset+4+attrLen]
|
|
||||||
|
|
||||||
switch attrType {
|
|
||||||
case attrXORMappedAddress:
|
|
||||||
addr, err := parseXORMappedAddress(attrValue, txID)
|
|
||||||
if err == nil {
|
|
||||||
resp.xorMappedAddr = addr
|
|
||||||
}
|
|
||||||
case attrMappedAddress:
|
|
||||||
addr, err := parseMappedAddress(attrValue)
|
|
||||||
if err == nil {
|
|
||||||
resp.mappedAddr = addr
|
|
||||||
}
|
|
||||||
case attrOtherAddress:
|
|
||||||
addr, err := parseMappedAddress(attrValue)
|
|
||||||
if err == nil {
|
|
||||||
resp.otherAddr = addr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += 4 + attrLen + paddingLen(attrLen)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseErrorResponse(data []byte) error {
|
|
||||||
offset := 0
|
|
||||||
for offset+4 <= len(data) {
|
|
||||||
attrType := binary.BigEndian.Uint16(data[offset : offset+2])
|
|
||||||
attrLen := int(binary.BigEndian.Uint16(data[offset+2 : offset+4]))
|
|
||||||
if offset+4+attrLen > len(data) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if attrType == attrErrorCode && attrLen >= 4 {
|
|
||||||
attrValue := data[offset+4 : offset+4+attrLen]
|
|
||||||
class := int(attrValue[2] & 0x07)
|
|
||||||
number := int(attrValue[3])
|
|
||||||
code := class*100 + number
|
|
||||||
if attrLen > 4 {
|
|
||||||
return E.New("STUN error ", code, ": ", string(attrValue[4:]))
|
|
||||||
}
|
|
||||||
return E.New("STUN error ", code)
|
|
||||||
}
|
|
||||||
offset += 4 + attrLen + paddingLen(attrLen)
|
|
||||||
}
|
|
||||||
return E.New("STUN error response")
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseXORMappedAddress(data []byte, txID TransactionID) (netip.AddrPort, error) {
|
|
||||||
if len(data) < 4 {
|
|
||||||
return netip.AddrPort{}, E.New("XOR-MAPPED-ADDRESS too short")
|
|
||||||
}
|
|
||||||
|
|
||||||
family := data[1]
|
|
||||||
xPort := binary.BigEndian.Uint16(data[2:4])
|
|
||||||
port := xPort ^ uint16(magicCookie>>16)
|
|
||||||
|
|
||||||
switch family {
|
|
||||||
case familyIPv4:
|
|
||||||
if len(data) < 8 {
|
|
||||||
return netip.AddrPort{}, E.New("XOR-MAPPED-ADDRESS IPv4 too short")
|
|
||||||
}
|
|
||||||
var ip [4]byte
|
|
||||||
binary.BigEndian.PutUint32(ip[:], binary.BigEndian.Uint32(data[4:8])^magicCookie)
|
|
||||||
return netip.AddrPortFrom(netip.AddrFrom4(ip), port), nil
|
|
||||||
case familyIPv6:
|
|
||||||
if len(data) < 20 {
|
|
||||||
return netip.AddrPort{}, E.New("XOR-MAPPED-ADDRESS IPv6 too short")
|
|
||||||
}
|
|
||||||
var ip [16]byte
|
|
||||||
var xorKey [16]byte
|
|
||||||
binary.BigEndian.PutUint32(xorKey[0:4], magicCookie)
|
|
||||||
copy(xorKey[4:16], txID[:])
|
|
||||||
for i := range 16 {
|
|
||||||
ip[i] = data[4+i] ^ xorKey[i]
|
|
||||||
}
|
|
||||||
return netip.AddrPortFrom(netip.AddrFrom16(ip), port), nil
|
|
||||||
default:
|
|
||||||
return netip.AddrPort{}, E.New("unknown address family: ", family)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseMappedAddress(data []byte) (netip.AddrPort, error) {
|
|
||||||
if len(data) < 4 {
|
|
||||||
return netip.AddrPort{}, E.New("MAPPED-ADDRESS too short")
|
|
||||||
}
|
|
||||||
|
|
||||||
family := data[1]
|
|
||||||
port := binary.BigEndian.Uint16(data[2:4])
|
|
||||||
|
|
||||||
switch family {
|
|
||||||
case familyIPv4:
|
|
||||||
if len(data) < 8 {
|
|
||||||
return netip.AddrPort{}, E.New("MAPPED-ADDRESS IPv4 too short")
|
|
||||||
}
|
|
||||||
return netip.AddrPortFrom(
|
|
||||||
netip.AddrFrom4([4]byte{data[4], data[5], data[6], data[7]}), port,
|
|
||||||
), nil
|
|
||||||
case familyIPv6:
|
|
||||||
if len(data) < 20 {
|
|
||||||
return netip.AddrPort{}, E.New("MAPPED-ADDRESS IPv6 too short")
|
|
||||||
}
|
|
||||||
var ip [16]byte
|
|
||||||
copy(ip[:], data[4:20])
|
|
||||||
return netip.AddrPortFrom(netip.AddrFrom16(ip), port), nil
|
|
||||||
default:
|
|
||||||
return netip.AddrPort{}, E.New("unknown address family: ", family)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func roundTrip(conn net.PacketConn, addr net.Addr, txID TransactionID, attrs []stunAttribute, rto time.Duration) (*parsedResponse, time.Duration, error) {
|
|
||||||
request := buildBindingRequest(txID, attrs...)
|
|
||||||
currentRTO := rto
|
|
||||||
retransmitCount := 0
|
|
||||||
|
|
||||||
sendTime := time.Now()
|
|
||||||
_, err := conn.WriteTo(request, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, E.Cause(err, "send STUN request")
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 1024)
|
|
||||||
for {
|
|
||||||
err = conn.SetReadDeadline(sendTime.Add(currentRTO))
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, E.Cause(err, "set read deadline")
|
|
||||||
}
|
|
||||||
|
|
||||||
n, _, readErr := conn.ReadFrom(buf)
|
|
||||||
if readErr != nil {
|
|
||||||
if E.IsTimeout(readErr) && retransmitCount < maxRetransmit {
|
|
||||||
retransmitCount++
|
|
||||||
currentRTO *= 2
|
|
||||||
sendTime = time.Now()
|
|
||||||
_, err = conn.WriteTo(request, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, E.Cause(err, "retransmit STUN request")
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, 0, E.Cause(readErr, "read STUN response")
|
|
||||||
}
|
|
||||||
|
|
||||||
if n < headerSize || buf[0]&0xC0 != 0 ||
|
|
||||||
binary.BigEndian.Uint32(buf[4:8]) != magicCookie {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var receivedTxID TransactionID
|
|
||||||
copy(receivedTxID[:], buf[8:20])
|
|
||||||
if receivedTxID != txID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
latency := time.Since(sendTime)
|
|
||||||
|
|
||||||
resp, parseErr := parseResponse(buf[:n], txID)
|
|
||||||
if parseErr != nil {
|
|
||||||
return nil, 0, parseErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, latency, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Run(options Options) (*Result, error) {
|
|
||||||
ctx := options.Context
|
|
||||||
if ctx == nil {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
|
|
||||||
server := options.Server
|
|
||||||
if server == "" {
|
|
||||||
server = DefaultServer
|
|
||||||
}
|
|
||||||
serverSocksaddr := M.ParseSocksaddr(server)
|
|
||||||
if serverSocksaddr.Port == 0 {
|
|
||||||
serverSocksaddr.Port = 3478
|
|
||||||
}
|
|
||||||
|
|
||||||
reportProgress := options.OnProgress
|
|
||||||
if reportProgress == nil {
|
|
||||||
reportProgress = func(Progress) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
packetConn net.PacketConn
|
|
||||||
serverAddr net.Addr
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if options.Dialer != nil {
|
|
||||||
packetConn, err = options.Dialer.ListenPacket(ctx, serverSocksaddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "create UDP socket")
|
|
||||||
}
|
|
||||||
serverAddr = serverSocksaddr
|
|
||||||
} else {
|
|
||||||
serverUDPAddr, resolveErr := net.ResolveUDPAddr("udp", serverSocksaddr.String())
|
|
||||||
if resolveErr != nil {
|
|
||||||
return nil, E.Cause(resolveErr, "resolve STUN server")
|
|
||||||
}
|
|
||||||
packetConn, err = net.ListenPacket("udp", "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "create UDP socket")
|
|
||||||
}
|
|
||||||
serverAddr = serverUDPAddr
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = packetConn.Close()
|
|
||||||
}()
|
|
||||||
if deadline.NeedAdditionalReadDeadline(packetConn) {
|
|
||||||
packetConn = deadline.NewPacketConn(bufio.NewPacketConn(packetConn))
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
rto := defaultRTO
|
|
||||||
|
|
||||||
// Phase 1: Binding
|
|
||||||
reportProgress(Progress{Phase: PhaseBinding})
|
|
||||||
|
|
||||||
txID := newTransactionID()
|
|
||||||
resp, latency, err := roundTrip(packetConn, serverAddr, txID, nil, rto)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "binding request")
|
|
||||||
}
|
|
||||||
|
|
||||||
rto = max(minRTO, 3*latency)
|
|
||||||
|
|
||||||
externalAddr, ok := resp.externalAddr()
|
|
||||||
if !ok {
|
|
||||||
return nil, E.New("no mapped address in response")
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &Result{
|
|
||||||
ExternalAddr: externalAddr.String(),
|
|
||||||
LatencyMs: int32(latency.Milliseconds()),
|
|
||||||
}
|
|
||||||
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseBinding,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
})
|
|
||||||
|
|
||||||
otherAddr := resp.otherAddr
|
|
||||||
if !otherAddr.IsValid() {
|
|
||||||
result.NATTypeSupported = false
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseDone,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
})
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
result.NATTypeSupported = true
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return result, nil
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 2: NAT Mapping Detection (RFC 5780 Section 4.3)
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseNATMapping,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
})
|
|
||||||
|
|
||||||
result.NATMapping = detectNATMapping(
|
|
||||||
packetConn, serverSocksaddr.Port, externalAddr, otherAddr, rto,
|
|
||||||
)
|
|
||||||
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseNATMapping,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
NATMapping: result.NATMapping,
|
|
||||||
})
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return result, nil
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 3: NAT Filtering Detection (RFC 5780 Section 4.4)
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseNATFiltering,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
NATMapping: result.NATMapping,
|
|
||||||
})
|
|
||||||
|
|
||||||
result.NATFiltering = detectNATFiltering(packetConn, serverAddr, rto)
|
|
||||||
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseDone,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
NATMapping: result.NATMapping,
|
|
||||||
NATFiltering: result.NATFiltering,
|
|
||||||
})
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func detectNATMapping(
|
|
||||||
conn net.PacketConn,
|
|
||||||
serverPort uint16,
|
|
||||||
externalAddr netip.AddrPort,
|
|
||||||
otherAddr netip.AddrPort,
|
|
||||||
rto time.Duration,
|
|
||||||
) NATMapping {
|
|
||||||
// Mapping Test II: Send to other_ip:server_port
|
|
||||||
testIIAddr := net.UDPAddrFromAddrPort(
|
|
||||||
netip.AddrPortFrom(otherAddr.Addr(), serverPort),
|
|
||||||
)
|
|
||||||
txID2 := newTransactionID()
|
|
||||||
resp2, _, err := roundTrip(conn, testIIAddr, txID2, nil, rto)
|
|
||||||
if err != nil {
|
|
||||||
return NATMappingUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
externalAddr2, ok := resp2.externalAddr()
|
|
||||||
if !ok {
|
|
||||||
return NATMappingUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
if externalAddr == externalAddr2 {
|
|
||||||
return NATMappingEndpointIndependent
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mapping Test III: Send to other_ip:other_port
|
|
||||||
testIIIAddr := net.UDPAddrFromAddrPort(otherAddr)
|
|
||||||
txID3 := newTransactionID()
|
|
||||||
resp3, _, err := roundTrip(conn, testIIIAddr, txID3, nil, rto)
|
|
||||||
if err != nil {
|
|
||||||
return NATMappingUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
externalAddr3, ok := resp3.externalAddr()
|
|
||||||
if !ok {
|
|
||||||
return NATMappingUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
if externalAddr2 == externalAddr3 {
|
|
||||||
return NATMappingAddressDependent
|
|
||||||
}
|
|
||||||
return NATMappingAddressAndPortDependent
|
|
||||||
}
|
|
||||||
|
|
||||||
func detectNATFiltering(
|
|
||||||
conn net.PacketConn,
|
|
||||||
serverAddr net.Addr,
|
|
||||||
rto time.Duration,
|
|
||||||
) NATFiltering {
|
|
||||||
// Filtering Test II: Request response from different IP and port
|
|
||||||
txID := newTransactionID()
|
|
||||||
_, _, err := roundTrip(conn, serverAddr, txID,
|
|
||||||
[]stunAttribute{changeRequestAttr(changeIP | changePort)}, rto)
|
|
||||||
if err == nil {
|
|
||||||
return NATFilteringEndpointIndependent
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filtering Test III: Request response from different port only
|
|
||||||
txID = newTransactionID()
|
|
||||||
_, _, err = roundTrip(conn, serverAddr, txID,
|
|
||||||
[]stunAttribute{changeRequestAttr(changePort)}, rto)
|
|
||||||
if err == nil {
|
|
||||||
return NATFilteringAddressDependent
|
|
||||||
}
|
|
||||||
|
|
||||||
return NATFilteringAddressAndPortDependent
|
|
||||||
}
|
|
||||||
|
|
||||||
func paddingLen(n int) int {
|
|
||||||
if n%4 == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return 4 - n%4
|
|
||||||
}
|
|
||||||
@@ -15,18 +15,19 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DNSTypeLegacy = "legacy"
|
DNSTypeLegacy = "legacy"
|
||||||
DNSTypeUDP = "udp"
|
DNSTypeLegacyRcode = "legacy_rcode"
|
||||||
DNSTypeTCP = "tcp"
|
DNSTypeUDP = "udp"
|
||||||
DNSTypeTLS = "tls"
|
DNSTypeTCP = "tcp"
|
||||||
DNSTypeHTTPS = "https"
|
DNSTypeTLS = "tls"
|
||||||
DNSTypeQUIC = "quic"
|
DNSTypeHTTPS = "https"
|
||||||
DNSTypeHTTP3 = "h3"
|
DNSTypeQUIC = "quic"
|
||||||
DNSTypeLocal = "local"
|
DNSTypeHTTP3 = "h3"
|
||||||
DNSTypeHosts = "hosts"
|
DNSTypeLocal = "local"
|
||||||
DNSTypeFakeIP = "fakeip"
|
DNSTypeHosts = "hosts"
|
||||||
DNSTypeDHCP = "dhcp"
|
DNSTypeFakeIP = "fakeip"
|
||||||
DNSTypeTailscale = "tailscale"
|
DNSTypeDHCP = "dhcp"
|
||||||
|
DNSTypeTailscale = "tailscale"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ const (
|
|||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
TypeHysteria2 = "hysteria2"
|
TypeHysteria2 = "hysteria2"
|
||||||
TypeTailscale = "tailscale"
|
TypeTailscale = "tailscale"
|
||||||
TypeCloudflared = "cloudflared"
|
|
||||||
TypeDERP = "derp"
|
TypeDERP = "derp"
|
||||||
TypeResolved = "resolved"
|
TypeResolved = "resolved"
|
||||||
TypeSSMAPI = "ssm-api"
|
TypeSSMAPI = "ssm-api"
|
||||||
@@ -91,8 +90,6 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "AnyTLS"
|
return "AnyTLS"
|
||||||
case TypeTailscale:
|
case TypeTailscale:
|
||||||
return "Tailscale"
|
return "Tailscale"
|
||||||
case TypeCloudflared:
|
|
||||||
return "Cloudflared"
|
|
||||||
case TypeSelector:
|
case TypeSelector:
|
||||||
return "Selector"
|
return "Selector"
|
||||||
case TypeURLTest:
|
case TypeURLTest:
|
||||||
|
|||||||
@@ -23,15 +23,12 @@ const (
|
|||||||
RuleSetVersion2
|
RuleSetVersion2
|
||||||
RuleSetVersion3
|
RuleSetVersion3
|
||||||
RuleSetVersion4
|
RuleSetVersion4
|
||||||
RuleSetVersion5
|
RuleSetVersionCurrent = RuleSetVersion4
|
||||||
RuleSetVersionCurrent = RuleSetVersion5
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RuleActionTypeRoute = "route"
|
RuleActionTypeRoute = "route"
|
||||||
RuleActionTypeRouteOptions = "route-options"
|
RuleActionTypeRouteOptions = "route-options"
|
||||||
RuleActionTypeEvaluate = "evaluate"
|
|
||||||
RuleActionTypeRespond = "respond"
|
|
||||||
RuleActionTypeDirect = "direct"
|
RuleActionTypeDirect = "direct"
|
||||||
RuleActionTypeBypass = "bypass"
|
RuleActionTypeBypass = "bypass"
|
||||||
RuleActionTypeReject = "reject"
|
RuleActionTypeReject = "reject"
|
||||||
|
|||||||
@@ -87,17 +87,12 @@ func (s *StartedService) newInstance(profileContent string, overrideOptions *Ove
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.oomKillerEnabled {
|
if s.oomKiller && C.IsIos {
|
||||||
if !common.Any(options.Services, func(it option.Service) bool {
|
if !common.Any(options.Services, func(it option.Service) bool {
|
||||||
return it.Type == C.TypeOOMKiller
|
return it.Type == C.TypeOOMKiller
|
||||||
}) {
|
}) {
|
||||||
oomOptions := &option.OOMKillerServiceOptions{
|
|
||||||
KillerDisabled: s.oomKillerDisabled,
|
|
||||||
MemoryLimitOverride: s.oomMemoryLimit,
|
|
||||||
}
|
|
||||||
options.Services = append(options.Services, option.Service{
|
options.Services = append(options.Services, option.Service{
|
||||||
Type: C.TypeOOMKiller,
|
Type: C.TypeOOMKiller,
|
||||||
Options: oomOptions,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,5 @@ type PlatformHandler interface {
|
|||||||
ServiceReload() error
|
ServiceReload() error
|
||||||
SystemProxyStatus() (*SystemProxyStatus, error)
|
SystemProxyStatus() (*SystemProxyStatus, error)
|
||||||
SetSystemProxyEnabled(enabled bool) error
|
SetSystemProxyEnabled(enabled bool) error
|
||||||
TriggerNativeCrash() error
|
|
||||||
WriteDebugMessage(message string)
|
WriteDebugMessage(message string)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,20 +6,14 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
|
||||||
"github.com/sagernet/sing-box/common/networkquality"
|
|
||||||
"github.com/sagernet/sing-box/common/stun"
|
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi"
|
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/protocol/group"
|
"github.com/sagernet/sing-box/protocol/group"
|
||||||
"github.com/sagernet/sing-box/service/oomkiller"
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/batch"
|
"github.com/sagernet/sing/common/batch"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -30,8 +24,6 @@ import (
|
|||||||
|
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid/v5"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,12 +32,10 @@ var _ StartedServiceServer = (*StartedService)(nil)
|
|||||||
type StartedService struct {
|
type StartedService struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
// platform adapter.PlatformInterface
|
// platform adapter.PlatformInterface
|
||||||
handler PlatformHandler
|
handler PlatformHandler
|
||||||
debug bool
|
debug bool
|
||||||
logMaxLines int
|
logMaxLines int
|
||||||
oomKillerEnabled bool
|
oomKiller bool
|
||||||
oomKillerDisabled bool
|
|
||||||
oomMemoryLimit uint64
|
|
||||||
// workingDirectory string
|
// workingDirectory string
|
||||||
// tempDirectory string
|
// tempDirectory string
|
||||||
// userID int
|
// userID int
|
||||||
@@ -74,12 +64,10 @@ type StartedService struct {
|
|||||||
type ServiceOptions struct {
|
type ServiceOptions struct {
|
||||||
Context context.Context
|
Context context.Context
|
||||||
// Platform adapter.PlatformInterface
|
// Platform adapter.PlatformInterface
|
||||||
Handler PlatformHandler
|
Handler PlatformHandler
|
||||||
Debug bool
|
Debug bool
|
||||||
LogMaxLines int
|
LogMaxLines int
|
||||||
OOMKillerEnabled bool
|
OOMKiller bool
|
||||||
OOMKillerDisabled bool
|
|
||||||
OOMMemoryLimit uint64
|
|
||||||
// WorkingDirectory string
|
// WorkingDirectory string
|
||||||
// TempDirectory string
|
// TempDirectory string
|
||||||
// UserID int
|
// UserID int
|
||||||
@@ -91,12 +79,10 @@ func NewStartedService(options ServiceOptions) *StartedService {
|
|||||||
s := &StartedService{
|
s := &StartedService{
|
||||||
ctx: options.Context,
|
ctx: options.Context,
|
||||||
// platform: options.Platform,
|
// platform: options.Platform,
|
||||||
handler: options.Handler,
|
handler: options.Handler,
|
||||||
debug: options.Debug,
|
debug: options.Debug,
|
||||||
logMaxLines: options.LogMaxLines,
|
logMaxLines: options.LogMaxLines,
|
||||||
oomKillerEnabled: options.OOMKillerEnabled,
|
oomKiller: options.OOMKiller,
|
||||||
oomKillerDisabled: options.OOMKillerDisabled,
|
|
||||||
oomMemoryLimit: options.OOMMemoryLimit,
|
|
||||||
// workingDirectory: options.WorkingDirectory,
|
// workingDirectory: options.WorkingDirectory,
|
||||||
// tempDirectory: options.TempDirectory,
|
// tempDirectory: options.TempDirectory,
|
||||||
// userID: options.UserID,
|
// userID: options.UserID,
|
||||||
@@ -696,42 +682,7 @@ func (s *StartedService) SetSystemProxyEnabled(ctx context.Context, request *Set
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &emptypb.Empty{}, nil
|
return nil, err
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) TriggerDebugCrash(ctx context.Context, request *DebugCrashRequest) (*emptypb.Empty, error) {
|
|
||||||
if !s.debug {
|
|
||||||
return nil, status.Error(codes.PermissionDenied, "debug crash trigger unavailable")
|
|
||||||
}
|
|
||||||
if request == nil {
|
|
||||||
return nil, status.Error(codes.InvalidArgument, "missing debug crash request")
|
|
||||||
}
|
|
||||||
switch request.Type {
|
|
||||||
case DebugCrashRequest_GO:
|
|
||||||
time.AfterFunc(200*time.Millisecond, func() {
|
|
||||||
*(*int)(unsafe.Pointer(uintptr(0))) = 0
|
|
||||||
})
|
|
||||||
case DebugCrashRequest_NATIVE:
|
|
||||||
err := s.handler.TriggerNativeCrash()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, status.Error(codes.InvalidArgument, "unknown debug crash type")
|
|
||||||
}
|
|
||||||
return &emptypb.Empty{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) TriggerOOMReport(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
||||||
instance := s.Instance()
|
|
||||||
if instance == nil {
|
|
||||||
return nil, status.Error(codes.FailedPrecondition, "service not started")
|
|
||||||
}
|
|
||||||
reporter := service.FromContext[oomkiller.OOMReporter](instance.ctx)
|
|
||||||
if reporter == nil {
|
|
||||||
return nil, status.Error(codes.Unavailable, "OOM reporter not available")
|
|
||||||
}
|
|
||||||
return &emptypb.Empty{}, reporter.WriteReport(memory.Total())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[ConnectionEvents]) error {
|
func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[ConnectionEvents]) error {
|
||||||
@@ -1068,12 +1019,9 @@ func (s *StartedService) GetDeprecatedWarnings(ctx context.Context, empty *empty
|
|||||||
return &DeprecatedWarnings{
|
return &DeprecatedWarnings{
|
||||||
Warnings: common.Map(notes, func(it deprecated.Note) *DeprecatedWarning {
|
Warnings: common.Map(notes, func(it deprecated.Note) *DeprecatedWarning {
|
||||||
return &DeprecatedWarning{
|
return &DeprecatedWarning{
|
||||||
Message: it.Message(),
|
Message: it.Message(),
|
||||||
Impending: it.Impending(),
|
Impending: it.Impending(),
|
||||||
MigrationLink: it.MigrationLink,
|
MigrationLink: it.MigrationLink,
|
||||||
Description: it.Description,
|
|
||||||
DeprecatedVersion: it.DeprecatedVersion,
|
|
||||||
ScheduledVersion: it.ScheduledVersion,
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}, nil
|
}, nil
|
||||||
@@ -1085,386 +1033,6 @@ func (s *StartedService) GetStartedAt(ctx context.Context, empty *emptypb.Empty)
|
|||||||
return &StartedAt{StartedAt: s.startedAt.UnixMilli()}, nil
|
return &StartedAt{StartedAt: s.startedAt.UnixMilli()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) SubscribeOutbounds(_ *emptypb.Empty, server grpc.ServerStreamingServer[OutboundList]) error {
|
|
||||||
err := s.waitForStarted(server.Context())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
subscription, done, err := s.urlTestObserver.Subscribe()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.urlTestObserver.UnSubscribe(subscription)
|
|
||||||
for {
|
|
||||||
s.serviceAccess.RLock()
|
|
||||||
if s.serviceStatus.Status != ServiceStatus_STARTED {
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
boxService := s.instance
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
historyStorage := boxService.urlTestHistoryStorage
|
|
||||||
var list OutboundList
|
|
||||||
for _, ob := range boxService.instance.Outbound().Outbounds() {
|
|
||||||
item := &GroupItem{
|
|
||||||
Tag: ob.Tag(),
|
|
||||||
Type: ob.Type(),
|
|
||||||
}
|
|
||||||
if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(ob)); history != nil {
|
|
||||||
item.UrlTestTime = history.Time.Unix()
|
|
||||||
item.UrlTestDelay = int32(history.Delay)
|
|
||||||
}
|
|
||||||
list.Outbounds = append(list.Outbounds, item)
|
|
||||||
}
|
|
||||||
for _, ep := range boxService.instance.Endpoint().Endpoints() {
|
|
||||||
item := &GroupItem{
|
|
||||||
Tag: ep.Tag(),
|
|
||||||
Type: ep.Type(),
|
|
||||||
}
|
|
||||||
if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(ep)); history != nil {
|
|
||||||
item.UrlTestTime = history.Time.Unix()
|
|
||||||
item.UrlTestDelay = int32(history.Delay)
|
|
||||||
}
|
|
||||||
list.Outbounds = append(list.Outbounds, item)
|
|
||||||
}
|
|
||||||
err = server.Send(&list)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-subscription:
|
|
||||||
case <-s.ctx.Done():
|
|
||||||
return s.ctx.Err()
|
|
||||||
case <-server.Context().Done():
|
|
||||||
return server.Context().Err()
|
|
||||||
case <-done:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveOutbound(instance *Instance, tag string) (adapter.Outbound, error) {
|
|
||||||
if tag == "" {
|
|
||||||
return instance.instance.Outbound().Default(), nil
|
|
||||||
}
|
|
||||||
outbound, loaded := instance.instance.Outbound().Outbound(tag)
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("outbound not found: ", tag)
|
|
||||||
}
|
|
||||||
return outbound, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) StartNetworkQualityTest(
|
|
||||||
request *NetworkQualityTestRequest,
|
|
||||||
server grpc.ServerStreamingServer[NetworkQualityTestProgress],
|
|
||||||
) error {
|
|
||||||
err := s.waitForStarted(server.Context())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.serviceAccess.RLock()
|
|
||||||
boxService := s.instance
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
|
|
||||||
outbound, err := resolveOutbound(boxService, request.OutboundTag)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resolvedDialer := dialer.NewResolveDialer(boxService.ctx, outbound, true, "", adapter.DNSQueryOptions{}, 0)
|
|
||||||
httpClient := networkquality.NewHTTPClient(resolvedDialer)
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
|
|
||||||
measurementClientFactory, err := networkquality.NewOptionalHTTP3Factory(resolvedDialer, request.Http3)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
result, nqErr := networkquality.Run(networkquality.Options{
|
|
||||||
ConfigURL: request.ConfigURL,
|
|
||||||
HTTPClient: httpClient,
|
|
||||||
NewMeasurementClient: measurementClientFactory,
|
|
||||||
Serial: request.Serial,
|
|
||||||
MaxRuntime: time.Duration(request.MaxRuntimeSeconds) * time.Second,
|
|
||||||
Context: server.Context(),
|
|
||||||
OnProgress: func(p networkquality.Progress) {
|
|
||||||
_ = server.Send(&NetworkQualityTestProgress{
|
|
||||||
Phase: int32(p.Phase),
|
|
||||||
DownloadCapacity: p.DownloadCapacity,
|
|
||||||
UploadCapacity: p.UploadCapacity,
|
|
||||||
DownloadRPM: p.DownloadRPM,
|
|
||||||
UploadRPM: p.UploadRPM,
|
|
||||||
IdleLatencyMs: p.IdleLatencyMs,
|
|
||||||
ElapsedMs: p.ElapsedMs,
|
|
||||||
DownloadCapacityAccuracy: int32(p.DownloadCapacityAccuracy),
|
|
||||||
UploadCapacityAccuracy: int32(p.UploadCapacityAccuracy),
|
|
||||||
DownloadRPMAccuracy: int32(p.DownloadRPMAccuracy),
|
|
||||||
UploadRPMAccuracy: int32(p.UploadRPMAccuracy),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if nqErr != nil {
|
|
||||||
return server.Send(&NetworkQualityTestProgress{
|
|
||||||
IsFinal: true,
|
|
||||||
Error: nqErr.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return server.Send(&NetworkQualityTestProgress{
|
|
||||||
Phase: int32(networkquality.PhaseDone),
|
|
||||||
DownloadCapacity: result.DownloadCapacity,
|
|
||||||
UploadCapacity: result.UploadCapacity,
|
|
||||||
DownloadRPM: result.DownloadRPM,
|
|
||||||
UploadRPM: result.UploadRPM,
|
|
||||||
IdleLatencyMs: result.IdleLatencyMs,
|
|
||||||
IsFinal: true,
|
|
||||||
DownloadCapacityAccuracy: int32(result.DownloadCapacityAccuracy),
|
|
||||||
UploadCapacityAccuracy: int32(result.UploadCapacityAccuracy),
|
|
||||||
DownloadRPMAccuracy: int32(result.DownloadRPMAccuracy),
|
|
||||||
UploadRPMAccuracy: int32(result.UploadRPMAccuracy),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) StartSTUNTest(
|
|
||||||
request *STUNTestRequest,
|
|
||||||
server grpc.ServerStreamingServer[STUNTestProgress],
|
|
||||||
) error {
|
|
||||||
err := s.waitForStarted(server.Context())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.serviceAccess.RLock()
|
|
||||||
boxService := s.instance
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
|
|
||||||
outbound, err := resolveOutbound(boxService, request.OutboundTag)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resolvedDialer := dialer.NewResolveDialer(boxService.ctx, outbound, true, "", adapter.DNSQueryOptions{}, 0)
|
|
||||||
|
|
||||||
result, stunErr := stun.Run(stun.Options{
|
|
||||||
Server: request.Server,
|
|
||||||
Dialer: resolvedDialer,
|
|
||||||
Context: server.Context(),
|
|
||||||
OnProgress: func(p stun.Progress) {
|
|
||||||
_ = server.Send(&STUNTestProgress{
|
|
||||||
Phase: int32(p.Phase),
|
|
||||||
ExternalAddr: p.ExternalAddr,
|
|
||||||
LatencyMs: p.LatencyMs,
|
|
||||||
NatMapping: int32(p.NATMapping),
|
|
||||||
NatFiltering: int32(p.NATFiltering),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if stunErr != nil {
|
|
||||||
return server.Send(&STUNTestProgress{
|
|
||||||
IsFinal: true,
|
|
||||||
Error: stunErr.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return server.Send(&STUNTestProgress{
|
|
||||||
Phase: int32(stun.PhaseDone),
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
NatMapping: int32(result.NATMapping),
|
|
||||||
NatFiltering: int32(result.NATFiltering),
|
|
||||||
IsFinal: true,
|
|
||||||
NatTypeSupported: result.NATTypeSupported,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) SubscribeTailscaleStatus(
|
|
||||||
_ *emptypb.Empty,
|
|
||||||
server grpc.ServerStreamingServer[TailscaleStatusUpdate],
|
|
||||||
) error {
|
|
||||||
err := s.waitForStarted(server.Context())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.serviceAccess.RLock()
|
|
||||||
boxService := s.instance
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
|
|
||||||
endpointManager := service.FromContext[adapter.EndpointManager](boxService.ctx)
|
|
||||||
if endpointManager == nil {
|
|
||||||
return status.Error(codes.FailedPrecondition, "endpoint manager not available")
|
|
||||||
}
|
|
||||||
|
|
||||||
type tailscaleEndpoint struct {
|
|
||||||
tag string
|
|
||||||
provider adapter.TailscaleEndpoint
|
|
||||||
}
|
|
||||||
var endpoints []tailscaleEndpoint
|
|
||||||
for _, endpoint := range endpointManager.Endpoints() {
|
|
||||||
if endpoint.Type() != C.TypeTailscale {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
provider, loaded := endpoint.(adapter.TailscaleEndpoint)
|
|
||||||
if !loaded {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
endpoints = append(endpoints, tailscaleEndpoint{
|
|
||||||
tag: endpoint.Tag(),
|
|
||||||
provider: provider,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if len(endpoints) == 0 {
|
|
||||||
return status.Error(codes.NotFound, "no Tailscale endpoint found")
|
|
||||||
}
|
|
||||||
|
|
||||||
type taggedStatus struct {
|
|
||||||
tag string
|
|
||||||
status *adapter.TailscaleEndpointStatus
|
|
||||||
}
|
|
||||||
updates := make(chan taggedStatus, len(endpoints))
|
|
||||||
ctx, cancel := context.WithCancel(server.Context())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
var waitGroup sync.WaitGroup
|
|
||||||
for _, endpoint := range endpoints {
|
|
||||||
waitGroup.Add(1)
|
|
||||||
go func(tag string, provider adapter.TailscaleEndpoint) {
|
|
||||||
defer waitGroup.Done()
|
|
||||||
_ = provider.SubscribeTailscaleStatus(ctx, func(endpointStatus *adapter.TailscaleEndpointStatus) {
|
|
||||||
select {
|
|
||||||
case updates <- taggedStatus{tag: tag, status: endpointStatus}:
|
|
||||||
case <-ctx.Done():
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}(endpoint.tag, endpoint.provider)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
waitGroup.Wait()
|
|
||||||
close(updates)
|
|
||||||
}()
|
|
||||||
|
|
||||||
var tags []string
|
|
||||||
statuses := make(map[string]*adapter.TailscaleEndpointStatus, len(endpoints))
|
|
||||||
for update := range updates {
|
|
||||||
if _, exists := statuses[update.tag]; !exists {
|
|
||||||
tags = append(tags, update.tag)
|
|
||||||
}
|
|
||||||
statuses[update.tag] = update.status
|
|
||||||
protoEndpoints := make([]*TailscaleEndpointStatus, 0, len(statuses))
|
|
||||||
for _, tag := range tags {
|
|
||||||
protoEndpoints = append(protoEndpoints, tailscaleEndpointStatusToProto(tag, statuses[tag]))
|
|
||||||
}
|
|
||||||
sendErr := server.Send(&TailscaleStatusUpdate{
|
|
||||||
Endpoints: protoEndpoints,
|
|
||||||
})
|
|
||||||
if sendErr != nil {
|
|
||||||
return sendErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func tailscaleEndpointStatusToProto(tag string, s *adapter.TailscaleEndpointStatus) *TailscaleEndpointStatus {
|
|
||||||
userGroups := make([]*TailscaleUserGroup, len(s.UserGroups))
|
|
||||||
for i, group := range s.UserGroups {
|
|
||||||
peers := make([]*TailscalePeer, len(group.Peers))
|
|
||||||
for j, peer := range group.Peers {
|
|
||||||
peers[j] = tailscalePeerToProto(peer)
|
|
||||||
}
|
|
||||||
userGroups[i] = &TailscaleUserGroup{
|
|
||||||
UserID: group.UserID,
|
|
||||||
LoginName: group.LoginName,
|
|
||||||
DisplayName: group.DisplayName,
|
|
||||||
ProfilePicURL: group.ProfilePicURL,
|
|
||||||
Peers: peers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result := &TailscaleEndpointStatus{
|
|
||||||
EndpointTag: tag,
|
|
||||||
BackendState: s.BackendState,
|
|
||||||
AuthURL: s.AuthURL,
|
|
||||||
NetworkName: s.NetworkName,
|
|
||||||
MagicDNSSuffix: s.MagicDNSSuffix,
|
|
||||||
UserGroups: userGroups,
|
|
||||||
}
|
|
||||||
if s.Self != nil {
|
|
||||||
result.Self = tailscalePeerToProto(s.Self)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func tailscalePeerToProto(peer *adapter.TailscalePeer) *TailscalePeer {
|
|
||||||
return &TailscalePeer{
|
|
||||||
HostName: peer.HostName,
|
|
||||||
DnsName: peer.DNSName,
|
|
||||||
Os: peer.OS,
|
|
||||||
TailscaleIPs: peer.TailscaleIPs,
|
|
||||||
Online: peer.Online,
|
|
||||||
ExitNode: peer.ExitNode,
|
|
||||||
ExitNodeOption: peer.ExitNodeOption,
|
|
||||||
Active: peer.Active,
|
|
||||||
RxBytes: peer.RxBytes,
|
|
||||||
TxBytes: peer.TxBytes,
|
|
||||||
KeyExpiry: peer.KeyExpiry,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) StartTailscalePing(
|
|
||||||
request *TailscalePingRequest,
|
|
||||||
server grpc.ServerStreamingServer[TailscalePingResponse],
|
|
||||||
) error {
|
|
||||||
err := s.waitForStarted(server.Context())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.serviceAccess.RLock()
|
|
||||||
boxService := s.instance
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
|
|
||||||
endpointManager := service.FromContext[adapter.EndpointManager](boxService.ctx)
|
|
||||||
if endpointManager == nil {
|
|
||||||
return status.Error(codes.FailedPrecondition, "endpoint manager not available")
|
|
||||||
}
|
|
||||||
|
|
||||||
var provider adapter.TailscaleEndpoint
|
|
||||||
if request.EndpointTag != "" {
|
|
||||||
endpoint, loaded := endpointManager.Get(request.EndpointTag)
|
|
||||||
if !loaded {
|
|
||||||
return status.Error(codes.NotFound, "endpoint not found: "+request.EndpointTag)
|
|
||||||
}
|
|
||||||
if endpoint.Type() != C.TypeTailscale {
|
|
||||||
return status.Error(codes.InvalidArgument, "endpoint is not Tailscale: "+request.EndpointTag)
|
|
||||||
}
|
|
||||||
pingProvider, loaded := endpoint.(adapter.TailscaleEndpoint)
|
|
||||||
if !loaded {
|
|
||||||
return status.Error(codes.FailedPrecondition, "endpoint does not support ping")
|
|
||||||
}
|
|
||||||
provider = pingProvider
|
|
||||||
} else {
|
|
||||||
for _, endpoint := range endpointManager.Endpoints() {
|
|
||||||
if endpoint.Type() != C.TypeTailscale {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pingProvider, loaded := endpoint.(adapter.TailscaleEndpoint)
|
|
||||||
if loaded {
|
|
||||||
provider = pingProvider
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if provider == nil {
|
|
||||||
return status.Error(codes.NotFound, "no Tailscale endpoint found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider.StartTailscalePing(server.Context(), request.PeerIP, func(result *adapter.TailscalePingResult) {
|
|
||||||
_ = server.Send(&TailscalePingResponse{
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
IsDirect: result.IsDirect,
|
|
||||||
Endpoint: result.Endpoint,
|
|
||||||
DerpRegionID: result.DERPRegionID,
|
|
||||||
DerpRegionCode: result.DERPRegionCode,
|
|
||||||
Error: result.Error,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) mustEmbedUnimplementedStartedServiceServer() {
|
func (s *StartedService) mustEmbedUnimplementedStartedServiceServer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -26,20 +26,12 @@ service StartedService {
|
|||||||
|
|
||||||
rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {}
|
rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {}
|
||||||
rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {}
|
rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {}
|
||||||
rpc TriggerDebugCrash(DebugCrashRequest) returns(google.protobuf.Empty) {}
|
|
||||||
rpc TriggerOOMReport(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
|
||||||
|
|
||||||
rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream ConnectionEvents) {}
|
rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream ConnectionEvents) {}
|
||||||
rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {}
|
rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {}
|
||||||
rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
||||||
rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {}
|
rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {}
|
||||||
rpc GetStartedAt(google.protobuf.Empty) returns(StartedAt) {}
|
rpc GetStartedAt(google.protobuf.Empty) returns(StartedAt) {}
|
||||||
|
|
||||||
rpc SubscribeOutbounds(google.protobuf.Empty) returns (stream OutboundList) {}
|
|
||||||
rpc StartNetworkQualityTest(NetworkQualityTestRequest) returns (stream NetworkQualityTestProgress) {}
|
|
||||||
rpc StartSTUNTest(STUNTestRequest) returns (stream STUNTestProgress) {}
|
|
||||||
rpc SubscribeTailscaleStatus(google.protobuf.Empty) returns (stream TailscaleStatusUpdate) {}
|
|
||||||
rpc StartTailscalePing(TailscalePingRequest) returns (stream TailscalePingResponse) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message ServiceStatus {
|
message ServiceStatus {
|
||||||
@@ -149,15 +141,6 @@ message SetSystemProxyEnabledRequest {
|
|||||||
bool enabled = 1;
|
bool enabled = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DebugCrashRequest {
|
|
||||||
enum Type {
|
|
||||||
GO = 0;
|
|
||||||
NATIVE = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Type type = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SubscribeConnectionsRequest {
|
message SubscribeConnectionsRequest {
|
||||||
int64 interval = 1;
|
int64 interval = 1;
|
||||||
}
|
}
|
||||||
@@ -227,105 +210,8 @@ message DeprecatedWarning {
|
|||||||
string message = 1;
|
string message = 1;
|
||||||
bool impending = 2;
|
bool impending = 2;
|
||||||
string migrationLink = 3;
|
string migrationLink = 3;
|
||||||
string description = 4;
|
|
||||||
string deprecatedVersion = 5;
|
|
||||||
string scheduledVersion = 6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message StartedAt {
|
message StartedAt {
|
||||||
int64 startedAt = 1;
|
int64 startedAt = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message OutboundList {
|
|
||||||
repeated GroupItem outbounds = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message NetworkQualityTestRequest {
|
|
||||||
string configURL = 1;
|
|
||||||
string outboundTag = 2;
|
|
||||||
bool serial = 3;
|
|
||||||
int32 maxRuntimeSeconds = 4;
|
|
||||||
bool http3 = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message NetworkQualityTestProgress {
|
|
||||||
int32 phase = 1;
|
|
||||||
int64 downloadCapacity = 2;
|
|
||||||
int64 uploadCapacity = 3;
|
|
||||||
int32 downloadRPM = 4;
|
|
||||||
int32 uploadRPM = 5;
|
|
||||||
int32 idleLatencyMs = 6;
|
|
||||||
int64 elapsedMs = 7;
|
|
||||||
bool isFinal = 8;
|
|
||||||
string error = 9;
|
|
||||||
int32 downloadCapacityAccuracy = 10;
|
|
||||||
int32 uploadCapacityAccuracy = 11;
|
|
||||||
int32 downloadRPMAccuracy = 12;
|
|
||||||
int32 uploadRPMAccuracy = 13;
|
|
||||||
}
|
|
||||||
|
|
||||||
message STUNTestRequest {
|
|
||||||
string server = 1;
|
|
||||||
string outboundTag = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message STUNTestProgress {
|
|
||||||
int32 phase = 1;
|
|
||||||
string externalAddr = 2;
|
|
||||||
int32 latencyMs = 3;
|
|
||||||
int32 natMapping = 4;
|
|
||||||
int32 natFiltering = 5;
|
|
||||||
bool isFinal = 6;
|
|
||||||
string error = 7;
|
|
||||||
bool natTypeSupported = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscaleStatusUpdate {
|
|
||||||
repeated TailscaleEndpointStatus endpoints = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscaleEndpointStatus {
|
|
||||||
string endpointTag = 1;
|
|
||||||
string backendState = 2;
|
|
||||||
string authURL = 3;
|
|
||||||
string networkName = 4;
|
|
||||||
string magicDNSSuffix = 5;
|
|
||||||
TailscalePeer self = 6;
|
|
||||||
repeated TailscaleUserGroup userGroups = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscaleUserGroup {
|
|
||||||
int64 userID = 1;
|
|
||||||
string loginName = 2;
|
|
||||||
string displayName = 3;
|
|
||||||
string profilePicURL = 4;
|
|
||||||
repeated TailscalePeer peers = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscalePeer {
|
|
||||||
string hostName = 1;
|
|
||||||
string dnsName = 2;
|
|
||||||
string os = 3;
|
|
||||||
repeated string tailscaleIPs = 4;
|
|
||||||
bool online = 5;
|
|
||||||
bool exitNode = 6;
|
|
||||||
bool exitNodeOption = 7;
|
|
||||||
bool active = 8;
|
|
||||||
int64 rxBytes = 9;
|
|
||||||
int64 txBytes = 10;
|
|
||||||
int64 keyExpiry = 11;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscalePingRequest {
|
|
||||||
string endpointTag = 1;
|
|
||||||
string peerIP = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscalePingResponse {
|
|
||||||
double latencyMs = 1;
|
|
||||||
bool isDirect = 2;
|
|
||||||
string endpoint = 3;
|
|
||||||
int32 derpRegionID = 4;
|
|
||||||
string derpRegionCode = 5;
|
|
||||||
string error = 6;
|
|
||||||
}
|
|
||||||
@@ -15,34 +15,27 @@ import (
|
|||||||
const _ = grpc.SupportPackageIsVersion9
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
const (
|
const (
|
||||||
StartedService_StopService_FullMethodName = "/daemon.StartedService/StopService"
|
StartedService_StopService_FullMethodName = "/daemon.StartedService/StopService"
|
||||||
StartedService_ReloadService_FullMethodName = "/daemon.StartedService/ReloadService"
|
StartedService_ReloadService_FullMethodName = "/daemon.StartedService/ReloadService"
|
||||||
StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus"
|
StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus"
|
||||||
StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog"
|
StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog"
|
||||||
StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel"
|
StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel"
|
||||||
StartedService_ClearLogs_FullMethodName = "/daemon.StartedService/ClearLogs"
|
StartedService_ClearLogs_FullMethodName = "/daemon.StartedService/ClearLogs"
|
||||||
StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus"
|
StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus"
|
||||||
StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups"
|
StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups"
|
||||||
StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus"
|
StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus"
|
||||||
StartedService_SubscribeClashMode_FullMethodName = "/daemon.StartedService/SubscribeClashMode"
|
StartedService_SubscribeClashMode_FullMethodName = "/daemon.StartedService/SubscribeClashMode"
|
||||||
StartedService_SetClashMode_FullMethodName = "/daemon.StartedService/SetClashMode"
|
StartedService_SetClashMode_FullMethodName = "/daemon.StartedService/SetClashMode"
|
||||||
StartedService_URLTest_FullMethodName = "/daemon.StartedService/URLTest"
|
StartedService_URLTest_FullMethodName = "/daemon.StartedService/URLTest"
|
||||||
StartedService_SelectOutbound_FullMethodName = "/daemon.StartedService/SelectOutbound"
|
StartedService_SelectOutbound_FullMethodName = "/daemon.StartedService/SelectOutbound"
|
||||||
StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand"
|
StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand"
|
||||||
StartedService_GetSystemProxyStatus_FullMethodName = "/daemon.StartedService/GetSystemProxyStatus"
|
StartedService_GetSystemProxyStatus_FullMethodName = "/daemon.StartedService/GetSystemProxyStatus"
|
||||||
StartedService_SetSystemProxyEnabled_FullMethodName = "/daemon.StartedService/SetSystemProxyEnabled"
|
StartedService_SetSystemProxyEnabled_FullMethodName = "/daemon.StartedService/SetSystemProxyEnabled"
|
||||||
StartedService_TriggerDebugCrash_FullMethodName = "/daemon.StartedService/TriggerDebugCrash"
|
StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections"
|
||||||
StartedService_TriggerOOMReport_FullMethodName = "/daemon.StartedService/TriggerOOMReport"
|
StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection"
|
||||||
StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections"
|
StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections"
|
||||||
StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection"
|
StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings"
|
||||||
StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections"
|
StartedService_GetStartedAt_FullMethodName = "/daemon.StartedService/GetStartedAt"
|
||||||
StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings"
|
|
||||||
StartedService_GetStartedAt_FullMethodName = "/daemon.StartedService/GetStartedAt"
|
|
||||||
StartedService_SubscribeOutbounds_FullMethodName = "/daemon.StartedService/SubscribeOutbounds"
|
|
||||||
StartedService_StartNetworkQualityTest_FullMethodName = "/daemon.StartedService/StartNetworkQualityTest"
|
|
||||||
StartedService_StartSTUNTest_FullMethodName = "/daemon.StartedService/StartSTUNTest"
|
|
||||||
StartedService_SubscribeTailscaleStatus_FullMethodName = "/daemon.StartedService/SubscribeTailscaleStatus"
|
|
||||||
StartedService_StartTailscalePing_FullMethodName = "/daemon.StartedService/StartTailscalePing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// StartedServiceClient is the client API for StartedService service.
|
// StartedServiceClient is the client API for StartedService service.
|
||||||
@@ -65,18 +58,11 @@ type StartedServiceClient interface {
|
|||||||
SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error)
|
GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error)
|
||||||
SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
TriggerDebugCrash(ctx context.Context, in *DebugCrashRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
|
||||||
TriggerOOMReport(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
|
||||||
SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error)
|
SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error)
|
||||||
CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error)
|
GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error)
|
||||||
GetStartedAt(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StartedAt, error)
|
GetStartedAt(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StartedAt, error)
|
||||||
SubscribeOutbounds(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OutboundList], error)
|
|
||||||
StartNetworkQualityTest(ctx context.Context, in *NetworkQualityTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NetworkQualityTestProgress], error)
|
|
||||||
StartSTUNTest(ctx context.Context, in *STUNTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[STUNTestProgress], error)
|
|
||||||
SubscribeTailscaleStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscaleStatusUpdate], error)
|
|
||||||
StartTailscalePing(ctx context.Context, in *TailscalePingRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscalePingResponse], error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type startedServiceClient struct {
|
type startedServiceClient struct {
|
||||||
@@ -292,26 +278,6 @@ func (c *startedServiceClient) SetSystemProxyEnabled(ctx context.Context, in *Se
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *startedServiceClient) TriggerDebugCrash(ctx context.Context, in *DebugCrashRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(emptypb.Empty)
|
|
||||||
err := c.cc.Invoke(ctx, StartedService_TriggerDebugCrash_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *startedServiceClient) TriggerOOMReport(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(emptypb.Empty)
|
|
||||||
err := c.cc.Invoke(ctx, StartedService_TriggerOOMReport_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error) {
|
func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...)
|
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...)
|
||||||
@@ -371,101 +337,6 @@ func (c *startedServiceClient) GetStartedAt(ctx context.Context, in *emptypb.Emp
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *startedServiceClient) SubscribeOutbounds(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OutboundList], error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[6], StartedService_SubscribeOutbounds_FullMethodName, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &grpc.GenericClientStream[emptypb.Empty, OutboundList]{ClientStream: stream}
|
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := x.ClientStream.CloseSend(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_SubscribeOutboundsClient = grpc.ServerStreamingClient[OutboundList]
|
|
||||||
|
|
||||||
func (c *startedServiceClient) StartNetworkQualityTest(ctx context.Context, in *NetworkQualityTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NetworkQualityTestProgress], error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[7], StartedService_StartNetworkQualityTest_FullMethodName, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &grpc.GenericClientStream[NetworkQualityTestRequest, NetworkQualityTestProgress]{ClientStream: stream}
|
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := x.ClientStream.CloseSend(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartNetworkQualityTestClient = grpc.ServerStreamingClient[NetworkQualityTestProgress]
|
|
||||||
|
|
||||||
func (c *startedServiceClient) StartSTUNTest(ctx context.Context, in *STUNTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[STUNTestProgress], error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[8], StartedService_StartSTUNTest_FullMethodName, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &grpc.GenericClientStream[STUNTestRequest, STUNTestProgress]{ClientStream: stream}
|
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := x.ClientStream.CloseSend(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartSTUNTestClient = grpc.ServerStreamingClient[STUNTestProgress]
|
|
||||||
|
|
||||||
func (c *startedServiceClient) SubscribeTailscaleStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscaleStatusUpdate], error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[9], StartedService_SubscribeTailscaleStatus_FullMethodName, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &grpc.GenericClientStream[emptypb.Empty, TailscaleStatusUpdate]{ClientStream: stream}
|
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := x.ClientStream.CloseSend(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_SubscribeTailscaleStatusClient = grpc.ServerStreamingClient[TailscaleStatusUpdate]
|
|
||||||
|
|
||||||
func (c *startedServiceClient) StartTailscalePing(ctx context.Context, in *TailscalePingRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscalePingResponse], error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[10], StartedService_StartTailscalePing_FullMethodName, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &grpc.GenericClientStream[TailscalePingRequest, TailscalePingResponse]{ClientStream: stream}
|
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := x.ClientStream.CloseSend(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartTailscalePingClient = grpc.ServerStreamingClient[TailscalePingResponse]
|
|
||||||
|
|
||||||
// StartedServiceServer is the server API for StartedService service.
|
// StartedServiceServer is the server API for StartedService service.
|
||||||
// All implementations must embed UnimplementedStartedServiceServer
|
// All implementations must embed UnimplementedStartedServiceServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
@@ -486,18 +357,11 @@ type StartedServiceServer interface {
|
|||||||
SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error)
|
SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error)
|
||||||
GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error)
|
GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error)
|
||||||
SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error)
|
SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error)
|
||||||
TriggerDebugCrash(context.Context, *DebugCrashRequest) (*emptypb.Empty, error)
|
|
||||||
TriggerOOMReport(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
|
||||||
SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error
|
SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error
|
||||||
CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error)
|
CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error)
|
||||||
CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||||
GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error)
|
GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error)
|
||||||
GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error)
|
GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error)
|
||||||
SubscribeOutbounds(*emptypb.Empty, grpc.ServerStreamingServer[OutboundList]) error
|
|
||||||
StartNetworkQualityTest(*NetworkQualityTestRequest, grpc.ServerStreamingServer[NetworkQualityTestProgress]) error
|
|
||||||
StartSTUNTest(*STUNTestRequest, grpc.ServerStreamingServer[STUNTestProgress]) error
|
|
||||||
SubscribeTailscaleStatus(*emptypb.Empty, grpc.ServerStreamingServer[TailscaleStatusUpdate]) error
|
|
||||||
StartTailscalePing(*TailscalePingRequest, grpc.ServerStreamingServer[TailscalePingResponse]) error
|
|
||||||
mustEmbedUnimplementedStartedServiceServer()
|
mustEmbedUnimplementedStartedServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,14 +436,6 @@ func (UnimplementedStartedServiceServer) SetSystemProxyEnabled(context.Context,
|
|||||||
return nil, status.Error(codes.Unimplemented, "method SetSystemProxyEnabled not implemented")
|
return nil, status.Error(codes.Unimplemented, "method SetSystemProxyEnabled not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) TriggerDebugCrash(context.Context, *DebugCrashRequest) (*emptypb.Empty, error) {
|
|
||||||
return nil, status.Error(codes.Unimplemented, "method TriggerDebugCrash not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) TriggerOOMReport(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
|
||||||
return nil, status.Error(codes.Unimplemented, "method TriggerOOMReport not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error {
|
func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error {
|
||||||
return status.Error(codes.Unimplemented, "method SubscribeConnections not implemented")
|
return status.Error(codes.Unimplemented, "method SubscribeConnections not implemented")
|
||||||
}
|
}
|
||||||
@@ -599,26 +455,6 @@ func (UnimplementedStartedServiceServer) GetDeprecatedWarnings(context.Context,
|
|||||||
func (UnimplementedStartedServiceServer) GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error) {
|
func (UnimplementedStartedServiceServer) GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method GetStartedAt not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetStartedAt not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SubscribeOutbounds(*emptypb.Empty, grpc.ServerStreamingServer[OutboundList]) error {
|
|
||||||
return status.Error(codes.Unimplemented, "method SubscribeOutbounds not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) StartNetworkQualityTest(*NetworkQualityTestRequest, grpc.ServerStreamingServer[NetworkQualityTestProgress]) error {
|
|
||||||
return status.Error(codes.Unimplemented, "method StartNetworkQualityTest not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) StartSTUNTest(*STUNTestRequest, grpc.ServerStreamingServer[STUNTestProgress]) error {
|
|
||||||
return status.Error(codes.Unimplemented, "method StartSTUNTest not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SubscribeTailscaleStatus(*emptypb.Empty, grpc.ServerStreamingServer[TailscaleStatusUpdate]) error {
|
|
||||||
return status.Error(codes.Unimplemented, "method SubscribeTailscaleStatus not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) StartTailscalePing(*TailscalePingRequest, grpc.ServerStreamingServer[TailscalePingResponse]) error {
|
|
||||||
return status.Error(codes.Unimplemented, "method StartTailscalePing not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {}
|
func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {}
|
||||||
func (UnimplementedStartedServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedStartedServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
@@ -893,42 +729,6 @@ func _StartedService_SetSystemProxyEnabled_Handler(srv interface{}, ctx context.
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _StartedService_TriggerDebugCrash_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(DebugCrashRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(StartedServiceServer).TriggerDebugCrash(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: StartedService_TriggerDebugCrash_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(StartedServiceServer).TriggerDebugCrash(ctx, req.(*DebugCrashRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _StartedService_TriggerOOMReport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(emptypb.Empty)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(StartedServiceServer).TriggerOOMReport(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: StartedService_TriggerOOMReport_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(StartedServiceServer).TriggerOOMReport(ctx, req.(*emptypb.Empty))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.ServerStream) error {
|
func _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
m := new(SubscribeConnectionsRequest)
|
m := new(SubscribeConnectionsRequest)
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
@@ -1012,61 +812,6 @@ func _StartedService_GetStartedAt_Handler(srv interface{}, ctx context.Context,
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _StartedService_SubscribeOutbounds_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
m := new(emptypb.Empty)
|
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return srv.(StartedServiceServer).SubscribeOutbounds(m, &grpc.GenericServerStream[emptypb.Empty, OutboundList]{ServerStream: stream})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_SubscribeOutboundsServer = grpc.ServerStreamingServer[OutboundList]
|
|
||||||
|
|
||||||
func _StartedService_StartNetworkQualityTest_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
m := new(NetworkQualityTestRequest)
|
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return srv.(StartedServiceServer).StartNetworkQualityTest(m, &grpc.GenericServerStream[NetworkQualityTestRequest, NetworkQualityTestProgress]{ServerStream: stream})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartNetworkQualityTestServer = grpc.ServerStreamingServer[NetworkQualityTestProgress]
|
|
||||||
|
|
||||||
func _StartedService_StartSTUNTest_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
m := new(STUNTestRequest)
|
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return srv.(StartedServiceServer).StartSTUNTest(m, &grpc.GenericServerStream[STUNTestRequest, STUNTestProgress]{ServerStream: stream})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartSTUNTestServer = grpc.ServerStreamingServer[STUNTestProgress]
|
|
||||||
|
|
||||||
func _StartedService_SubscribeTailscaleStatus_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
m := new(emptypb.Empty)
|
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return srv.(StartedServiceServer).SubscribeTailscaleStatus(m, &grpc.GenericServerStream[emptypb.Empty, TailscaleStatusUpdate]{ServerStream: stream})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_SubscribeTailscaleStatusServer = grpc.ServerStreamingServer[TailscaleStatusUpdate]
|
|
||||||
|
|
||||||
func _StartedService_StartTailscalePing_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
m := new(TailscalePingRequest)
|
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return srv.(StartedServiceServer).StartTailscalePing(m, &grpc.GenericServerStream[TailscalePingRequest, TailscalePingResponse]{ServerStream: stream})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartTailscalePingServer = grpc.ServerStreamingServer[TailscalePingResponse]
|
|
||||||
|
|
||||||
// StartedService_ServiceDesc is the grpc.ServiceDesc for StartedService service.
|
// StartedService_ServiceDesc is the grpc.ServiceDesc for StartedService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -1118,14 +863,6 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "SetSystemProxyEnabled",
|
MethodName: "SetSystemProxyEnabled",
|
||||||
Handler: _StartedService_SetSystemProxyEnabled_Handler,
|
Handler: _StartedService_SetSystemProxyEnabled_Handler,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
MethodName: "TriggerDebugCrash",
|
|
||||||
Handler: _StartedService_TriggerDebugCrash_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "TriggerOOMReport",
|
|
||||||
Handler: _StartedService_TriggerOOMReport_Handler,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
MethodName: "CloseConnection",
|
MethodName: "CloseConnection",
|
||||||
Handler: _StartedService_CloseConnection_Handler,
|
Handler: _StartedService_CloseConnection_Handler,
|
||||||
@@ -1174,31 +911,6 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
Handler: _StartedService_SubscribeConnections_Handler,
|
Handler: _StartedService_SubscribeConnections_Handler,
|
||||||
ServerStreams: true,
|
ServerStreams: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
StreamName: "SubscribeOutbounds",
|
|
||||||
Handler: _StartedService_SubscribeOutbounds_Handler,
|
|
||||||
ServerStreams: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StreamName: "StartNetworkQualityTest",
|
|
||||||
Handler: _StartedService_StartNetworkQualityTest_Handler,
|
|
||||||
ServerStreams: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StreamName: "StartSTUNTest",
|
|
||||||
Handler: _StartedService_StartSTUNTest_Handler,
|
|
||||||
ServerStreams: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StreamName: "SubscribeTailscaleStatus",
|
|
||||||
Handler: _StartedService_SubscribeTailscaleStatus_Handler,
|
|
||||||
ServerStreams: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StreamName: "StartTailscalePing",
|
|
||||||
Handler: _StartedService_StartTailscalePing_Handler,
|
|
||||||
ServerStreams: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Metadata: "daemon/started_service.proto",
|
Metadata: "daemon/started_service.proto",
|
||||||
}
|
}
|
||||||
|
|||||||
580
dns/client.go
580
dns/client.go
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/task"
|
"github.com/sagernet/sing/common/task"
|
||||||
"github.com/sagernet/sing/contrab/freelru"
|
"github.com/sagernet/sing/contrab/freelru"
|
||||||
"github.com/sagernet/sing/contrab/maphash"
|
"github.com/sagernet/sing/contrab/maphash"
|
||||||
@@ -30,63 +32,59 @@ var (
|
|||||||
var _ adapter.DNSClient = (*Client)(nil)
|
var _ adapter.DNSClient = (*Client)(nil)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
ctx context.Context
|
timeout time.Duration
|
||||||
timeout time.Duration
|
disableCache bool
|
||||||
disableCache bool
|
disableExpire bool
|
||||||
disableExpire bool
|
independentCache bool
|
||||||
optimisticTimeout time.Duration
|
clientSubnet netip.Prefix
|
||||||
cacheCapacity uint32
|
rdrc adapter.RDRCStore
|
||||||
clientSubnet netip.Prefix
|
initRDRCFunc func() adapter.RDRCStore
|
||||||
rdrc adapter.RDRCStore
|
logger logger.ContextLogger
|
||||||
initRDRCFunc func() adapter.RDRCStore
|
cache freelru.Cache[dns.Question, *dns.Msg]
|
||||||
dnsCache adapter.DNSCacheStore
|
cacheLock compatible.Map[dns.Question, chan struct{}]
|
||||||
initDNSCacheFunc func() adapter.DNSCacheStore
|
transportCache freelru.Cache[transportCacheKey, *dns.Msg]
|
||||||
logger logger.ContextLogger
|
transportCacheLock compatible.Map[dns.Question, chan struct{}]
|
||||||
cache freelru.Cache[dnsCacheKey, *dns.Msg]
|
|
||||||
cacheLock compatible.Map[dnsCacheKey, chan struct{}]
|
|
||||||
backgroundRefresh compatible.Map[dnsCacheKey, struct{}]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientOptions struct {
|
type ClientOptions struct {
|
||||||
Context context.Context
|
Timeout time.Duration
|
||||||
Timeout time.Duration
|
DisableCache bool
|
||||||
DisableCache bool
|
DisableExpire bool
|
||||||
DisableExpire bool
|
IndependentCache bool
|
||||||
OptimisticTimeout time.Duration
|
CacheCapacity uint32
|
||||||
CacheCapacity uint32
|
ClientSubnet netip.Prefix
|
||||||
ClientSubnet netip.Prefix
|
RDRC func() adapter.RDRCStore
|
||||||
RDRC func() adapter.RDRCStore
|
Logger logger.ContextLogger
|
||||||
DNSCache func() adapter.DNSCacheStore
|
|
||||||
Logger logger.ContextLogger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(options ClientOptions) *Client {
|
func NewClient(options ClientOptions) *Client {
|
||||||
cacheCapacity := options.CacheCapacity
|
|
||||||
if cacheCapacity < 1024 {
|
|
||||||
cacheCapacity = 1024
|
|
||||||
}
|
|
||||||
client := &Client{
|
client := &Client{
|
||||||
ctx: options.Context,
|
timeout: options.Timeout,
|
||||||
timeout: options.Timeout,
|
disableCache: options.DisableCache,
|
||||||
disableCache: options.DisableCache,
|
disableExpire: options.DisableExpire,
|
||||||
disableExpire: options.DisableExpire,
|
independentCache: options.IndependentCache,
|
||||||
optimisticTimeout: options.OptimisticTimeout,
|
clientSubnet: options.ClientSubnet,
|
||||||
cacheCapacity: cacheCapacity,
|
initRDRCFunc: options.RDRC,
|
||||||
clientSubnet: options.ClientSubnet,
|
logger: options.Logger,
|
||||||
initRDRCFunc: options.RDRC,
|
|
||||||
initDNSCacheFunc: options.DNSCache,
|
|
||||||
logger: options.Logger,
|
|
||||||
}
|
}
|
||||||
if client.timeout == 0 {
|
if client.timeout == 0 {
|
||||||
client.timeout = C.DNSTimeout
|
client.timeout = C.DNSTimeout
|
||||||
}
|
}
|
||||||
if !client.disableCache && client.initDNSCacheFunc == nil {
|
cacheCapacity := options.CacheCapacity
|
||||||
client.initializeMemoryCache()
|
if cacheCapacity < 1024 {
|
||||||
|
cacheCapacity = 1024
|
||||||
|
}
|
||||||
|
if !client.disableCache {
|
||||||
|
if !client.independentCache {
|
||||||
|
client.cache = common.Must1(freelru.NewSharded[dns.Question, *dns.Msg](cacheCapacity, maphash.NewHasher[dns.Question]().Hash32))
|
||||||
|
} else {
|
||||||
|
client.transportCache = common.Must1(freelru.NewSharded[transportCacheKey, *dns.Msg](cacheCapacity, maphash.NewHasher[transportCacheKey]().Hash32))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
type dnsCacheKey struct {
|
type transportCacheKey struct {
|
||||||
dns.Question
|
dns.Question
|
||||||
transportTag string
|
transportTag string
|
||||||
}
|
}
|
||||||
@@ -95,19 +93,6 @@ func (c *Client) Start() {
|
|||||||
if c.initRDRCFunc != nil {
|
if c.initRDRCFunc != nil {
|
||||||
c.rdrc = c.initRDRCFunc()
|
c.rdrc = c.initRDRCFunc()
|
||||||
}
|
}
|
||||||
if c.initDNSCacheFunc != nil {
|
|
||||||
c.dnsCache = c.initDNSCacheFunc()
|
|
||||||
}
|
|
||||||
if c.dnsCache == nil {
|
|
||||||
c.initializeMemoryCache()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) initializeMemoryCache() {
|
|
||||||
if c.disableCache || c.cache != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.cache = common.Must1(freelru.NewSharded[dnsCacheKey, *dns.Msg](c.cacheCapacity, maphash.NewHasher[dnsCacheKey]().Hash32))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
|
func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
|
||||||
@@ -124,38 +109,7 @@ func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
|
|||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func computeTimeToLive(response *dns.Msg) uint32 {
|
func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error) {
|
||||||
var timeToLive uint32
|
|
||||||
if len(response.Answer) == 0 {
|
|
||||||
if soaTTL, hasSOA := extractNegativeTTL(response); hasSOA {
|
|
||||||
return soaTTL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
|
||||||
for _, record := range recordList {
|
|
||||||
if record.Header().Rrtype == dns.TypeOPT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {
|
|
||||||
timeToLive = record.Header().Ttl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return timeToLive
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeTTL(response *dns.Msg, timeToLive uint32) {
|
|
||||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
|
||||||
for _, record := range recordList {
|
|
||||||
if record.Header().Rrtype == dns.TypeOPT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
record.Header().Ttl = timeToLive
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) (*dns.Msg, error) {
|
|
||||||
if len(message.Question) == 0 {
|
if len(message.Question) == 0 {
|
||||||
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))
|
||||||
@@ -169,7 +123,13 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
}
|
}
|
||||||
return FixedResponseStatus(message, dns.RcodeSuccess), nil
|
return FixedResponseStatus(message, dns.RcodeSuccess), nil
|
||||||
}
|
}
|
||||||
message = c.prepareExchangeMessage(message, options)
|
clientSubnet := options.ClientSubnet
|
||||||
|
if !clientSubnet.IsValid() {
|
||||||
|
clientSubnet = c.clientSubnet
|
||||||
|
}
|
||||||
|
if clientSubnet.IsValid() {
|
||||||
|
message = SetClientSubnet(message, clientSubnet)
|
||||||
|
}
|
||||||
|
|
||||||
isSimpleRequest := len(message.Question) == 1 &&
|
isSimpleRequest := len(message.Question) == 1 &&
|
||||||
len(message.Ns) == 0 &&
|
len(message.Ns) == 0 &&
|
||||||
@@ -181,32 +141,40 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
!options.ClientSubnet.IsValid()
|
!options.ClientSubnet.IsValid()
|
||||||
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
|
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
|
||||||
if !disableCache {
|
if !disableCache {
|
||||||
cacheKey := dnsCacheKey{Question: question, transportTag: transport.Tag()}
|
if c.cache != nil {
|
||||||
cond, loaded := c.cacheLock.LoadOrStore(cacheKey, make(chan struct{}))
|
cond, loaded := c.cacheLock.LoadOrStore(question, make(chan struct{}))
|
||||||
if loaded {
|
if loaded {
|
||||||
select {
|
select {
|
||||||
case <-cond:
|
case <-cond:
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
} 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 {
|
||||||
|
select {
|
||||||
|
case <-cond:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
defer func() {
|
||||||
|
c.transportCacheLock.Delete(question)
|
||||||
|
close(cond)
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
defer func() {
|
|
||||||
c.cacheLock.Delete(cacheKey)
|
|
||||||
close(cond)
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
response, ttl, isStale := c.loadResponse(question, transport)
|
response, ttl := c.loadResponse(question, transport)
|
||||||
if response != nil {
|
if response != nil {
|
||||||
if isStale && !options.DisableOptimisticCache {
|
logCachedResponse(c.logger, ctx, response, ttl)
|
||||||
c.backgroundRefreshDNS(transport, question, message.Copy(), options, responseChecker)
|
response.Id = message.Id
|
||||||
logOptimisticResponse(c.logger, ctx, response)
|
return response, nil
|
||||||
response.Id = message.Id
|
|
||||||
return response, nil
|
|
||||||
} else if !isStale {
|
|
||||||
logCachedResponse(c.logger, ctx, response, ttl)
|
|
||||||
response.Id = message.Id
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,17 +190,62 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
return nil, ErrResponseRejectedCached
|
return nil, ErrResponseRejectedCached
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response, err := c.exchangeToTransport(ctx, transport, message)
|
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||||
|
response, err := transport.Exchange(ctx, message)
|
||||||
|
cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
var rcodeError RcodeError
|
||||||
|
if errors.As(err, &rcodeError) {
|
||||||
|
response = FixedResponseStatus(message, int(rcodeError))
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
/*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA {
|
||||||
|
validResponse := response
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
var (
|
||||||
|
addresses int
|
||||||
|
queryCNAME string
|
||||||
|
)
|
||||||
|
for _, rawRR := range validResponse.Answer {
|
||||||
|
switch rr := rawRR.(type) {
|
||||||
|
case *dns.A:
|
||||||
|
break loop
|
||||||
|
case *dns.AAAA:
|
||||||
|
break loop
|
||||||
|
case *dns.CNAME:
|
||||||
|
queryCNAME = rr.Target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if queryCNAME == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
exMessage := *message
|
||||||
|
exMessage.Question = []dns.Question{{
|
||||||
|
Name: queryCNAME,
|
||||||
|
Qtype: question.Qtype,
|
||||||
|
}}
|
||||||
|
validResponse, err = c.Exchange(ctx, transport, &exMessage, options, responseChecker)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if validResponse != response {
|
||||||
|
response.Answer = append(response.Answer, validResponse.Answer...)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
disableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError)
|
disableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError)
|
||||||
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 && response.Rcode != dns.RcodeNameError {
|
||||||
rejected = true
|
rejected = true
|
||||||
|
} else if len(response.Answer) == 0 {
|
||||||
|
rejected = !responseChecker(nil)
|
||||||
} else {
|
} else {
|
||||||
rejected = !responseChecker(response)
|
rejected = !responseChecker(MessageToAddresses(response))
|
||||||
}
|
}
|
||||||
if rejected {
|
if rejected {
|
||||||
if !disableCache && c.rdrc != nil {
|
if !disableCache && c.rdrc != nil {
|
||||||
@@ -242,7 +255,48 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
return response, ErrResponseRejected
|
return response, ErrResponseRejected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timeToLive := applyResponseOptions(question, response, options)
|
if question.Qtype == dns.TypeHTTPS {
|
||||||
|
if options.Strategy == C.DomainStrategyIPv4Only || options.Strategy == C.DomainStrategyIPv6Only {
|
||||||
|
for _, rr := range response.Answer {
|
||||||
|
https, isHTTPS := rr.(*dns.HTTPS)
|
||||||
|
if !isHTTPS {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
content := https.SVCB
|
||||||
|
content.Value = common.Filter(content.Value, func(it dns.SVCBKeyValue) bool {
|
||||||
|
if options.Strategy == C.DomainStrategyIPv4Only {
|
||||||
|
return it.Key() != dns.SVCB_IPV6HINT
|
||||||
|
} else {
|
||||||
|
return it.Key() != dns.SVCB_IPV4HINT
|
||||||
|
}
|
||||||
|
})
|
||||||
|
https.SVCB = content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var timeToLive uint32
|
||||||
|
if len(response.Answer) == 0 {
|
||||||
|
if soaTTL, hasSOA := extractNegativeTTL(response); hasSOA {
|
||||||
|
timeToLive = soaTTL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if timeToLive == 0 {
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {
|
||||||
|
timeToLive = record.Header().Ttl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.RewriteTTL != nil {
|
||||||
|
timeToLive = *options.RewriteTTL
|
||||||
|
}
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
record.Header().Ttl = timeToLive
|
||||||
|
}
|
||||||
|
}
|
||||||
if !disableCache {
|
if !disableCache {
|
||||||
c.storeCache(transport, question, response, timeToLive)
|
c.storeCache(transport, question, response, timeToLive)
|
||||||
}
|
}
|
||||||
@@ -261,7 +315,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error) {
|
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||||
domain = FqdnToDomain(domain)
|
domain = FqdnToDomain(domain)
|
||||||
dnsName := dns.Fqdn(domain)
|
dnsName := dns.Fqdn(domain)
|
||||||
var strategy C.DomainStrategy
|
var strategy C.DomainStrategy
|
||||||
@@ -308,12 +362,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.dnsCache != nil {
|
c.transportCache.Purge()
|
||||||
err := c.dnsCache.ClearDNSCache()
|
|
||||||
if err != nil && c.logger != nil {
|
|
||||||
c.logger.Warn("clear DNS cache: ", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,44 +379,46 @@ func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Questio
|
|||||||
if timeToLive == 0 {
|
if timeToLive == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.dnsCache != nil {
|
|
||||||
packed, err := message.Pack()
|
|
||||||
if err == nil {
|
|
||||||
expireAt := time.Now().Add(time.Second * time.Duration(timeToLive))
|
|
||||||
c.dnsCache.SaveDNSCacheAsync(transport.Tag(), question.Name, question.Qtype, packed, expireAt, c.logger)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.cache == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
key := dnsCacheKey{Question: question, transportTag: transport.Tag()}
|
|
||||||
if c.disableExpire {
|
if c.disableExpire {
|
||||||
c.cache.Add(key, message.Copy())
|
if !c.independentCache {
|
||||||
|
c.cache.Add(question, message)
|
||||||
|
} else {
|
||||||
|
c.transportCache.Add(transportCacheKey{
|
||||||
|
Question: question,
|
||||||
|
transportTag: transport.Tag(),
|
||||||
|
}, message)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
c.cache.AddWithLifetime(key, message.Copy(), time.Second*time.Duration(timeToLive))
|
if !c.independentCache {
|
||||||
|
c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive))
|
||||||
|
} else {
|
||||||
|
c.transportCache.AddWithLifetime(transportCacheKey{
|
||||||
|
Question: question,
|
||||||
|
transportTag: transport.Tag(),
|
||||||
|
}, 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(response *dns.Msg) 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{
|
||||||
Name: name,
|
Name: name,
|
||||||
Qtype: qType,
|
Qtype: qType,
|
||||||
Qclass: dns.ClassINET,
|
Qclass: dns.ClassINET,
|
||||||
}
|
}
|
||||||
|
disableCache := c.disableCache || options.DisableCache
|
||||||
|
if !disableCache {
|
||||||
|
cachedAddresses, err := c.questionCache(question, transport)
|
||||||
|
if err != ErrNotCached {
|
||||||
|
return cachedAddresses, err
|
||||||
|
}
|
||||||
|
}
|
||||||
message := dns.Msg{
|
message := dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
RecursionDesired: true,
|
RecursionDesired: true,
|
||||||
},
|
},
|
||||||
Question: []dns.Question{question},
|
Question: []dns.Question{question},
|
||||||
}
|
}
|
||||||
disableCache := c.disableCache || options.DisableCache
|
|
||||||
if !disableCache {
|
|
||||||
cachedAddresses, err := c.questionCache(ctx, transport, &message, options, responseChecker)
|
|
||||||
if err != ErrNotCached {
|
|
||||||
return cachedAddresses, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
response, err := c.Exchange(ctx, transport, &message, options, responseChecker)
|
response, err := c.Exchange(ctx, transport, &message, options, responseChecker)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -377,181 +429,111 @@ func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTran
|
|||||||
return MessageToAddresses(response), nil
|
return MessageToAddresses(response), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) questionCache(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error) {
|
func (c *Client) questionCache(question dns.Question, transport adapter.DNSTransport) ([]netip.Addr, error) {
|
||||||
question := message.Question[0]
|
response, _ := c.loadResponse(question, transport)
|
||||||
response, _, isStale := c.loadResponse(question, transport)
|
|
||||||
if response == nil {
|
if response == nil {
|
||||||
return nil, ErrNotCached
|
return nil, ErrNotCached
|
||||||
}
|
}
|
||||||
if isStale {
|
|
||||||
if options.DisableOptimisticCache {
|
|
||||||
return nil, ErrNotCached
|
|
||||||
}
|
|
||||||
c.backgroundRefreshDNS(transport, question, c.prepareExchangeMessage(message.Copy(), options), options, responseChecker)
|
|
||||||
logOptimisticResponse(c.logger, ctx, response)
|
|
||||||
}
|
|
||||||
if response.Rcode != dns.RcodeSuccess {
|
if response.Rcode != dns.RcodeSuccess {
|
||||||
return nil, RcodeError(response.Rcode)
|
return nil, RcodeError(response.Rcode)
|
||||||
}
|
}
|
||||||
return MessageToAddresses(response), nil
|
return MessageToAddresses(response), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int, bool) {
|
func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int) {
|
||||||
if c.dnsCache != nil {
|
var (
|
||||||
return c.loadPersistentResponse(question, transport)
|
response *dns.Msg
|
||||||
}
|
loaded bool
|
||||||
if c.cache == nil {
|
)
|
||||||
return nil, 0, false
|
|
||||||
}
|
|
||||||
key := dnsCacheKey{Question: question, transportTag: transport.Tag()}
|
|
||||||
if c.disableExpire {
|
if c.disableExpire {
|
||||||
response, loaded := c.cache.Get(key)
|
if !c.independentCache {
|
||||||
if !loaded {
|
response, loaded = c.cache.Get(question)
|
||||||
return nil, 0, false
|
} else {
|
||||||
}
|
response, loaded = c.transportCache.Get(transportCacheKey{
|
||||||
return response.Copy(), 0, false
|
Question: question,
|
||||||
}
|
transportTag: transport.Tag(),
|
||||||
response, expireAt, loaded := c.cache.GetWithLifetimeNoExpire(key)
|
|
||||||
if !loaded {
|
|
||||||
return nil, 0, false
|
|
||||||
}
|
|
||||||
timeNow := time.Now()
|
|
||||||
if timeNow.After(expireAt) {
|
|
||||||
if c.optimisticTimeout > 0 && timeNow.Before(expireAt.Add(c.optimisticTimeout)) {
|
|
||||||
response = response.Copy()
|
|
||||||
normalizeTTL(response, 1)
|
|
||||||
return response, 0, true
|
|
||||||
}
|
|
||||||
c.cache.Remove(key)
|
|
||||||
return nil, 0, false
|
|
||||||
}
|
|
||||||
nowTTL := int(expireAt.Sub(timeNow).Seconds())
|
|
||||||
if nowTTL < 0 {
|
|
||||||
nowTTL = 0
|
|
||||||
}
|
|
||||||
response = response.Copy()
|
|
||||||
normalizeTTL(response, uint32(nowTTL))
|
|
||||||
return response, nowTTL, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) loadPersistentResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int, bool) {
|
|
||||||
rawMessage, expireAt, loaded := c.dnsCache.LoadDNSCache(transport.Tag(), question.Name, question.Qtype)
|
|
||||||
if !loaded {
|
|
||||||
return nil, 0, false
|
|
||||||
}
|
|
||||||
response := new(dns.Msg)
|
|
||||||
err := response.Unpack(rawMessage)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, false
|
|
||||||
}
|
|
||||||
if c.disableExpire {
|
|
||||||
return response, 0, false
|
|
||||||
}
|
|
||||||
timeNow := time.Now()
|
|
||||||
if timeNow.After(expireAt) {
|
|
||||||
if c.optimisticTimeout > 0 && timeNow.Before(expireAt.Add(c.optimisticTimeout)) {
|
|
||||||
normalizeTTL(response, 1)
|
|
||||||
return response, 0, true
|
|
||||||
}
|
|
||||||
return nil, 0, false
|
|
||||||
}
|
|
||||||
nowTTL := int(expireAt.Sub(timeNow).Seconds())
|
|
||||||
if nowTTL < 0 {
|
|
||||||
nowTTL = 0
|
|
||||||
}
|
|
||||||
normalizeTTL(response, uint32(nowTTL))
|
|
||||||
return response, nowTTL, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyResponseOptions(question dns.Question, response *dns.Msg, options adapter.DNSQueryOptions) uint32 {
|
|
||||||
if question.Qtype == dns.TypeHTTPS && (options.Strategy == C.DomainStrategyIPv4Only || options.Strategy == C.DomainStrategyIPv6Only) {
|
|
||||||
for _, rr := range response.Answer {
|
|
||||||
https, isHTTPS := rr.(*dns.HTTPS)
|
|
||||||
if !isHTTPS {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
content := https.SVCB
|
|
||||||
content.Value = common.Filter(content.Value, func(it dns.SVCBKeyValue) bool {
|
|
||||||
if options.Strategy == C.DomainStrategyIPv4Only {
|
|
||||||
return it.Key() != dns.SVCB_IPV6HINT
|
|
||||||
}
|
|
||||||
return it.Key() != dns.SVCB_IPV4HINT
|
|
||||||
})
|
})
|
||||||
https.SVCB = content
|
|
||||||
}
|
}
|
||||||
}
|
if !loaded {
|
||||||
timeToLive := computeTimeToLive(response)
|
return nil, 0
|
||||||
if options.RewriteTTL != nil {
|
|
||||||
timeToLive = *options.RewriteTTL
|
|
||||||
}
|
|
||||||
normalizeTTL(response, timeToLive)
|
|
||||||
return timeToLive
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) backgroundRefreshDNS(transport adapter.DNSTransport, question dns.Question, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) {
|
|
||||||
key := dnsCacheKey{Question: question, transportTag: transport.Tag()}
|
|
||||||
_, loaded := c.backgroundRefresh.LoadOrStore(key, struct{}{})
|
|
||||||
if loaded {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
defer c.backgroundRefresh.Delete(key)
|
|
||||||
ctx := contextWithTransportTag(c.ctx, transport.Tag())
|
|
||||||
response, err := c.exchangeToTransport(ctx, transport, message)
|
|
||||||
if err != nil {
|
|
||||||
if c.logger != nil {
|
|
||||||
c.logger.Debug("optimistic refresh failed for ", FqdnToDomain(question.Name), ": ", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if responseChecker != nil {
|
return response.Copy(), 0
|
||||||
var rejected bool
|
} else {
|
||||||
if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
|
var expireAt time.Time
|
||||||
rejected = true
|
if !c.independentCache {
|
||||||
|
response, expireAt, loaded = c.cache.GetWithLifetime(question)
|
||||||
|
} else {
|
||||||
|
response, expireAt, loaded = c.transportCache.GetWithLifetime(transportCacheKey{
|
||||||
|
Question: question,
|
||||||
|
transportTag: transport.Tag(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if !loaded {
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
timeNow := time.Now()
|
||||||
|
if timeNow.After(expireAt) {
|
||||||
|
if !c.independentCache {
|
||||||
|
c.cache.Remove(question)
|
||||||
} else {
|
} else {
|
||||||
rejected = !responseChecker(response)
|
c.transportCache.Remove(transportCacheKey{
|
||||||
|
Question: question,
|
||||||
|
transportTag: transport.Tag(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if rejected {
|
return nil, 0
|
||||||
if c.rdrc != nil {
|
|
||||||
c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
timeToLive := applyResponseOptions(question, response, options)
|
var originTTL int
|
||||||
c.storeCache(transport, question, response, timeToLive)
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
}()
|
for _, record := range recordList {
|
||||||
}
|
if originTTL == 0 || record.Header().Ttl > 0 && int(record.Header().Ttl) < originTTL {
|
||||||
|
originTTL = int(record.Header().Ttl)
|
||||||
func (c *Client) prepareExchangeMessage(message *dns.Msg, options adapter.DNSQueryOptions) *dns.Msg {
|
}
|
||||||
clientSubnet := options.ClientSubnet
|
}
|
||||||
if !clientSubnet.IsValid() {
|
}
|
||||||
clientSubnet = c.clientSubnet
|
nowTTL := int(expireAt.Sub(timeNow).Seconds())
|
||||||
|
if nowTTL < 0 {
|
||||||
|
nowTTL = 0
|
||||||
|
}
|
||||||
|
response = response.Copy()
|
||||||
|
if originTTL > 0 {
|
||||||
|
duration := uint32(originTTL - nowTTL)
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
record.Header().Ttl = record.Header().Ttl - duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
record.Header().Ttl = uint32(nowTTL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response, nowTTL
|
||||||
}
|
}
|
||||||
if clientSubnet.IsValid() {
|
|
||||||
message = SetClientSubnet(message, clientSubnet)
|
|
||||||
}
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) exchangeToTransport(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg) (*dns.Msg, error) {
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
|
||||||
defer cancel()
|
|
||||||
response, err := transport.Exchange(ctx, message)
|
|
||||||
if err == nil {
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
var rcodeError RcodeError
|
|
||||||
if errors.As(err, &rcodeError) {
|
|
||||||
return FixedResponseStatus(message, int(rcodeError)), nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
||||||
return adapter.DNSResponseAddresses(response)
|
if response == nil || response.Rcode != dns.RcodeSuccess {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
addresses := make([]netip.Addr, 0, len(response.Answer))
|
||||||
|
for _, rawAnswer := range response.Answer {
|
||||||
|
switch answer := rawAnswer.(type) {
|
||||||
|
case *dns.A:
|
||||||
|
addresses = append(addresses, M.AddrFromIP(answer.A))
|
||||||
|
case *dns.AAAA:
|
||||||
|
addresses = append(addresses, M.AddrFromIP(answer.AAAA))
|
||||||
|
case *dns.HTTPS:
|
||||||
|
for _, value := range answer.SVCB.Value {
|
||||||
|
if value.Key() == dns.SVCB_IPV4HINT || value.Key() == dns.SVCB_IPV6HINT {
|
||||||
|
addresses = append(addresses, common.Map(strings.Split(value.String(), ","), M.ParseAddr)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addresses
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapError(err error) error {
|
func wrapError(err error) error {
|
||||||
|
|||||||
@@ -22,19 +22,6 @@ func logCachedResponse(logger logger.ContextLogger, ctx context.Context, respons
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func logOptimisticResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg) {
|
|
||||||
if logger == nil || len(response.Question) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
domain := FqdnToDomain(response.Question[0].Name)
|
|
||||||
logger.DebugContext(ctx, "optimistic ", domain, " ", dns.RcodeToString[response.Rcode])
|
|
||||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
|
||||||
for _, record := range recordList {
|
|
||||||
logger.InfoContext(ctx, "optimistic ", dns.Type(record.Header().Rrtype).String(), " ", FormatQuestion(record.String()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func logExchangedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg, ttl uint32) {
|
func logExchangedResponse(logger logger.ContextLogger, ctx context.Context, response *dns.Msg, ttl uint32) {
|
||||||
if logger == nil || len(response.Question) == 0 {
|
if logger == nil || len(response.Question) == 0 {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -5,11 +5,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RcodeSuccess RcodeError = mDNS.RcodeSuccess
|
RcodeSuccess RcodeError = mDNS.RcodeSuccess
|
||||||
RcodeServerFailure RcodeError = mDNS.RcodeServerFailure
|
RcodeFormatError RcodeError = mDNS.RcodeFormatError
|
||||||
RcodeFormatError RcodeError = mDNS.RcodeFormatError
|
RcodeNameError RcodeError = mDNS.RcodeNameError
|
||||||
RcodeNameError RcodeError = mDNS.RcodeNameError
|
RcodeRefused RcodeError = mDNS.RcodeRefused
|
||||||
RcodeRefused RcodeError = mDNS.RcodeRefused
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RcodeError int
|
type RcodeError int
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/netip"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/json/badoption"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestReproLookupWithRulesUsesRequestStrategy(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
|
|
||||||
var qTypes []uint16
|
|
||||||
router := newTestRouter(t, nil, &fakeDNSTransportManager{
|
|
||||||
defaultTransport: defaultTransport,
|
|
||||||
transports: map[string]adapter.DNSTransport{
|
|
||||||
"default": defaultTransport,
|
|
||||||
},
|
|
||||||
}, &fakeDNSClient{
|
|
||||||
exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
qTypes = append(qTypes, message.Question[0].Qtype)
|
|
||||||
if message.Question[0].Qtype == mDNS.TypeA {
|
|
||||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2.2.2.2")}, 60), nil
|
|
||||||
}
|
|
||||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2001:db8::1")}, 60), nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{
|
|
||||||
Strategy: C.DomainStrategyIPv4Only,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, []uint16{mDNS.TypeA}, qTypes)
|
|
||||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("2.2.2.2")}, addresses)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReproLogicalMatchResponseIPCIDR(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
transportManager := &fakeDNSTransportManager{
|
|
||||||
defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP},
|
|
||||||
transports: map[string]adapter.DNSTransport{
|
|
||||||
"upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP},
|
|
||||||
"selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP},
|
|
||||||
"default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
client := &fakeDNSClient{
|
|
||||||
exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
switch transport.Tag() {
|
|
||||||
case "upstream":
|
|
||||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil
|
|
||||||
case "selected":
|
|
||||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("8.8.8.8")}, 60), nil
|
|
||||||
default:
|
|
||||||
return nil, E.New("unexpected transport")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
rules := []option.DNSRule{
|
|
||||||
{
|
|
||||||
Type: C.RuleTypeDefault,
|
|
||||||
DefaultOptions: option.DefaultDNSRule{
|
|
||||||
RawDefaultDNSRule: option.RawDefaultDNSRule{
|
|
||||||
Domain: badoption.Listable[string]{"example.com"},
|
|
||||||
},
|
|
||||||
DNSRuleAction: option.DNSRuleAction{
|
|
||||||
Action: C.RuleActionTypeEvaluate,
|
|
||||||
RouteOptions: option.DNSRouteActionOptions{Server: "upstream"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: C.RuleTypeLogical,
|
|
||||||
LogicalOptions: option.LogicalDNSRule{
|
|
||||||
RawLogicalDNSRule: option.RawLogicalDNSRule{
|
|
||||||
Mode: C.LogicalTypeOr,
|
|
||||||
Rules: []option.DNSRule{{
|
|
||||||
Type: C.RuleTypeDefault,
|
|
||||||
DefaultOptions: option.DefaultDNSRule{
|
|
||||||
RawDefaultDNSRule: option.RawDefaultDNSRule{
|
|
||||||
MatchResponse: true,
|
|
||||||
IPCIDR: badoption.Listable[string]{"1.1.1.0/24"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
DNSRuleAction: option.DNSRuleAction{
|
|
||||||
Action: C.RuleActionTypeRoute,
|
|
||||||
RouteOptions: option.DNSRouteActionOptions{Server: "selected"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
router := newTestRouter(t, rules, transportManager, client)
|
|
||||||
|
|
||||||
response, err := router.Exchange(context.Background(), &mDNS.Msg{
|
|
||||||
Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)},
|
|
||||||
}, adapter.DNSQueryOptions{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("8.8.8.8")}, MessageToAddresses(response))
|
|
||||||
}
|
|
||||||
851
dns/router.go
851
dns/router.go
File diff suppressed because it is too large
Load Diff
2547
dns/router_test.go
2547
dns/router_test.go
File diff suppressed because it is too large
Load Diff
@@ -23,25 +23,16 @@ var _ adapter.FakeIPTransport = (*Transport)(nil)
|
|||||||
|
|
||||||
type Transport struct {
|
type Transport struct {
|
||||||
dns.TransportAdapter
|
dns.TransportAdapter
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
store adapter.FakeIPStore
|
store adapter.FakeIPStore
|
||||||
inet4Enabled bool
|
|
||||||
inet6Enabled bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.FakeIPDNSServerOptions) (adapter.DNSTransport, error) {
|
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.FakeIPDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
inet4Range := options.Inet4Range.Build(netip.Prefix{})
|
store := NewStore(ctx, logger, options.Inet4Range.Build(netip.Prefix{}), options.Inet6Range.Build(netip.Prefix{}))
|
||||||
inet6Range := options.Inet6Range.Build(netip.Prefix{})
|
|
||||||
if !inet4Range.IsValid() && !inet6Range.IsValid() {
|
|
||||||
return nil, E.New("at least one of inet4_range or inet6_range must be set")
|
|
||||||
}
|
|
||||||
store := NewStore(ctx, logger, inet4Range, inet6Range)
|
|
||||||
return &Transport{
|
return &Transport{
|
||||||
TransportAdapter: dns.NewTransportAdapter(C.DNSTypeFakeIP, tag, nil),
|
TransportAdapter: dns.NewTransportAdapter(C.DNSTypeFakeIP, tag, nil),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
store: store,
|
store: store,
|
||||||
inet4Enabled: inet4Range.IsValid(),
|
|
||||||
inet6Enabled: inet6Range.IsValid(),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,9 +55,6 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
|||||||
if question.Qtype != mDNS.TypeA && question.Qtype != mDNS.TypeAAAA {
|
if question.Qtype != mDNS.TypeA && question.Qtype != mDNS.TypeAAAA {
|
||||||
return nil, E.New("only IP queries are supported by fakeip")
|
return nil, E.New("only IP queries are supported by fakeip")
|
||||||
}
|
}
|
||||||
if question.Qtype == mDNS.TypeA && !t.inet4Enabled || question.Qtype == mDNS.TypeAAAA && !t.inet6Enabled {
|
|
||||||
return dns.FixedResponseStatus(message, mDNS.RcodeSuccess), nil
|
|
||||||
}
|
|
||||||
address, err := t.store.Create(dns.FqdnToDomain(question.Name), question.Qtype == mDNS.TypeAAAA)
|
address, err := t.store.Create(dns.FqdnToDomain(question.Name), question.Qtype == mDNS.TypeAAAA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ package local
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
@@ -12,6 +14,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -32,8 +35,10 @@ type Transport struct {
|
|||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
hosts *hosts.File
|
hosts *hosts.File
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
|
preferGo bool
|
||||||
fallback bool
|
fallback bool
|
||||||
dhcpTransport dhcpTransport
|
dhcpTransport dhcpTransport
|
||||||
|
resolver net.Resolver
|
||||||
}
|
}
|
||||||
|
|
||||||
type dhcpTransport interface {
|
type dhcpTransport interface {
|
||||||
@@ -47,12 +52,14 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
transportAdapter := dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options)
|
||||||
return &Transport{
|
return &Transport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
TransportAdapter: transportAdapter,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
hosts: hosts.NewFile(hosts.DefaultPath),
|
hosts: hosts.NewFile(hosts.DefaultPath),
|
||||||
dialer: transportDialer,
|
dialer: transportDialer,
|
||||||
|
preferGo: options.PreferGo,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,3 +97,44 @@ func (t *Transport) Reset() {
|
|||||||
t.dhcpTransport.Reset()
|
t.dhcpTransport.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
question := message.Question[0]
|
||||||
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
|
addresses := t.hosts.Lookup(dns.FqdnToDomain(question.Name))
|
||||||
|
if len(addresses) > 0 {
|
||||||
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !t.fallback {
|
||||||
|
return t.exchange(ctx, message, question.Name)
|
||||||
|
}
|
||||||
|
if t.dhcpTransport != nil {
|
||||||
|
dhcpTransports := t.dhcpTransport.Fetch()
|
||||||
|
if len(dhcpTransports) > 0 {
|
||||||
|
return t.dhcpTransport.Exchange0(ctx, message, dhcpTransports)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t.preferGo {
|
||||||
|
// Assuming the user knows what they are doing, we still execute the query which will fail.
|
||||||
|
return t.exchange(ctx, message, question.Name)
|
||||||
|
}
|
||||||
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
|
var network string
|
||||||
|
if question.Qtype == mDNS.TypeA {
|
||||||
|
network = "ip4"
|
||||||
|
} else {
|
||||||
|
network = "ip6"
|
||||||
|
}
|
||||||
|
addresses, err := t.resolver.LookupNetIP(ctx, network, question.Name)
|
||||||
|
if err != nil {
|
||||||
|
var dnsError *net.DNSError
|
||||||
|
if errors.As(err, &dnsError) && dnsError.IsNotFound {
|
||||||
|
return nil, dns.RcodeRefused
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
|
}
|
||||||
|
return nil, E.New("only A and AAAA queries are supported on Apple platforms when using TUN and DHCP unavailable.")
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,249 +0,0 @@
|
|||||||
//go:build darwin
|
|
||||||
|
|
||||||
package local
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <dns.h>
|
|
||||||
#include <resolv.h>
|
|
||||||
|
|
||||||
static void *cgo_dns_open_super() {
|
|
||||||
return (void *)dns_open(NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cgo_dns_close(void *opaque) {
|
|
||||||
if (opaque != NULL) dns_free((dns_handle_t)opaque);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cgo_dns_search(void *opaque, const char *name, int class, int type,
|
|
||||||
unsigned char *answer, int anslen) {
|
|
||||||
dns_handle_t handle = (dns_handle_t)opaque;
|
|
||||||
struct sockaddr_storage from;
|
|
||||||
uint32_t fromlen = sizeof(from);
|
|
||||||
return dns_search(handle, name, class, type, (char *)answer, anslen, (struct sockaddr *)&from, &fromlen);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *cgo_res_init() {
|
|
||||||
res_state state = calloc(1, sizeof(struct __res_state));
|
|
||||||
if (state == NULL) return NULL;
|
|
||||||
if (res_ninit(state) != 0) {
|
|
||||||
free(state);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cgo_res_destroy(void *opaque) {
|
|
||||||
res_state state = (res_state)opaque;
|
|
||||||
res_ndestroy(state);
|
|
||||||
free(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cgo_res_nsearch(void *opaque, const char *dname, int class, int type,
|
|
||||||
unsigned char *answer, int anslen,
|
|
||||||
int timeout_seconds,
|
|
||||||
int *out_h_errno) {
|
|
||||||
res_state state = (res_state)opaque;
|
|
||||||
state->retrans = timeout_seconds;
|
|
||||||
state->retry = 1;
|
|
||||||
int n = res_nsearch(state, dname, class, type, answer, anslen);
|
|
||||||
if (n < 0) {
|
|
||||||
*out_h_errno = state->res_h_errno;
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
boxC "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/dns"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
darwinResolverHostNotFound = 1
|
|
||||||
darwinResolverTryAgain = 2
|
|
||||||
darwinResolverNoRecovery = 3
|
|
||||||
darwinResolverNoData = 4
|
|
||||||
|
|
||||||
darwinResolverMaxPacketSize = 65535
|
|
||||||
)
|
|
||||||
|
|
||||||
var errDarwinNeedLargerBuffer = errors.New("darwin resolver response truncated")
|
|
||||||
|
|
||||||
func darwinLookupSystemDNS(name string, class, qtype, timeoutSeconds int) (*mDNS.Msg, error) {
|
|
||||||
response, err := darwinSearchWithSystemRouting(name, class, qtype)
|
|
||||||
if err == nil {
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
fallbackResponse, fallbackErr := darwinSearchWithResolv(name, class, qtype, timeoutSeconds)
|
|
||||||
if fallbackErr == nil || fallbackResponse != nil {
|
|
||||||
return fallbackResponse, fallbackErr
|
|
||||||
}
|
|
||||||
return nil, E.Errors(
|
|
||||||
E.Cause(err, "dns_search"),
|
|
||||||
E.Cause(fallbackErr, "res_nsearch"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinSearchWithSystemRouting(name string, class, qtype int) (*mDNS.Msg, error) {
|
|
||||||
handle := C.cgo_dns_open_super()
|
|
||||||
if handle == nil {
|
|
||||||
return nil, E.New("dns_open failed")
|
|
||||||
}
|
|
||||||
defer C.cgo_dns_close(handle)
|
|
||||||
|
|
||||||
cName := C.CString(name)
|
|
||||||
defer C.free(unsafe.Pointer(cName))
|
|
||||||
|
|
||||||
bufSize := 1232
|
|
||||||
for {
|
|
||||||
answer := make([]byte, bufSize)
|
|
||||||
n := C.cgo_dns_search(handle, cName, C.int(class), C.int(qtype),
|
|
||||||
(*C.uchar)(unsafe.Pointer(&answer[0])), C.int(len(answer)))
|
|
||||||
if n <= 0 {
|
|
||||||
return nil, E.New("dns_search failed for ", name)
|
|
||||||
}
|
|
||||||
if int(n) > bufSize {
|
|
||||||
bufSize = int(n)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return unpackDarwinResolverMessage(answer[:int(n)], "dns_search")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinSearchWithResolv(name string, class, qtype int, timeoutSeconds int) (*mDNS.Msg, error) {
|
|
||||||
state := C.cgo_res_init()
|
|
||||||
if state == nil {
|
|
||||||
return nil, E.New("res_ninit failed")
|
|
||||||
}
|
|
||||||
defer C.cgo_res_destroy(state)
|
|
||||||
|
|
||||||
cName := C.CString(name)
|
|
||||||
defer C.free(unsafe.Pointer(cName))
|
|
||||||
|
|
||||||
bufSize := 1232
|
|
||||||
for {
|
|
||||||
answer := make([]byte, bufSize)
|
|
||||||
var hErrno C.int
|
|
||||||
n := C.cgo_res_nsearch(state, cName, C.int(class), C.int(qtype),
|
|
||||||
(*C.uchar)(unsafe.Pointer(&answer[0])), C.int(len(answer)),
|
|
||||||
C.int(timeoutSeconds),
|
|
||||||
&hErrno)
|
|
||||||
if n >= 0 {
|
|
||||||
if int(n) > bufSize {
|
|
||||||
bufSize = int(n)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return unpackDarwinResolverMessage(answer[:int(n)], "res_nsearch")
|
|
||||||
}
|
|
||||||
response, err := handleDarwinResolvFailure(name, answer, int(hErrno))
|
|
||||||
if err == nil {
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
if errors.Is(err, errDarwinNeedLargerBuffer) && bufSize < darwinResolverMaxPacketSize {
|
|
||||||
bufSize *= 2
|
|
||||||
if bufSize > darwinResolverMaxPacketSize {
|
|
||||||
bufSize = darwinResolverMaxPacketSize
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func unpackDarwinResolverMessage(packet []byte, source string) (*mDNS.Msg, error) {
|
|
||||||
var response mDNS.Msg
|
|
||||||
err := response.Unpack(packet)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "unpack ", source, " response")
|
|
||||||
}
|
|
||||||
return &response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleDarwinResolvFailure(name string, answer []byte, hErrno int) (*mDNS.Msg, error) {
|
|
||||||
response, err := unpackDarwinResolverMessage(answer, "res_nsearch failure")
|
|
||||||
if err == nil && response.Response {
|
|
||||||
if response.Truncated && len(answer) < darwinResolverMaxPacketSize {
|
|
||||||
return nil, errDarwinNeedLargerBuffer
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
return nil, darwinResolverHErrno(name, hErrno)
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinResolverHErrno(name string, hErrno int) error {
|
|
||||||
switch hErrno {
|
|
||||||
case darwinResolverHostNotFound:
|
|
||||||
return dns.RcodeNameError
|
|
||||||
case darwinResolverTryAgain:
|
|
||||||
return dns.RcodeServerFailure
|
|
||||||
case darwinResolverNoRecovery:
|
|
||||||
return dns.RcodeServerFailure
|
|
||||||
case darwinResolverNoData:
|
|
||||||
return dns.RcodeSuccess
|
|
||||||
default:
|
|
||||||
return E.New("res_nsearch: unknown error ", hErrno, " for ", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
question := message.Question[0]
|
|
||||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
|
||||||
addresses := t.hosts.Lookup(dns.FqdnToDomain(question.Name))
|
|
||||||
if len(addresses) > 0 {
|
|
||||||
return dns.FixedResponse(message.Id, question, addresses, boxC.DefaultDNSTTL), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if t.fallback && t.dhcpTransport != nil {
|
|
||||||
dhcpServers := t.dhcpTransport.Fetch()
|
|
||||||
if len(dhcpServers) > 0 {
|
|
||||||
return t.dhcpTransport.Exchange0(ctx, message, dhcpServers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
name := question.Name
|
|
||||||
timeoutSeconds := int(boxC.DNSTimeout / time.Second)
|
|
||||||
if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
|
|
||||||
remaining := time.Until(deadline)
|
|
||||||
if remaining <= 0 {
|
|
||||||
return nil, context.DeadlineExceeded
|
|
||||||
}
|
|
||||||
seconds := int(remaining.Seconds())
|
|
||||||
if seconds < 1 {
|
|
||||||
seconds = 1
|
|
||||||
}
|
|
||||||
timeoutSeconds = seconds
|
|
||||||
}
|
|
||||||
type resolvResult struct {
|
|
||||||
response *mDNS.Msg
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
resultCh := make(chan resolvResult, 1)
|
|
||||||
go func() {
|
|
||||||
response, err := darwinLookupSystemDNS(name, int(question.Qclass), int(question.Qtype), timeoutSeconds)
|
|
||||||
resultCh <- resolvResult{response, err}
|
|
||||||
}()
|
|
||||||
var result resolvResult
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
case result = <-resultCh:
|
|
||||||
}
|
|
||||||
if result.err != nil {
|
|
||||||
var rcodeError dns.RcodeError
|
|
||||||
if errors.As(result.err, &rcodeError) {
|
|
||||||
return dns.FixedResponseStatus(message, int(rcodeError)), nil
|
|
||||||
}
|
|
||||||
return nil, result.err
|
|
||||||
}
|
|
||||||
result.response.Id = message.Id
|
|
||||||
return result.response, nil
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:build !darwin
|
|
||||||
|
|
||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ adapter.LegacyDNSTransport = (*TransportAdapter)(nil)
|
||||||
|
|
||||||
type TransportAdapter struct {
|
type TransportAdapter struct {
|
||||||
transportType string
|
transportType string
|
||||||
transportTag string
|
transportTag string
|
||||||
dependencies []string
|
dependencies []string
|
||||||
|
strategy C.DomainStrategy
|
||||||
|
clientSubnet netip.Prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTransportAdapter(transportType string, transportTag string, dependencies []string) TransportAdapter {
|
func NewTransportAdapter(transportType string, transportTag string, dependencies []string) TransportAdapter {
|
||||||
@@ -27,6 +35,8 @@ func NewTransportAdapterWithLocalOptions(transportType string, transportTag stri
|
|||||||
transportType: transportType,
|
transportType: transportType,
|
||||||
transportTag: transportTag,
|
transportTag: transportTag,
|
||||||
dependencies: dependencies,
|
dependencies: dependencies,
|
||||||
|
strategy: C.DomainStrategy(localOptions.LegacyStrategy),
|
||||||
|
clientSubnet: localOptions.LegacyClientSubnet,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,10 +45,15 @@ func NewTransportAdapterWithRemoteOptions(transportType string, transportTag str
|
|||||||
if remoteOptions.DomainResolver != nil && remoteOptions.DomainResolver.Server != "" {
|
if remoteOptions.DomainResolver != nil && remoteOptions.DomainResolver.Server != "" {
|
||||||
dependencies = append(dependencies, remoteOptions.DomainResolver.Server)
|
dependencies = append(dependencies, remoteOptions.DomainResolver.Server)
|
||||||
}
|
}
|
||||||
|
if remoteOptions.LegacyAddressResolver != "" {
|
||||||
|
dependencies = append(dependencies, remoteOptions.LegacyAddressResolver)
|
||||||
|
}
|
||||||
return TransportAdapter{
|
return TransportAdapter{
|
||||||
transportType: transportType,
|
transportType: transportType,
|
||||||
transportTag: transportTag,
|
transportTag: transportTag,
|
||||||
dependencies: dependencies,
|
dependencies: dependencies,
|
||||||
|
strategy: C.DomainStrategy(remoteOptions.LegacyStrategy),
|
||||||
|
clientSubnet: remoteOptions.LegacyClientSubnet,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,3 +68,11 @@ func (a *TransportAdapter) Tag() string {
|
|||||||
func (a *TransportAdapter) Dependencies() []string {
|
func (a *TransportAdapter) Dependencies() []string {
|
||||||
return a.dependencies
|
return a.dependencies
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *TransportAdapter) LegacyStrategy() C.DomainStrategy {
|
||||||
|
return a.strategy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TransportAdapter) LegacyClientSubnet() netip.Prefix {
|
||||||
|
return a.clientSubnet
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,25 +2,104 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
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/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (N.Dialer, error) {
|
func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (N.Dialer, error) {
|
||||||
return dialer.NewWithOptions(dialer.Options{
|
if options.LegacyDefaultDialer {
|
||||||
Context: ctx,
|
return dialer.NewDefaultOutbound(ctx), nil
|
||||||
Options: options.DialerOptions,
|
} else {
|
||||||
DirectResolver: true,
|
return dialer.NewWithOptions(dialer.Options{
|
||||||
})
|
Context: ctx,
|
||||||
|
Options: options.DialerOptions,
|
||||||
|
DirectResolver: true,
|
||||||
|
LegacyDNSDialer: options.Legacy,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) {
|
func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) {
|
||||||
return dialer.NewWithOptions(dialer.Options{
|
if options.LegacyDefaultDialer {
|
||||||
Context: ctx,
|
transportDialer := dialer.NewDefaultOutbound(ctx)
|
||||||
Options: options.DialerOptions,
|
if options.LegacyAddressResolver != "" {
|
||||||
RemoteIsDomain: options.ServerIsDomain(),
|
transport := service.FromContext[adapter.DNSTransportManager](ctx)
|
||||||
DirectResolver: true,
|
resolverTransport, loaded := transport.Transport(options.LegacyAddressResolver)
|
||||||
})
|
if !loaded {
|
||||||
|
return nil, E.New("address resolver not found: ", options.LegacyAddressResolver)
|
||||||
|
}
|
||||||
|
transportDialer = newTransportDialer(transportDialer, service.FromContext[adapter.DNSRouter](ctx), resolverTransport, C.DomainStrategy(options.LegacyAddressStrategy), time.Duration(options.LegacyAddressFallbackDelay))
|
||||||
|
} else if options.ServerIsDomain() {
|
||||||
|
return nil, E.New("missing address resolver for server: ", options.Server)
|
||||||
|
}
|
||||||
|
return transportDialer, nil
|
||||||
|
} else {
|
||||||
|
return dialer.NewWithOptions(dialer.Options{
|
||||||
|
Context: ctx,
|
||||||
|
Options: options.DialerOptions,
|
||||||
|
RemoteIsDomain: options.ServerIsDomain(),
|
||||||
|
DirectResolver: true,
|
||||||
|
LegacyDNSDialer: options.Legacy,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type legacyTransportDialer struct {
|
||||||
|
dialer N.Dialer
|
||||||
|
dnsRouter adapter.DNSRouter
|
||||||
|
transport adapter.DNSTransport
|
||||||
|
strategy C.DomainStrategy
|
||||||
|
fallbackDelay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *legacyTransportDialer {
|
||||||
|
return &legacyTransportDialer{
|
||||||
|
dialer,
|
||||||
|
dnsRouter,
|
||||||
|
transport,
|
||||||
|
strategy,
|
||||||
|
fallbackDelay,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *legacyTransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
if destination.IsIP() {
|
||||||
|
return d.dialer.DialContext(ctx, network, destination)
|
||||||
|
}
|
||||||
|
addresses, err := d.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{
|
||||||
|
Transport: d.transport,
|
||||||
|
Strategy: d.strategy,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *legacyTransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
if destination.IsIP() {
|
||||||
|
return d.dialer.ListenPacket(ctx, destination)
|
||||||
|
}
|
||||||
|
addresses, err := d.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{
|
||||||
|
Transport: d.transport,
|
||||||
|
Strategy: d.strategy,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn, _, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *legacyTransportDialer) Upstream() any {
|
||||||
|
return d.dialer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,116 +2,6 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.14.0-alpha.12
|
|
||||||
|
|
||||||
* Fix fake-ip DNS server should return SUCCESS when another address type is not configured
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.13.8
|
|
||||||
|
|
||||||
* Update naiveproxy to v147.0.7727.49-1
|
|
||||||
* Fix fake-ip DNS server should return SUCCESS when another address type is not configured
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.14.0-alpha.11
|
|
||||||
|
|
||||||
* Add optimistic DNS cache **1**
|
|
||||||
* Update NaiveProxy to 147.0.7727.49
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
Optimistic DNS cache returns an expired cached response immediately while
|
|
||||||
refreshing it in the background, reducing tail latency for repeated
|
|
||||||
queries. Enabled via [`optimistic`](/configuration/dns/#optimistic)
|
|
||||||
in DNS options, and can be persisted across restarts with the new
|
|
||||||
[`store_dns`](/configuration/experimental/cache-file/#store_dns) cache
|
|
||||||
file option. A per-query
|
|
||||||
[`disable_optimistic_cache`](/configuration/dns/rule_action/#disable_optimistic_cache)
|
|
||||||
field is also available on DNS rule actions and the `resolve` route rule
|
|
||||||
action.
|
|
||||||
|
|
||||||
This deprecates the `independent_cache` DNS option (the DNS cache now
|
|
||||||
always keys by transport) and the `store_rdrc` cache file option
|
|
||||||
(replaced by `store_dns`); both will be removed in sing-box 1.16.0.
|
|
||||||
See [Migration](/migration/#migrate-independent-dns-cache).
|
|
||||||
|
|
||||||
#### 1.14.0-alpha.10
|
|
||||||
|
|
||||||
* Add `evaluate` DNS rule action and Response Match Fields **1**
|
|
||||||
* `ip_version` and `query_type` now also take effect on internal DNS lookups **2**
|
|
||||||
* Add `package_name_regex` route, DNS and headless rule item **3**
|
|
||||||
* Add cloudflared inbound **4**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
Response Match Fields
|
|
||||||
([`response_rcode`](/configuration/dns/rule/#response_rcode),
|
|
||||||
[`response_answer`](/configuration/dns/rule/#response_answer),
|
|
||||||
[`response_ns`](/configuration/dns/rule/#response_ns),
|
|
||||||
and [`response_extra`](/configuration/dns/rule/#response_extra))
|
|
||||||
match the evaluated DNS response. They are gated by the new
|
|
||||||
[`match_response`](/configuration/dns/rule/#match_response) field and
|
|
||||||
populated by a preceding
|
|
||||||
[`evaluate`](/configuration/dns/rule_action/#evaluate) DNS rule action;
|
|
||||||
the evaluated response can also be returned directly by a
|
|
||||||
[`respond`](/configuration/dns/rule_action/#respond) action.
|
|
||||||
|
|
||||||
This deprecates the Legacy Address Filter Fields (`ip_cidr`,
|
|
||||||
`ip_is_private` without `match_response`) in DNS rules, the Legacy
|
|
||||||
`strategy` DNS rule action option, and the Legacy
|
|
||||||
`rule_set_ip_cidr_accept_empty` DNS rule item; all three will be removed
|
|
||||||
in sing-box 1.16.0.
|
|
||||||
See [Migration](/migration/#migrate-address-filter-fields-to-response-matching).
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
`ip_version` and `query_type` in DNS rules, together with `query_type` in
|
|
||||||
referenced rule-sets, now take effect on every DNS rule evaluation,
|
|
||||||
including matches from internal domain resolutions that do not target a
|
|
||||||
specific DNS server (for example a `resolve` route rule action without
|
|
||||||
`server` set). In earlier versions they were silently ignored in that
|
|
||||||
path. Combining these fields with any of the legacy DNS fields deprecated
|
|
||||||
in **1** in the same DNS configuration is no longer supported and is
|
|
||||||
rejected at startup.
|
|
||||||
See [Migration](/migration/#ip_version-and-query_type-behavior-changes-in-dns-rules).
|
|
||||||
|
|
||||||
**3**:
|
|
||||||
|
|
||||||
See [Route Rule](/configuration/route/rule/#package_name_regex),
|
|
||||||
[DNS Rule](/configuration/dns/rule/#package_name_regex) and
|
|
||||||
[Headless Rule](/configuration/rule-set/headless-rule/#package_name_regex).
|
|
||||||
|
|
||||||
**4**:
|
|
||||||
|
|
||||||
See [Cloudflared](/configuration/inbound/cloudflared/).
|
|
||||||
|
|
||||||
#### 1.13.7
|
|
||||||
|
|
||||||
* Fixes and improvement
|
|
||||||
|
|
||||||
#### 1.13.6
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.14.0-alpha.8
|
|
||||||
|
|
||||||
* Add BBR profile and hop interval randomization for Hysteria2 **1**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
See [Hysteria2 Inbound](/configuration/inbound/hysteria2/#bbr_profile) and [Hysteria2 Outbound](/configuration/outbound/hysteria2/#bbr_profile).
|
|
||||||
|
|
||||||
#### 1.14.0-alpha.8
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.13.5
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.14.0-alpha.7
|
#### 1.14.0-alpha.7
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
@@ -837,7 +727,7 @@ DNS servers are refactored for better performance and scalability.
|
|||||||
|
|
||||||
See [DNS server](/configuration/dns/server/).
|
See [DNS server](/configuration/dns/server/).
|
||||||
|
|
||||||
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-server-formats).
|
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-servers).
|
||||||
|
|
||||||
Compatibility for old formats will be removed in sing-box 1.14.0.
|
Compatibility for old formats will be removed in sing-box 1.14.0.
|
||||||
|
|
||||||
@@ -1307,7 +1197,7 @@ DNS servers are refactored for better performance and scalability.
|
|||||||
|
|
||||||
See [DNS server](/configuration/dns/server/).
|
See [DNS server](/configuration/dns/server/).
|
||||||
|
|
||||||
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-server-formats).
|
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-servers).
|
||||||
|
|
||||||
Compatibility for old formats will be removed in sing-box 1.14.0.
|
Compatibility for old formats will be removed in sing-box 1.14.0.
|
||||||
|
|
||||||
@@ -2143,7 +2033,7 @@ See [Migration](/migration/#process_path-format-update-on-windows).
|
|||||||
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS
|
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS
|
||||||
if using this method.
|
if using this method.
|
||||||
|
|
||||||
See [Legacy Address Filter Fields](/configuration/dns/rule#legacy-address-filter-fields).
|
See [Address Filter Fields](/configuration/dns/rule#address-filter-fields).
|
||||||
|
|
||||||
[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.
|
[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.
|
||||||
|
|
||||||
@@ -2157,7 +2047,7 @@ the [Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users
|
|||||||
**5**:
|
**5**:
|
||||||
|
|
||||||
The new feature allows you to cache the check results of
|
The new feature allows you to cache the check results of
|
||||||
[Legacy Address Filter Fields](/configuration/dns/rule/#legacy-address-filter-fields) until expiration.
|
[Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields) until expiration.
|
||||||
|
|
||||||
**6**:
|
**6**:
|
||||||
|
|
||||||
@@ -2338,7 +2228,7 @@ See [TUN](/configuration/inbound/tun) inbound.
|
|||||||
**1**:
|
**1**:
|
||||||
|
|
||||||
The new feature allows you to cache the check results of
|
The new feature allows you to cache the check results of
|
||||||
[Legacy Address Filter Fields](/configuration/dns/rule/#legacy-address-filter-fields) until expiration.
|
[Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields) until expiration.
|
||||||
|
|
||||||
#### 1.9.0-alpha.7
|
#### 1.9.0-alpha.7
|
||||||
|
|
||||||
@@ -2385,7 +2275,7 @@ See [Migration](/migration/#process_path-format-update-on-windows).
|
|||||||
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS
|
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS
|
||||||
if using this method.
|
if using this method.
|
||||||
|
|
||||||
See [Legacy Address Filter Fields](/configuration/dns/rule#legacy-address-filter-fields).
|
See [Address Filter Fields](/configuration/dns/rule#address-filter-fields).
|
||||||
|
|
||||||
[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.
|
[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ SFA provides an unprivileged TUN implementation through Android VpnService.
|
|||||||
| `process_path` | :material-close: | No permission |
|
| `process_path` | :material-close: | No permission |
|
||||||
| `process_path_regex` | :material-close: | No permission |
|
| `process_path_regex` | :material-close: | No permission |
|
||||||
| `package_name` | :material-check: | / |
|
| `package_name` | :material-check: | / |
|
||||||
| `package_name_regex` | :material-check: | / |
|
|
||||||
| `user` | :material-close: | Use `package_name` instead |
|
| `user` | :material-close: | Use `package_name` instead |
|
||||||
| `user_id` | :material-close: | Use `package_name` instead |
|
| `user_id` | :material-close: | Use `package_name` instead |
|
||||||
| `wifi_ssid` | :material-check: | Fine location permission required |
|
| `wifi_ssid` | :material-check: | Fine location permission required |
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ SFI/SFM/SFT provides an unprivileged TUN implementation through NetworkExtension
|
|||||||
| `process_path` | :material-close: | No permission |
|
| `process_path` | :material-close: | No permission |
|
||||||
| `process_path_regex` | :material-close: | No permission |
|
| `process_path_regex` | :material-close: | No permission |
|
||||||
| `package_name` | :material-close: | / |
|
| `package_name` | :material-close: | / |
|
||||||
| `package_name_regex` | :material-close: | / |
|
|
||||||
| `user` | :material-close: | No permission |
|
| `user` | :material-close: | No permission |
|
||||||
| `user_id` | :material-close: | No permission |
|
| `user_id` | :material-close: | No permission |
|
||||||
| `wifi_ssid` | :material-alert: | Only supported on iOS |
|
| `wifi_ssid` | :material-alert: | Only supported on iOS |
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
icon: material/note-remove
|
icon: material/delete-clock
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! failure "Removed in sing-box 1.14.0"
|
!!! failure "Deprecated in sing-box 1.12.0"
|
||||||
|
|
||||||
Legacy fake-ip configuration is deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-server-formats).
|
Legacy fake-ip configuration is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
@@ -26,6 +26,6 @@ Enable FakeIP service.
|
|||||||
|
|
||||||
IPv4 address range for FakeIP.
|
IPv4 address range for FakeIP.
|
||||||
|
|
||||||
#### inet6_range
|
#### inet6_address
|
||||||
|
|
||||||
IPv6 address range for FakeIP.
|
IPv6 address range for FakeIP.
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
icon: material/note-remove
|
icon: material/delete-clock
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.14.0 移除"
|
!!! failure "已在 sing-box 1.12.0 废弃"
|
||||||
|
|
||||||
旧的 fake-ip 配置已在 sing-box 1.12.0 废弃且已在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
旧的 fake-ip 配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-delete-clock: [independent_cache](#independent_cache)
|
|
||||||
:material-plus: [optimistic](#optimistic)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-decagram: [servers](#servers)
|
:material-decagram: [servers](#servers)
|
||||||
@@ -30,7 +25,6 @@ icon: material/alert-decagram
|
|||||||
"disable_expire": false,
|
"disable_expire": false,
|
||||||
"independent_cache": false,
|
"independent_cache": false,
|
||||||
"cache_capacity": 0,
|
"cache_capacity": 0,
|
||||||
"optimistic": false, // or {}
|
|
||||||
"reverse_mapping": false,
|
"reverse_mapping": false,
|
||||||
"client_subnet": "",
|
"client_subnet": "",
|
||||||
"fakeip": {}
|
"fakeip": {}
|
||||||
@@ -45,7 +39,7 @@ icon: material/alert-decagram
|
|||||||
|----------|---------------------------------|
|
|----------|---------------------------------|
|
||||||
| `server` | List of [DNS Server](./server/) |
|
| `server` | List of [DNS Server](./server/) |
|
||||||
| `rules` | List of [DNS Rule](./rule/) |
|
| `rules` | List of [DNS Rule](./rule/) |
|
||||||
| `fakeip` | :material-note-remove: [FakeIP](./fakeip/) |
|
| `fakeip` | [FakeIP](./fakeip/) |
|
||||||
|
|
||||||
#### final
|
#### final
|
||||||
|
|
||||||
@@ -63,20 +57,12 @@ One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|||||||
|
|
||||||
Disable dns cache.
|
Disable dns cache.
|
||||||
|
|
||||||
Conflict with `optimistic`.
|
|
||||||
|
|
||||||
#### disable_expire
|
#### disable_expire
|
||||||
|
|
||||||
Disable dns cache expire.
|
Disable dns cache expire.
|
||||||
|
|
||||||
Conflict with `optimistic`.
|
|
||||||
|
|
||||||
#### independent_cache
|
#### independent_cache
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.14.0"
|
|
||||||
|
|
||||||
`independent_cache` is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-independent-dns-cache).
|
|
||||||
|
|
||||||
Make each DNS server's cache independent for special purposes. If enabled, will slightly degrade performance.
|
Make each DNS server's cache independent for special purposes. If enabled, will slightly degrade performance.
|
||||||
|
|
||||||
#### cache_capacity
|
#### cache_capacity
|
||||||
@@ -87,34 +73,6 @@ LRU cache capacity.
|
|||||||
|
|
||||||
Value less than 1024 will be ignored.
|
Value less than 1024 will be ignored.
|
||||||
|
|
||||||
#### optimistic
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
Enable optimistic DNS caching. When a cached DNS entry has expired but is still within the timeout window,
|
|
||||||
the stale response is returned immediately while a background refresh is triggered.
|
|
||||||
|
|
||||||
Conflict with `disable_cache` and `disable_expire`.
|
|
||||||
|
|
||||||
Accepts a boolean or an object. When set to `true`, the default timeout of `3d` is used.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"enabled": true,
|
|
||||||
"timeout": "3d"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
##### enabled
|
|
||||||
|
|
||||||
Enable optimistic DNS caching.
|
|
||||||
|
|
||||||
##### timeout
|
|
||||||
|
|
||||||
The maximum time an expired cache entry can be served optimistically.
|
|
||||||
|
|
||||||
`3d` is used by default.
|
|
||||||
|
|
||||||
#### reverse_mapping
|
#### reverse_mapping
|
||||||
|
|
||||||
Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.
|
Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.
|
||||||
@@ -130,4 +88,4 @@ Append a `edns0-subnet` OPT extra record with the specified IP prefix to every q
|
|||||||
|
|
||||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||||
|
|
||||||
Can be overridden by `servers.[].client_subnet` or `rules.[].client_subnet`.
|
Can be overrides by `servers.[].client_subnet` or `rules.[].client_subnet`.
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-delete-clock: [independent_cache](#independent_cache)
|
|
||||||
:material-plus: [optimistic](#optimistic)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-decagram: [servers](#servers)
|
:material-decagram: [servers](#servers)
|
||||||
@@ -30,7 +25,6 @@ icon: material/alert-decagram
|
|||||||
"disable_expire": false,
|
"disable_expire": false,
|
||||||
"independent_cache": false,
|
"independent_cache": false,
|
||||||
"cache_capacity": 0,
|
"cache_capacity": 0,
|
||||||
"optimistic": false, // or {}
|
|
||||||
"reverse_mapping": false,
|
"reverse_mapping": false,
|
||||||
"client_subnet": "",
|
"client_subnet": "",
|
||||||
"fakeip": {}
|
"fakeip": {}
|
||||||
@@ -62,20 +56,12 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
禁用 DNS 缓存。
|
禁用 DNS 缓存。
|
||||||
|
|
||||||
与 `optimistic` 冲突。
|
|
||||||
|
|
||||||
#### disable_expire
|
#### disable_expire
|
||||||
|
|
||||||
禁用 DNS 缓存过期。
|
禁用 DNS 缓存过期。
|
||||||
|
|
||||||
与 `optimistic` 冲突。
|
|
||||||
|
|
||||||
#### independent_cache
|
#### independent_cache
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.14.0 废弃"
|
|
||||||
|
|
||||||
`independent_cache` 已在 sing-box 1.14.0 废弃,且将在 sing-box 1.16.0 中被移除,参阅[迁移指南](/zh/migration/#迁移-independent-dns-cache)。
|
|
||||||
|
|
||||||
使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。
|
使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。
|
||||||
|
|
||||||
#### cache_capacity
|
#### cache_capacity
|
||||||
@@ -86,34 +72,6 @@ LRU 缓存容量。
|
|||||||
|
|
||||||
小于 1024 的值将被忽略。
|
小于 1024 的值将被忽略。
|
||||||
|
|
||||||
#### optimistic
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
启用乐观 DNS 缓存。当缓存的 DNS 条目已过期但仍在超时窗口内时,
|
|
||||||
立即返回过期的响应,同时在后台触发刷新。
|
|
||||||
|
|
||||||
与 `disable_cache` 和 `disable_expire` 冲突。
|
|
||||||
|
|
||||||
接受布尔值或对象。当设置为 `true` 时,使用默认超时 `3d`。
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"enabled": true,
|
|
||||||
"timeout": "3d"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
##### enabled
|
|
||||||
|
|
||||||
启用乐观 DNS 缓存。
|
|
||||||
|
|
||||||
##### timeout
|
|
||||||
|
|
||||||
过期缓存条目可被乐观提供的最长时间。
|
|
||||||
|
|
||||||
默认使用 `3d`。
|
|
||||||
|
|
||||||
#### reverse_mapping
|
#### reverse_mapping
|
||||||
|
|
||||||
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
|
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
|
||||||
@@ -130,6 +88,6 @@ LRU 缓存容量。
|
|||||||
|
|
||||||
可以被 `servers.[].client_subnet` 或 `rules.[].client_subnet` 覆盖。
|
可以被 `servers.[].client_subnet` 或 `rules.[].client_subnet` 覆盖。
|
||||||
|
|
||||||
#### fakeip :material-note-remove:
|
#### fakeip
|
||||||
|
|
||||||
[FakeIP](./fakeip/) 设置。
|
[FakeIP](./fakeip/) 设置。
|
||||||
|
|||||||
@@ -5,16 +5,7 @@ icon: material/alert-decagram
|
|||||||
!!! quote "Changes in sing-box 1.14.0"
|
!!! quote "Changes in sing-box 1.14.0"
|
||||||
|
|
||||||
:material-plus: [source_mac_address](#source_mac_address)
|
:material-plus: [source_mac_address](#source_mac_address)
|
||||||
:material-plus: [source_hostname](#source_hostname)
|
:material-plus: [source_hostname](#source_hostname)
|
||||||
:material-plus: [match_response](#match_response)
|
|
||||||
:material-delete-clock: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
|
||||||
:material-plus: [response_rcode](#response_rcode)
|
|
||||||
:material-plus: [response_answer](#response_answer)
|
|
||||||
:material-plus: [response_ns](#response_ns)
|
|
||||||
:material-plus: [response_extra](#response_extra)
|
|
||||||
:material-plus: [package_name_regex](#package_name_regex)
|
|
||||||
:material-alert: [ip_version](#ip_version)
|
|
||||||
:material-alert: [query_type](#query_type)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
@@ -103,6 +94,12 @@ icon: material/alert-decagram
|
|||||||
"192.168.0.1"
|
"192.168.0.1"
|
||||||
],
|
],
|
||||||
"source_ip_is_private": false,
|
"source_ip_is_private": false,
|
||||||
|
"ip_cidr": [
|
||||||
|
"10.0.0.0/24",
|
||||||
|
"192.168.0.1"
|
||||||
|
],
|
||||||
|
"ip_is_private": false,
|
||||||
|
"ip_accept_any": false,
|
||||||
"source_port": [
|
"source_port": [
|
||||||
12345
|
12345
|
||||||
],
|
],
|
||||||
@@ -132,9 +129,6 @@ icon: material/alert-decagram
|
|||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
"package_name_regex": [
|
|
||||||
"^com\\.termux.*"
|
|
||||||
],
|
|
||||||
"user": [
|
"user": [
|
||||||
"sekai"
|
"sekai"
|
||||||
],
|
],
|
||||||
@@ -177,17 +171,7 @@ icon: material/alert-decagram
|
|||||||
"geosite-cn"
|
"geosite-cn"
|
||||||
],
|
],
|
||||||
"rule_set_ip_cidr_match_source": false,
|
"rule_set_ip_cidr_match_source": false,
|
||||||
"match_response": false,
|
"rule_set_ip_cidr_accept_empty": false,
|
||||||
"ip_cidr": [
|
|
||||||
"10.0.0.0/24",
|
|
||||||
"192.168.0.1"
|
|
||||||
],
|
|
||||||
"ip_is_private": false,
|
|
||||||
"ip_accept_any": false,
|
|
||||||
"response_rcode": "",
|
|
||||||
"response_answer": [],
|
|
||||||
"response_ns": [],
|
|
||||||
"response_extra": [],
|
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"outbound": [
|
"outbound": [
|
||||||
"direct"
|
"direct"
|
||||||
@@ -196,8 +180,7 @@ icon: material/alert-decagram
|
|||||||
"server": "local",
|
"server": "local",
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
|
|
||||||
"rule_set_ip_cidr_accept_empty": false,
|
|
||||||
"rule_set_ipcidr_match_source": false,
|
"rule_set_ipcidr_match_source": false,
|
||||||
"geosite": [
|
"geosite": [
|
||||||
"cn"
|
"cn"
|
||||||
@@ -245,46 +228,12 @@ Tags of [Inbound](/configuration/inbound/).
|
|||||||
|
|
||||||
#### ip_version
|
#### ip_version
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
This field now also applies when a DNS rule is matched from an internal
|
|
||||||
domain resolution that does not target a specific DNS server, such as a
|
|
||||||
[`resolve`](../../route/rule_action/#resolve) route rule action without a
|
|
||||||
`server` set. In earlier versions, only DNS queries received from a
|
|
||||||
client evaluated this field. See
|
|
||||||
[Migration](/migration/#ip_version-and-query_type-behavior-changes-in-dns-rules)
|
|
||||||
for the full list.
|
|
||||||
|
|
||||||
Setting this field makes the DNS rule incompatible in the same DNS
|
|
||||||
configuration with Legacy Address Filter Fields in DNS rules, the Legacy
|
|
||||||
`strategy` DNS rule action option, and the Legacy
|
|
||||||
`rule_set_ip_cidr_accept_empty` DNS rule item. To combine with
|
|
||||||
address-based filtering, use the [`evaluate`](../rule_action/#evaluate)
|
|
||||||
action and [`match_response`](#match_response).
|
|
||||||
|
|
||||||
4 (A DNS query) or 6 (AAAA DNS query).
|
4 (A DNS query) or 6 (AAAA DNS query).
|
||||||
|
|
||||||
Not limited if empty.
|
Not limited if empty.
|
||||||
|
|
||||||
#### query_type
|
#### query_type
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
This field now also applies when a DNS rule is matched from an internal
|
|
||||||
domain resolution that does not target a specific DNS server, such as a
|
|
||||||
[`resolve`](../../route/rule_action/#resolve) route rule action without a
|
|
||||||
`server` set. In earlier versions, only DNS queries received from a
|
|
||||||
client evaluated this field. See
|
|
||||||
[Migration](/migration/#ip_version-and-query_type-behavior-changes-in-dns-rules)
|
|
||||||
for the full list.
|
|
||||||
|
|
||||||
Setting this field makes the DNS rule incompatible in the same DNS
|
|
||||||
configuration with Legacy Address Filter Fields in DNS rules, the Legacy
|
|
||||||
`strategy` DNS rule action option, and the Legacy
|
|
||||||
`rule_set_ip_cidr_accept_empty` DNS rule item. To combine with
|
|
||||||
address-based filtering, use the [`evaluate`](../rule_action/#evaluate)
|
|
||||||
action and [`match_response`](#match_response).
|
|
||||||
|
|
||||||
DNS query type. Values can be integers or type name strings.
|
DNS query type. Values can be integers or type name strings.
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
@@ -387,12 +336,6 @@ Match process path using regular expression.
|
|||||||
|
|
||||||
Match android package name.
|
Match android package name.
|
||||||
|
|
||||||
#### package_name_regex
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
Match android package name using regular expression.
|
|
||||||
|
|
||||||
#### user
|
#### user
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@@ -534,25 +477,6 @@ Make `ip_cidr` rule items in rule-sets match the source IP.
|
|||||||
|
|
||||||
Make `ip_cidr` rule items in rule-sets match the source IP.
|
Make `ip_cidr` rule items in rule-sets match the source IP.
|
||||||
|
|
||||||
#### match_response
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
Enable response-based matching. When enabled, this rule matches against the evaluated response
|
|
||||||
(set by a preceding [`evaluate`](/configuration/dns/rule_action/#evaluate) action)
|
|
||||||
instead of only matching the original query.
|
|
||||||
|
|
||||||
The evaluated response can also be returned directly by a later [`respond`](/configuration/dns/rule_action/#respond) action.
|
|
||||||
|
|
||||||
Required for Response Match Fields (`response_rcode`, `response_answer`, `response_ns`, `response_extra`).
|
|
||||||
Also required for `ip_cidr`, `ip_is_private`, and `ip_accept_any` when used with `evaluate` or Response Match Fields.
|
|
||||||
|
|
||||||
#### ip_accept_any
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
Match when the DNS query response contains at least one address.
|
|
||||||
|
|
||||||
#### invert
|
#### invert
|
||||||
|
|
||||||
Invert match result.
|
Invert match result.
|
||||||
@@ -597,12 +521,7 @@ See [DNS Rule Actions](../rule_action/) for details.
|
|||||||
|
|
||||||
Moved to [DNS Rule Action](../rule_action#route).
|
Moved to [DNS Rule Action](../rule_action#route).
|
||||||
|
|
||||||
### Legacy Address Filter Fields
|
### Address Filter Fields
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.14.0"
|
|
||||||
|
|
||||||
Legacy Address Filter Fields are deprecated and will be removed in sing-box 1.16.0,
|
|
||||||
check [Migration](/migration/#migrate-address-filter-fields-to-response-matching).
|
|
||||||
|
|
||||||
Only takes effect for address requests (A/AAAA/HTTPS). When the query results do not match the address filtering rule items, the current rule will be skipped.
|
Only takes effect for address requests (A/AAAA/HTTPS). When the query results do not match the address filtering rule items, the current rule will be skipped.
|
||||||
|
|
||||||
@@ -628,61 +547,23 @@ Match GeoIP with query response.
|
|||||||
|
|
||||||
Match IP CIDR with query response.
|
Match IP CIDR with query response.
|
||||||
|
|
||||||
As a Legacy Address Filter Field, deprecated. Use with `match_response` instead,
|
|
||||||
check [Migration](/migration/#migrate-address-filter-fields-to-response-matching).
|
|
||||||
|
|
||||||
#### ip_is_private
|
#### ip_is_private
|
||||||
|
|
||||||
!!! question "Since sing-box 1.9.0"
|
!!! question "Since sing-box 1.9.0"
|
||||||
|
|
||||||
Match private IP with query response.
|
Match private IP with query response.
|
||||||
|
|
||||||
As a Legacy Address Filter Field, deprecated. Use with `match_response` instead,
|
|
||||||
check [Migration](/migration/#migrate-address-filter-fields-to-response-matching).
|
|
||||||
|
|
||||||
#### rule_set_ip_cidr_accept_empty
|
#### rule_set_ip_cidr_accept_empty
|
||||||
|
|
||||||
!!! question "Since sing-box 1.10.0"
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.14.0"
|
|
||||||
|
|
||||||
`rule_set_ip_cidr_accept_empty` is deprecated and will be removed in sing-box 1.16.0,
|
|
||||||
check [Migration](/migration/#migrate-address-filter-fields-to-response-matching).
|
|
||||||
|
|
||||||
Make `ip_cidr` rules in rule-sets accept empty query response.
|
Make `ip_cidr` rules in rule-sets accept empty query response.
|
||||||
|
|
||||||
### Response Match Fields
|
#### ip_accept_any
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
!!! question "Since sing-box 1.12.0"
|
||||||
|
|
||||||
Match fields for the evaluated response. Require `match_response` to be set to `true`
|
Match any IP with query response.
|
||||||
and a preceding rule with [`evaluate`](/configuration/dns/rule_action/#evaluate) action to populate the response.
|
|
||||||
|
|
||||||
That evaluated response may also be returned directly by a later [`respond`](/configuration/dns/rule_action/#respond) action.
|
|
||||||
|
|
||||||
#### response_rcode
|
|
||||||
|
|
||||||
Match DNS response code.
|
|
||||||
|
|
||||||
Accepted values are the same as in the [predefined action rcode](/configuration/dns/rule_action/#rcode).
|
|
||||||
|
|
||||||
#### response_answer
|
|
||||||
|
|
||||||
Match DNS answer records.
|
|
||||||
|
|
||||||
Record format is the same as in [predefined action answer](/configuration/dns/rule_action/#answer).
|
|
||||||
|
|
||||||
#### response_ns
|
|
||||||
|
|
||||||
Match DNS name server records.
|
|
||||||
|
|
||||||
Record format is the same as in [predefined action ns](/configuration/dns/rule_action/#ns).
|
|
||||||
|
|
||||||
#### response_extra
|
|
||||||
|
|
||||||
Match DNS extra records.
|
|
||||||
|
|
||||||
Record format is the same as in [predefined action extra](/configuration/dns/rule_action/#extra).
|
|
||||||
|
|
||||||
### Logical Fields
|
### Logical Fields
|
||||||
|
|
||||||
|
|||||||
@@ -5,16 +5,7 @@ icon: material/alert-decagram
|
|||||||
!!! quote "sing-box 1.14.0 中的更改"
|
!!! quote "sing-box 1.14.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [source_mac_address](#source_mac_address)
|
:material-plus: [source_mac_address](#source_mac_address)
|
||||||
:material-plus: [source_hostname](#source_hostname)
|
:material-plus: [source_hostname](#source_hostname)
|
||||||
:material-plus: [match_response](#match_response)
|
|
||||||
:material-delete-clock: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
|
||||||
:material-plus: [response_rcode](#response_rcode)
|
|
||||||
:material-plus: [response_answer](#response_answer)
|
|
||||||
:material-plus: [response_ns](#response_ns)
|
|
||||||
:material-plus: [response_extra](#response_extra)
|
|
||||||
:material-plus: [package_name_regex](#package_name_regex)
|
|
||||||
:material-alert: [ip_version](#ip_version)
|
|
||||||
:material-alert: [query_type](#query_type)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
@@ -103,6 +94,12 @@ icon: material/alert-decagram
|
|||||||
"192.168.0.1"
|
"192.168.0.1"
|
||||||
],
|
],
|
||||||
"source_ip_is_private": false,
|
"source_ip_is_private": false,
|
||||||
|
"ip_cidr": [
|
||||||
|
"10.0.0.0/24",
|
||||||
|
"192.168.0.1"
|
||||||
|
],
|
||||||
|
"ip_is_private": false,
|
||||||
|
"ip_accept_any": false,
|
||||||
"source_port": [
|
"source_port": [
|
||||||
12345
|
12345
|
||||||
],
|
],
|
||||||
@@ -132,9 +129,6 @@ icon: material/alert-decagram
|
|||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
"package_name_regex": [
|
|
||||||
"^com\\.termux.*"
|
|
||||||
],
|
|
||||||
"user": [
|
"user": [
|
||||||
"sekai"
|
"sekai"
|
||||||
],
|
],
|
||||||
@@ -177,17 +171,7 @@ icon: material/alert-decagram
|
|||||||
"geosite-cn"
|
"geosite-cn"
|
||||||
],
|
],
|
||||||
"rule_set_ip_cidr_match_source": false,
|
"rule_set_ip_cidr_match_source": false,
|
||||||
"match_response": false,
|
"rule_set_ip_cidr_accept_empty": false,
|
||||||
"ip_cidr": [
|
|
||||||
"10.0.0.0/24",
|
|
||||||
"192.168.0.1"
|
|
||||||
],
|
|
||||||
"ip_is_private": false,
|
|
||||||
"ip_accept_any": false,
|
|
||||||
"response_rcode": "",
|
|
||||||
"response_answer": [],
|
|
||||||
"response_ns": [],
|
|
||||||
"response_extra": [],
|
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"outbound": [
|
"outbound": [
|
||||||
"direct"
|
"direct"
|
||||||
@@ -196,8 +180,6 @@ icon: material/alert-decagram
|
|||||||
"server": "local",
|
"server": "local",
|
||||||
|
|
||||||
// 已弃用
|
// 已弃用
|
||||||
|
|
||||||
"rule_set_ip_cidr_accept_empty": false,
|
|
||||||
"rule_set_ipcidr_match_source": false,
|
"rule_set_ipcidr_match_source": false,
|
||||||
"geosite": [
|
"geosite": [
|
||||||
"cn"
|
"cn"
|
||||||
@@ -245,38 +227,12 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
#### ip_version
|
#### ip_version
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
此字段现在也会在 DNS 规则被未指定具体 DNS 服务器的内部域名解析匹配时生效,
|
|
||||||
例如未设置 `server` 的 [`resolve`](../../route/rule_action/#resolve) 路由规则动作。
|
|
||||||
此前只有来自客户端的 DNS 查询才会评估此字段。完整列表参阅
|
|
||||||
[迁移指南](/zh/migration/#dns-规则中的-ip_version-和-query_type-行为更改)。
|
|
||||||
|
|
||||||
在 DNS 规则中设置此字段后,该 DNS 规则在同一 DNS 配置中不能与
|
|
||||||
旧版地址筛选字段 (DNS 规则)、旧版 DNS 规则动作 `strategy` 选项,
|
|
||||||
或旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项共存。如需与
|
|
||||||
基于地址的筛选组合,请使用 [`evaluate`](../rule_action/#evaluate) 动作和
|
|
||||||
[`match_response`](#match_response)。
|
|
||||||
|
|
||||||
4 (A DNS 查询) 或 6 (AAAA DNS 查询)。
|
4 (A DNS 查询) 或 6 (AAAA DNS 查询)。
|
||||||
|
|
||||||
默认不限制。
|
默认不限制。
|
||||||
|
|
||||||
#### query_type
|
#### query_type
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
此字段现在也会在 DNS 规则被未指定具体 DNS 服务器的内部域名解析匹配时生效,
|
|
||||||
例如未设置 `server` 的 [`resolve`](../../route/rule_action/#resolve) 路由规则动作。
|
|
||||||
此前只有来自客户端的 DNS 查询才会评估此字段。完整列表参阅
|
|
||||||
[迁移指南](/zh/migration/#dns-规则中的-ip_version-和-query_type-行为更改)。
|
|
||||||
|
|
||||||
在 DNS 规则中设置此字段后,该 DNS 规则在同一 DNS 配置中不能与
|
|
||||||
旧版地址筛选字段 (DNS 规则)、旧版 DNS 规则动作 `strategy` 选项,
|
|
||||||
或旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项共存。如需与
|
|
||||||
基于地址的筛选组合,请使用 [`evaluate`](../rule_action/#evaluate) 动作和
|
|
||||||
[`match_response`](#match_response)。
|
|
||||||
|
|
||||||
DNS 查询类型。值可以为整数或者类型名称字符串。
|
DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
@@ -379,12 +335,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
匹配 Android 应用包名。
|
匹配 Android 应用包名。
|
||||||
|
|
||||||
#### package_name_regex
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
使用正则表达式匹配 Android 应用包名。
|
|
||||||
|
|
||||||
#### user
|
#### user
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@@ -526,23 +476,6 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
使规则集中的 `ip_cidr` 规则匹配源 IP。
|
使规则集中的 `ip_cidr` 规则匹配源 IP。
|
||||||
|
|
||||||
#### match_response
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
启用响应匹配。启用后,此规则将匹配已评估的响应(由前序 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作设置),而不仅是匹配原始查询。
|
|
||||||
|
|
||||||
该已评估的响应也可以被后续的 [`respond`](/zh/configuration/dns/rule_action/#respond) 动作直接返回。
|
|
||||||
|
|
||||||
响应匹配字段(`response_rcode`、`response_answer`、`response_ns`、`response_extra`)需要此选项。
|
|
||||||
当与 `evaluate` 或响应匹配字段一起使用时,`ip_cidr`、`ip_is_private` 和 `ip_accept_any` 也需要此选项。
|
|
||||||
|
|
||||||
#### ip_accept_any
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
|
||||||
|
|
||||||
当 DNS 查询响应包含至少一个地址时匹配。
|
|
||||||
|
|
||||||
#### invert
|
#### invert
|
||||||
|
|
||||||
反选匹配结果。
|
反选匹配结果。
|
||||||
@@ -587,12 +520,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
已移动到 [DNS 规则动作](../rule_action#route).
|
已移动到 [DNS 规则动作](../rule_action#route).
|
||||||
|
|
||||||
### 旧版地址筛选字段
|
### 地址筛选字段
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.14.0 废弃"
|
|
||||||
|
|
||||||
旧版地址筛选字段已废弃,且将在 sing-box 1.16.0 中被移除,
|
|
||||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
|
||||||
|
|
||||||
仅对地址请求 (A/AAAA/HTTPS) 生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。
|
仅对地址请求 (A/AAAA/HTTPS) 生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。
|
||||||
|
|
||||||
@@ -619,62 +547,24 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
与查询响应匹配 IP CIDR。
|
与查询响应匹配 IP CIDR。
|
||||||
|
|
||||||
作为旧版地址筛选字段已废弃。请改为配合 `match_response` 使用,
|
|
||||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
|
||||||
|
|
||||||
#### ip_is_private
|
#### ip_is_private
|
||||||
|
|
||||||
!!! question "自 sing-box 1.9.0 起"
|
!!! question "自 sing-box 1.9.0 起"
|
||||||
|
|
||||||
与查询响应匹配非公开 IP。
|
与查询响应匹配非公开 IP。
|
||||||
|
|
||||||
作为旧版地址筛选字段已废弃。请改为配合 `match_response` 使用,
|
#### ip_accept_any
|
||||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
|
||||||
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|
||||||
|
匹配任意 IP。
|
||||||
|
|
||||||
#### rule_set_ip_cidr_accept_empty
|
#### rule_set_ip_cidr_accept_empty
|
||||||
|
|
||||||
!!! question "自 sing-box 1.10.0 起"
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.14.0 废弃"
|
|
||||||
|
|
||||||
`rule_set_ip_cidr_accept_empty` 已废弃且将在 sing-box 1.16.0 中被移除,
|
|
||||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
|
||||||
|
|
||||||
使规则集中的 `ip_cidr` 规则接受空查询响应。
|
使规则集中的 `ip_cidr` 规则接受空查询响应。
|
||||||
|
|
||||||
### 响应匹配字段
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
已评估的响应的匹配字段。需要将 `match_response` 设为 `true`,
|
|
||||||
且需要前序规则使用 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作来填充响应。
|
|
||||||
|
|
||||||
该已评估的响应也可以被后续的 [`respond`](/zh/configuration/dns/rule_action/#respond) 动作直接返回。
|
|
||||||
|
|
||||||
#### response_rcode
|
|
||||||
|
|
||||||
匹配 DNS 响应码。
|
|
||||||
|
|
||||||
接受的值与 [predefined 动作 rcode](/zh/configuration/dns/rule_action/#rcode) 中相同。
|
|
||||||
|
|
||||||
#### response_answer
|
|
||||||
|
|
||||||
匹配 DNS 应答记录。
|
|
||||||
|
|
||||||
记录格式与 [predefined 动作 answer](/zh/configuration/dns/rule_action/#answer) 中相同。
|
|
||||||
|
|
||||||
#### response_ns
|
|
||||||
|
|
||||||
匹配 DNS 名称服务器记录。
|
|
||||||
|
|
||||||
记录格式与 [predefined 动作 ns](/zh/configuration/dns/rule_action/#ns) 中相同。
|
|
||||||
|
|
||||||
#### response_extra
|
|
||||||
|
|
||||||
匹配 DNS 额外记录。
|
|
||||||
|
|
||||||
记录格式与 [predefined 动作 extra](/zh/configuration/dns/rule_action/#extra) 中相同。
|
|
||||||
|
|
||||||
### 逻辑字段
|
### 逻辑字段
|
||||||
|
|
||||||
#### type
|
#### type
|
||||||
|
|||||||
@@ -2,13 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-delete-clock: [strategy](#strategy)
|
|
||||||
:material-plus: [evaluate](#evaluate)
|
|
||||||
:material-plus: [respond](#respond)
|
|
||||||
:material-plus: [disable_optimistic_cache](#disable_optimistic_cache)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [strategy](#strategy)
|
:material-plus: [strategy](#strategy)
|
||||||
@@ -24,7 +17,6 @@ icon: material/new-box
|
|||||||
"server": "",
|
"server": "",
|
||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"disable_optimistic_cache": false,
|
|
||||||
"rewrite_ttl": null,
|
"rewrite_ttl": null,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
}
|
}
|
||||||
@@ -42,10 +34,6 @@ Tag of target server.
|
|||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
!!! question "Since sing-box 1.12.0"
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.14.0"
|
|
||||||
|
|
||||||
`strategy` is deprecated in sing-box 1.14.0 and will be removed in sing-box 1.16.0.
|
|
||||||
|
|
||||||
Set domain strategy for this query.
|
Set domain strategy for this query.
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||||
@@ -54,12 +42,6 @@ One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|||||||
|
|
||||||
Disable cache and save cache in this query.
|
Disable cache and save cache in this query.
|
||||||
|
|
||||||
#### disable_optimistic_cache
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
Disable optimistic DNS caching in this query.
|
|
||||||
|
|
||||||
#### rewrite_ttl
|
#### rewrite_ttl
|
||||||
|
|
||||||
Rewrite TTL in DNS responses.
|
Rewrite TTL in DNS responses.
|
||||||
@@ -70,75 +52,7 @@ Append a `edns0-subnet` OPT extra record with the specified IP prefix to every q
|
|||||||
|
|
||||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||||
|
|
||||||
Will override `dns.client_subnet`.
|
Will overrides `dns.client_subnet`.
|
||||||
|
|
||||||
### evaluate
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "evaluate",
|
|
||||||
"server": "",
|
|
||||||
"disable_cache": false,
|
|
||||||
"disable_optimistic_cache": false,
|
|
||||||
"rewrite_ttl": null,
|
|
||||||
"client_subnet": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`evaluate` sends a DNS query to the specified server and saves the evaluated response for subsequent rules
|
|
||||||
to match against using [`match_response`](/configuration/dns/rule/#match_response) and response fields.
|
|
||||||
Unlike `route`, it does **not** terminate rule evaluation.
|
|
||||||
|
|
||||||
Only allowed on top-level DNS rules (not inside logical sub-rules).
|
|
||||||
Rules that use [`match_response`](/configuration/dns/rule/#match_response) or Response Match Fields
|
|
||||||
require a preceding top-level rule with `evaluate` action. A rule's own `evaluate` action
|
|
||||||
does not satisfy this requirement, because matching happens before the action runs.
|
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Tag of target server.
|
|
||||||
|
|
||||||
#### disable_cache
|
|
||||||
|
|
||||||
Disable cache and save cache in this query.
|
|
||||||
|
|
||||||
#### disable_optimistic_cache
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
Disable optimistic DNS caching in this query.
|
|
||||||
|
|
||||||
#### rewrite_ttl
|
|
||||||
|
|
||||||
Rewrite TTL in DNS responses.
|
|
||||||
|
|
||||||
#### client_subnet
|
|
||||||
|
|
||||||
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
|
|
||||||
|
|
||||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
|
||||||
|
|
||||||
Will override `dns.client_subnet`.
|
|
||||||
|
|
||||||
### respond
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "respond"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`respond` terminates rule evaluation and returns the evaluated response from a preceding [`evaluate`](/configuration/dns/rule_action/#evaluate) action.
|
|
||||||
|
|
||||||
This action does not send a new DNS query and has no extra options.
|
|
||||||
|
|
||||||
Only allowed after a preceding top-level `evaluate` rule. If the action is reached without an evaluated response at runtime, the request fails with an error instead of falling through to later rules.
|
|
||||||
|
|
||||||
### route-options
|
### route-options
|
||||||
|
|
||||||
@@ -146,7 +60,6 @@ Only allowed after a preceding top-level `evaluate` rule. If the action is reach
|
|||||||
{
|
{
|
||||||
"action": "route-options",
|
"action": "route-options",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"disable_optimistic_cache": false,
|
|
||||||
"rewrite_ttl": null,
|
"rewrite_ttl": null,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-delete-clock: [strategy](#strategy)
|
|
||||||
:material-plus: [evaluate](#evaluate)
|
|
||||||
:material-plus: [respond](#respond)
|
|
||||||
:material-plus: [disable_optimistic_cache](#disable_optimistic_cache)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [strategy](#strategy)
|
:material-plus: [strategy](#strategy)
|
||||||
@@ -24,7 +17,6 @@ icon: material/new-box
|
|||||||
"server": "",
|
"server": "",
|
||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"disable_optimistic_cache": false,
|
|
||||||
"rewrite_ttl": null,
|
"rewrite_ttl": null,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
}
|
}
|
||||||
@@ -42,10 +34,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.14.0 废弃"
|
|
||||||
|
|
||||||
`strategy` 已在 sing-box 1.14.0 废弃,且将在 sing-box 1.16.0 中被移除。
|
|
||||||
|
|
||||||
为此查询设置域名策略。
|
为此查询设置域名策略。
|
||||||
|
|
||||||
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||||
@@ -54,12 +42,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
在此查询中禁用缓存。
|
在此查询中禁用缓存。
|
||||||
|
|
||||||
#### disable_optimistic_cache
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
在此查询中禁用乐观 DNS 缓存。
|
|
||||||
|
|
||||||
#### rewrite_ttl
|
#### rewrite_ttl
|
||||||
|
|
||||||
重写 DNS 回应中的 TTL。
|
重写 DNS 回应中的 TTL。
|
||||||
@@ -72,79 +54,12 @@ icon: material/new-box
|
|||||||
|
|
||||||
将覆盖 `dns.client_subnet`.
|
将覆盖 `dns.client_subnet`.
|
||||||
|
|
||||||
### evaluate
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "evaluate",
|
|
||||||
"server": "",
|
|
||||||
"disable_cache": false,
|
|
||||||
"disable_optimistic_cache": false,
|
|
||||||
"rewrite_ttl": null,
|
|
||||||
"client_subnet": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`evaluate` 向指定服务器发送 DNS 查询并保存已评估的响应,供后续规则通过 [`match_response`](/zh/configuration/dns/rule/#match_response) 和响应字段进行匹配。与 `route` 不同,它**不会**终止规则评估。
|
|
||||||
|
|
||||||
仅允许在顶层 DNS 规则中使用(不可在逻辑子规则内部使用)。
|
|
||||||
使用 [`match_response`](/zh/configuration/dns/rule/#match_response) 或响应匹配字段的规则,
|
|
||||||
需要位于更早的顶层 `evaluate` 规则之后。规则自身的 `evaluate` 动作不能满足这个条件,
|
|
||||||
因为匹配发生在动作执行之前。
|
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
目标 DNS 服务器的标签。
|
|
||||||
|
|
||||||
#### disable_cache
|
|
||||||
|
|
||||||
在此查询中禁用缓存。
|
|
||||||
|
|
||||||
#### disable_optimistic_cache
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
在此查询中禁用乐观 DNS 缓存。
|
|
||||||
|
|
||||||
#### rewrite_ttl
|
|
||||||
|
|
||||||
重写 DNS 回应中的 TTL。
|
|
||||||
|
|
||||||
#### client_subnet
|
|
||||||
|
|
||||||
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
|
||||||
|
|
||||||
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
|
|
||||||
|
|
||||||
将覆盖 `dns.client_subnet`.
|
|
||||||
|
|
||||||
### respond
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "respond"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`respond` 会终止规则评估,并直接返回前序 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作保存的已评估的响应。
|
|
||||||
|
|
||||||
此动作不会发起新的 DNS 查询,也没有额外选项。
|
|
||||||
|
|
||||||
只能用于前面已有顶层 `evaluate` 规则的场景。如果运行时命中该动作时没有已评估的响应,则请求会直接返回错误,而不是继续匹配后续规则。
|
|
||||||
|
|
||||||
### route-options
|
### route-options
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"action": "route-options",
|
"action": "route-options",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"disable_optimistic_cache": false,
|
|
||||||
"rewrite_ttl": null,
|
"rewrite_ttl": null,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
}
|
}
|
||||||
@@ -169,7 +84,7 @@ icon: material/new-box
|
|||||||
- `default`: 返回 REFUSED。
|
- `default`: 返回 REFUSED。
|
||||||
- `drop`: 丢弃请求。
|
- `drop`: 丢弃请求。
|
||||||
|
|
||||||
默认使用 `default`。
|
默认使用 `defualt`。
|
||||||
|
|
||||||
#### no_drop
|
#### no_drop
|
||||||
|
|
||||||
|
|||||||
@@ -73,55 +73,24 @@ Example:
|
|||||||
|
|
||||||
=== "Use hosts if available"
|
=== "Use hosts if available"
|
||||||
|
|
||||||
=== ":material-card-multiple: sing-box 1.14.0"
|
```json
|
||||||
|
{
|
||||||
```json
|
"dns": {
|
||||||
{
|
"servers": [
|
||||||
"dns": {
|
{
|
||||||
"servers": [
|
...
|
||||||
{
|
},
|
||||||
...
|
{
|
||||||
},
|
"type": "hosts",
|
||||||
{
|
"tag": "hosts"
|
||||||
"type": "hosts",
|
|
||||||
"tag": "hosts"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "evaluate",
|
|
||||||
"server": "hosts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match_response": true,
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"action": "respond"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
],
|
||||||
```
|
"rules": [
|
||||||
|
{
|
||||||
=== ":material-card-remove: sing-box < 1.14.0"
|
"ip_accept_any": true,
|
||||||
|
"server": "hosts"
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
...
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "hosts",
|
|
||||||
"tag": "hosts"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"server": "hosts"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
```
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -73,55 +73,24 @@ hosts 文件路径列表。
|
|||||||
|
|
||||||
=== "如果可用则使用 hosts"
|
=== "如果可用则使用 hosts"
|
||||||
|
|
||||||
=== ":material-card-multiple: sing-box 1.14.0"
|
```json
|
||||||
|
{
|
||||||
```json
|
"dns": {
|
||||||
{
|
"servers": [
|
||||||
"dns": {
|
{
|
||||||
"servers": [
|
...
|
||||||
{
|
},
|
||||||
...
|
{
|
||||||
},
|
"type": "hosts",
|
||||||
{
|
"tag": "hosts"
|
||||||
"type": "hosts",
|
|
||||||
"tag": "hosts"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "evaluate",
|
|
||||||
"server": "hosts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match_response": true,
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"action": "respond"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
],
|
||||||
```
|
"rules": [
|
||||||
|
{
|
||||||
=== ":material-card-remove: sing-box < 1.14.0"
|
"ip_accept_any": true,
|
||||||
|
"server": "hosts"
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
...
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "hosts",
|
|
||||||
"tag": "hosts"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"server": "hosts"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
```
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -29,7 +29,7 @@ The type of the DNS server.
|
|||||||
|
|
||||||
| Type | Format |
|
| Type | Format |
|
||||||
|-----------------|---------------------------|
|
|-----------------|---------------------------|
|
||||||
| empty (default) | :material-note-remove: [Legacy](./legacy/) |
|
| empty (default) | [Legacy](./legacy/) |
|
||||||
| `local` | [Local](./local/) |
|
| `local` | [Local](./local/) |
|
||||||
| `hosts` | [Hosts](./hosts/) |
|
| `hosts` | [Hosts](./hosts/) |
|
||||||
| `tcp` | [TCP](./tcp/) |
|
| `tcp` | [TCP](./tcp/) |
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ DNS 服务器的类型。
|
|||||||
|
|
||||||
| 类型 | 格式 |
|
| 类型 | 格式 |
|
||||||
|-----------------|---------------------------|
|
|-----------------|---------------------------|
|
||||||
| empty (default) | :material-note-remove: [Legacy](./legacy/) |
|
| empty (default) | [Legacy](./legacy/) |
|
||||||
| `local` | [Local](./local/) |
|
| `local` | [Local](./local/) |
|
||||||
| `hosts` | [Hosts](./hosts/) |
|
| `hosts` | [Hosts](./hosts/) |
|
||||||
| `tcp` | [TCP](./tcp/) |
|
| `tcp` | [TCP](./tcp/) |
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
icon: material/note-remove
|
icon: material/delete-clock
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! failure "Removed in sing-box 1.14.0"
|
!!! failure "Deprecated in sing-box 1.12.0"
|
||||||
|
|
||||||
Legacy DNS servers are deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-server-formats).
|
Legacy DNS servers is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.9.0"
|
!!! quote "Changes in sing-box 1.9.0"
|
||||||
|
|
||||||
@@ -108,6 +108,6 @@ Append a `edns0-subnet` OPT extra record with the specified IP prefix to every q
|
|||||||
|
|
||||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||||
|
|
||||||
Can be overridden by `rules.[].client_subnet`.
|
Can be overrides by `rules.[].client_subnet`.
|
||||||
|
|
||||||
Will override `dns.client_subnet`.
|
Will overrides `dns.client_subnet`.
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
icon: material/note-remove
|
icon: material/delete-clock
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.14.0 移除"
|
!!! failure "Deprecated in sing-box 1.12.0"
|
||||||
|
|
||||||
旧的 DNS 服务器配置已在 sing-box 1.12.0 废弃且已在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
旧的 DNS 服务器配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
||||||
|
|
||||||
!!! quote "sing-box 1.9.0 中的更改"
|
!!! quote "sing-box 1.9.0 中的更改"
|
||||||
|
|
||||||
|
|||||||
@@ -43,62 +43,29 @@ If not enabled, `NXDOMAIN` will be returned for requests that do not match searc
|
|||||||
|
|
||||||
=== "Split DNS only"
|
=== "Split DNS only"
|
||||||
|
|
||||||
=== ":material-card-multiple: sing-box 1.14.0"
|
```json
|
||||||
|
{
|
||||||
```json
|
"dns": {
|
||||||
{
|
"servers": [
|
||||||
"dns": {
|
{
|
||||||
"servers": [
|
"type": "local",
|
||||||
{
|
"tag": "local"
|
||||||
"type": "local",
|
},
|
||||||
"tag": "local"
|
{
|
||||||
},
|
"type": "resolved",
|
||||||
{
|
"tag": "resolved",
|
||||||
"type": "resolved",
|
"service": "resolved"
|
||||||
"tag": "resolved",
|
|
||||||
"service": "resolved"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "evaluate",
|
|
||||||
"server": "resolved"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match_response": true,
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"action": "respond"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
],
|
||||||
```
|
"rules": [
|
||||||
|
{
|
||||||
=== ":material-card-remove: sing-box < 1.14.0"
|
"ip_accept_any": true,
|
||||||
|
"server": "resolved"
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"type": "local",
|
|
||||||
"tag": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "resolved",
|
|
||||||
"tag": "resolved",
|
|
||||||
"service": "resolved"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"server": "resolved"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
```
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
=== "Use as global DNS"
|
=== "Use as global DNS"
|
||||||
|
|
||||||
|
|||||||
@@ -42,62 +42,29 @@ icon: material/new-box
|
|||||||
|
|
||||||
=== "仅分割 DNS"
|
=== "仅分割 DNS"
|
||||||
|
|
||||||
=== ":material-card-multiple: sing-box 1.14.0"
|
```json
|
||||||
|
{
|
||||||
```json
|
"dns": {
|
||||||
{
|
"servers": [
|
||||||
"dns": {
|
{
|
||||||
"servers": [
|
"type": "local",
|
||||||
{
|
"tag": "local"
|
||||||
"type": "local",
|
},
|
||||||
"tag": "local"
|
{
|
||||||
},
|
"type": "resolved",
|
||||||
{
|
"tag": "resolved",
|
||||||
"type": "resolved",
|
"service": "resolved"
|
||||||
"tag": "resolved",
|
|
||||||
"service": "resolved"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "evaluate",
|
|
||||||
"server": "resolved"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match_response": true,
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"action": "respond"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
],
|
||||||
```
|
"rules": [
|
||||||
|
{
|
||||||
=== ":material-card-remove: sing-box < 1.14.0"
|
"ip_accept_any": true,
|
||||||
|
"server": "resolved"
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"type": "local",
|
|
||||||
"tag": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "resolved",
|
|
||||||
"tag": "resolved",
|
|
||||||
"service": "resolved"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"server": "resolved"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
```
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
=== "用作全局 DNS"
|
=== "用作全局 DNS"
|
||||||
|
|
||||||
|
|||||||
@@ -42,62 +42,29 @@ if not enabled, `NXDOMAIN` will be returned for non-Tailscale domain queries.
|
|||||||
|
|
||||||
=== "MagicDNS only"
|
=== "MagicDNS only"
|
||||||
|
|
||||||
=== ":material-card-multiple: sing-box 1.14.0"
|
```json
|
||||||
|
{
|
||||||
```json
|
"dns": {
|
||||||
{
|
"servers": [
|
||||||
"dns": {
|
{
|
||||||
"servers": [
|
"type": "local",
|
||||||
{
|
"tag": "local"
|
||||||
"type": "local",
|
},
|
||||||
"tag": "local"
|
{
|
||||||
},
|
"type": "tailscale",
|
||||||
{
|
"tag": "ts",
|
||||||
"type": "tailscale",
|
"endpoint": "ts-ep"
|
||||||
"tag": "ts",
|
|
||||||
"endpoint": "ts-ep"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "evaluate",
|
|
||||||
"server": "ts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match_response": true,
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"action": "respond"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
],
|
||||||
```
|
"rules": [
|
||||||
|
{
|
||||||
=== ":material-card-remove: sing-box < 1.14.0"
|
"ip_accept_any": true,
|
||||||
|
"server": "ts"
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"type": "local",
|
|
||||||
"tag": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "tailscale",
|
|
||||||
"tag": "ts",
|
|
||||||
"endpoint": "ts-ep"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"server": "ts"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
```
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
=== "Use as global DNS"
|
=== "Use as global DNS"
|
||||||
|
|
||||||
|
|||||||
@@ -42,62 +42,29 @@ icon: material/new-box
|
|||||||
|
|
||||||
=== "仅 MagicDNS"
|
=== "仅 MagicDNS"
|
||||||
|
|
||||||
=== ":material-card-multiple: sing-box 1.14.0"
|
```json
|
||||||
|
{
|
||||||
```json
|
"dns": {
|
||||||
{
|
"servers": [
|
||||||
"dns": {
|
{
|
||||||
"servers": [
|
"type": "local",
|
||||||
{
|
"tag": "local"
|
||||||
"type": "local",
|
},
|
||||||
"tag": "local"
|
{
|
||||||
},
|
"type": "tailscale",
|
||||||
{
|
"tag": "ts",
|
||||||
"type": "tailscale",
|
"endpoint": "ts-ep"
|
||||||
"tag": "ts",
|
|
||||||
"endpoint": "ts-ep"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "evaluate",
|
|
||||||
"server": "ts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match_response": true,
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"action": "respond"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
],
|
||||||
```
|
"rules": [
|
||||||
|
{
|
||||||
=== ":material-card-remove: sing-box < 1.14.0"
|
"ip_accept_any": true,
|
||||||
|
"server": "ts"
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"type": "local",
|
|
||||||
"tag": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "tailscale",
|
|
||||||
"tag": "ts",
|
|
||||||
"endpoint": "ts-ep"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"server": "ts"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
```
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
=== "用作全局 DNS"
|
=== "用作全局 DNS"
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
!!! question "Since sing-box 1.8.0"
|
!!! question "Since sing-box 1.8.0"
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-delete-clock: [store_rdrc](#store_rdrc)
|
|
||||||
:material-plus: [store_dns](#store_dns)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.9.0"
|
!!! quote "Changes in sing-box 1.9.0"
|
||||||
|
|
||||||
:material-plus: [store_rdrc](#store_rdrc)
|
:material-plus: [store_rdrc](#store_rdrc)
|
||||||
@@ -19,8 +14,7 @@
|
|||||||
"cache_id": "",
|
"cache_id": "",
|
||||||
"store_fakeip": false,
|
"store_fakeip": false,
|
||||||
"store_rdrc": false,
|
"store_rdrc": false,
|
||||||
"rdrc_timeout": "",
|
"rdrc_timeout": ""
|
||||||
"store_dns": false
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -48,13 +42,9 @@ Store fakeip in the cache file
|
|||||||
|
|
||||||
#### store_rdrc
|
#### store_rdrc
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.14.0"
|
|
||||||
|
|
||||||
`store_rdrc` is deprecated and will be removed in sing-box 1.16.0, check [Migration](/migration/#migrate-store-rdrc).
|
|
||||||
|
|
||||||
Store rejected DNS response cache in the cache file
|
Store rejected DNS response cache in the cache file
|
||||||
|
|
||||||
The check results of [Legacy Address Filter Fields](/configuration/dns/rule/#legacy-address-filter-fields)
|
The check results of [Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields)
|
||||||
will be cached until expiration.
|
will be cached until expiration.
|
||||||
|
|
||||||
#### rdrc_timeout
|
#### rdrc_timeout
|
||||||
@@ -62,9 +52,3 @@ will be cached until expiration.
|
|||||||
Timeout of rejected DNS response cache.
|
Timeout of rejected DNS response cache.
|
||||||
|
|
||||||
`7d` is used by default.
|
`7d` is used by default.
|
||||||
|
|
||||||
#### store_dns
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
Store DNS cache in the cache file.
|
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
!!! question "自 sing-box 1.8.0 起"
|
!!! question "自 sing-box 1.8.0 起"
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-delete-clock: [store_rdrc](#store_rdrc)
|
|
||||||
:material-plus: [store_dns](#store_dns)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.9.0 中的更改"
|
!!! quote "sing-box 1.9.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [store_rdrc](#store_rdrc)
|
:material-plus: [store_rdrc](#store_rdrc)
|
||||||
@@ -19,8 +14,7 @@
|
|||||||
"cache_id": "",
|
"cache_id": "",
|
||||||
"store_fakeip": false,
|
"store_fakeip": false,
|
||||||
"store_rdrc": false,
|
"store_rdrc": false,
|
||||||
"rdrc_timeout": "",
|
"rdrc_timeout": ""
|
||||||
"store_dns": false
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -46,22 +40,12 @@
|
|||||||
|
|
||||||
#### store_rdrc
|
#### store_rdrc
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.14.0 废弃"
|
|
||||||
|
|
||||||
`store_rdrc` 已在 sing-box 1.14.0 废弃,且将在 sing-box 1.16.0 中被移除,参阅[迁移指南](/zh/migration/#迁移-store_rdrc)。
|
|
||||||
|
|
||||||
将拒绝的 DNS 响应缓存存储在缓存文件中。
|
将拒绝的 DNS 响应缓存存储在缓存文件中。
|
||||||
|
|
||||||
[旧版地址筛选字段](/zh/configuration/dns/rule/#旧版地址筛选字段) 的检查结果将被缓存至过期。
|
[地址筛选 DNS 规则项](/zh/configuration/dns/rule/#地址筛选字段) 的检查结果将被缓存至过期。
|
||||||
|
|
||||||
#### rdrc_timeout
|
#### rdrc_timeout
|
||||||
|
|
||||||
拒绝的 DNS 响应缓存超时。
|
拒绝的 DNS 响应缓存超时。
|
||||||
|
|
||||||
默认使用 `7d`。
|
默认使用 `7d`。
|
||||||
|
|
||||||
#### store_dns
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
将 DNS 缓存存储在缓存文件中。
|
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
`cloudflared` inbound runs an embedded Cloudflare Tunnel client and routes all
|
|
||||||
incoming tunnel traffic (TCP, UDP, ICMP) through sing-box's routing engine.
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "cloudflared",
|
|
||||||
"tag": "",
|
|
||||||
|
|
||||||
"token": "",
|
|
||||||
"ha_connections": 0,
|
|
||||||
"protocol": "",
|
|
||||||
"post_quantum": false,
|
|
||||||
"edge_ip_version": 0,
|
|
||||||
"datagram_version": "",
|
|
||||||
"grace_period": "",
|
|
||||||
"region": "",
|
|
||||||
"control_dialer": {
|
|
||||||
... // Dial Fields
|
|
||||||
},
|
|
||||||
"tunnel_dialer": {
|
|
||||||
... // Dial Fields
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### token
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Base64-encoded tunnel token from the Cloudflare Zero Trust dashboard
|
|
||||||
(`Networks → Tunnels → Install connector`).
|
|
||||||
|
|
||||||
#### ha_connections
|
|
||||||
|
|
||||||
Number of high-availability connections to the Cloudflare edge.
|
|
||||||
|
|
||||||
Capped by the number of discovered edge addresses.
|
|
||||||
|
|
||||||
#### protocol
|
|
||||||
|
|
||||||
Transport protocol for edge connections.
|
|
||||||
|
|
||||||
One of `quic` `http2`.
|
|
||||||
|
|
||||||
#### post_quantum
|
|
||||||
|
|
||||||
Enable post-quantum key exchange on the control connection.
|
|
||||||
|
|
||||||
#### edge_ip_version
|
|
||||||
|
|
||||||
IP version used when connecting to the Cloudflare edge.
|
|
||||||
|
|
||||||
One of `0` (automatic) `4` `6`.
|
|
||||||
|
|
||||||
#### datagram_version
|
|
||||||
|
|
||||||
Datagram protocol version used for UDP proxying over QUIC.
|
|
||||||
|
|
||||||
One of `v2` `v3`. Only meaningful when `protocol` is `quic`.
|
|
||||||
|
|
||||||
#### grace_period
|
|
||||||
|
|
||||||
Graceful shutdown window for in-flight edge connections.
|
|
||||||
|
|
||||||
#### region
|
|
||||||
|
|
||||||
Cloudflare edge region selector.
|
|
||||||
|
|
||||||
Conflict with endpoints embedded in `token`.
|
|
||||||
|
|
||||||
#### control_dialer
|
|
||||||
|
|
||||||
[Dial Fields](/configuration/shared/dial/) used when the tunnel client dials the
|
|
||||||
Cloudflare control plane.
|
|
||||||
|
|
||||||
#### tunnel_dialer
|
|
||||||
|
|
||||||
[Dial Fields](/configuration/shared/dial/) used when the tunnel client dials the
|
|
||||||
Cloudflare edge data plane.
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
`cloudflared` 入站运行一个内嵌的 Cloudflare Tunnel 客户端,并将所有传入的隧道流量
|
|
||||||
(TCP、UDP、ICMP)通过 sing-box 的路由引擎转发。
|
|
||||||
|
|
||||||
### 结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "cloudflared",
|
|
||||||
"tag": "",
|
|
||||||
|
|
||||||
"token": "",
|
|
||||||
"ha_connections": 0,
|
|
||||||
"protocol": "",
|
|
||||||
"post_quantum": false,
|
|
||||||
"edge_ip_version": 0,
|
|
||||||
"datagram_version": "",
|
|
||||||
"grace_period": "",
|
|
||||||
"region": "",
|
|
||||||
"control_dialer": {
|
|
||||||
... // 拨号字段
|
|
||||||
},
|
|
||||||
"tunnel_dialer": {
|
|
||||||
... // 拨号字段
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 字段
|
|
||||||
|
|
||||||
#### token
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
来自 Cloudflare Zero Trust 仪表板的 Base64 编码隧道令牌
|
|
||||||
(`Networks → Tunnels → Install connector`)。
|
|
||||||
|
|
||||||
#### ha_connections
|
|
||||||
|
|
||||||
到 Cloudflare edge 的高可用连接数。
|
|
||||||
|
|
||||||
上限为已发现的 edge 地址数量。
|
|
||||||
|
|
||||||
#### protocol
|
|
||||||
|
|
||||||
edge 连接使用的传输协议。
|
|
||||||
|
|
||||||
`quic` `http2` 之一。
|
|
||||||
|
|
||||||
#### post_quantum
|
|
||||||
|
|
||||||
在控制连接上启用后量子密钥交换。
|
|
||||||
|
|
||||||
#### edge_ip_version
|
|
||||||
|
|
||||||
连接 Cloudflare edge 时使用的 IP 版本。
|
|
||||||
|
|
||||||
`0`(自动)`4` `6` 之一。
|
|
||||||
|
|
||||||
#### datagram_version
|
|
||||||
|
|
||||||
通过 QUIC 进行 UDP 代理时使用的数据报协议版本。
|
|
||||||
|
|
||||||
`v2` `v3` 之一。仅在 `protocol` 为 `quic` 时有效。
|
|
||||||
|
|
||||||
#### grace_period
|
|
||||||
|
|
||||||
正在处理的 edge 连接的优雅关闭窗口。
|
|
||||||
|
|
||||||
#### region
|
|
||||||
|
|
||||||
Cloudflare edge 区域选择器。
|
|
||||||
|
|
||||||
与 `token` 中嵌入的 endpoint 冲突。
|
|
||||||
|
|
||||||
#### control_dialer
|
|
||||||
|
|
||||||
隧道客户端拨向 Cloudflare 控制面时使用的
|
|
||||||
[拨号字段](/zh/configuration/shared/dial/)。
|
|
||||||
|
|
||||||
#### tunnel_dialer
|
|
||||||
|
|
||||||
隧道客户端拨向 Cloudflare edge 数据面时使用的
|
|
||||||
[拨号字段](/zh/configuration/shared/dial/)。
|
|
||||||
@@ -2,10 +2,6 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-plus: [bbr_profile](#bbr_profile)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-alert: [masquerade](#masquerade)
|
:material-alert: [masquerade](#masquerade)
|
||||||
@@ -35,7 +31,6 @@ icon: material/alert-decagram
|
|||||||
"ignore_client_bandwidth": false,
|
"ignore_client_bandwidth": false,
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"masquerade": "", // or {}
|
"masquerade": "", // or {}
|
||||||
"bbr_profile": "",
|
|
||||||
"brutal_debug": false
|
"brutal_debug": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -146,14 +141,6 @@ Fixed response headers.
|
|||||||
|
|
||||||
Fixed response content.
|
Fixed response content.
|
||||||
|
|
||||||
#### bbr_profile
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
BBR congestion control algorithm profile, one of `conservative` `standard` `aggressive`.
|
|
||||||
|
|
||||||
`standard` is used by default.
|
|
||||||
|
|
||||||
#### brutal_debug
|
#### brutal_debug
|
||||||
|
|
||||||
Enable debug information logging for Hysteria Brutal CC.
|
Enable debug information logging for Hysteria Brutal CC.
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [bbr_profile](#bbr_profile)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-alert: [masquerade](#masquerade)
|
:material-alert: [masquerade](#masquerade)
|
||||||
@@ -35,7 +31,6 @@ icon: material/alert-decagram
|
|||||||
"ignore_client_bandwidth": false,
|
"ignore_client_bandwidth": false,
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"masquerade": "", // 或 {}
|
"masquerade": "", // 或 {}
|
||||||
"bbr_profile": "",
|
|
||||||
"brutal_debug": false
|
"brutal_debug": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -143,14 +138,6 @@ HTTP3 服务器认证失败时的行为 (对象配置)。
|
|||||||
|
|
||||||
固定响应内容。
|
固定响应内容。
|
||||||
|
|
||||||
#### bbr_profile
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
BBR 拥塞控制算法配置,可选 `conservative` `standard` `aggressive`。
|
|
||||||
|
|
||||||
默认使用 `standard`。
|
|
||||||
|
|
||||||
#### brutal_debug
|
#### brutal_debug
|
||||||
|
|
||||||
启用 Hysteria Brutal CC 的调试信息日志记录。
|
启用 Hysteria Brutal CC 的调试信息日志记录。
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
| `tun` | [Tun](./tun/) | :material-close: |
|
| `tun` | [Tun](./tun/) | :material-close: |
|
||||||
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
||||||
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
||||||
| `cloudflared` | [Cloudflared](./cloudflared/) | :material-close: |
|
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
| `tun` | [Tun](./tun/) | :material-close: |
|
| `tun` | [Tun](./tun/) | :material-close: |
|
||||||
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
||||||
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
||||||
| `cloudflared` | [Cloudflared](./cloudflared/) | :material-close: |
|
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,3 @@
|
|||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-plus: [hop_interval_max](#hop_interval_max)
|
|
||||||
:material-plus: [bbr_profile](#bbr_profile)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [server_ports](#server_ports)
|
:material-plus: [server_ports](#server_ports)
|
||||||
@@ -14,14 +9,13 @@
|
|||||||
{
|
{
|
||||||
"type": "hysteria2",
|
"type": "hysteria2",
|
||||||
"tag": "hy2-out",
|
"tag": "hy2-out",
|
||||||
|
|
||||||
"server": "127.0.0.1",
|
"server": "127.0.0.1",
|
||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
"server_ports": [
|
"server_ports": [
|
||||||
"2080:3000"
|
"2080:3000"
|
||||||
],
|
],
|
||||||
"hop_interval": "",
|
"hop_interval": "",
|
||||||
"hop_interval_max": "",
|
|
||||||
"up_mbps": 100,
|
"up_mbps": 100,
|
||||||
"down_mbps": 100,
|
"down_mbps": 100,
|
||||||
"obfs": {
|
"obfs": {
|
||||||
@@ -31,9 +25,8 @@
|
|||||||
"password": "goofy_ahh_password",
|
"password": "goofy_ahh_password",
|
||||||
"network": "tcp",
|
"network": "tcp",
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"bbr_profile": "",
|
|
||||||
"brutal_debug": false,
|
"brutal_debug": false,
|
||||||
|
|
||||||
... // Dial Fields
|
... // Dial Fields
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -82,14 +75,6 @@ Port hopping interval.
|
|||||||
|
|
||||||
`30s` is used by default.
|
`30s` is used by default.
|
||||||
|
|
||||||
#### hop_interval_max
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
Maximum port hopping interval, used for randomization.
|
|
||||||
|
|
||||||
If set, the actual hop interval will be randomly chosen between `hop_interval` and `hop_interval_max`.
|
|
||||||
|
|
||||||
#### up_mbps, down_mbps
|
#### up_mbps, down_mbps
|
||||||
|
|
||||||
Max bandwidth, in Mbps.
|
Max bandwidth, in Mbps.
|
||||||
@@ -124,14 +109,6 @@ Both is enabled by default.
|
|||||||
|
|
||||||
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||||
|
|
||||||
#### bbr_profile
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
BBR congestion control algorithm profile, one of `conservative` `standard` `aggressive`.
|
|
||||||
|
|
||||||
`standard` is used by default.
|
|
||||||
|
|
||||||
#### brutal_debug
|
#### brutal_debug
|
||||||
|
|
||||||
Enable debug information logging for Hysteria Brutal CC.
|
Enable debug information logging for Hysteria Brutal CC.
|
||||||
|
|||||||
@@ -1,8 +1,3 @@
|
|||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [hop_interval_max](#hop_interval_max)
|
|
||||||
:material-plus: [bbr_profile](#bbr_profile)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [server_ports](#server_ports)
|
:material-plus: [server_ports](#server_ports)
|
||||||
@@ -21,7 +16,6 @@
|
|||||||
"2080:3000"
|
"2080:3000"
|
||||||
],
|
],
|
||||||
"hop_interval": "",
|
"hop_interval": "",
|
||||||
"hop_interval_max": "",
|
|
||||||
"up_mbps": 100,
|
"up_mbps": 100,
|
||||||
"down_mbps": 100,
|
"down_mbps": 100,
|
||||||
"obfs": {
|
"obfs": {
|
||||||
@@ -31,9 +25,8 @@
|
|||||||
"password": "goofy_ahh_password",
|
"password": "goofy_ahh_password",
|
||||||
"network": "tcp",
|
"network": "tcp",
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"bbr_profile": "",
|
|
||||||
"brutal_debug": false,
|
"brutal_debug": false,
|
||||||
|
|
||||||
... // 拨号字段
|
... // 拨号字段
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -80,14 +73,6 @@
|
|||||||
|
|
||||||
默认使用 `30s`。
|
默认使用 `30s`。
|
||||||
|
|
||||||
#### hop_interval_max
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
最大端口跳跃间隔,用于随机化。
|
|
||||||
|
|
||||||
如果设置,实际跳跃间隔将在 `hop_interval` 和 `hop_interval_max` 之间随机选择。
|
|
||||||
|
|
||||||
#### up_mbps, down_mbps
|
#### up_mbps, down_mbps
|
||||||
|
|
||||||
最大带宽。
|
最大带宽。
|
||||||
@@ -122,14 +107,6 @@ QUIC 流量混淆器密码.
|
|||||||
|
|
||||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。
|
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。
|
||||||
|
|
||||||
#### bbr_profile
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
BBR 拥塞控制算法配置,可选 `conservative` `standard` `aggressive`。
|
|
||||||
|
|
||||||
默认使用 `standard`。
|
|
||||||
|
|
||||||
#### brutal_debug
|
#### brutal_debug
|
||||||
|
|
||||||
启用 Hysteria Brutal CC 的调试信息日志记录。
|
启用 Hysteria Brutal CC 的调试信息日志记录。
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ Automatically detected from common DHCP servers (dnsmasq, odhcpd, ISC dhcpd, Kea
|
|||||||
|
|
||||||
See [Dial Fields](/configuration/shared/dial/#domain_resolver) for details.
|
See [Dial Fields](/configuration/shared/dial/#domain_resolver) for details.
|
||||||
|
|
||||||
Can be overridden by `outbound.domain_resolver`.
|
Can be overrides by `outbound.domain_resolver`.
|
||||||
|
|
||||||
#### default_network_strategy
|
#### default_network_strategy
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ See [Dial Fields](/configuration/shared/dial/#network_strategy) for details.
|
|||||||
|
|
||||||
Takes no effect if `outbound.bind_interface`, `outbound.inet4_bind_address` or `outbound.inet6_bind_address` is set.
|
Takes no effect if `outbound.bind_interface`, `outbound.inet4_bind_address` or `outbound.inet6_bind_address` is set.
|
||||||
|
|
||||||
Can be overridden by `outbound.network_strategy`.
|
Can be overrides by `outbound.network_strategy`.
|
||||||
|
|
||||||
Conflicts with `default_interface`.
|
Conflicts with `default_interface`.
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ icon: material/new-box
|
|||||||
!!! quote "Changes in sing-box 1.14.0"
|
!!! quote "Changes in sing-box 1.14.0"
|
||||||
|
|
||||||
:material-plus: [source_mac_address](#source_mac_address)
|
:material-plus: [source_mac_address](#source_mac_address)
|
||||||
:material-plus: [source_hostname](#source_hostname)
|
:material-plus: [source_hostname](#source_hostname)
|
||||||
:material-plus: [package_name_regex](#package_name_regex)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
@@ -130,9 +129,6 @@ icon: material/new-box
|
|||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
"package_name_regex": [
|
|
||||||
"^com\\.termux.*"
|
|
||||||
],
|
|
||||||
"user": [
|
"user": [
|
||||||
"sekai"
|
"sekai"
|
||||||
],
|
],
|
||||||
@@ -358,12 +354,6 @@ Match process path using regular expression.
|
|||||||
|
|
||||||
Match android package name.
|
Match android package name.
|
||||||
|
|
||||||
#### package_name_regex
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
Match android package name using regular expression.
|
|
||||||
|
|
||||||
#### user
|
#### user
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ icon: material/new-box
|
|||||||
!!! quote "sing-box 1.14.0 中的更改"
|
!!! quote "sing-box 1.14.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [source_mac_address](#source_mac_address)
|
:material-plus: [source_mac_address](#source_mac_address)
|
||||||
:material-plus: [source_hostname](#source_hostname)
|
:material-plus: [source_hostname](#source_hostname)
|
||||||
:material-plus: [package_name_regex](#package_name_regex)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
@@ -128,9 +127,6 @@ icon: material/new-box
|
|||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
"package_name_regex": [
|
|
||||||
"^com\\.termux.*"
|
|
||||||
],
|
|
||||||
"user": [
|
"user": [
|
||||||
"sekai"
|
"sekai"
|
||||||
],
|
],
|
||||||
@@ -356,12 +352,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
匹配 Android 应用包名。
|
匹配 Android 应用包名。
|
||||||
|
|
||||||
#### package_name_regex
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
使用正则表达式匹配 Android 应用包名。
|
|
||||||
|
|
||||||
#### user
|
#### user
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|||||||
@@ -7,10 +7,6 @@ icon: material/new-box
|
|||||||
:material-plus: [bypass](#bypass)
|
:material-plus: [bypass](#bypass)
|
||||||
:material-alert: [reject](#reject)
|
:material-alert: [reject](#reject)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-plus: [resolve.disable_optimistic_cache](#disable_optimistic_cache)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [tls_fragment](#tls_fragment)
|
:material-plus: [tls_fragment](#tls_fragment)
|
||||||
@@ -283,7 +279,6 @@ Timeout for sniffing.
|
|||||||
"server": "",
|
"server": "",
|
||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"disable_optimistic_cache": false,
|
|
||||||
"rewrite_ttl": null,
|
"rewrite_ttl": null,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
}
|
}
|
||||||
@@ -307,12 +302,6 @@ DNS resolution strategy, available values are: `prefer_ipv4`, `prefer_ipv6`, `ip
|
|||||||
|
|
||||||
Disable cache and save cache in this query.
|
Disable cache and save cache in this query.
|
||||||
|
|
||||||
#### disable_optimistic_cache
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
Disable optimistic DNS caching in this query.
|
|
||||||
|
|
||||||
#### rewrite_ttl
|
#### rewrite_ttl
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
!!! question "Since sing-box 1.12.0"
|
||||||
@@ -327,4 +316,4 @@ Append a `edns0-subnet` OPT extra record with the specified IP prefix to every q
|
|||||||
|
|
||||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||||
|
|
||||||
Will override `dns.client_subnet`.
|
Will overrides `dns.client_subnet`.
|
||||||
|
|||||||
@@ -7,10 +7,6 @@ icon: material/new-box
|
|||||||
:material-plus: [bypass](#bypass)
|
:material-plus: [bypass](#bypass)
|
||||||
:material-alert: [reject](#reject)
|
:material-alert: [reject](#reject)
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [resolve.disable_optimistic_cache](#disable_optimistic_cache)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [tls_fragment](#tls_fragment)
|
:material-plus: [tls_fragment](#tls_fragment)
|
||||||
@@ -272,7 +268,6 @@ UDP 连接超时时间。
|
|||||||
"server": "",
|
"server": "",
|
||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"disable_optimistic_cache": false,
|
|
||||||
"rewrite_ttl": null,
|
"rewrite_ttl": null,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
}
|
}
|
||||||
@@ -296,12 +291,6 @@ DNS 解析策略,可用值有:`prefer_ipv4`、`prefer_ipv6`、`ipv4_only`、
|
|||||||
|
|
||||||
在此查询中禁用缓存。
|
在此查询中禁用缓存。
|
||||||
|
|
||||||
#### disable_optimistic_cache
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
在此查询中禁用乐观 DNS 缓存。
|
|
||||||
|
|
||||||
#### rewrite_ttl
|
#### rewrite_ttl
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-plus: [package_name_regex](#package_name_regex)
|
|
||||||
:material-alert: [query_type](#query_type)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
:material-plus: [network_interface_address](#network_interface_address)
|
:material-plus: [network_interface_address](#network_interface_address)
|
||||||
@@ -83,9 +78,6 @@ icon: material/new-box
|
|||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
"package_name_regex": [
|
|
||||||
"^com\\.termux.*"
|
|
||||||
],
|
|
||||||
"network_type": [
|
"network_type": [
|
||||||
"wifi"
|
"wifi"
|
||||||
],
|
],
|
||||||
@@ -133,20 +125,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
#### query_type
|
#### query_type
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
When a DNS rule references this rule-set, this field now also applies
|
|
||||||
when the DNS rule is matched from an internal domain resolution that
|
|
||||||
does not target a specific DNS server. In earlier versions, only DNS
|
|
||||||
queries received from a client evaluated this field. See
|
|
||||||
[Migration](/migration/#ip_version-and-query_type-behavior-changes-in-dns-rules)
|
|
||||||
for the full list.
|
|
||||||
|
|
||||||
When a DNS rule references a rule-set containing this field, the DNS
|
|
||||||
rule is incompatible in the same DNS configuration with Legacy Address
|
|
||||||
Filter Fields in DNS rules, the Legacy `strategy` DNS rule action
|
|
||||||
option, and the Legacy `rule_set_ip_cidr_accept_empty` DNS rule item.
|
|
||||||
|
|
||||||
DNS query type. Values can be integers or type name strings.
|
DNS query type. Values can be integers or type name strings.
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
@@ -227,12 +205,6 @@ Match process path using regular expression.
|
|||||||
|
|
||||||
Match android package name.
|
Match android package name.
|
||||||
|
|
||||||
#### package_name_regex
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
Match android package name using regular expression.
|
|
||||||
|
|
||||||
#### network_type
|
#### network_type
|
||||||
|
|
||||||
!!! question "Since sing-box 1.11.0"
|
!!! question "Since sing-box 1.11.0"
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [package_name_regex](#package_name_regex)
|
|
||||||
:material-alert: [query_type](#query_type)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [network_interface_address](#network_interface_address)
|
:material-plus: [network_interface_address](#network_interface_address)
|
||||||
@@ -83,9 +78,6 @@ icon: material/new-box
|
|||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
"package_name_regex": [
|
|
||||||
"^com\\.termux.*"
|
|
||||||
],
|
|
||||||
"network_type": [
|
"network_type": [
|
||||||
"wifi"
|
"wifi"
|
||||||
],
|
],
|
||||||
@@ -133,17 +125,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
#### query_type
|
#### query_type
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
当 DNS 规则引用此规则集时,此字段现在也会在 DNS 规则被未指定具体
|
|
||||||
DNS 服务器的内部域名解析匹配时生效。此前只有来自客户端的 DNS 查询
|
|
||||||
才会评估此字段。完整列表参阅
|
|
||||||
[迁移指南](/zh/migration/#dns-规则中的-ip_version-和-query_type-行为更改)。
|
|
||||||
|
|
||||||
当 DNS 规则引用了包含此字段的规则集时,该 DNS 规则在同一 DNS 配置中
|
|
||||||
不能与旧版地址筛选字段 (DNS 规则)、旧版 DNS 规则动作 `strategy` 选项,
|
|
||||||
或旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项共存。
|
|
||||||
|
|
||||||
DNS 查询类型。值可以为整数或者类型名称字符串。
|
DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
@@ -220,12 +201,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
匹配 Android 应用包名。
|
匹配 Android 应用包名。
|
||||||
|
|
||||||
#### package_name_regex
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
使用正则表达式匹配 Android 应用包名。
|
|
||||||
|
|
||||||
#### network_type
|
#### network_type
|
||||||
|
|
||||||
!!! question "自 sing-box 1.11.0 起"
|
!!! question "自 sing-box 1.11.0 起"
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-plus: version `5`
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
:material-plus: version `4`
|
:material-plus: version `4`
|
||||||
@@ -45,7 +41,6 @@ Version of rule-set.
|
|||||||
* 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets.
|
* 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets.
|
||||||
* 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items.
|
* 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items.
|
||||||
* 4: sing-box 1.13.0: Added `network_interface_address` and `default_interface_address` rule items.
|
* 4: sing-box 1.13.0: Added `network_interface_address` and `default_interface_address` rule items.
|
||||||
* 5: sing-box 1.14.0: Added `package_name_regex` rule item.
|
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: version `5`
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
:material-plus: version `4`
|
:material-plus: version `4`
|
||||||
@@ -45,7 +41,6 @@ icon: material/new-box
|
|||||||
* 2: sing-box 1.10.0: 优化了二进制规则集中 `domain_suffix` 规则的内存使用。
|
* 2: sing-box 1.10.0: 优化了二进制规则集中 `domain_suffix` 规则的内存使用。
|
||||||
* 3: sing-box 1.11.0: 添加了 `network_type`、 `network_is_expensive` 和 `network_is_constrainted` 规则项。
|
* 3: sing-box 1.11.0: 添加了 `network_type`、 `network_is_expensive` 和 `network_is_constrainted` 规则项。
|
||||||
* 4: sing-box 1.13.0: 添加了 `network_interface_address` 和 `default_interface_address` 规则项。
|
* 4: sing-box 1.13.0: 添加了 `network_interface_address` 和 `default_interface_address` 规则项。
|
||||||
* 5: sing-box 1.14.0: 添加了 `package_name_regex` 规则项。
|
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
|
|||||||
@@ -14,50 +14,14 @@ check [Migration](../migration/#migrate-inline-acme-to-certificate-provider).
|
|||||||
|
|
||||||
Old fields will be removed in sing-box 1.16.0.
|
Old fields will be removed in sing-box 1.16.0.
|
||||||
|
|
||||||
#### Legacy `strategy` DNS rule action option
|
|
||||||
|
|
||||||
Legacy `strategy` DNS rule action option is deprecated.
|
|
||||||
|
|
||||||
Old fields will be removed in sing-box 1.16.0.
|
|
||||||
|
|
||||||
#### Legacy `rule_set_ip_cidr_accept_empty` DNS rule item
|
|
||||||
|
|
||||||
Legacy `rule_set_ip_cidr_accept_empty` DNS rule item is deprecated,
|
|
||||||
check [Migration](../migration/#migrate-address-filter-fields-to-response-matching).
|
|
||||||
|
|
||||||
Old fields will be removed in sing-box 1.16.0.
|
|
||||||
|
|
||||||
#### `independent_cache` DNS option
|
|
||||||
|
|
||||||
`independent_cache` DNS option is deprecated.
|
|
||||||
The DNS cache now always keys by transport, making this option unnecessary,
|
|
||||||
check [Migration](../migration/#migrate-independent-dns-cache).
|
|
||||||
|
|
||||||
Old fields will be removed in sing-box 1.16.0.
|
|
||||||
|
|
||||||
#### `store_rdrc` cache file option
|
|
||||||
|
|
||||||
`store_rdrc` cache file option is deprecated,
|
|
||||||
check [Migration](../migration/#migrate-store-rdrc).
|
|
||||||
|
|
||||||
Old fields will be removed in sing-box 1.16.0.
|
|
||||||
|
|
||||||
#### Legacy Address Filter Fields in DNS rules
|
|
||||||
|
|
||||||
Legacy Address Filter Fields (`ip_cidr`, `ip_is_private` without `match_response`)
|
|
||||||
in DNS rules are deprecated,
|
|
||||||
check [Migration](../migration/#migrate-address-filter-fields-to-response-matching).
|
|
||||||
|
|
||||||
Old behavior will be removed in sing-box 1.16.0.
|
|
||||||
|
|
||||||
## 1.12.0
|
## 1.12.0
|
||||||
|
|
||||||
#### Legacy DNS server formats
|
#### Legacy DNS server formats
|
||||||
|
|
||||||
DNS servers are refactored,
|
DNS servers are refactored,
|
||||||
check [Migration](../migration/#migrate-to-new-dns-server-formats).
|
check [Migration](../migration/#migrate-to-new-dns-servers).
|
||||||
|
|
||||||
Old formats were removed in sing-box 1.14.0.
|
Compatibility for old formats will be removed in sing-box 1.14.0.
|
||||||
|
|
||||||
#### `outbound` DNS rule item
|
#### `outbound` DNS rule item
|
||||||
|
|
||||||
|
|||||||
@@ -14,41 +14,6 @@ TLS 中的内联 ACME 选项(`tls.acme`)已废弃,
|
|||||||
|
|
||||||
旧字段将在 sing-box 1.16.0 中被移除。
|
旧字段将在 sing-box 1.16.0 中被移除。
|
||||||
|
|
||||||
#### 旧版 DNS 规则动作 `strategy` 选项
|
|
||||||
|
|
||||||
旧版 DNS 规则动作 `strategy` 选项已废弃。
|
|
||||||
|
|
||||||
旧字段将在 sing-box 1.16.0 中被移除。
|
|
||||||
|
|
||||||
#### 旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项
|
|
||||||
|
|
||||||
旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项已废弃,
|
|
||||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
|
||||||
|
|
||||||
旧字段将在 sing-box 1.16.0 中被移除。
|
|
||||||
|
|
||||||
#### `independent_cache` DNS 选项
|
|
||||||
|
|
||||||
`independent_cache` DNS 选项已废弃。
|
|
||||||
DNS 缓存现在始终按传输分离,使此选项不再需要,
|
|
||||||
参阅[迁移指南](/zh/migration/#迁移-independent-dns-cache)。
|
|
||||||
|
|
||||||
旧字段将在 sing-box 1.16.0 中被移除。
|
|
||||||
|
|
||||||
#### `store_rdrc` 缓存文件选项
|
|
||||||
|
|
||||||
`store_rdrc` 缓存文件选项已废弃,
|
|
||||||
参阅[迁移指南](/zh/migration/#迁移-store_rdrc)。
|
|
||||||
|
|
||||||
旧字段将在 sing-box 1.16.0 中被移除。
|
|
||||||
|
|
||||||
#### 旧版地址筛选字段 (DNS 规则)
|
|
||||||
|
|
||||||
旧版地址筛选字段(不使用 `match_response` 的 `ip_cidr`、`ip_is_private`)已废弃,
|
|
||||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
|
||||||
|
|
||||||
旧行为将在 sing-box 1.16.0 中被移除。
|
|
||||||
|
|
||||||
## 1.12.0
|
## 1.12.0
|
||||||
|
|
||||||
#### 旧的 DNS 服务器格式
|
#### 旧的 DNS 服务器格式
|
||||||
@@ -56,7 +21,7 @@ DNS 缓存现在始终按传输分离,使此选项不再需要,
|
|||||||
DNS 服务器已重构,
|
DNS 服务器已重构,
|
||||||
参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式).
|
参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式).
|
||||||
|
|
||||||
旧格式已在 sing-box 1.14.0 中被移除。
|
对旧格式的兼容性将在 sing-box 1.14.0 中被移除。
|
||||||
|
|
||||||
#### `outbound` DNS 规则项
|
#### `outbound` DNS 规则项
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
|
|||||||
| `with_ccm` | :material-check: | Build with Claude Code Multiplexer service support. |
|
| `with_ccm` | :material-check: | Build with Claude Code Multiplexer service support. |
|
||||||
| `with_ocm` | :material-check: | Build with OpenAI Codex Multiplexer service support. |
|
| `with_ocm` | :material-check: | Build with OpenAI Codex Multiplexer service support. |
|
||||||
| `with_naive_outbound` | :material-check: | Build with NaiveProxy outbound support, see [NaiveProxy outbound](/configuration/outbound/naive/). |
|
| `with_naive_outbound` | :material-check: | Build with NaiveProxy outbound support, see [NaiveProxy outbound](/configuration/outbound/naive/). |
|
||||||
| `with_cloudflared` | :material-check: | Build with Cloudflare Tunnel inbound support, see [Cloudflared inbound](/configuration/inbound/cloudflared/). |
|
|
||||||
| `badlinkname` | :material-check: | Enable `go:linkname` access to internal standard library functions. Required because the Go standard library does not expose many low-level APIs needed by this project, and reimplementing them externally is impractical. Used for kTLS (kernel TLS offload) and raw TLS record manipulation. |
|
| `badlinkname` | :material-check: | Enable `go:linkname` access to internal standard library functions. Required because the Go standard library does not expose many low-level APIs needed by this project, and reimplementing them externally is impractical. Used for kTLS (kernel TLS offload) and raw TLS record manipulation. |
|
||||||
| `tfogo_checklinkname0` | :material-check: | Companion to `badlinkname`. Go 1.23+ enforces `go:linkname` restrictions via the linker; this tag signals the build uses `-checklinkname=0` to bypass that enforcement. |
|
| `tfogo_checklinkname0` | :material-check: | Companion to `badlinkname`. Go 1.23+ enforces `go:linkname` restrictions via the linker; this tag signals the build uses `-checklinkname=0` to bypass that enforcement. |
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
|
|||||||
| `with_ccm` | :material-check: | 构建 Claude Code Multiplexer 服务支持。 |
|
| `with_ccm` | :material-check: | 构建 Claude Code Multiplexer 服务支持。 |
|
||||||
| `with_ocm` | :material-check: | 构建 OpenAI Codex Multiplexer 服务支持。 |
|
| `with_ocm` | :material-check: | 构建 OpenAI Codex Multiplexer 服务支持。 |
|
||||||
| `with_naive_outbound` | :material-check: | 构建 NaiveProxy 出站支持,参阅 [NaiveProxy 出站](/zh/configuration/outbound/naive/)。 |
|
| `with_naive_outbound` | :material-check: | 构建 NaiveProxy 出站支持,参阅 [NaiveProxy 出站](/zh/configuration/outbound/naive/)。 |
|
||||||
| `with_cloudflared` | :material-check: | 构建 Cloudflare Tunnel 入站支持,参阅 [Cloudflared 入站](/zh/configuration/inbound/cloudflared/)。 |
|
|
||||||
| `badlinkname` | :material-check: | 启用 `go:linkname` 以访问标准库内部函数。Go 标准库未提供本项目需要的许多底层 API,且在外部重新实现不切实际。用于 kTLS(内核 TLS 卸载)和原始 TLS 记录操作。 |
|
| `badlinkname` | :material-check: | 启用 `go:linkname` 以访问标准库内部函数。Go 标准库未提供本项目需要的许多底层 API,且在外部重新实现不切实际。用于 kTLS(内核 TLS 卸载)和原始 TLS 记录操作。 |
|
||||||
| `tfogo_checklinkname0` | :material-check: | `badlinkname` 的伴随标记。Go 1.23+ 链接器强制限制 `go:linkname` 使用;此标记表示构建使用 `-checklinkname=0` 以绕过该限制。 |
|
| `tfogo_checklinkname0` | :material-check: | `badlinkname` 的伴随标记。Go 1.23+ 链接器强制限制 `go:linkname` 使用;此标记表示构建使用 `-checklinkname=0` 以绕过该限制。 |
|
||||||
|
|
||||||
|
|||||||
@@ -79,176 +79,6 @@ See [ACME](/configuration/shared/certificate-provider/acme/) for fields newly ad
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Migrate address filter fields to response matching
|
|
||||||
|
|
||||||
Legacy Address Filter Fields (`ip_cidr`, `ip_is_private` without `match_response`) in DNS rules are deprecated,
|
|
||||||
along with the Legacy `rule_set_ip_cidr_accept_empty` DNS rule item.
|
|
||||||
|
|
||||||
In sing-box 1.14.0, use the [`evaluate`](/configuration/dns/rule_action/#evaluate) action
|
|
||||||
to fetch a DNS response, then match against it explicitly with `match_response`.
|
|
||||||
|
|
||||||
!!! info "References"
|
|
||||||
|
|
||||||
[DNS Rule](/configuration/dns/rule/) /
|
|
||||||
[DNS Rule Action](/configuration/dns/rule_action/#evaluate)
|
|
||||||
|
|
||||||
=== ":material-card-remove: Deprecated"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"rule_set": "geoip-cn",
|
|
||||||
"action": "route",
|
|
||||||
"server": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "route",
|
|
||||||
"server": "remote"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: New"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "evaluate",
|
|
||||||
"server": "remote"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match_response": true,
|
|
||||||
"rule_set": "geoip-cn",
|
|
||||||
"action": "route",
|
|
||||||
"server": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "route",
|
|
||||||
"server": "remote"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Migrate independent DNS cache
|
|
||||||
|
|
||||||
The DNS cache now always keys by transport name, making `independent_cache` unnecessary.
|
|
||||||
Simply remove the field.
|
|
||||||
|
|
||||||
!!! info "References"
|
|
||||||
|
|
||||||
[DNS](/configuration/dns/)
|
|
||||||
|
|
||||||
=== ":material-card-remove: Deprecated"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"independent_cache": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: New"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Migrate store_rdrc
|
|
||||||
|
|
||||||
`store_rdrc` is deprecated and can be replaced by `store_dns`,
|
|
||||||
which persists the full DNS cache to the cache file.
|
|
||||||
|
|
||||||
!!! info "References"
|
|
||||||
|
|
||||||
[Cache File](/configuration/experimental/cache-file/)
|
|
||||||
|
|
||||||
=== ":material-card-remove: Deprecated"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"experimental": {
|
|
||||||
"cache_file": {
|
|
||||||
"enabled": true,
|
|
||||||
"store_rdrc": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: New"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"experimental": {
|
|
||||||
"cache_file": {
|
|
||||||
"enabled": true,
|
|
||||||
"store_dns": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### ip_version and query_type behavior changes in DNS rules
|
|
||||||
|
|
||||||
In sing-box 1.14.0, the behavior of
|
|
||||||
[`ip_version`](/configuration/dns/rule/#ip_version) and
|
|
||||||
[`query_type`](/configuration/dns/rule/#query_type) in DNS rules, together with
|
|
||||||
[`query_type`](/configuration/rule-set/headless-rule/#query_type) in referenced
|
|
||||||
rule-sets, changes in two ways.
|
|
||||||
|
|
||||||
First, these fields now take effect on every DNS rule evaluation. In earlier
|
|
||||||
versions they were evaluated only for DNS queries received from a client
|
|
||||||
(for example, from a DNS inbound or intercepted by `tun`), and were silently
|
|
||||||
ignored when a DNS rule was matched from an internal domain resolution that
|
|
||||||
did not target a specific DNS server. Such internal resolutions include:
|
|
||||||
|
|
||||||
- The [`resolve`](/configuration/route/rule_action/#resolve) route rule
|
|
||||||
action without a `server` set.
|
|
||||||
- ICMP traffic routed to a domain destination through a `direct` outbound.
|
|
||||||
- A [WireGuard](/configuration/endpoint/wireguard/) or
|
|
||||||
[Tailscale](/configuration/endpoint/tailscale/) endpoint used as an
|
|
||||||
outbound, when resolving its own destination address.
|
|
||||||
- A [SOCKS4](/configuration/outbound/socks/) outbound, which must resolve
|
|
||||||
the destination locally because the protocol has no in-protocol domain
|
|
||||||
support.
|
|
||||||
- The [DERP](/configuration/service/derp/) `bootstrap-dns` endpoint and the
|
|
||||||
[`resolved`](/configuration/service/resolved/) service (when resolving a
|
|
||||||
hostname or an SRV target).
|
|
||||||
|
|
||||||
Resolutions that target a specific DNS server — via
|
|
||||||
[`domain_resolver`](/configuration/shared/dial/#domain_resolver) on a dial
|
|
||||||
field, [`default_domain_resolver`](/configuration/route/#default_domain_resolver)
|
|
||||||
in route options, or an explicit `server` on a DNS rule action or the
|
|
||||||
`resolve` route rule action — do not go through DNS rule matching and are
|
|
||||||
unaffected.
|
|
||||||
|
|
||||||
Second, setting `ip_version` or `query_type` in a DNS rule, or referencing a
|
|
||||||
rule-set containing `query_type`, is no longer compatible in the same DNS
|
|
||||||
configuration with Legacy Address Filter Fields in DNS rules, the Legacy
|
|
||||||
`strategy` DNS rule action option, or the Legacy `rule_set_ip_cidr_accept_empty`
|
|
||||||
DNS rule item. Such a configuration will be rejected at startup. To combine
|
|
||||||
these fields with address-based filtering, migrate to response matching via
|
|
||||||
the [`evaluate`](/configuration/dns/rule_action/#evaluate) action and
|
|
||||||
[`match_response`](/configuration/dns/rule/#match_response), see
|
|
||||||
[Migrate address filter fields to response matching](#migrate-address-filter-fields-to-response-matching).
|
|
||||||
|
|
||||||
!!! info "References"
|
|
||||||
|
|
||||||
[DNS Rule](/configuration/dns/rule/) /
|
|
||||||
[Headless Rule](/configuration/rule-set/headless-rule/) /
|
|
||||||
[Route Rule Action](/configuration/route/rule_action/#resolve)
|
|
||||||
|
|
||||||
## 1.12.0
|
## 1.12.0
|
||||||
|
|
||||||
### Migrate to new DNS server formats
|
### Migrate to new DNS server formats
|
||||||
|
|||||||
@@ -79,167 +79,6 @@ sing-box 1.14.0 新增字段参阅 [ACME](/zh/configuration/shared/certificate-p
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 迁移地址筛选字段到响应匹配
|
|
||||||
|
|
||||||
旧版地址筛选字段(不使用 `match_response` 的 `ip_cidr`、`ip_is_private`)已废弃,
|
|
||||||
旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项也已废弃。
|
|
||||||
|
|
||||||
在 sing-box 1.14.0 中,请使用 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作
|
|
||||||
获取 DNS 响应,然后通过 `match_response` 显式匹配。
|
|
||||||
|
|
||||||
!!! info "参考"
|
|
||||||
|
|
||||||
[DNS 规则](/zh/configuration/dns/rule/) /
|
|
||||||
[DNS 规则动作](/zh/configuration/dns/rule_action/#evaluate)
|
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"rule_set": "geoip-cn",
|
|
||||||
"action": "route",
|
|
||||||
"server": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "route",
|
|
||||||
"server": "remote"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "evaluate",
|
|
||||||
"server": "remote"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match_response": true,
|
|
||||||
"rule_set": "geoip-cn",
|
|
||||||
"action": "route",
|
|
||||||
"server": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "route",
|
|
||||||
"server": "remote"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 迁移 independent DNS cache
|
|
||||||
|
|
||||||
DNS 缓存现在始终按传输名称分离,使 `independent_cache` 不再需要。
|
|
||||||
直接移除该字段即可。
|
|
||||||
|
|
||||||
!!! info "参考"
|
|
||||||
|
|
||||||
[DNS](/zh/configuration/dns/)
|
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"independent_cache": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 迁移 store_rdrc
|
|
||||||
|
|
||||||
`store_rdrc` 已废弃,且可以被 `store_dns` 替代,
|
|
||||||
后者将完整的 DNS 缓存持久化到缓存文件中。
|
|
||||||
|
|
||||||
!!! info "参考"
|
|
||||||
|
|
||||||
[缓存文件](/zh/configuration/experimental/cache-file/)
|
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"experimental": {
|
|
||||||
"cache_file": {
|
|
||||||
"enabled": true,
|
|
||||||
"store_rdrc": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"experimental": {
|
|
||||||
"cache_file": {
|
|
||||||
"enabled": true,
|
|
||||||
"store_dns": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### DNS 规则中的 ip_version 和 query_type 行为更改
|
|
||||||
|
|
||||||
在 sing-box 1.14.0 中,DNS 规则中的
|
|
||||||
[`ip_version`](/zh/configuration/dns/rule/#ip_version) 和
|
|
||||||
[`query_type`](/zh/configuration/dns/rule/#query_type),以及被引用规则集中的
|
|
||||||
[`query_type`](/zh/configuration/rule-set/headless-rule/#query_type),
|
|
||||||
行为有两项更改。
|
|
||||||
|
|
||||||
其一,这些字段现在对每一次 DNS 规则评估都会生效。此前它们仅对来自客户端的 DNS 查询
|
|
||||||
(例如来自 DNS 入站或被 `tun` 截获的查询)生效,当 DNS 规则被未指定具体 DNS 服务器的
|
|
||||||
内部域名解析匹配时,会被静默忽略。此类内部解析包括:
|
|
||||||
|
|
||||||
- 未设置 `server` 的 [`resolve`](/zh/configuration/route/rule_action/#resolve) 路由规则动作。
|
|
||||||
- 通过 `direct` 出站路由到域名目标的 ICMP 流量。
|
|
||||||
- 作为出站使用的 [WireGuard](/zh/configuration/endpoint/wireguard/) 或
|
|
||||||
[Tailscale](/zh/configuration/endpoint/tailscale/) 端点在解析自身目标地址时。
|
|
||||||
- [SOCKS4](/zh/configuration/outbound/socks/) 出站,因为协议本身不支持域名,
|
|
||||||
必须在本地解析目标。
|
|
||||||
- [DERP](/zh/configuration/service/derp/) 的 `bootstrap-dns` 端点,以及
|
|
||||||
[`resolved`](/zh/configuration/service/resolved/) 服务在解析主机名或 SRV 目标时。
|
|
||||||
|
|
||||||
通过拨号字段中的
|
|
||||||
[`domain_resolver`](/zh/configuration/shared/dial/#domain_resolver)、
|
|
||||||
路由选项中的 [`default_domain_resolver`](/zh/configuration/route/#default_domain_resolver),
|
|
||||||
或 DNS 规则动作与 `resolve` 路由规则动作上显式的 `server` 指定具体 DNS 服务器的
|
|
||||||
解析,不会经过 DNS 规则匹配,不受此次更改影响。
|
|
||||||
|
|
||||||
其二,设置了 `ip_version` 或 `query_type` 的 DNS 规则,或引用了包含 `query_type` 的
|
|
||||||
规则集的 DNS 规则,在同一 DNS 配置中不再能与旧版地址筛选字段 (DNS 规则)、旧版
|
|
||||||
DNS 规则动作 `strategy` 选项,或旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项共存。
|
|
||||||
此类配置将在启动时被拒绝。如需将这些字段与基于地址的筛选组合,请通过
|
|
||||||
[`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作和
|
|
||||||
[`match_response`](/zh/configuration/dns/rule/#match_response) 迁移到响应匹配,
|
|
||||||
参阅 [迁移地址筛选字段到响应匹配](#迁移地址筛选字段到响应匹配)。
|
|
||||||
|
|
||||||
!!! info "参考"
|
|
||||||
|
|
||||||
[DNS 规则](/zh/configuration/dns/rule/) /
|
|
||||||
[Headless 规则](/zh/configuration/rule-set/headless-rule/) /
|
|
||||||
[路由规则动作](/zh/configuration/route/rule_action/#resolve)
|
|
||||||
|
|
||||||
## 1.12.0
|
## 1.12.0
|
||||||
|
|
||||||
### 迁移到新的 DNS 服务器格式
|
### 迁移到新的 DNS 服务器格式
|
||||||
|
|||||||
@@ -12,11 +12,9 @@ import (
|
|||||||
"github.com/sagernet/bbolt"
|
"github.com/sagernet/bbolt"
|
||||||
bboltErrors "github.com/sagernet/bbolt/errors"
|
bboltErrors "github.com/sagernet/bbolt/errors"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
"github.com/sagernet/sing/service/filemanager"
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,7 +30,6 @@ var (
|
|||||||
string(bucketMode),
|
string(bucketMode),
|
||||||
string(bucketRuleSet),
|
string(bucketRuleSet),
|
||||||
string(bucketRDRC),
|
string(bucketRDRC),
|
||||||
string(bucketDNSCache),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheIDDefault = []byte("default")
|
cacheIDDefault = []byte("default")
|
||||||
@@ -41,43 +38,30 @@ var (
|
|||||||
var _ adapter.CacheFile = (*CacheFile)(nil)
|
var _ adapter.CacheFile = (*CacheFile)(nil)
|
||||||
|
|
||||||
type CacheFile struct {
|
type CacheFile struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
logger logger.Logger
|
path string
|
||||||
path string
|
cacheID []byte
|
||||||
cacheID []byte
|
storeFakeIP bool
|
||||||
storeFakeIP bool
|
storeRDRC bool
|
||||||
storeRDRC bool
|
rdrcTimeout time.Duration
|
||||||
storeDNS bool
|
DB *bbolt.DB
|
||||||
disableExpire bool
|
resetAccess sync.Mutex
|
||||||
rdrcTimeout time.Duration
|
saveMetadataTimer *time.Timer
|
||||||
optimisticTimeout time.Duration
|
saveFakeIPAccess sync.RWMutex
|
||||||
DB *bbolt.DB
|
saveDomain map[netip.Addr]string
|
||||||
resetAccess sync.Mutex
|
saveAddress4 map[string]netip.Addr
|
||||||
saveMetadataTimer *time.Timer
|
saveAddress6 map[string]netip.Addr
|
||||||
saveFakeIPAccess sync.RWMutex
|
saveRDRCAccess sync.RWMutex
|
||||||
saveDomain map[netip.Addr]string
|
saveRDRC map[saveRDRCCacheKey]bool
|
||||||
saveAddress4 map[string]netip.Addr
|
|
||||||
saveAddress6 map[string]netip.Addr
|
|
||||||
saveRDRCAccess sync.RWMutex
|
|
||||||
saveRDRC map[saveCacheKey]bool
|
|
||||||
saveDNSCacheAccess sync.RWMutex
|
|
||||||
saveDNSCache map[saveCacheKey]saveDNSCacheEntry
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type saveCacheKey struct {
|
type saveRDRCCacheKey struct {
|
||||||
TransportName string
|
TransportName string
|
||||||
QuestionName string
|
QuestionName string
|
||||||
QType uint16
|
QType uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
type saveDNSCacheEntry struct {
|
func New(ctx context.Context, options option.CacheFileOptions) *CacheFile {
|
||||||
rawMessage []byte
|
|
||||||
expireAt time.Time
|
|
||||||
sequence uint64
|
|
||||||
saving bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(ctx context.Context, logger logger.Logger, options option.CacheFileOptions) *CacheFile {
|
|
||||||
var path string
|
var path string
|
||||||
if options.Path != "" {
|
if options.Path != "" {
|
||||||
path = options.Path
|
path = options.Path
|
||||||
@@ -88,9 +72,6 @@ func New(ctx context.Context, logger logger.Logger, options option.CacheFileOpti
|
|||||||
if options.CacheID != "" {
|
if options.CacheID != "" {
|
||||||
cacheIDBytes = append([]byte{0}, []byte(options.CacheID)...)
|
cacheIDBytes = append([]byte{0}, []byte(options.CacheID)...)
|
||||||
}
|
}
|
||||||
if options.StoreRDRC {
|
|
||||||
deprecated.Report(ctx, deprecated.OptionStoreRDRC)
|
|
||||||
}
|
|
||||||
var rdrcTimeout time.Duration
|
var rdrcTimeout time.Duration
|
||||||
if options.StoreRDRC {
|
if options.StoreRDRC {
|
||||||
if options.RDRCTimeout > 0 {
|
if options.RDRCTimeout > 0 {
|
||||||
@@ -101,18 +82,15 @@ func New(ctx context.Context, logger logger.Logger, options option.CacheFileOpti
|
|||||||
}
|
}
|
||||||
return &CacheFile{
|
return &CacheFile{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
logger: logger,
|
|
||||||
path: filemanager.BasePath(ctx, path),
|
path: filemanager.BasePath(ctx, path),
|
||||||
cacheID: cacheIDBytes,
|
cacheID: cacheIDBytes,
|
||||||
storeFakeIP: options.StoreFakeIP,
|
storeFakeIP: options.StoreFakeIP,
|
||||||
storeRDRC: options.StoreRDRC,
|
storeRDRC: options.StoreRDRC,
|
||||||
storeDNS: options.StoreDNS,
|
|
||||||
rdrcTimeout: rdrcTimeout,
|
rdrcTimeout: rdrcTimeout,
|
||||||
saveDomain: make(map[netip.Addr]string),
|
saveDomain: make(map[netip.Addr]string),
|
||||||
saveAddress4: make(map[string]netip.Addr),
|
saveAddress4: make(map[string]netip.Addr),
|
||||||
saveAddress6: make(map[string]netip.Addr),
|
saveAddress6: make(map[string]netip.Addr),
|
||||||
saveRDRC: make(map[saveCacheKey]bool),
|
saveRDRC: make(map[saveRDRCCacheKey]bool),
|
||||||
saveDNSCache: make(map[saveCacheKey]saveDNSCacheEntry),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,44 +102,10 @@ func (c *CacheFile) Dependencies() []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CacheFile) SetOptimisticTimeout(timeout time.Duration) {
|
|
||||||
c.optimisticTimeout = timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) SetDisableExpire(disableExpire bool) {
|
|
||||||
c.disableExpire = disableExpire
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) Start(stage adapter.StartStage) error {
|
func (c *CacheFile) Start(stage adapter.StartStage) error {
|
||||||
switch stage {
|
if stage != adapter.StartStateInitialize {
|
||||||
case adapter.StartStateInitialize:
|
return nil
|
||||||
return c.start()
|
|
||||||
case adapter.StartStateStart:
|
|
||||||
c.startCacheCleanup()
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) startCacheCleanup() {
|
|
||||||
if c.storeDNS {
|
|
||||||
c.clearRDRC()
|
|
||||||
c.cleanupDNSCache()
|
|
||||||
interval := c.optimisticTimeout / 2
|
|
||||||
if interval <= 0 {
|
|
||||||
interval = time.Hour
|
|
||||||
}
|
|
||||||
go c.loopCacheCleanup(interval, c.cleanupDNSCache)
|
|
||||||
} else if c.storeRDRC {
|
|
||||||
c.cleanupRDRC()
|
|
||||||
interval := c.rdrcTimeout / 2
|
|
||||||
if interval <= 0 {
|
|
||||||
interval = time.Hour
|
|
||||||
}
|
|
||||||
go c.loopCacheCleanup(interval, c.cleanupRDRC)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) start() error {
|
|
||||||
const fileMode = 0o666
|
const fileMode = 0o666
|
||||||
options := bbolt.Options{Timeout: time.Second}
|
options := bbolt.Options{Timeout: time.Second}
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -1,299 +0,0 @@
|
|||||||
package cachefile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/bbolt"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
var bucketDNSCache = []byte("dns_cache")
|
|
||||||
|
|
||||||
func (c *CacheFile) StoreDNS() bool {
|
|
||||||
return c.storeDNS
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) LoadDNSCache(transportName string, qName string, qType uint16) (rawMessage []byte, expireAt time.Time, loaded bool) {
|
|
||||||
c.saveDNSCacheAccess.RLock()
|
|
||||||
entry, cached := c.saveDNSCache[saveCacheKey{transportName, qName, qType}]
|
|
||||||
c.saveDNSCacheAccess.RUnlock()
|
|
||||||
if cached {
|
|
||||||
return entry.rawMessage, entry.expireAt, true
|
|
||||||
}
|
|
||||||
key := buf.Get(2 + len(qName))
|
|
||||||
binary.BigEndian.PutUint16(key, qType)
|
|
||||||
copy(key[2:], qName)
|
|
||||||
defer buf.Put(key)
|
|
||||||
err := c.view(func(tx *bbolt.Tx) error {
|
|
||||||
bucket := c.bucket(tx, bucketDNSCache)
|
|
||||||
if bucket == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
bucket = bucket.Bucket([]byte(transportName))
|
|
||||||
if bucket == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
content := bucket.Get(key)
|
|
||||||
if len(content) < 8 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
expireAt = time.Unix(int64(binary.BigEndian.Uint64(content[:8])), 0)
|
|
||||||
rawMessage = make([]byte, len(content)-8)
|
|
||||||
copy(rawMessage, content[8:])
|
|
||||||
loaded = true
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, time.Time{}, false
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) SaveDNSCache(transportName string, qName string, qType uint16, rawMessage []byte, expireAt time.Time) error {
|
|
||||||
return c.batch(func(tx *bbolt.Tx) error {
|
|
||||||
bucket, err := c.createBucket(tx, bucketDNSCache)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
bucket, err = bucket.CreateBucketIfNotExists([]byte(transportName))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
key := buf.Get(2 + len(qName))
|
|
||||||
binary.BigEndian.PutUint16(key, qType)
|
|
||||||
copy(key[2:], qName)
|
|
||||||
defer buf.Put(key)
|
|
||||||
value := buf.Get(8 + len(rawMessage))
|
|
||||||
defer buf.Put(value)
|
|
||||||
binary.BigEndian.PutUint64(value[:8], uint64(expireAt.Unix()))
|
|
||||||
copy(value[8:], rawMessage)
|
|
||||||
return bucket.Put(key, value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) SaveDNSCacheAsync(transportName string, qName string, qType uint16, rawMessage []byte, expireAt time.Time, logger logger.Logger) {
|
|
||||||
saveKey := saveCacheKey{transportName, qName, qType}
|
|
||||||
if !c.queueDNSCacheSave(saveKey, rawMessage, expireAt) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
go c.flushPendingDNSCache(saveKey, logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) queueDNSCacheSave(saveKey saveCacheKey, rawMessage []byte, expireAt time.Time) bool {
|
|
||||||
c.saveDNSCacheAccess.Lock()
|
|
||||||
defer c.saveDNSCacheAccess.Unlock()
|
|
||||||
entry := c.saveDNSCache[saveKey]
|
|
||||||
entry.rawMessage = append([]byte(nil), rawMessage...)
|
|
||||||
entry.expireAt = expireAt
|
|
||||||
entry.sequence++
|
|
||||||
startFlush := !entry.saving
|
|
||||||
entry.saving = true
|
|
||||||
c.saveDNSCache[saveKey] = entry
|
|
||||||
return startFlush
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) flushPendingDNSCache(saveKey saveCacheKey, logger logger.Logger) {
|
|
||||||
c.flushPendingDNSCacheWith(saveKey, logger, func(entry saveDNSCacheEntry) error {
|
|
||||||
return c.SaveDNSCache(saveKey.TransportName, saveKey.QuestionName, saveKey.QType, entry.rawMessage, entry.expireAt)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) flushPendingDNSCacheWith(saveKey saveCacheKey, logger logger.Logger, save func(saveDNSCacheEntry) error) {
|
|
||||||
for {
|
|
||||||
c.saveDNSCacheAccess.RLock()
|
|
||||||
entry, loaded := c.saveDNSCache[saveKey]
|
|
||||||
c.saveDNSCacheAccess.RUnlock()
|
|
||||||
if !loaded {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err := save(entry)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warn("save DNS cache: ", err)
|
|
||||||
}
|
|
||||||
c.saveDNSCacheAccess.Lock()
|
|
||||||
currentEntry, loaded := c.saveDNSCache[saveKey]
|
|
||||||
if !loaded {
|
|
||||||
c.saveDNSCacheAccess.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if currentEntry.sequence != entry.sequence {
|
|
||||||
c.saveDNSCacheAccess.Unlock()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delete(c.saveDNSCache, saveKey)
|
|
||||||
c.saveDNSCacheAccess.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) ClearDNSCache() error {
|
|
||||||
c.saveDNSCacheAccess.Lock()
|
|
||||||
clear(c.saveDNSCache)
|
|
||||||
c.saveDNSCacheAccess.Unlock()
|
|
||||||
return c.batch(func(tx *bbolt.Tx) error {
|
|
||||||
if c.cacheID == nil {
|
|
||||||
bucket := tx.Bucket(bucketDNSCache)
|
|
||||||
if bucket == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return tx.DeleteBucket(bucketDNSCache)
|
|
||||||
}
|
|
||||||
bucket := tx.Bucket(c.cacheID)
|
|
||||||
if bucket == nil || bucket.Bucket(bucketDNSCache) == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return bucket.DeleteBucket(bucketDNSCache)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) loopCacheCleanup(interval time.Duration, cleanupFunc func()) {
|
|
||||||
ticker := time.NewTicker(interval)
|
|
||||||
defer ticker.Stop()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-c.ctx.Done():
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
cleanupFunc()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) cleanupDNSCache() {
|
|
||||||
now := time.Now()
|
|
||||||
err := c.batch(func(tx *bbolt.Tx) error {
|
|
||||||
bucket := c.bucket(tx, bucketDNSCache)
|
|
||||||
if bucket == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var emptyTransports [][]byte
|
|
||||||
err := bucket.ForEachBucket(func(transportName []byte) error {
|
|
||||||
transportBucket := bucket.Bucket(transportName)
|
|
||||||
if transportBucket == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var expiredKeys [][]byte
|
|
||||||
err := transportBucket.ForEach(func(key, value []byte) error {
|
|
||||||
if len(value) < 8 {
|
|
||||||
expiredKeys = append(expiredKeys, append([]byte(nil), key...))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if c.disableExpire {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
expireAt := time.Unix(int64(binary.BigEndian.Uint64(value[:8])), 0)
|
|
||||||
if now.After(expireAt.Add(c.optimisticTimeout)) {
|
|
||||||
expiredKeys = append(expiredKeys, append([]byte(nil), key...))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, key := range expiredKeys {
|
|
||||||
err = transportBucket.Delete(key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
first, _ := transportBucket.Cursor().First()
|
|
||||||
if first == nil {
|
|
||||||
emptyTransports = append(emptyTransports, append([]byte(nil), transportName...))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, name := range emptyTransports {
|
|
||||||
err = bucket.DeleteBucket(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Warn("cleanup DNS cache: ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) clearRDRC() {
|
|
||||||
c.saveRDRCAccess.Lock()
|
|
||||||
clear(c.saveRDRC)
|
|
||||||
c.saveRDRCAccess.Unlock()
|
|
||||||
err := c.batch(func(tx *bbolt.Tx) error {
|
|
||||||
if c.cacheID == nil {
|
|
||||||
if tx.Bucket(bucketRDRC) == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return tx.DeleteBucket(bucketRDRC)
|
|
||||||
}
|
|
||||||
bucket := tx.Bucket(c.cacheID)
|
|
||||||
if bucket == nil || bucket.Bucket(bucketRDRC) == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return bucket.DeleteBucket(bucketRDRC)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Warn("clear RDRC: ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) cleanupRDRC() {
|
|
||||||
now := time.Now()
|
|
||||||
err := c.batch(func(tx *bbolt.Tx) error {
|
|
||||||
bucket := c.bucket(tx, bucketRDRC)
|
|
||||||
if bucket == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var emptyTransports [][]byte
|
|
||||||
err := bucket.ForEachBucket(func(transportName []byte) error {
|
|
||||||
transportBucket := bucket.Bucket(transportName)
|
|
||||||
if transportBucket == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var expiredKeys [][]byte
|
|
||||||
err := transportBucket.ForEach(func(key, value []byte) error {
|
|
||||||
if len(value) < 8 {
|
|
||||||
expiredKeys = append(expiredKeys, append([]byte(nil), key...))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
expiresAt := time.Unix(int64(binary.BigEndian.Uint64(value)), 0)
|
|
||||||
if now.After(expiresAt) {
|
|
||||||
expiredKeys = append(expiredKeys, append([]byte(nil), key...))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, key := range expiredKeys {
|
|
||||||
err = transportBucket.Delete(key)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
first, _ := transportBucket.Cursor().First()
|
|
||||||
if first == nil {
|
|
||||||
emptyTransports = append(emptyTransports, append([]byte(nil), transportName...))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, name := range emptyTransports {
|
|
||||||
err = bucket.DeleteBucket(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Warn("cleanup RDRC: ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -21,7 +21,7 @@ func (c *CacheFile) RDRCTimeout() time.Duration {
|
|||||||
|
|
||||||
func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) (rejected bool) {
|
func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) (rejected bool) {
|
||||||
c.saveRDRCAccess.RLock()
|
c.saveRDRCAccess.RLock()
|
||||||
rejected, cached := c.saveRDRC[saveCacheKey{transportName, qName, qType}]
|
rejected, cached := c.saveRDRC[saveRDRCCacheKey{transportName, qName, qType}]
|
||||||
c.saveRDRCAccess.RUnlock()
|
c.saveRDRCAccess.RUnlock()
|
||||||
if cached {
|
if cached {
|
||||||
return
|
return
|
||||||
@@ -93,7 +93,7 @@ func (c *CacheFile) SaveRDRC(transportName string, qName string, qType uint16) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *CacheFile) SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger) {
|
func (c *CacheFile) SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger) {
|
||||||
saveKey := saveCacheKey{transportName, qName, qType}
|
saveKey := saveRDRCCacheKey{transportName, qName, qType}
|
||||||
c.saveRDRCAccess.Lock()
|
c.saveRDRCAccess.Lock()
|
||||||
c.saveRDRC[saveKey] = true
|
c.saveRDRC[saveKey] = true
|
||||||
c.saveRDRCAccess.Unlock()
|
c.saveRDRCAccess.Unlock()
|
||||||
|
|||||||
@@ -57,6 +57,24 @@ func (n Note) MessageWithLink() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var OptionLegacyDNSTransport = Note{
|
||||||
|
Name: "legacy-dns-transport",
|
||||||
|
Description: "legacy DNS servers",
|
||||||
|
DeprecatedVersion: "1.12.0",
|
||||||
|
ScheduledVersion: "1.14.0",
|
||||||
|
EnvName: "LEGACY_DNS_SERVERS",
|
||||||
|
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats",
|
||||||
|
}
|
||||||
|
|
||||||
|
var OptionLegacyDNSFakeIPOptions = Note{
|
||||||
|
Name: "legacy-dns-fakeip-options",
|
||||||
|
Description: "legacy DNS fakeip options",
|
||||||
|
DeprecatedVersion: "1.12.0",
|
||||||
|
ScheduledVersion: "1.14.0",
|
||||||
|
EnvName: "LEGACY_DNS_FAKEIP_OPTIONS",
|
||||||
|
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats",
|
||||||
|
}
|
||||||
|
|
||||||
var OptionOutboundDNSRuleItem = Note{
|
var OptionOutboundDNSRuleItem = Note{
|
||||||
Name: "outbound-dns-rule-item",
|
Name: "outbound-dns-rule-item",
|
||||||
Description: "outbound DNS rule item",
|
Description: "outbound DNS rule item",
|
||||||
@@ -93,59 +111,11 @@ var OptionInlineACME = Note{
|
|||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-inline-acme-to-certificate-provider",
|
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-inline-acme-to-certificate-provider",
|
||||||
}
|
}
|
||||||
|
|
||||||
var OptionRuleSetIPCIDRAcceptEmpty = Note{
|
|
||||||
Name: "dns-rule-rule-set-ip-cidr-accept-empty",
|
|
||||||
Description: "Legacy `rule_set_ip_cidr_accept_empty` DNS rule item",
|
|
||||||
DeprecatedVersion: "1.14.0",
|
|
||||||
ScheduledVersion: "1.16.0",
|
|
||||||
EnvName: "DNS_RULE_RULE_SET_IP_CIDR_ACCEPT_EMPTY",
|
|
||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-address-filter-fields-to-response-matching",
|
|
||||||
}
|
|
||||||
|
|
||||||
var OptionLegacyDNSAddressFilter = Note{
|
|
||||||
Name: "legacy-dns-address-filter",
|
|
||||||
Description: "Legacy Address Filter Fields in DNS rules",
|
|
||||||
DeprecatedVersion: "1.14.0",
|
|
||||||
ScheduledVersion: "1.16.0",
|
|
||||||
EnvName: "LEGACY_DNS_ADDRESS_FILTER",
|
|
||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-address-filter-fields-to-response-matching",
|
|
||||||
}
|
|
||||||
|
|
||||||
var OptionLegacyDNSRuleStrategy = Note{
|
|
||||||
Name: "legacy-dns-rule-strategy",
|
|
||||||
Description: "Legacy `strategy` DNS rule action option",
|
|
||||||
DeprecatedVersion: "1.14.0",
|
|
||||||
ScheduledVersion: "1.16.0",
|
|
||||||
EnvName: "LEGACY_DNS_RULE_STRATEGY",
|
|
||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-dns-rule-action-strategy-to-rule-items",
|
|
||||||
}
|
|
||||||
|
|
||||||
var OptionIndependentDNSCache = Note{
|
|
||||||
Name: "independent-dns-cache",
|
|
||||||
Description: "`independent_cache` DNS option",
|
|
||||||
DeprecatedVersion: "1.14.0",
|
|
||||||
ScheduledVersion: "1.16.0",
|
|
||||||
EnvName: "INDEPENDENT_DNS_CACHE",
|
|
||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-independent-dns-cache",
|
|
||||||
}
|
|
||||||
|
|
||||||
var OptionStoreRDRC = Note{
|
|
||||||
Name: "store-rdrc",
|
|
||||||
Description: "`store_rdrc` cache file option",
|
|
||||||
DeprecatedVersion: "1.14.0",
|
|
||||||
ScheduledVersion: "1.16.0",
|
|
||||||
EnvName: "STORE_RDRC",
|
|
||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-store-rdrc",
|
|
||||||
}
|
|
||||||
|
|
||||||
var Options = []Note{
|
var Options = []Note{
|
||||||
|
OptionLegacyDNSTransport,
|
||||||
|
OptionLegacyDNSFakeIPOptions,
|
||||||
OptionOutboundDNSRuleItem,
|
OptionOutboundDNSRuleItem,
|
||||||
OptionMissingDomainResolver,
|
OptionMissingDomainResolver,
|
||||||
OptionLegacyDomainStrategyOptions,
|
OptionLegacyDomainStrategyOptions,
|
||||||
OptionInlineACME,
|
OptionInlineACME,
|
||||||
OptionRuleSetIPCIDRAcceptEmpty,
|
|
||||||
OptionLegacyDNSAddressFilter,
|
|
||||||
OptionLegacyDNSRuleStrategy,
|
|
||||||
OptionIndependentDNSCache,
|
|
||||||
OptionStoreRDRC,
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user