mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
67 Commits
dependabot
...
470c605118
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
470c605118 | ||
|
|
3795ac1503 | ||
|
|
8be4f38eaf | ||
|
|
4152022b89 | ||
|
|
5b01eaa149 | ||
|
|
3d32ad79cd | ||
|
|
e49a7bcc86 | ||
|
|
c94696df9e | ||
|
|
a7c4096a07 | ||
|
|
0e87476ee5 | ||
|
|
21f3acef81 | ||
|
|
861fa897e0 | ||
|
|
c471d7fee7 | ||
|
|
7de00c7cd1 | ||
|
|
29024191ee | ||
|
|
16480095f7 | ||
|
|
cf33f1f375 | ||
|
|
1d872a6835 | ||
|
|
fb19bf6111 | ||
|
|
da210af48d | ||
|
|
6a351be73a | ||
|
|
1913376113 | ||
|
|
b05f58b469 | ||
|
|
baf1da892b | ||
|
|
f3c8fe59ac | ||
|
|
117422db68 | ||
|
|
5b32dbf57f | ||
|
|
d103fc2aea | ||
|
|
532f350637 | ||
|
|
d82e7cd4b6 | ||
|
|
f628519333 | ||
|
|
7def08b5a1 | ||
|
|
8ba8ad5f0c | ||
|
|
07f2fd65b2 | ||
|
|
31c707f8e8 | ||
|
|
3549c02b8c | ||
|
|
e5aaf782c6 | ||
|
|
5b08ae150f | ||
|
|
704482bb4a | ||
|
|
5436192ada | ||
|
|
d2f005aea3 | ||
|
|
dc9b2089ea | ||
|
|
b16b6f8b18 | ||
|
|
ab414f20f5 | ||
|
|
f8cbe27b39 | ||
|
|
2544d26664 | ||
|
|
bcaba94c61 | ||
|
|
ebf8a213b6 | ||
|
|
ab323e0eb9 | ||
|
|
2132e68d3a | ||
|
|
47742abe93 | ||
|
|
77e51035bd | ||
|
|
eeb5dead2a | ||
|
|
45339d101b | ||
|
|
04c0490992 | ||
|
|
7ffdc48b49 | ||
|
|
e15bdf11eb | ||
|
|
e3bcb06c3e | ||
|
|
84d2280960 | ||
|
|
4fd2532b0a | ||
|
|
02ccde6c71 | ||
|
|
e98b4ad449 | ||
|
|
d09182614c | ||
|
|
6381de7bab | ||
|
|
b0c6762bc1 | ||
|
|
7425100bac | ||
|
|
d454aa0fdf |
@@ -4,6 +4,7 @@
|
||||
--license GPL-3.0-or-later
|
||||
--description "The universal proxy platform."
|
||||
--url "https://sing-box.sagernet.org/"
|
||||
--vendor SagerNet
|
||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||
--no-deb-generate-changes
|
||||
|
||||
33
.github/detect_track.sh
vendored
Executable file
33
.github/detect_track.sh
vendored
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
branches=$(git branch -r --contains HEAD)
|
||||
if echo "$branches" | grep -q 'origin/stable'; then
|
||||
track=stable
|
||||
elif echo "$branches" | grep -q 'origin/testing'; then
|
||||
track=testing
|
||||
elif echo "$branches" | grep -q 'origin/oldstable'; then
|
||||
track=oldstable
|
||||
else
|
||||
echo "ERROR: HEAD is not on any known release branch (stable/testing/oldstable)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$track" == "stable" ]]; then
|
||||
tag=$(git describe --tags --exact-match HEAD 2>/dev/null || true)
|
||||
if [[ -n "$tag" && "$tag" == *"-"* ]]; then
|
||||
track=beta
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$track" in
|
||||
stable) name=sing-box; docker_tag=latest ;;
|
||||
beta) name=sing-box-beta; docker_tag=latest-beta ;;
|
||||
testing) name=sing-box-testing; docker_tag=latest-testing ;;
|
||||
oldstable) name=sing-box-oldstable; docker_tag=latest-oldstable ;;
|
||||
esac
|
||||
|
||||
echo "track=${track} name=${name} docker_tag=${docker_tag}" >&2
|
||||
echo "TRACK=${track}" >> "$GITHUB_ENV"
|
||||
echo "NAME=${name}" >> "$GITHUB_ENV"
|
||||
echo "DOCKER_TAG=${docker_tag}" >> "$GITHUB_ENV"
|
||||
19
.github/workflows/docker.yml
vendored
19
.github/workflows/docker.yml
vendored
@@ -19,7 +19,6 @@ env:
|
||||
jobs:
|
||||
build_binary:
|
||||
name: Build binary
|
||||
if: github.event_name != 'release' || github.event.release.target_commitish != 'oldstable'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: true
|
||||
@@ -260,13 +259,13 @@ jobs:
|
||||
fi
|
||||
echo "ref=$ref"
|
||||
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||
if [[ $ref == *"-"* ]]; then
|
||||
latest=latest-beta
|
||||
else
|
||||
latest=latest
|
||||
fi
|
||||
echo "latest=$latest"
|
||||
echo "latest=$latest" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
ref: ${{ steps.ref.outputs.ref }}
|
||||
fetch-depth: 0
|
||||
- name: Detect track
|
||||
run: bash .github/detect_track.sh
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
@@ -286,11 +285,11 @@ jobs:
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}" \
|
||||
-t "${{ env.REGISTRY_IMAGE }}:${{ env.DOCKER_TAG }}" \
|
||||
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}" \
|
||||
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
||||
- name: Inspect image
|
||||
if: github.event_name != 'push'
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}
|
||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ env.DOCKER_TAG }}
|
||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}
|
||||
|
||||
16
.github/workflows/linux.yml
vendored
16
.github/workflows/linux.yml
vendored
@@ -11,11 +11,6 @@ on:
|
||||
description: "Version name"
|
||||
required: true
|
||||
type: string
|
||||
forceBeta:
|
||||
description: "Force beta"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
@@ -23,7 +18,6 @@ on:
|
||||
jobs:
|
||||
calculate_version:
|
||||
name: Calculate version
|
||||
if: github.event_name != 'release' || github.event.release.target_commitish != 'oldstable'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.outputs.outputs.version }}
|
||||
@@ -168,14 +162,8 @@ jobs:
|
||||
- name: Set mtime
|
||||
run: |-
|
||||
TZ=UTC touch -t '197001010000' dist/sing-box
|
||||
- name: Set name
|
||||
if: (! contains(needs.calculate_version.outputs.version, '-')) && !inputs.forceBeta
|
||||
run: |-
|
||||
echo "NAME=sing-box" >> "$GITHUB_ENV"
|
||||
- name: Set beta name
|
||||
if: contains(needs.calculate_version.outputs.version, '-') || inputs.forceBeta
|
||||
run: |-
|
||||
echo "NAME=sing-box-beta" >> "$GITHUB_ENV"
|
||||
- name: Detect track
|
||||
run: bash .github/detect_track.sh
|
||||
- name: Set version
|
||||
run: |-
|
||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||
|
||||
@@ -25,8 +25,8 @@ type DNSRouter interface {
|
||||
|
||||
type DNSClient interface {
|
||||
Start()
|
||||
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(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)
|
||||
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(response *dns.Msg) bool) (*dns.Msg, error)
|
||||
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error)
|
||||
ClearCache()
|
||||
}
|
||||
|
||||
@@ -72,11 +72,6 @@ type DNSTransport interface {
|
||||
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
||||
}
|
||||
|
||||
type LegacyDNSTransport interface {
|
||||
LegacyStrategy() C.DomainStrategy
|
||||
LegacyClientSubnet() netip.Prefix
|
||||
}
|
||||
|
||||
type DNSTransportRegistry interface {
|
||||
option.DNSTransportOptionsRegistry
|
||||
CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error)
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type Inbound interface {
|
||||
@@ -79,14 +81,16 @@ type InboundContext struct {
|
||||
FallbackNetworkType []C.InterfaceType
|
||||
FallbackDelay time.Duration
|
||||
|
||||
DestinationAddresses []netip.Addr
|
||||
SourceGeoIPCode string
|
||||
GeoIPCode string
|
||||
ProcessInfo *ConnectionOwner
|
||||
SourceMACAddress net.HardwareAddr
|
||||
SourceHostname string
|
||||
QueryType uint16
|
||||
FakeIP bool
|
||||
DestinationAddresses []netip.Addr
|
||||
DNSResponse *dns.Msg
|
||||
DestinationAddressMatchFromResponse bool
|
||||
SourceGeoIPCode string
|
||||
GeoIPCode string
|
||||
ProcessInfo *ConnectionOwner
|
||||
SourceMACAddress net.HardwareAddr
|
||||
SourceHostname string
|
||||
QueryType uint16
|
||||
FakeIP bool
|
||||
|
||||
// rule cache
|
||||
|
||||
@@ -95,10 +99,9 @@ type InboundContext struct {
|
||||
|
||||
SourceAddressMatch bool
|
||||
SourcePortMatch bool
|
||||
DestinationAddressMatch bool
|
||||
DestinationPortMatch bool
|
||||
DidMatch bool
|
||||
IgnoreDestinationIPCIDRMatch bool
|
||||
DestinationAddressMatch bool
|
||||
DestinationPortMatch bool
|
||||
DidMatch bool
|
||||
}
|
||||
|
||||
func (c *InboundContext) ResetRuleCache() {
|
||||
@@ -115,6 +118,39 @@ func (c *InboundContext) ResetRuleMatchCache() {
|
||||
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:
|
||||
addresses = append(addresses, M.AddrFromIP(record.A))
|
||||
case *dns.AAAA:
|
||||
addresses = append(addresses, M.AddrFromIP(record.AAAA))
|
||||
case *dns.HTTPS:
|
||||
for _, value := range record.SVCB.Value {
|
||||
switch hint := value.(type) {
|
||||
case *dns.SVCBIPv4Hint:
|
||||
for _, ip := range hint.Hint {
|
||||
addresses = append(addresses, M.AddrFromIP(ip).Unmap())
|
||||
}
|
||||
case *dns.SVCBIPv6Hint:
|
||||
for _, ip := range hint.Hint {
|
||||
addresses = append(addresses, M.AddrFromIP(ip))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
|
||||
type inboundContextKey struct{}
|
||||
|
||||
func WithContext(ctx context.Context, inboundContext *InboundContext) context.Context {
|
||||
|
||||
45
adapter/inbound_test.go
Normal file
45
adapter/inbound_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
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,10 +66,14 @@ type RuleSet interface {
|
||||
|
||||
type RuleSetUpdateCallback func(it RuleSet)
|
||||
|
||||
// Rule-set metadata only exposes headless-rule capabilities that outer routers
|
||||
// need before evaluating nested matches. Headless rules do not support
|
||||
// ip_version, so there is intentionally no ContainsIPVersionRule flag here.
|
||||
type RuleSetMetadata struct {
|
||||
ContainsProcessRule bool
|
||||
ContainsWIFIRule bool
|
||||
ContainsIPCIDRRule bool
|
||||
ContainsProcessRule bool
|
||||
ContainsWIFIRule bool
|
||||
ContainsIPCIDRRule bool
|
||||
ContainsDNSQueryTypeRule bool
|
||||
}
|
||||
type HTTPStartContext struct {
|
||||
ctx context.Context
|
||||
|
||||
@@ -2,6 +2,8 @@ package adapter
|
||||
|
||||
import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type HeadlessRule interface {
|
||||
@@ -18,8 +20,9 @@ type Rule interface {
|
||||
|
||||
type DNSRule interface {
|
||||
Rule
|
||||
LegacyPreMatch(metadata *InboundContext) bool
|
||||
WithAddressLimit() bool
|
||||
MatchAddressLimit(metadata *InboundContext) bool
|
||||
MatchAddressLimit(metadata *InboundContext, response *dns.Msg) bool
|
||||
}
|
||||
|
||||
type RuleAction interface {
|
||||
|
||||
2
box.go
2
box.go
@@ -486,7 +486,7 @@ func (s *Box) preStart() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.network, s.connection, s.router, s.dnsRouter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Submodule clients/android updated: 82ffed6cd7...cba1cc3ce0
Submodule clients/apple updated: bcdd385b79...ffbf405b52
@@ -15,19 +15,18 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
DNSTypeLegacy = "legacy"
|
||||
DNSTypeLegacyRcode = "legacy_rcode"
|
||||
DNSTypeUDP = "udp"
|
||||
DNSTypeTCP = "tcp"
|
||||
DNSTypeTLS = "tls"
|
||||
DNSTypeHTTPS = "https"
|
||||
DNSTypeQUIC = "quic"
|
||||
DNSTypeHTTP3 = "h3"
|
||||
DNSTypeLocal = "local"
|
||||
DNSTypeHosts = "hosts"
|
||||
DNSTypeFakeIP = "fakeip"
|
||||
DNSTypeDHCP = "dhcp"
|
||||
DNSTypeTailscale = "tailscale"
|
||||
DNSTypeLegacy = "legacy"
|
||||
DNSTypeUDP = "udp"
|
||||
DNSTypeTCP = "tcp"
|
||||
DNSTypeTLS = "tls"
|
||||
DNSTypeHTTPS = "https"
|
||||
DNSTypeQUIC = "quic"
|
||||
DNSTypeHTTP3 = "h3"
|
||||
DNSTypeLocal = "local"
|
||||
DNSTypeHosts = "hosts"
|
||||
DNSTypeFakeIP = "fakeip"
|
||||
DNSTypeDHCP = "dhcp"
|
||||
DNSTypeTailscale = "tailscale"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -29,6 +29,7 @@ const (
|
||||
const (
|
||||
RuleActionTypeRoute = "route"
|
||||
RuleActionTypeRouteOptions = "route-options"
|
||||
RuleActionTypeEvaluate = "evaluate"
|
||||
RuleActionTypeDirect = "direct"
|
||||
RuleActionTypeBypass = "bypass"
|
||||
RuleActionTypeReject = "reject"
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -14,7 +13,6 @@ import (
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/task"
|
||||
"github.com/sagernet/sing/contrab/freelru"
|
||||
"github.com/sagernet/sing/contrab/maphash"
|
||||
@@ -109,7 +107,7 @@ func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
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) {
|
||||
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 c.logger != nil {
|
||||
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
||||
@@ -239,13 +237,10 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
disableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError)
|
||||
if responseChecker != nil {
|
||||
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 {
|
||||
rejected = true
|
||||
} else if len(response.Answer) == 0 {
|
||||
rejected = !responseChecker(nil)
|
||||
} else {
|
||||
rejected = !responseChecker(MessageToAddresses(response))
|
||||
rejected = !responseChecker(response)
|
||||
}
|
||||
if rejected {
|
||||
if !disableCache && c.rdrc != nil {
|
||||
@@ -315,7 +310,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error) {
|
||||
domain = FqdnToDomain(domain)
|
||||
dnsName := dns.Fqdn(domain)
|
||||
var strategy C.DomainStrategy
|
||||
@@ -400,7 +395,7 @@ func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Questio
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name string, qType uint16, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||
func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name string, qType uint16, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error) {
|
||||
question := dns.Question{
|
||||
Name: name,
|
||||
Qtype: qType,
|
||||
@@ -515,25 +510,7 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
|
||||
}
|
||||
|
||||
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
||||
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
|
||||
return adapter.DNSResponseAddresses(response)
|
||||
}
|
||||
|
||||
func wrapError(err error) error {
|
||||
|
||||
111
dns/repro_test.go
Normal file
111
dns/repro_test.go
Normal file
@@ -0,0 +1,111 @@
|
||||
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))
|
||||
}
|
||||
874
dns/router.go
874
dns/router.go
File diff suppressed because it is too large
Load Diff
3147
dns/router_test.go
Normal file
3147
dns/router_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,21 +1,13 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
var _ adapter.LegacyDNSTransport = (*TransportAdapter)(nil)
|
||||
|
||||
type TransportAdapter struct {
|
||||
transportType string
|
||||
transportTag string
|
||||
dependencies []string
|
||||
strategy C.DomainStrategy
|
||||
clientSubnet netip.Prefix
|
||||
}
|
||||
|
||||
func NewTransportAdapter(transportType string, transportTag string, dependencies []string) TransportAdapter {
|
||||
@@ -35,8 +27,6 @@ func NewTransportAdapterWithLocalOptions(transportType string, transportTag stri
|
||||
transportType: transportType,
|
||||
transportTag: transportTag,
|
||||
dependencies: dependencies,
|
||||
strategy: C.DomainStrategy(localOptions.LegacyStrategy),
|
||||
clientSubnet: localOptions.LegacyClientSubnet,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,15 +35,10 @@ func NewTransportAdapterWithRemoteOptions(transportType string, transportTag str
|
||||
if remoteOptions.DomainResolver != nil && remoteOptions.DomainResolver.Server != "" {
|
||||
dependencies = append(dependencies, remoteOptions.DomainResolver.Server)
|
||||
}
|
||||
if remoteOptions.LegacyAddressResolver != "" {
|
||||
dependencies = append(dependencies, remoteOptions.LegacyAddressResolver)
|
||||
}
|
||||
return TransportAdapter{
|
||||
transportType: transportType,
|
||||
transportTag: transportTag,
|
||||
dependencies: dependencies,
|
||||
strategy: C.DomainStrategy(remoteOptions.LegacyStrategy),
|
||||
clientSubnet: remoteOptions.LegacyClientSubnet,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,11 +53,3 @@ func (a *TransportAdapter) Tag() string {
|
||||
func (a *TransportAdapter) Dependencies() []string {
|
||||
return a.dependencies
|
||||
}
|
||||
|
||||
func (a *TransportAdapter) LegacyStrategy() C.DomainStrategy {
|
||||
return a.strategy
|
||||
}
|
||||
|
||||
func (a *TransportAdapter) LegacyClientSubnet() netip.Prefix {
|
||||
return a.clientSubnet
|
||||
}
|
||||
|
||||
@@ -2,104 +2,25 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"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"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (N.Dialer, error) {
|
||||
if options.LegacyDefaultDialer {
|
||||
return dialer.NewDefaultOutbound(ctx), nil
|
||||
} else {
|
||||
return dialer.NewWithOptions(dialer.Options{
|
||||
Context: ctx,
|
||||
Options: options.DialerOptions,
|
||||
DirectResolver: true,
|
||||
LegacyDNSDialer: options.Legacy,
|
||||
})
|
||||
}
|
||||
return dialer.NewWithOptions(dialer.Options{
|
||||
Context: ctx,
|
||||
Options: options.DialerOptions,
|
||||
DirectResolver: true,
|
||||
})
|
||||
}
|
||||
|
||||
func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) {
|
||||
if options.LegacyDefaultDialer {
|
||||
transportDialer := dialer.NewDefaultOutbound(ctx)
|
||||
if options.LegacyAddressResolver != "" {
|
||||
transport := service.FromContext[adapter.DNSTransportManager](ctx)
|
||||
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,
|
||||
return dialer.NewWithOptions(dialer.Options{
|
||||
Context: ctx,
|
||||
Options: options.DialerOptions,
|
||||
RemoteIsDomain: options.ServerIsDomain(),
|
||||
DirectResolver: true,
|
||||
})
|
||||
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,13 +2,49 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 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
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.13.4
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.14.0-alpha.4
|
||||
|
||||
* Refactor ACME support to certificate provider system **1**
|
||||
* Add Cloudflare Origin CA certificate provider **2**
|
||||
* Add Tailscale certificate provider **3**
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.13.4-beta.1
|
||||
**1**:
|
||||
|
||||
* Fixes and improvements
|
||||
See [Certificate Provider](/configuration/shared/certificate-provider/) and [Migration](/migration/#migrate-inline-acme-to-certificate-provider).
|
||||
|
||||
**2**:
|
||||
|
||||
See [Cloudflare Origin CA](/configuration/shared/certificate-provider/cloudflare-origin-ca).
|
||||
|
||||
**3**:
|
||||
|
||||
See [Tailscale](/configuration/shared/certificate-provider/tailscale).
|
||||
|
||||
#### 1.13.3
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
icon: material/note-remove
|
||||
---
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.12.0"
|
||||
!!! failure "Removed in sing-box 1.14.0"
|
||||
|
||||
Legacy fake-ip configuration is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
|
||||
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-servers).
|
||||
|
||||
### Structure
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
icon: material/note-remove
|
||||
---
|
||||
|
||||
!!! failure "已在 sing-box 1.12.0 废弃"
|
||||
!!! failure "已在 sing-box 1.14.0 移除"
|
||||
|
||||
旧的 fake-ip 配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
||||
旧的 fake-ip 配置已在 sing-box 1.12.0 废弃且已在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
||||
|
||||
### 结构
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ icon: material/alert-decagram
|
||||
|----------|---------------------------------|
|
||||
| `server` | List of [DNS Server](./server/) |
|
||||
| `rules` | List of [DNS Rule](./rule/) |
|
||||
| `fakeip` | [FakeIP](./fakeip/) |
|
||||
| `fakeip` | :material-note-remove: [FakeIP](./fakeip/) |
|
||||
|
||||
#### final
|
||||
|
||||
@@ -88,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.
|
||||
|
||||
Can be overrides by `servers.[].client_subnet` or `rules.[].client_subnet`.
|
||||
Can be overridden by `servers.[].client_subnet` or `rules.[].client_subnet`.
|
||||
|
||||
@@ -88,6 +88,6 @@ LRU 缓存容量。
|
||||
|
||||
可以被 `servers.[].client_subnet` 或 `rules.[].client_subnet` 覆盖。
|
||||
|
||||
#### fakeip
|
||||
#### fakeip :material-note-remove:
|
||||
|
||||
[FakeIP](./fakeip/) 设置。
|
||||
|
||||
@@ -4,8 +4,15 @@ icon: material/alert-decagram
|
||||
|
||||
!!! quote "Changes in sing-box 1.14.0"
|
||||
|
||||
:material-plus: [match_response](#match_response)
|
||||
: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: [source_mac_address](#source_mac_address)
|
||||
:material-plus: [source_hostname](#source_hostname)
|
||||
:material-plus: [source_hostname](#source_hostname)
|
||||
:material-delete-clock: [ip_accept_any](#ip_accept_any)
|
||||
:material-delete-clock: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
||||
|
||||
!!! quote "Changes in sing-box 1.13.0"
|
||||
|
||||
@@ -94,12 +101,6 @@ icon: material/alert-decagram
|
||||
"192.168.0.1"
|
||||
],
|
||||
"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": [
|
||||
12345
|
||||
],
|
||||
@@ -171,7 +172,16 @@ icon: material/alert-decagram
|
||||
"geosite-cn"
|
||||
],
|
||||
"rule_set_ip_cidr_match_source": false,
|
||||
"rule_set_ip_cidr_accept_empty": false,
|
||||
"match_response": false,
|
||||
"ip_cidr": [
|
||||
"10.0.0.0/24",
|
||||
"192.168.0.1"
|
||||
],
|
||||
"ip_is_private": false,
|
||||
"response_rcode": "",
|
||||
"response_answer": [],
|
||||
"response_ns": [],
|
||||
"response_extra": [],
|
||||
"invert": false,
|
||||
"outbound": [
|
||||
"direct"
|
||||
@@ -180,7 +190,9 @@ icon: material/alert-decagram
|
||||
"server": "local",
|
||||
|
||||
// Deprecated
|
||||
|
||||
|
||||
"ip_accept_any": false,
|
||||
"rule_set_ip_cidr_accept_empty": false,
|
||||
"rule_set_ipcidr_match_source": false,
|
||||
"geosite": [
|
||||
"cn"
|
||||
@@ -220,7 +232,7 @@ icon: material/alert-decagram
|
||||
(`source_port` || `source_port_range`) &&
|
||||
`other fields`
|
||||
|
||||
Additionally, included rule-sets can be considered merged rather than as a single rule sub-item.
|
||||
Additionally, each branch inside an included rule-set can be considered merged into the outer rule, while different branches keep OR semantics.
|
||||
|
||||
#### inbound
|
||||
|
||||
@@ -477,6 +489,17 @@ 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 DNS response data
|
||||
(set by a preceding [`evaluate`](/configuration/dns/rule_action/#evaluate) action)
|
||||
instead of only matching the original query.
|
||||
|
||||
Required for `response_rcode`, `response_answer`, `response_ns`, `response_extra` fields.
|
||||
Also required for `ip_cidr` and `ip_is_private` when `legacyDNSMode` is disabled.
|
||||
|
||||
#### invert
|
||||
|
||||
Invert match result.
|
||||
@@ -521,6 +544,15 @@ See [DNS Rule Actions](../rule_action/) for details.
|
||||
|
||||
Moved to [DNS Rule Action](../rule_action#route).
|
||||
|
||||
### Legacy DNS Mode
|
||||
|
||||
`legacyDNSMode` is an internal compatibility mode that is automatically detected from your DNS rule
|
||||
configuration. It is disabled when any rule uses features introduced in sing-box 1.14.0 such as
|
||||
`evaluate`, `match_response`, response fields (`response_rcode`, `response_answer`, etc.),
|
||||
`query_type`, or `ip_version`. When disabled, `ip_cidr` and `ip_is_private` require `match_response`
|
||||
to be set, and deprecated fields like `strategy`, `ip_accept_any`, and `rule_set_ip_cidr_accept_empty`
|
||||
are no longer accepted.
|
||||
|
||||
### Address Filter Fields
|
||||
|
||||
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.
|
||||
@@ -547,24 +579,69 @@ Match GeoIP with query response.
|
||||
|
||||
Match IP CIDR with query response.
|
||||
|
||||
When `legacyDNSMode` is disabled, `match_response` must be set to `true`.
|
||||
|
||||
#### ip_is_private
|
||||
|
||||
!!! question "Since sing-box 1.9.0"
|
||||
|
||||
Match private IP with query response.
|
||||
|
||||
When `legacyDNSMode` is disabled, `match_response` must be set to `true`.
|
||||
|
||||
#### rule_set_ip_cidr_accept_empty
|
||||
|
||||
!!! 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.
|
||||
Only supported in `legacyDNSMode`.
|
||||
|
||||
Make `ip_cidr` rules in rule-sets accept empty query response.
|
||||
|
||||
#### ip_accept_any
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.14.0"
|
||||
|
||||
`ip_accept_any` is deprecated and will be removed in sing-box 1.16.0.
|
||||
Only supported in `legacyDNSMode`. Use `match_response` with response items instead.
|
||||
|
||||
Match any IP with query response.
|
||||
|
||||
### Response Fields
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
Match fields for DNS response data. Require `match_response` to be set to `true`
|
||||
and a preceding rule with [`evaluate`](/configuration/dns/rule_action/#evaluate) action to populate the response.
|
||||
|
||||
#### 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
|
||||
|
||||
#### type
|
||||
@@ -577,4 +654,4 @@ Match any IP with query response.
|
||||
|
||||
#### rules
|
||||
|
||||
Included rules.
|
||||
Included rules.
|
||||
|
||||
@@ -4,8 +4,15 @@ icon: material/alert-decagram
|
||||
|
||||
!!! quote "sing-box 1.14.0 中的更改"
|
||||
|
||||
:material-plus: [match_response](#match_response)
|
||||
: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: [source_mac_address](#source_mac_address)
|
||||
:material-plus: [source_hostname](#source_hostname)
|
||||
:material-plus: [source_hostname](#source_hostname)
|
||||
:material-delete-clock: [ip_accept_any](#ip_accept_any)
|
||||
:material-delete-clock: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
||||
|
||||
!!! quote "sing-box 1.13.0 中的更改"
|
||||
|
||||
@@ -94,12 +101,6 @@ icon: material/alert-decagram
|
||||
"192.168.0.1"
|
||||
],
|
||||
"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": [
|
||||
12345
|
||||
],
|
||||
@@ -171,7 +172,16 @@ icon: material/alert-decagram
|
||||
"geosite-cn"
|
||||
],
|
||||
"rule_set_ip_cidr_match_source": false,
|
||||
"rule_set_ip_cidr_accept_empty": false,
|
||||
"match_response": false,
|
||||
"ip_cidr": [
|
||||
"10.0.0.0/24",
|
||||
"192.168.0.1"
|
||||
],
|
||||
"ip_is_private": false,
|
||||
"response_rcode": "",
|
||||
"response_answer": [],
|
||||
"response_ns": [],
|
||||
"response_extra": [],
|
||||
"invert": false,
|
||||
"outbound": [
|
||||
"direct"
|
||||
@@ -180,6 +190,9 @@ icon: material/alert-decagram
|
||||
"server": "local",
|
||||
|
||||
// 已弃用
|
||||
|
||||
"ip_accept_any": false,
|
||||
"rule_set_ip_cidr_accept_empty": false,
|
||||
"rule_set_ipcidr_match_source": false,
|
||||
"geosite": [
|
||||
"cn"
|
||||
@@ -219,7 +232,7 @@ icon: material/alert-decagram
|
||||
(`source_port` || `source_port_range`) &&
|
||||
`other fields`
|
||||
|
||||
另外,引用的规则集可视为被合并,而不是作为一个单独的规则子项。
|
||||
另外,引用规则集中的每个分支都可视为与外层规则合并,不同分支之间仍保持 OR 语义。
|
||||
|
||||
#### inbound
|
||||
|
||||
@@ -476,6 +489,15 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
使规则集中的 `ip_cidr` 规则匹配源 IP。
|
||||
|
||||
#### match_response
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
启用响应匹配。启用后,此规则将匹配 DNS 响应数据(由前序 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作设置),而不仅是匹配原始查询。
|
||||
|
||||
`response_rcode`、`response_answer`、`response_ns`、`response_extra` 字段需要此选项。
|
||||
当 `legacyDNSMode` 未启用时,`ip_cidr` 和 `ip_is_private` 也需要此选项。
|
||||
|
||||
#### invert
|
||||
|
||||
反选匹配结果。
|
||||
@@ -520,6 +542,14 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
已移动到 [DNS 规则动作](../rule_action#route).
|
||||
|
||||
### Legacy DNS Mode
|
||||
|
||||
`legacyDNSMode` 是一种内部兼容模式,会根据 DNS 规则配置自动检测。
|
||||
当任何规则使用了 sing-box 1.14.0 引入的特性(如 `evaluate`、`match_response`、
|
||||
响应字段(`response_rcode`、`response_answer` 等)、`query_type` 或 `ip_version`)时,
|
||||
该模式将被自动禁用。禁用后,`ip_cidr` 和 `ip_is_private` 需要设置 `match_response`,
|
||||
且已废弃的字段(如 `strategy`、`ip_accept_any`、`rule_set_ip_cidr_accept_empty`)将不再被接受。
|
||||
|
||||
### 地址筛选字段
|
||||
|
||||
仅对地址请求 (A/AAAA/HTTPS) 生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。
|
||||
@@ -547,24 +577,69 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
与查询响应匹配 IP CIDR。
|
||||
|
||||
当 `legacyDNSMode` 未启用时,`match_response` 必须设为 `true`。
|
||||
|
||||
#### ip_is_private
|
||||
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
|
||||
与查询响应匹配非公开 IP。
|
||||
|
||||
#### ip_accept_any
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
匹配任意 IP。
|
||||
当 `legacyDNSMode` 未启用时,`match_response` 必须设为 `true`。
|
||||
|
||||
#### rule_set_ip_cidr_accept_empty
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
!!! failure "已在 sing-box 1.14.0 废弃"
|
||||
|
||||
`rule_set_ip_cidr_accept_empty` 已废弃且将在 sing-box 1.16.0 中被移除。
|
||||
仅在 `legacyDNSMode` 中可用。
|
||||
|
||||
使规则集中的 `ip_cidr` 规则接受空查询响应。
|
||||
|
||||
#### ip_accept_any
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
!!! failure "已在 sing-box 1.14.0 废弃"
|
||||
|
||||
`ip_accept_any` 已废弃且将在 sing-box 1.16.0 中被移除。
|
||||
仅在 `legacyDNSMode` 中可用。请使用 `match_response` 和响应项替代。
|
||||
|
||||
匹配任意 IP。
|
||||
|
||||
### 响应字段
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
DNS 响应数据的匹配字段。需要将 `match_response` 设为 `true`,
|
||||
且需要前序规则使用 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作来填充响应。
|
||||
|
||||
#### 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
|
||||
@@ -581,4 +656,4 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
==必填==
|
||||
|
||||
包括的规则。
|
||||
包括的规则。
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.14.0"
|
||||
|
||||
:material-plus: [evaluate](#evaluate)
|
||||
:material-delete-clock: [strategy](#strategy)
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
:material-plus: [strategy](#strategy)
|
||||
@@ -34,7 +39,11 @@ Tag of target server.
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
Set domain strategy for this query.
|
||||
!!! 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. Only supported when `legacyDNSMode` is active.
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
@@ -52,7 +61,49 @@ 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.
|
||||
|
||||
Will overrides `dns.client_subnet`.
|
||||
Will override `dns.client_subnet`.
|
||||
|
||||
### evaluate
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "evaluate",
|
||||
"server": "",
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": null,
|
||||
"client_subnet": null
|
||||
}
|
||||
```
|
||||
|
||||
`evaluate` sends a DNS query to the specified server and saves the 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).
|
||||
|
||||
#### server
|
||||
|
||||
==Required==
|
||||
|
||||
Tag of target server.
|
||||
|
||||
#### disable_cache
|
||||
|
||||
Disable cache and save cache 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`.
|
||||
|
||||
### route-options
|
||||
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.14.0 中的更改"
|
||||
|
||||
:material-plus: [evaluate](#evaluate)
|
||||
:material-delete-clock: [strategy](#strategy)
|
||||
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
:material-plus: [strategy](#strategy)
|
||||
@@ -34,7 +39,11 @@ icon: material/new-box
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
为此查询设置域名策略。
|
||||
!!! failure "已在 sing-box 1.14.0 废弃"
|
||||
|
||||
`strategy` 已在 sing-box 1.14.0 废弃,且将在 sing-box 1.16.0 中被移除。
|
||||
|
||||
为此查询设置域名策略。仅在 `legacyDNSMode` 启用时可用。
|
||||
|
||||
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||
|
||||
@@ -54,6 +63,46 @@ icon: material/new-box
|
||||
|
||||
将覆盖 `dns.client_subnet`.
|
||||
|
||||
### evaluate
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "evaluate",
|
||||
"server": "",
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": null,
|
||||
"client_subnet": null
|
||||
}
|
||||
```
|
||||
|
||||
`evaluate` 向指定服务器发送 DNS 查询并保存响应,供后续规则通过 [`match_response`](/zh/configuration/dns/rule/#match_response) 和响应字段进行匹配。与 `route` 不同,它**不会**终止规则评估。
|
||||
|
||||
仅允许在顶层 DNS 规则中使用(不可在逻辑子规则内部使用)。
|
||||
|
||||
#### server
|
||||
|
||||
==必填==
|
||||
|
||||
目标 DNS 服务器的标签。
|
||||
|
||||
#### disable_cache
|
||||
|
||||
在此查询中禁用缓存。
|
||||
|
||||
#### rewrite_ttl
|
||||
|
||||
重写 DNS 回应中的 TTL。
|
||||
|
||||
#### client_subnet
|
||||
|
||||
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
||||
|
||||
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
|
||||
|
||||
将覆盖 `dns.client_subnet`.
|
||||
|
||||
### route-options
|
||||
|
||||
```json
|
||||
@@ -84,7 +133,7 @@ icon: material/new-box
|
||||
- `default`: 返回 REFUSED。
|
||||
- `drop`: 丢弃请求。
|
||||
|
||||
默认使用 `defualt`。
|
||||
默认使用 `default`。
|
||||
|
||||
#### no_drop
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ The type of the DNS server.
|
||||
|
||||
| Type | Format |
|
||||
|-----------------|---------------------------|
|
||||
| empty (default) | [Legacy](./legacy/) |
|
||||
| empty (default) | :material-note-remove: [Legacy](./legacy/) |
|
||||
| `local` | [Local](./local/) |
|
||||
| `hosts` | [Hosts](./hosts/) |
|
||||
| `tcp` | [TCP](./tcp/) |
|
||||
|
||||
@@ -29,7 +29,7 @@ DNS 服务器的类型。
|
||||
|
||||
| 类型 | 格式 |
|
||||
|-----------------|---------------------------|
|
||||
| empty (default) | [Legacy](./legacy/) |
|
||||
| empty (default) | :material-note-remove: [Legacy](./legacy/) |
|
||||
| `local` | [Local](./local/) |
|
||||
| `hosts` | [Hosts](./hosts/) |
|
||||
| `tcp` | [TCP](./tcp/) |
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
icon: material/note-remove
|
||||
---
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.12.0"
|
||||
!!! failure "Removed in sing-box 1.14.0"
|
||||
|
||||
Legacy DNS servers is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
|
||||
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-servers).
|
||||
|
||||
!!! 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.
|
||||
|
||||
Can be overrides by `rules.[].client_subnet`.
|
||||
Can be overridden by `rules.[].client_subnet`.
|
||||
|
||||
Will overrides `dns.client_subnet`.
|
||||
Will override `dns.client_subnet`.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
icon: material/note-remove
|
||||
---
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.12.0"
|
||||
!!! failure "已在 sing-box 1.14.0 移除"
|
||||
|
||||
旧的 DNS 服务器配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
||||
旧的 DNS 服务器配置已在 sing-box 1.12.0 废弃且已在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
||||
|
||||
!!! quote "sing-box 1.9.0 中的更改"
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
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"
|
||||
|
||||
:material-alert: [masquerade](#masquerade)
|
||||
@@ -31,6 +35,7 @@ icon: material/alert-decagram
|
||||
"ignore_client_bandwidth": false,
|
||||
"tls": {},
|
||||
"masquerade": "", // or {}
|
||||
"bbr_profile": "",
|
||||
"brutal_debug": false
|
||||
}
|
||||
```
|
||||
@@ -141,6 +146,14 @@ Fixed response headers.
|
||||
|
||||
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
|
||||
|
||||
Enable debug information logging for Hysteria Brutal CC.
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.14.0 中的更改"
|
||||
|
||||
:material-plus: [bbr_profile](#bbr_profile)
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-alert: [masquerade](#masquerade)
|
||||
@@ -31,6 +35,7 @@ icon: material/alert-decagram
|
||||
"ignore_client_bandwidth": false,
|
||||
"tls": {},
|
||||
"masquerade": "", // 或 {}
|
||||
"bbr_profile": "",
|
||||
"brutal_debug": false
|
||||
}
|
||||
```
|
||||
@@ -138,6 +143,14 @@ HTTP3 服务器认证失败时的行为 (对象配置)。
|
||||
|
||||
固定响应内容。
|
||||
|
||||
#### bbr_profile
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
BBR 拥塞控制算法配置,可选 `conservative` `standard` `aggressive`。
|
||||
|
||||
默认使用 `standard`。
|
||||
|
||||
#### brutal_debug
|
||||
|
||||
启用 Hysteria Brutal CC 的调试信息日志记录。
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
!!! 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"
|
||||
|
||||
:material-plus: [server_ports](#server_ports)
|
||||
@@ -9,13 +14,14 @@
|
||||
{
|
||||
"type": "hysteria2",
|
||||
"tag": "hy2-out",
|
||||
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"server_ports": [
|
||||
"2080:3000"
|
||||
],
|
||||
"hop_interval": "",
|
||||
"hop_interval_max": "",
|
||||
"up_mbps": 100,
|
||||
"down_mbps": 100,
|
||||
"obfs": {
|
||||
@@ -25,8 +31,9 @@
|
||||
"password": "goofy_ahh_password",
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"bbr_profile": "",
|
||||
"brutal_debug": false,
|
||||
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
```
|
||||
@@ -75,6 +82,14 @@ Port hopping interval.
|
||||
|
||||
`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
|
||||
|
||||
Max bandwidth, in Mbps.
|
||||
@@ -109,6 +124,14 @@ Both is enabled by default.
|
||||
|
||||
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
|
||||
|
||||
Enable debug information logging for Hysteria Brutal CC.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
!!! 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 中的更改"
|
||||
|
||||
:material-plus: [server_ports](#server_ports)
|
||||
@@ -16,6 +21,7 @@
|
||||
"2080:3000"
|
||||
],
|
||||
"hop_interval": "",
|
||||
"hop_interval_max": "",
|
||||
"up_mbps": 100,
|
||||
"down_mbps": 100,
|
||||
"obfs": {
|
||||
@@ -25,8 +31,9 @@
|
||||
"password": "goofy_ahh_password",
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"bbr_profile": "",
|
||||
"brutal_debug": false,
|
||||
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
```
|
||||
@@ -73,6 +80,14 @@
|
||||
|
||||
默认使用 `30s`。
|
||||
|
||||
#### hop_interval_max
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
最大端口跳跃间隔,用于随机化。
|
||||
|
||||
如果设置,实际跳跃间隔将在 `hop_interval` 和 `hop_interval_max` 之间随机选择。
|
||||
|
||||
#### up_mbps, down_mbps
|
||||
|
||||
最大带宽。
|
||||
@@ -107,6 +122,14 @@ QUIC 流量混淆器密码.
|
||||
|
||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。
|
||||
|
||||
#### bbr_profile
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
BBR 拥塞控制算法配置,可选 `conservative` `standard` `aggressive`。
|
||||
|
||||
默认使用 `standard`。
|
||||
|
||||
#### brutal_debug
|
||||
|
||||
启用 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.
|
||||
|
||||
Can be overrides by `outbound.domain_resolver`.
|
||||
Can be overridden by `outbound.domain_resolver`.
|
||||
|
||||
#### 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.
|
||||
|
||||
Can be overrides by `outbound.network_strategy`.
|
||||
Can be overridden by `outbound.network_strategy`.
|
||||
|
||||
Conflicts with `default_interface`.
|
||||
|
||||
|
||||
@@ -210,7 +210,7 @@ icon: material/new-box
|
||||
(`source_port` || `source_port_range`) &&
|
||||
`other fields`
|
||||
|
||||
Additionally, included rule-sets can be considered merged rather than as a single rule sub-item.
|
||||
Additionally, each branch inside an included rule-set can be considered merged into the outer rule, while different branches keep OR semantics.
|
||||
|
||||
#### inbound
|
||||
|
||||
|
||||
@@ -208,7 +208,7 @@ icon: material/new-box
|
||||
(`source_port` || `source_port_range`) &&
|
||||
`other fields`
|
||||
|
||||
另外,引用的规则集可视为被合并,而不是作为一个单独的规则子项。
|
||||
另外,引用规则集中的每个分支都可视为与外层规则合并,不同分支之间仍保持 OR 语义。
|
||||
|
||||
#### inbound
|
||||
|
||||
@@ -532,4 +532,4 @@ icon: material/new-box
|
||||
|
||||
==必填==
|
||||
|
||||
包括的规则。
|
||||
包括的规则。
|
||||
|
||||
@@ -316,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.
|
||||
|
||||
Will overrides `dns.client_subnet`.
|
||||
Will override `dns.client_subnet`.
|
||||
|
||||
@@ -14,6 +14,36 @@ check [Migration](../migration/#migrate-inline-acme-to-certificate-provider).
|
||||
|
||||
Old fields will be removed in sing-box 1.16.0.
|
||||
|
||||
#### `strategy` in DNS rule actions
|
||||
|
||||
`strategy` in DNS rule actions is deprecated
|
||||
and only supported in `legacyDNSMode`.
|
||||
|
||||
Old fields will be removed in sing-box 1.16.0.
|
||||
|
||||
#### `ip_accept_any` in DNS rules
|
||||
|
||||
`ip_accept_any` in DNS rules is deprecated
|
||||
and only supported in `legacyDNSMode`.
|
||||
Use `match_response` with response items instead.
|
||||
|
||||
Old fields will be removed in sing-box 1.16.0.
|
||||
|
||||
#### `rule_set_ip_cidr_accept_empty` in DNS rules
|
||||
|
||||
`rule_set_ip_cidr_accept_empty` in DNS rules is deprecated
|
||||
and only supported in `legacyDNSMode`.
|
||||
|
||||
Old fields will be removed in sing-box 1.16.0.
|
||||
|
||||
#### Legacy address filter DNS rule items
|
||||
|
||||
Legacy address filter DNS rule items (`ip_cidr`, `ip_is_private` without `match_response`)
|
||||
are deprecated and only supported in `legacyDNSMode`.
|
||||
Use `match_response` with the `evaluate` action instead.
|
||||
|
||||
Old behavior will be removed in sing-box 1.16.0.
|
||||
|
||||
## 1.12.0
|
||||
|
||||
#### Legacy DNS server formats
|
||||
@@ -21,7 +51,7 @@ Old fields will be removed in sing-box 1.16.0.
|
||||
DNS servers are refactored,
|
||||
check [Migration](../migration/#migrate-to-new-dns-servers).
|
||||
|
||||
Compatibility for old formats will be removed in sing-box 1.14.0.
|
||||
Old formats were removed in sing-box 1.14.0.
|
||||
|
||||
#### `outbound` DNS rule item
|
||||
|
||||
|
||||
@@ -14,6 +14,36 @@ TLS 中的内联 ACME 选项(`tls.acme`)已废弃,
|
||||
|
||||
旧字段将在 sing-box 1.16.0 中被移除。
|
||||
|
||||
#### DNS 规则动作中的 `strategy`
|
||||
|
||||
DNS 规则动作中的 `strategy` 已废弃,
|
||||
且仅在 `legacyDNSMode` 中可用。
|
||||
|
||||
旧字段将在 sing-box 1.16.0 中被移除。
|
||||
|
||||
#### DNS 规则中的 `ip_accept_any`
|
||||
|
||||
DNS 规则中的 `ip_accept_any` 已废弃,
|
||||
且仅在 `legacyDNSMode` 中可用。
|
||||
请使用 `match_response` 和响应项替代。
|
||||
|
||||
旧字段将在 sing-box 1.16.0 中被移除。
|
||||
|
||||
#### DNS 规则中的 `rule_set_ip_cidr_accept_empty`
|
||||
|
||||
DNS 规则中的 `rule_set_ip_cidr_accept_empty` 已废弃,
|
||||
且仅在 `legacyDNSMode` 中可用。
|
||||
|
||||
旧字段将在 sing-box 1.16.0 中被移除。
|
||||
|
||||
#### 旧的地址筛选 DNS 规则项
|
||||
|
||||
旧的地址筛选 DNS 规则项(不使用 `match_response` 的 `ip_cidr`、`ip_is_private`)已废弃,
|
||||
且仅在 `legacyDNSMode` 中可用。
|
||||
请使用 `match_response` 和 `evaluate` 动作替代。
|
||||
|
||||
旧行为将在 sing-box 1.16.0 中被移除。
|
||||
|
||||
## 1.12.0
|
||||
|
||||
#### 旧的 DNS 服务器格式
|
||||
@@ -21,7 +51,7 @@ TLS 中的内联 ACME 选项(`tls.acme`)已废弃,
|
||||
DNS 服务器已重构,
|
||||
参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式).
|
||||
|
||||
对旧格式的兼容性将在 sing-box 1.14.0 中被移除。
|
||||
旧格式已在 sing-box 1.14.0 中被移除。
|
||||
|
||||
#### `outbound` DNS 规则项
|
||||
|
||||
|
||||
@@ -57,24 +57,6 @@ 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{
|
||||
Name: "outbound-dns-rule-item",
|
||||
Description: "outbound DNS rule item",
|
||||
@@ -111,11 +93,49 @@ var OptionInlineACME = Note{
|
||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-inline-acme-to-certificate-provider",
|
||||
}
|
||||
|
||||
var OptionIPAcceptAny = Note{
|
||||
Name: "dns-rule-ip-accept-any",
|
||||
Description: "`ip_accept_any` in DNS rules",
|
||||
DeprecatedVersion: "1.14.0",
|
||||
ScheduledVersion: "1.16.0",
|
||||
EnvName: "DNS_RULE_IP_ACCEPT_ANY",
|
||||
MigrationLink: "https://sing-box.sagernet.org/configuration/dns/rule/",
|
||||
}
|
||||
|
||||
var OptionRuleSetIPCIDRAcceptEmpty = Note{
|
||||
Name: "dns-rule-rule-set-ip-cidr-accept-empty",
|
||||
Description: "`rule_set_ip_cidr_accept_empty` in DNS rules",
|
||||
DeprecatedVersion: "1.14.0",
|
||||
ScheduledVersion: "1.16.0",
|
||||
EnvName: "DNS_RULE_RULE_SET_IP_CIDR_ACCEPT_EMPTY",
|
||||
MigrationLink: "https://sing-box.sagernet.org/configuration/dns/rule/",
|
||||
}
|
||||
|
||||
var OptionLegacyDNSAddressFilter = Note{
|
||||
Name: "legacy-dns-address-filter",
|
||||
Description: "legacy address filter DNS rule items",
|
||||
DeprecatedVersion: "1.14.0",
|
||||
ScheduledVersion: "1.16.0",
|
||||
EnvName: "LEGACY_DNS_ADDRESS_FILTER",
|
||||
MigrationLink: "https://sing-box.sagernet.org/configuration/dns/rule/",
|
||||
}
|
||||
|
||||
var OptionLegacyDNSRuleStrategy = Note{
|
||||
Name: "legacy-dns-rule-strategy",
|
||||
Description: "`strategy` in DNS rule actions",
|
||||
DeprecatedVersion: "1.14.0",
|
||||
ScheduledVersion: "1.16.0",
|
||||
EnvName: "LEGACY_DNS_RULE_STRATEGY",
|
||||
MigrationLink: "https://sing-box.sagernet.org/configuration/dns/rule_action/",
|
||||
}
|
||||
|
||||
var Options = []Note{
|
||||
OptionLegacyDNSTransport,
|
||||
OptionLegacyDNSFakeIPOptions,
|
||||
OptionOutboundDNSRuleItem,
|
||||
OptionMissingDomainResolver,
|
||||
OptionLegacyDomainStrategyOptions,
|
||||
OptionInlineACME,
|
||||
OptionIPAcceptAny,
|
||||
OptionRuleSetIPCIDRAcceptEmpty,
|
||||
OptionLegacyDNSAddressFilter,
|
||||
OptionLegacyDNSRuleStrategy,
|
||||
}
|
||||
|
||||
@@ -52,6 +52,11 @@ type HTTPRequest interface {
|
||||
type HTTPResponse interface {
|
||||
GetContent() (*StringBox, error)
|
||||
WriteTo(path string) error
|
||||
WriteToWithProgress(path string, handler HTTPResponseWriteToProgressHandler) error
|
||||
}
|
||||
|
||||
type HTTPResponseWriteToProgressHandler interface {
|
||||
Update(progress int64, total int64)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -239,3 +244,31 @@ func (h *httpResponse) WriteTo(path string) error {
|
||||
defer file.Close()
|
||||
return common.Error(bufio.Copy(file, h.Body))
|
||||
}
|
||||
|
||||
func (h *httpResponse) WriteToWithProgress(path string, handler HTTPResponseWriteToProgressHandler) error {
|
||||
defer h.Body.Close()
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
return common.Error(bufio.Copy(&progressWriter{
|
||||
writer: file,
|
||||
handler: handler,
|
||||
total: h.ContentLength,
|
||||
}, h.Body))
|
||||
}
|
||||
|
||||
type progressWriter struct {
|
||||
writer io.Writer
|
||||
handler HTTPResponseWriteToProgressHandler
|
||||
total int64
|
||||
written int64
|
||||
}
|
||||
|
||||
func (w *progressWriter) Write(p []byte) (int, error) {
|
||||
n, err := w.writer.Write(p)
|
||||
w.written += int64(n)
|
||||
w.handler.Update(w.written, w.total)
|
||||
return n, err
|
||||
}
|
||||
|
||||
8
go.mod
8
go.mod
@@ -6,6 +6,7 @@ require (
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0
|
||||
github.com/anytls/sing-anytls v0.0.11
|
||||
github.com/caddyserver/certmagic v0.25.2
|
||||
github.com/caddyserver/zerossl v0.1.5
|
||||
github.com/coder/websocket v1.8.14
|
||||
github.com/cretz/bine v0.2.0
|
||||
github.com/database64128/tfo-go/v2 v2.3.2
|
||||
@@ -19,6 +20,7 @@ require (
|
||||
github.com/libdns/acmedns v0.5.0
|
||||
github.com/libdns/alidns v1.0.6
|
||||
github.com/libdns/cloudflare v0.2.2
|
||||
github.com/libdns/libdns v1.1.1
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
github.com/mdlayher/netlink v1.9.0
|
||||
github.com/metacubex/utls v1.8.4
|
||||
@@ -35,9 +37,9 @@ require (
|
||||
github.com/sagernet/gomobile v0.1.12
|
||||
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
|
||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4
|
||||
github.com/sagernet/sing v0.8.3-0.20260315153529-ed51f65fbfde
|
||||
github.com/sagernet/sing v0.8.3
|
||||
github.com/sagernet/sing-mux v0.3.4
|
||||
github.com/sagernet/sing-quic v0.6.0
|
||||
github.com/sagernet/sing-quic v0.6.2-0.20260330152607-bf674c163212
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
||||
@@ -69,7 +71,6 @@ require (
|
||||
github.com/akutz/memconn v0.1.0 // indirect
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.5 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
|
||||
github.com/database64128/netx-go v0.1.1 // indirect
|
||||
@@ -96,7 +97,6 @@ require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/libdns/libdns v1.1.1 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@@ -236,12 +236,12 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen
|
||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o=
|
||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
|
||||
github.com/sagernet/sing v0.8.3-0.20260315153529-ed51f65fbfde h1:RNQzlpnsXIuu1HGts/fIzJ1PR7RhrzaNlU52MDyiX1c=
|
||||
github.com/sagernet/sing v0.8.3-0.20260315153529-ed51f65fbfde/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.8.3 h1:zGMy9M1deBPEew9pCYIUHKeE+/lDQ5A2CBqjBjjzqkA=
|
||||
github.com/sagernet/sing v0.8.3/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s=
|
||||
github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
|
||||
github.com/sagernet/sing-quic v0.6.0 h1:dhrFnP45wgVKEOT1EvtsToxdzRnHIDIAgj6WHV9pLyM=
|
||||
github.com/sagernet/sing-quic v0.6.0/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8=
|
||||
github.com/sagernet/sing-quic v0.6.2-0.20260330152607-bf674c163212 h1:7mFOUqy+DyOj7qKGd1X54UMXbnbJiiMileK/tn17xYc=
|
||||
github.com/sagernet/sing-quic v0.6.2-0.20260330152607-bf674c163212/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
||||
|
||||
272
option/dns.go
272
option/dns.go
@@ -3,19 +3,14 @@ package option
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/json/badjson"
|
||||
"github.com/sagernet/sing/common/json/badoption"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type RawDNSOptions struct {
|
||||
@@ -26,80 +21,29 @@ type RawDNSOptions struct {
|
||||
DNSClientOptions
|
||||
}
|
||||
|
||||
type LegacyDNSOptions struct {
|
||||
FakeIP *LegacyDNSFakeIPOptions `json:"fakeip,omitempty"`
|
||||
}
|
||||
|
||||
type DNSOptions struct {
|
||||
RawDNSOptions
|
||||
LegacyDNSOptions
|
||||
}
|
||||
|
||||
type contextKeyDontUpgrade struct{}
|
||||
const (
|
||||
legacyDNSFakeIPRemovedMessage = "legacy DNS fakeip options are deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, checkout migration: https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats"
|
||||
legacyDNSServerRemovedMessage = "legacy DNS server formats are deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, checkout migration: https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats"
|
||||
)
|
||||
|
||||
func ContextWithDontUpgrade(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, (*contextKeyDontUpgrade)(nil), true)
|
||||
}
|
||||
|
||||
func dontUpgradeFromContext(ctx context.Context) bool {
|
||||
return ctx.Value((*contextKeyDontUpgrade)(nil)) == true
|
||||
type removedLegacyDNSOptions struct {
|
||||
FakeIP json.RawMessage `json:"fakeip,omitempty"`
|
||||
}
|
||||
|
||||
func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error {
|
||||
err := json.UnmarshalContext(ctx, content, &o.LegacyDNSOptions)
|
||||
var legacyOptions removedLegacyDNSOptions
|
||||
err := json.UnmarshalContext(ctx, content, &legacyOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dontUpgrade := dontUpgradeFromContext(ctx)
|
||||
legacyOptions := o.LegacyDNSOptions
|
||||
if !dontUpgrade {
|
||||
if o.FakeIP != nil && o.FakeIP.Enabled {
|
||||
deprecated.Report(ctx, deprecated.OptionLegacyDNSFakeIPOptions)
|
||||
ctx = context.WithValue(ctx, (*LegacyDNSFakeIPOptions)(nil), o.FakeIP)
|
||||
}
|
||||
o.LegacyDNSOptions = LegacyDNSOptions{}
|
||||
if len(legacyOptions.FakeIP) != 0 {
|
||||
return E.New(legacyDNSFakeIPRemovedMessage)
|
||||
}
|
||||
err = badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !dontUpgrade {
|
||||
rcodeMap := make(map[string]int)
|
||||
o.Servers = common.Filter(o.Servers, func(it DNSServerOptions) bool {
|
||||
if it.Type == C.DNSTypeLegacyRcode {
|
||||
rcodeMap[it.Tag] = it.Options.(int)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if len(rcodeMap) > 0 {
|
||||
for i := 0; i < len(o.Rules); i++ {
|
||||
rewriteRcode(rcodeMap, &o.Rules[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rewriteRcode(rcodeMap map[string]int, rule *DNSRule) {
|
||||
switch rule.Type {
|
||||
case C.RuleTypeDefault:
|
||||
rewriteRcodeAction(rcodeMap, &rule.DefaultOptions.DNSRuleAction)
|
||||
case C.RuleTypeLogical:
|
||||
rewriteRcodeAction(rcodeMap, &rule.LogicalOptions.DNSRuleAction)
|
||||
}
|
||||
}
|
||||
|
||||
func rewriteRcodeAction(rcodeMap map[string]int, ruleAction *DNSRuleAction) {
|
||||
if ruleAction.Action != C.RuleActionTypeRoute {
|
||||
return
|
||||
}
|
||||
rcode, loaded := rcodeMap[ruleAction.RouteOptions.Server]
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
ruleAction.Action = C.RuleActionTypePredefined
|
||||
ruleAction.PredefinedOptions.Rcode = common.Ptr(DNSRCode(rcode))
|
||||
return badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions)
|
||||
}
|
||||
|
||||
type DNSClientOptions struct {
|
||||
@@ -111,12 +55,6 @@ type DNSClientOptions struct {
|
||||
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
||||
}
|
||||
|
||||
type LegacyDNSFakeIPOptions struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
Inet4Range *badoption.Prefix `json:"inet4_range,omitempty"`
|
||||
Inet6Range *badoption.Prefix `json:"inet6_range,omitempty"`
|
||||
}
|
||||
|
||||
type DNSTransportOptionsRegistry interface {
|
||||
CreateOptions(transportType string) (any, bool)
|
||||
}
|
||||
@@ -129,10 +67,6 @@ type _DNSServerOptions struct {
|
||||
type DNSServerOptions _DNSServerOptions
|
||||
|
||||
func (o *DNSServerOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) {
|
||||
switch o.Type {
|
||||
case C.DNSTypeLegacy:
|
||||
o.Type = ""
|
||||
}
|
||||
return badjson.MarshallObjectsContext(ctx, (*_DNSServerOptions)(o), o.Options)
|
||||
}
|
||||
|
||||
@@ -148,9 +82,7 @@ func (o *DNSServerOptions) UnmarshalJSONContext(ctx context.Context, content []b
|
||||
var options any
|
||||
switch o.Type {
|
||||
case "", C.DNSTypeLegacy:
|
||||
o.Type = C.DNSTypeLegacy
|
||||
options = new(LegacyDNSServerOptions)
|
||||
deprecated.Report(ctx, deprecated.OptionLegacyDNSTransport)
|
||||
return E.New(legacyDNSServerRemovedMessage)
|
||||
default:
|
||||
var loaded bool
|
||||
options, loaded = registry.CreateOptions(o.Type)
|
||||
@@ -163,169 +95,6 @@ func (o *DNSServerOptions) UnmarshalJSONContext(ctx context.Context, content []b
|
||||
return err
|
||||
}
|
||||
o.Options = options
|
||||
if o.Type == C.DNSTypeLegacy && !dontUpgradeFromContext(ctx) {
|
||||
err = o.Upgrade(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
|
||||
if o.Type != C.DNSTypeLegacy {
|
||||
return nil
|
||||
}
|
||||
options := o.Options.(*LegacyDNSServerOptions)
|
||||
serverURL, _ := url.Parse(options.Address)
|
||||
var serverType string
|
||||
if serverURL != nil && serverURL.Scheme != "" {
|
||||
serverType = serverURL.Scheme
|
||||
} else {
|
||||
switch options.Address {
|
||||
case "local", "fakeip":
|
||||
serverType = options.Address
|
||||
default:
|
||||
serverType = C.DNSTypeUDP
|
||||
}
|
||||
}
|
||||
remoteOptions := RemoteDNSServerOptions{
|
||||
RawLocalDNSServerOptions: RawLocalDNSServerOptions{
|
||||
DialerOptions: DialerOptions{
|
||||
Detour: options.Detour,
|
||||
DomainResolver: &DomainResolveOptions{
|
||||
Server: options.AddressResolver,
|
||||
Strategy: options.AddressStrategy,
|
||||
},
|
||||
FallbackDelay: options.AddressFallbackDelay,
|
||||
},
|
||||
Legacy: true,
|
||||
LegacyStrategy: options.Strategy,
|
||||
LegacyDefaultDialer: options.Detour == "",
|
||||
LegacyClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
||||
},
|
||||
LegacyAddressResolver: options.AddressResolver,
|
||||
LegacyAddressStrategy: options.AddressStrategy,
|
||||
LegacyAddressFallbackDelay: options.AddressFallbackDelay,
|
||||
}
|
||||
switch serverType {
|
||||
case C.DNSTypeLocal:
|
||||
o.Type = C.DNSTypeLocal
|
||||
o.Options = &LocalDNSServerOptions{
|
||||
RawLocalDNSServerOptions: remoteOptions.RawLocalDNSServerOptions,
|
||||
}
|
||||
case C.DNSTypeUDP:
|
||||
o.Type = C.DNSTypeUDP
|
||||
o.Options = &remoteOptions
|
||||
var serverAddr M.Socksaddr
|
||||
if serverURL == nil || serverURL.Scheme == "" {
|
||||
serverAddr = M.ParseSocksaddr(options.Address)
|
||||
} else {
|
||||
serverAddr = M.ParseSocksaddr(serverURL.Host)
|
||||
}
|
||||
if !serverAddr.IsValid() {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
remoteOptions.Server = serverAddr.AddrString()
|
||||
if serverAddr.Port != 0 && serverAddr.Port != 53 {
|
||||
remoteOptions.ServerPort = serverAddr.Port
|
||||
}
|
||||
case C.DNSTypeTCP:
|
||||
o.Type = C.DNSTypeTCP
|
||||
o.Options = &remoteOptions
|
||||
if serverURL == nil {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
serverAddr := M.ParseSocksaddr(serverURL.Host)
|
||||
if !serverAddr.IsValid() {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
remoteOptions.Server = serverAddr.AddrString()
|
||||
if serverAddr.Port != 0 && serverAddr.Port != 53 {
|
||||
remoteOptions.ServerPort = serverAddr.Port
|
||||
}
|
||||
case C.DNSTypeTLS, C.DNSTypeQUIC:
|
||||
o.Type = serverType
|
||||
if serverURL == nil {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
serverAddr := M.ParseSocksaddr(serverURL.Host)
|
||||
if !serverAddr.IsValid() {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
remoteOptions.Server = serverAddr.AddrString()
|
||||
if serverAddr.Port != 0 && serverAddr.Port != 853 {
|
||||
remoteOptions.ServerPort = serverAddr.Port
|
||||
}
|
||||
o.Options = &RemoteTLSDNSServerOptions{
|
||||
RemoteDNSServerOptions: remoteOptions,
|
||||
}
|
||||
case C.DNSTypeHTTPS, C.DNSTypeHTTP3:
|
||||
o.Type = serverType
|
||||
httpsOptions := RemoteHTTPSDNSServerOptions{
|
||||
RemoteTLSDNSServerOptions: RemoteTLSDNSServerOptions{
|
||||
RemoteDNSServerOptions: remoteOptions,
|
||||
},
|
||||
}
|
||||
o.Options = &httpsOptions
|
||||
if serverURL == nil {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
serverAddr := M.ParseSocksaddr(serverURL.Host)
|
||||
if !serverAddr.IsValid() {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
httpsOptions.Server = serverAddr.AddrString()
|
||||
if serverAddr.Port != 0 && serverAddr.Port != 443 {
|
||||
httpsOptions.ServerPort = serverAddr.Port
|
||||
}
|
||||
if serverURL.Path != "/dns-query" {
|
||||
httpsOptions.Path = serverURL.Path
|
||||
}
|
||||
case "rcode":
|
||||
var rcode int
|
||||
if serverURL == nil {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
switch serverURL.Host {
|
||||
case "success":
|
||||
rcode = dns.RcodeSuccess
|
||||
case "format_error":
|
||||
rcode = dns.RcodeFormatError
|
||||
case "server_failure":
|
||||
rcode = dns.RcodeServerFailure
|
||||
case "name_error":
|
||||
rcode = dns.RcodeNameError
|
||||
case "not_implemented":
|
||||
rcode = dns.RcodeNotImplemented
|
||||
case "refused":
|
||||
rcode = dns.RcodeRefused
|
||||
default:
|
||||
return E.New("unknown rcode: ", serverURL.Host)
|
||||
}
|
||||
o.Type = C.DNSTypeLegacyRcode
|
||||
o.Options = rcode
|
||||
case C.DNSTypeDHCP:
|
||||
o.Type = C.DNSTypeDHCP
|
||||
dhcpOptions := DHCPDNSServerOptions{}
|
||||
if serverURL == nil {
|
||||
return E.New("invalid server address")
|
||||
}
|
||||
if serverURL.Host != "" && serverURL.Host != "auto" {
|
||||
dhcpOptions.Interface = serverURL.Host
|
||||
}
|
||||
o.Options = &dhcpOptions
|
||||
case C.DNSTypeFakeIP:
|
||||
o.Type = C.DNSTypeFakeIP
|
||||
fakeipOptions := FakeIPDNSServerOptions{}
|
||||
if legacyOptions, loaded := ctx.Value((*LegacyDNSFakeIPOptions)(nil)).(*LegacyDNSFakeIPOptions); loaded {
|
||||
fakeipOptions.Inet4Range = legacyOptions.Inet4Range
|
||||
fakeipOptions.Inet6Range = legacyOptions.Inet6Range
|
||||
}
|
||||
o.Options = &fakeipOptions
|
||||
default:
|
||||
return E.New("unsupported DNS server scheme: ", serverType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -350,16 +119,6 @@ func (o *DNSServerAddressOptions) ReplaceServerOptions(options ServerOptions) {
|
||||
*o = DNSServerAddressOptions(options)
|
||||
}
|
||||
|
||||
type LegacyDNSServerOptions struct {
|
||||
Address string `json:"address"`
|
||||
AddressResolver string `json:"address_resolver,omitempty"`
|
||||
AddressStrategy DomainStrategy `json:"address_strategy,omitempty"`
|
||||
AddressFallbackDelay badoption.Duration `json:"address_fallback_delay,omitempty"`
|
||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||
Detour string `json:"detour,omitempty"`
|
||||
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
||||
}
|
||||
|
||||
type HostsDNSServerOptions struct {
|
||||
Path badoption.Listable[string] `json:"path,omitempty"`
|
||||
Predefined *badjson.TypedMap[string, badoption.Listable[netip.Addr]] `json:"predefined,omitempty"`
|
||||
@@ -367,10 +126,6 @@ type HostsDNSServerOptions struct {
|
||||
|
||||
type RawLocalDNSServerOptions struct {
|
||||
DialerOptions
|
||||
Legacy bool `json:"-"`
|
||||
LegacyStrategy DomainStrategy `json:"-"`
|
||||
LegacyDefaultDialer bool `json:"-"`
|
||||
LegacyClientSubnet netip.Prefix `json:"-"`
|
||||
}
|
||||
|
||||
type LocalDNSServerOptions struct {
|
||||
@@ -381,9 +136,6 @@ type LocalDNSServerOptions struct {
|
||||
type RemoteDNSServerOptions struct {
|
||||
RawLocalDNSServerOptions
|
||||
DNSServerAddressOptions
|
||||
LegacyAddressResolver string `json:"-"`
|
||||
LegacyAddressStrategy DomainStrategy `json:"-"`
|
||||
LegacyAddressFallbackDelay badoption.Duration `json:"-"`
|
||||
}
|
||||
|
||||
type RemoteTLSDNSServerOptions struct {
|
||||
|
||||
@@ -2,6 +2,7 @@ package option
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -11,6 +12,8 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const defaultDNSRecordTTL uint32 = 3600
|
||||
|
||||
type DNSRCode int
|
||||
|
||||
func (r DNSRCode) MarshalJSON() ([]byte, error) {
|
||||
@@ -76,10 +79,13 @@ func (o *DNSRecordOptions) UnmarshalJSON(data []byte) error {
|
||||
if err == nil {
|
||||
return o.unmarshalBase64(binary)
|
||||
}
|
||||
record, err := dns.NewRR(stringValue)
|
||||
record, err := parseDNSRecord(stringValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if record == nil {
|
||||
return E.New("empty DNS record")
|
||||
}
|
||||
if a, isA := record.(*dns.A); isA {
|
||||
a.A = M.AddrFromIP(a.A).Unmap().AsSlice()
|
||||
}
|
||||
@@ -87,6 +93,16 @@ func (o *DNSRecordOptions) UnmarshalJSON(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseDNSRecord(stringValue string) (dns.RR, error) {
|
||||
if len(stringValue) > 0 && stringValue[len(stringValue)-1] != '\n' {
|
||||
stringValue += "\n"
|
||||
}
|
||||
parser := dns.NewZoneParser(strings.NewReader(stringValue), "", "")
|
||||
parser.SetDefaultTTL(defaultDNSRecordTTL)
|
||||
record, _ := parser.Next()
|
||||
return record, parser.Err()
|
||||
}
|
||||
|
||||
func (o *DNSRecordOptions) unmarshalBase64(binary []byte) error {
|
||||
record, _, err := dns.UnpackRR(binary, 0)
|
||||
if err != nil {
|
||||
@@ -100,3 +116,10 @@ func (o *DNSRecordOptions) unmarshalBase64(binary []byte) error {
|
||||
func (o DNSRecordOptions) Build() dns.RR {
|
||||
return o.RR
|
||||
}
|
||||
|
||||
func (o DNSRecordOptions) Match(record dns.RR) bool {
|
||||
if o.RR == nil || record == nil {
|
||||
return false
|
||||
}
|
||||
return dns.IsDuplicate(o.RR, record)
|
||||
}
|
||||
|
||||
52
option/dns_record_test.go
Normal file
52
option/dns_record_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func mustRecordOptions(t *testing.T, record string) DNSRecordOptions {
|
||||
t.Helper()
|
||||
var value DNSRecordOptions
|
||||
require.NoError(t, value.UnmarshalJSON([]byte(`"`+record+`"`)))
|
||||
return value
|
||||
}
|
||||
|
||||
func TestDNSRecordOptionsUnmarshalJSONAcceptsFullyQualifiedNames(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, record := range []string{
|
||||
"example.com. A 1.1.1.1",
|
||||
"www.example.com. IN CNAME example.com.",
|
||||
} {
|
||||
value := mustRecordOptions(t, record)
|
||||
require.NotNil(t, value.RR)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSRecordOptionsUnmarshalJSONRejectsRelativeNames(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, record := range []string{
|
||||
"@ IN A 1.1.1.1",
|
||||
"www IN CNAME example.com.",
|
||||
"example.com. IN CNAME @",
|
||||
"example.com. IN CNAME www",
|
||||
} {
|
||||
var value DNSRecordOptions
|
||||
err := value.UnmarshalJSON([]byte(`"` + record + `"`))
|
||||
require.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSRecordOptionsMatchIgnoresTTL(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
expected := mustRecordOptions(t, "example.com. 600 IN A 1.1.1.1")
|
||||
record, err := dns.NewRR("example.com. 60 IN A 1.1.1.1")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, expected.Match(record))
|
||||
}
|
||||
91
option/dns_test.go
Normal file
91
option/dns_test.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type stubDNSTransportOptionsRegistry struct{}
|
||||
|
||||
func (stubDNSTransportOptionsRegistry) CreateOptions(transportType string) (any, bool) {
|
||||
switch transportType {
|
||||
case C.DNSTypeUDP:
|
||||
return new(RemoteDNSServerOptions), true
|
||||
case C.DNSTypeFakeIP:
|
||||
return new(FakeIPDNSServerOptions), true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSOptionsRejectsLegacyFakeIPOptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := service.ContextWith[DNSTransportOptionsRegistry](context.Background(), stubDNSTransportOptionsRegistry{})
|
||||
var options DNSOptions
|
||||
err := json.UnmarshalContext(ctx, []byte(`{
|
||||
"fakeip": {
|
||||
"enabled": true,
|
||||
"inet4_range": "198.18.0.0/15"
|
||||
}
|
||||
}`), &options)
|
||||
require.EqualError(t, err, legacyDNSFakeIPRemovedMessage)
|
||||
}
|
||||
|
||||
func TestDNSServerOptionsRejectsLegacyFormats(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := service.ContextWith[DNSTransportOptionsRegistry](context.Background(), stubDNSTransportOptionsRegistry{})
|
||||
testCases := []string{
|
||||
`{"address":"1.1.1.1"}`,
|
||||
`{"type":"legacy","address":"1.1.1.1"}`,
|
||||
}
|
||||
for _, content := range testCases {
|
||||
var options DNSServerOptions
|
||||
err := json.UnmarshalContext(ctx, []byte(content), &options)
|
||||
require.EqualError(t, err, legacyDNSServerRemovedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSOptionsAcceptsTypedServers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := service.ContextWith[DNSTransportOptionsRegistry](context.Background(), stubDNSTransportOptionsRegistry{})
|
||||
var options DNSOptions
|
||||
err := json.UnmarshalContext(ctx, []byte(`{
|
||||
"servers": [
|
||||
{"type": "udp", "tag": "default", "server": "1.1.1.1"},
|
||||
{"type": "fakeip", "tag": "fake", "inet4_range": "198.18.0.0/15"}
|
||||
]
|
||||
}`), &options)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, options.Servers, 2)
|
||||
require.Equal(t, C.DNSTypeUDP, options.Servers[0].Type)
|
||||
require.Equal(t, "1.1.1.1", options.Servers[0].Options.(*RemoteDNSServerOptions).Server)
|
||||
require.Equal(t, C.DNSTypeFakeIP, options.Servers[1].Type)
|
||||
}
|
||||
|
||||
func TestDNSRuleActionEvaluateRoundTrip(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
action := DNSRuleAction{
|
||||
Action: C.RuleActionTypeEvaluate,
|
||||
RouteOptions: DNSRouteActionOptions{
|
||||
Server: "default",
|
||||
},
|
||||
}
|
||||
|
||||
content, err := json.Marshal(action)
|
||||
require.NoError(t, err)
|
||||
|
||||
var decoded DNSRuleAction
|
||||
err = json.UnmarshalContext(context.Background(), content, &decoded)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, action, decoded)
|
||||
}
|
||||
@@ -19,6 +19,7 @@ type Hysteria2InboundOptions struct {
|
||||
IgnoreClientBandwidth bool `json:"ignore_client_bandwidth,omitempty"`
|
||||
InboundTLSOptionsContainer
|
||||
Masquerade *Hysteria2Masquerade `json:"masquerade,omitempty"`
|
||||
BBRProfile string `json:"bbr_profile,omitempty"`
|
||||
BrutalDebug bool `json:"brutal_debug,omitempty"`
|
||||
}
|
||||
|
||||
@@ -112,13 +113,15 @@ type Hysteria2MasqueradeString struct {
|
||||
type Hysteria2OutboundOptions struct {
|
||||
DialerOptions
|
||||
ServerOptions
|
||||
ServerPorts badoption.Listable[string] `json:"server_ports,omitempty"`
|
||||
HopInterval badoption.Duration `json:"hop_interval,omitempty"`
|
||||
UpMbps int `json:"up_mbps,omitempty"`
|
||||
DownMbps int `json:"down_mbps,omitempty"`
|
||||
Obfs *Hysteria2Obfs `json:"obfs,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
ServerPorts badoption.Listable[string] `json:"server_ports,omitempty"`
|
||||
HopInterval badoption.Duration `json:"hop_interval,omitempty"`
|
||||
HopIntervalMax badoption.Duration `json:"hop_interval_max,omitempty"`
|
||||
UpMbps int `json:"up_mbps,omitempty"`
|
||||
DownMbps int `json:"down_mbps,omitempty"`
|
||||
Obfs *Hysteria2Obfs `json:"obfs,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
OutboundTLSOptionsContainer
|
||||
BrutalDebug bool `json:"brutal_debug,omitempty"`
|
||||
BBRProfile string `json:"bbr_profile,omitempty"`
|
||||
BrutalDebug bool `json:"brutal_debug,omitempty"`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
@@ -33,26 +34,24 @@ func (r Rule) MarshalJSON() ([]byte, error) {
|
||||
return badjson.MarshallObjects((_Rule)(r), v)
|
||||
}
|
||||
|
||||
func (r *Rule) UnmarshalJSON(bytes []byte) error {
|
||||
err := json.Unmarshal(bytes, (*_Rule)(r))
|
||||
func (r *Rule) UnmarshalJSONContext(ctx context.Context, bytes []byte) error {
|
||||
err := json.UnmarshalContext(ctx, bytes, (*_Rule)(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payload, err := rulePayloadWithoutType(ctx, bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var v any
|
||||
switch r.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
r.Type = C.RuleTypeDefault
|
||||
v = &r.DefaultOptions
|
||||
return unmarshalDefaultRuleContext(ctx, payload, &r.DefaultOptions)
|
||||
case C.RuleTypeLogical:
|
||||
v = &r.LogicalOptions
|
||||
return unmarshalLogicalRuleContext(ctx, payload, &r.LogicalOptions)
|
||||
default:
|
||||
return E.New("unknown rule type: " + r.Type)
|
||||
}
|
||||
err = badjson.UnmarshallExcluded(bytes, (*_Rule)(r), v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Rule) IsValid() bool {
|
||||
@@ -160,6 +159,64 @@ func (r *LogicalRule) UnmarshalJSON(data []byte) error {
|
||||
return badjson.UnmarshallExcluded(data, &r.RawLogicalRule, &r.RuleAction)
|
||||
}
|
||||
|
||||
func rulePayloadWithoutType(ctx context.Context, data []byte) ([]byte, error) {
|
||||
var content badjson.JSONObject
|
||||
err := content.UnmarshalJSONContext(ctx, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content.Remove("type")
|
||||
return content.MarshalJSONContext(ctx)
|
||||
}
|
||||
|
||||
func unmarshalDefaultRuleContext(ctx context.Context, data []byte, rule *DefaultRule) error {
|
||||
rawAction, routeOptions, err := inspectRouteRuleAction(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = rejectNestedRouteRuleAction(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
depth := nestedRuleDepth(ctx)
|
||||
err = json.UnmarshalContext(ctx, data, &rule.RawDefaultRule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = badjson.UnmarshallExcludedContext(ctx, data, &rule.RawDefaultRule, &rule.RuleAction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if depth > 0 && rawAction == "" && routeOptions == (RouteActionOptions{}) {
|
||||
rule.RuleAction = RuleAction{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalLogicalRuleContext(ctx context.Context, data []byte, rule *LogicalRule) error {
|
||||
rawAction, routeOptions, err := inspectRouteRuleAction(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = rejectNestedRouteRuleAction(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
depth := nestedRuleDepth(ctx)
|
||||
err = json.UnmarshalContext(nestedRuleChildContext(ctx), data, &rule.RawLogicalRule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = badjson.UnmarshallExcludedContext(ctx, data, &rule.RawLogicalRule, &rule.RuleAction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if depth > 0 && rawAction == "" && routeOptions == (RouteActionOptions{}) {
|
||||
rule.RuleAction = RuleAction{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *LogicalRule) IsValid() bool {
|
||||
return len(r.Rules) > 0 && common.All(r.Rules, Rule.IsValid)
|
||||
}
|
||||
|
||||
@@ -115,6 +115,8 @@ func (r DNSRuleAction) MarshalJSON() ([]byte, error) {
|
||||
case C.RuleActionTypeRoute:
|
||||
r.Action = ""
|
||||
v = r.RouteOptions
|
||||
case C.RuleActionTypeEvaluate:
|
||||
v = r.RouteOptions
|
||||
case C.RuleActionTypeRouteOptions:
|
||||
v = r.RouteOptionsOptions
|
||||
case C.RuleActionTypeReject:
|
||||
@@ -137,6 +139,8 @@ func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) e
|
||||
case "", C.RuleActionTypeRoute:
|
||||
r.Action = C.RuleActionTypeRoute
|
||||
v = &r.RouteOptions
|
||||
case C.RuleActionTypeEvaluate:
|
||||
v = &r.RouteOptions
|
||||
case C.RuleActionTypeRouteOptions:
|
||||
v = &r.RouteOptionsOptions
|
||||
case C.RuleActionTypeReject:
|
||||
|
||||
@@ -35,7 +35,7 @@ func (r DNSRule) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (r *DNSRule) UnmarshalJSONContext(ctx context.Context, bytes []byte) error {
|
||||
err := json.Unmarshal(bytes, (*_DNSRule)(r))
|
||||
err := json.UnmarshalContext(ctx, bytes, (*_DNSRule)(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -78,12 +78,6 @@ type RawDefaultDNSRule struct {
|
||||
DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
|
||||
DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
|
||||
DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
|
||||
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
|
||||
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
|
||||
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
|
||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
||||
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
||||
IPAcceptAny bool `json:"ip_accept_any,omitempty"`
|
||||
SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||
SourceIPIsPrivate bool `json:"source_ip_is_private,omitempty"`
|
||||
SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
|
||||
@@ -110,9 +104,23 @@ type RawDefaultDNSRule struct {
|
||||
SourceHostname badoption.Listable[string] `json:"source_hostname,omitempty"`
|
||||
RuleSet badoption.Listable[string] `json:"rule_set,omitempty"`
|
||||
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
|
||||
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"`
|
||||
MatchResponse bool `json:"match_response,omitempty"`
|
||||
IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
|
||||
IPIsPrivate bool `json:"ip_is_private,omitempty"`
|
||||
ResponseRcode *DNSRCode `json:"response_rcode,omitempty"`
|
||||
ResponseAnswer badoption.Listable[DNSRecordOptions] `json:"response_answer,omitempty"`
|
||||
ResponseNs badoption.Listable[DNSRecordOptions] `json:"response_ns,omitempty"`
|
||||
ResponseExtra badoption.Listable[DNSRecordOptions] `json:"response_extra,omitempty"`
|
||||
Invert bool `json:"invert,omitempty"`
|
||||
|
||||
// Deprecated: removed in sing-box 1.12.0
|
||||
Geosite badoption.Listable[string] `json:"geosite,omitempty"`
|
||||
SourceGeoIP badoption.Listable[string] `json:"source_geoip,omitempty"`
|
||||
GeoIP badoption.Listable[string] `json:"geoip,omitempty"`
|
||||
// Deprecated: use match_response with response items
|
||||
IPAcceptAny bool `json:"ip_accept_any,omitempty"`
|
||||
// Deprecated: removed in sing-box 1.11.0
|
||||
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"`
|
||||
// Deprecated: renamed to rule_set_ip_cidr_match_source
|
||||
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
|
||||
}
|
||||
@@ -127,11 +135,27 @@ func (r DefaultDNSRule) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) UnmarshalJSONContext(ctx context.Context, data []byte) error {
|
||||
err := json.UnmarshalContext(ctx, data, &r.RawDefaultDNSRule)
|
||||
rawAction, routeOptions, err := inspectDNSRuleAction(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return badjson.UnmarshallExcludedContext(ctx, data, &r.RawDefaultDNSRule, &r.DNSRuleAction)
|
||||
err = rejectNestedDNSRuleAction(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
depth := nestedRuleDepth(ctx)
|
||||
err = json.UnmarshalContext(ctx, data, &r.RawDefaultDNSRule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = badjson.UnmarshallExcludedContext(ctx, data, &r.RawDefaultDNSRule, &r.DNSRuleAction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if depth > 0 && rawAction == "" && routeOptions == (DNSRouteActionOptions{}) {
|
||||
r.DNSRuleAction = DNSRuleAction{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r DefaultDNSRule) IsValid() bool {
|
||||
@@ -156,11 +180,27 @@ func (r LogicalDNSRule) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) UnmarshalJSONContext(ctx context.Context, data []byte) error {
|
||||
err := json.Unmarshal(data, &r.RawLogicalDNSRule)
|
||||
rawAction, routeOptions, err := inspectDNSRuleAction(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return badjson.UnmarshallExcludedContext(ctx, data, &r.RawLogicalDNSRule, &r.DNSRuleAction)
|
||||
err = rejectNestedDNSRuleAction(ctx, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
depth := nestedRuleDepth(ctx)
|
||||
err = json.UnmarshalContext(nestedRuleChildContext(ctx), data, &r.RawLogicalDNSRule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = badjson.UnmarshallExcludedContext(ctx, data, &r.RawLogicalDNSRule, &r.DNSRuleAction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if depth > 0 && rawAction == "" && routeOptions == (DNSRouteActionOptions{}) {
|
||||
r.DNSRuleAction = DNSRuleAction{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) IsValid() bool {
|
||||
|
||||
133
option/rule_nested.go
Normal file
133
option/rule_nested.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/json/badjson"
|
||||
)
|
||||
|
||||
type nestedRuleDepthContextKey struct{}
|
||||
|
||||
const (
|
||||
routeRuleActionNestedUnsupportedMessage = "rule action is not supported in nested rules"
|
||||
dnsRuleActionNestedUnsupportedMessage = "DNS rule action is not supported in nested rules"
|
||||
)
|
||||
|
||||
var (
|
||||
routeRuleActionKeys = jsonFieldNames(reflect.TypeFor[_RuleAction](), reflect.TypeFor[RouteActionOptions]())
|
||||
dnsRuleActionKeys = jsonFieldNames(reflect.TypeFor[_DNSRuleAction](), reflect.TypeFor[DNSRouteActionOptions]())
|
||||
)
|
||||
|
||||
func nestedRuleChildContext(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, nestedRuleDepthContextKey{}, nestedRuleDepth(ctx)+1)
|
||||
}
|
||||
|
||||
func rejectNestedRouteRuleAction(ctx context.Context, content []byte) error {
|
||||
return rejectNestedRuleAction(ctx, content, routeRuleActionKeys, routeRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
|
||||
func rejectNestedDNSRuleAction(ctx context.Context, content []byte) error {
|
||||
return rejectNestedRuleAction(ctx, content, dnsRuleActionKeys, dnsRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
|
||||
func nestedRuleDepth(ctx context.Context) int {
|
||||
depth, _ := ctx.Value(nestedRuleDepthContextKey{}).(int)
|
||||
return depth
|
||||
}
|
||||
|
||||
func rejectNestedRuleAction(ctx context.Context, content []byte, keys []string, message string) error {
|
||||
if nestedRuleDepth(ctx) == 0 {
|
||||
return nil
|
||||
}
|
||||
hasActionKey, err := hasAnyJSONKey(ctx, content, keys...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hasActionKey {
|
||||
return E.New(message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasAnyJSONKey(ctx context.Context, content []byte, keys ...string) (bool, error) {
|
||||
var object badjson.JSONObject
|
||||
err := object.UnmarshalJSONContext(ctx, content)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, key := range keys {
|
||||
if object.ContainsKey(key) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func inspectRouteRuleAction(ctx context.Context, content []byte) (string, RouteActionOptions, error) {
|
||||
var rawAction _RuleAction
|
||||
err := json.UnmarshalContext(ctx, content, &rawAction)
|
||||
if err != nil {
|
||||
return "", RouteActionOptions{}, err
|
||||
}
|
||||
var routeOptions RouteActionOptions
|
||||
err = json.UnmarshalContext(ctx, content, &routeOptions)
|
||||
if err != nil {
|
||||
return "", RouteActionOptions{}, err
|
||||
}
|
||||
return rawAction.Action, routeOptions, nil
|
||||
}
|
||||
|
||||
func inspectDNSRuleAction(ctx context.Context, content []byte) (string, DNSRouteActionOptions, error) {
|
||||
var rawAction _DNSRuleAction
|
||||
err := json.UnmarshalContext(ctx, content, &rawAction)
|
||||
if err != nil {
|
||||
return "", DNSRouteActionOptions{}, err
|
||||
}
|
||||
var routeOptions DNSRouteActionOptions
|
||||
err = json.UnmarshalContext(ctx, content, &routeOptions)
|
||||
if err != nil {
|
||||
return "", DNSRouteActionOptions{}, err
|
||||
}
|
||||
return rawAction.Action, routeOptions, nil
|
||||
}
|
||||
|
||||
func jsonFieldNames(types ...reflect.Type) []string {
|
||||
fieldMap := make(map[string]struct{})
|
||||
for _, fieldType := range types {
|
||||
appendJSONFieldNames(fieldMap, fieldType)
|
||||
}
|
||||
fieldNames := make([]string, 0, len(fieldMap))
|
||||
for fieldName := range fieldMap {
|
||||
fieldNames = append(fieldNames, fieldName)
|
||||
}
|
||||
return fieldNames
|
||||
}
|
||||
|
||||
func appendJSONFieldNames(fieldMap map[string]struct{}, fieldType reflect.Type) {
|
||||
for fieldType.Kind() == reflect.Pointer {
|
||||
fieldType = fieldType.Elem()
|
||||
}
|
||||
if fieldType.Kind() != reflect.Struct {
|
||||
return
|
||||
}
|
||||
for i := range fieldType.NumField() {
|
||||
field := fieldType.Field(i)
|
||||
tagValue := field.Tag.Get("json")
|
||||
tagName, _, _ := strings.Cut(tagValue, ",")
|
||||
if tagName == "-" {
|
||||
continue
|
||||
}
|
||||
if field.Anonymous && tagName == "" {
|
||||
appendJSONFieldNames(fieldMap, field.Type)
|
||||
continue
|
||||
}
|
||||
if tagName == "" {
|
||||
tagName = field.Name
|
||||
}
|
||||
fieldMap[tagName] = struct{}{}
|
||||
}
|
||||
}
|
||||
271
option/rule_nested_test.go
Normal file
271
option/rule_nested_test.go
Normal file
@@ -0,0 +1,271 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRuleRejectsNestedDefaultRuleAction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var rule Rule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{"domain": "example.com", "outbound": "direct"}
|
||||
]
|
||||
}`), &rule)
|
||||
require.ErrorContains(t, err, routeRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
|
||||
func TestRuleRejectsNestedLogicalRuleAction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var rule Rule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"action": "route",
|
||||
"outbound": "direct",
|
||||
"rules": [{"domain": "example.com"}]
|
||||
}
|
||||
]
|
||||
}`), &rule)
|
||||
require.ErrorContains(t, err, routeRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
|
||||
func TestRuleRejectsNestedDefaultRuleZeroValueOutbound(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var rule Rule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{"domain": "example.com", "outbound": ""}
|
||||
]
|
||||
}`), &rule)
|
||||
require.ErrorContains(t, err, routeRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
|
||||
func TestRuleRejectsNestedDefaultRuleZeroValueRouteOption(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var rule Rule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{"domain": "example.com", "udp_connect": false}
|
||||
]
|
||||
}`), &rule)
|
||||
require.ErrorContains(t, err, routeRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
|
||||
func TestRuleRejectsNestedLogicalRuleZeroValueAction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var rule Rule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"action": "",
|
||||
"rules": [{"domain": "example.com"}]
|
||||
}
|
||||
]
|
||||
}`), &rule)
|
||||
require.ErrorContains(t, err, routeRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
|
||||
func TestRuleRejectsNestedLogicalRuleZeroValueRouteOption(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var rule Rule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"override_port": 0,
|
||||
"rules": [{"domain": "example.com"}]
|
||||
}
|
||||
]
|
||||
}`), &rule)
|
||||
require.ErrorContains(t, err, routeRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
|
||||
func TestRuleAllowsTopLevelLogicalAction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var rule Rule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"outbound": "direct",
|
||||
"rules": [{"domain": "example.com"}]
|
||||
}`), &rule)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.RuleActionTypeRoute, rule.LogicalOptions.Action)
|
||||
require.Equal(t, "direct", rule.LogicalOptions.RouteOptions.Outbound)
|
||||
}
|
||||
|
||||
func TestRuleLeavesUnknownNestedKeysToNormalValidation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var rule Rule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{"domain": "example.com", "foo": "bar"}
|
||||
]
|
||||
}`), &rule)
|
||||
require.ErrorContains(t, err, "unknown field")
|
||||
require.NotContains(t, err.Error(), routeRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
|
||||
func TestDNSRuleRejectsNestedDefaultRuleAction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var rule DNSRule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{"domain": "example.com", "server": "default"}
|
||||
]
|
||||
}`), &rule)
|
||||
require.ErrorContains(t, err, dnsRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
|
||||
func TestDNSRuleRejectsNestedLogicalRuleAction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var rule DNSRule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"action": "route",
|
||||
"server": "default",
|
||||
"rules": [{"domain": "example.com"}]
|
||||
}
|
||||
]
|
||||
}`), &rule)
|
||||
require.ErrorContains(t, err, dnsRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
|
||||
func TestDNSRuleRejectsNestedDefaultRuleZeroValueServer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var rule DNSRule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{"domain": "example.com", "server": ""}
|
||||
]
|
||||
}`), &rule)
|
||||
require.ErrorContains(t, err, dnsRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
|
||||
func TestDNSRuleRejectsNestedDefaultRuleZeroValueRouteOption(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var rule DNSRule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{"domain": "example.com", "disable_cache": false}
|
||||
]
|
||||
}`), &rule)
|
||||
require.ErrorContains(t, err, dnsRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
|
||||
func TestDNSRuleRejectsNestedLogicalRuleZeroValueAction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var rule DNSRule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"action": "",
|
||||
"rules": [{"domain": "example.com"}]
|
||||
}
|
||||
]
|
||||
}`), &rule)
|
||||
require.ErrorContains(t, err, dnsRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
|
||||
func TestDNSRuleRejectsNestedLogicalRuleZeroValueRouteOption(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var rule DNSRule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"disable_cache": false,
|
||||
"rules": [{"domain": "example.com"}]
|
||||
}
|
||||
]
|
||||
}`), &rule)
|
||||
require.ErrorContains(t, err, dnsRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
|
||||
func TestDNSRuleAllowsTopLevelLogicalAction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var rule DNSRule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"server": "default",
|
||||
"rules": [{"domain": "example.com"}]
|
||||
}`), &rule)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.RuleActionTypeRoute, rule.LogicalOptions.Action)
|
||||
require.Equal(t, "default", rule.LogicalOptions.RouteOptions.Server)
|
||||
}
|
||||
|
||||
func TestDNSRuleLeavesUnknownNestedKeysToNormalValidation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var rule DNSRule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{"domain": "example.com", "foo": "bar"}
|
||||
]
|
||||
}`), &rule)
|
||||
require.ErrorContains(t, err, "unknown field")
|
||||
require.NotContains(t, err.Error(), dnsRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
@@ -125,6 +125,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
UDPTimeout: udpTimeout,
|
||||
Handler: inbound,
|
||||
MasqueradeHandler: masqueradeHandler,
|
||||
BBRProfile: options.BBRProfile,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -73,12 +73,14 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||
ServerAddress: options.ServerOptions.Build(),
|
||||
ServerPorts: options.ServerPorts,
|
||||
HopInterval: time.Duration(options.HopInterval),
|
||||
HopIntervalMax: time.Duration(options.HopIntervalMax),
|
||||
SendBPS: uint64(options.UpMbps * hysteria.MbpsToBps),
|
||||
ReceiveBPS: uint64(options.DownMbps * hysteria.MbpsToBps),
|
||||
SalamanderPassword: salamanderPassword,
|
||||
Password: options.Password,
|
||||
TLSConfig: tlsConfig,
|
||||
UDPDisabled: !common.Contains(networkList, N.NetworkUDP),
|
||||
BBRProfile: options.BBRProfile,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -29,7 +29,10 @@ import (
|
||||
"golang.org/x/net/http2/h2c"
|
||||
)
|
||||
|
||||
var ConfigureHTTP3ListenerFunc func(ctx context.Context, logger logger.Logger, listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, options option.NaiveInboundOptions) (io.Closer, error)
|
||||
var (
|
||||
ConfigureHTTP3ListenerFunc func(ctx context.Context, logger logger.Logger, listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, options option.NaiveInboundOptions) (io.Closer, error)
|
||||
WrapError func(error) error
|
||||
)
|
||||
|
||||
func RegisterInbound(registry *inbound.Registry) {
|
||||
inbound.Register[option.NaiveInboundOptions](registry, C.TypeNaive, NewInbound)
|
||||
|
||||
@@ -179,18 +179,18 @@ type naiveConn struct {
|
||||
|
||||
func (c *naiveConn) Read(p []byte) (n int, err error) {
|
||||
n, err = c.readWithPadding(c.Conn, p)
|
||||
return n, baderror.WrapH2(err)
|
||||
return n, wrapError(err)
|
||||
}
|
||||
|
||||
func (c *naiveConn) Write(p []byte) (n int, err error) {
|
||||
n, err = c.writeChunked(c.Conn, p)
|
||||
return n, baderror.WrapH2(err)
|
||||
return n, wrapError(err)
|
||||
}
|
||||
|
||||
func (c *naiveConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
defer buffer.Release()
|
||||
err := c.writeBufferWithPadding(c.Conn, buffer)
|
||||
return baderror.WrapH2(err)
|
||||
return wrapError(err)
|
||||
}
|
||||
|
||||
func (c *naiveConn) FrontHeadroom() int { return c.frontHeadroom() }
|
||||
@@ -210,7 +210,7 @@ type naiveH2Conn struct {
|
||||
|
||||
func (c *naiveH2Conn) Read(p []byte) (n int, err error) {
|
||||
n, err = c.readWithPadding(c.reader, p)
|
||||
return n, baderror.WrapH2(err)
|
||||
return n, wrapError(err)
|
||||
}
|
||||
|
||||
func (c *naiveH2Conn) Write(p []byte) (n int, err error) {
|
||||
@@ -218,7 +218,7 @@ func (c *naiveH2Conn) Write(p []byte) (n int, err error) {
|
||||
if err == nil {
|
||||
c.flusher.Flush()
|
||||
}
|
||||
return n, baderror.WrapH2(err)
|
||||
return n, wrapError(err)
|
||||
}
|
||||
|
||||
func (c *naiveH2Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
@@ -227,7 +227,15 @@ func (c *naiveH2Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
if err == nil {
|
||||
c.flusher.Flush()
|
||||
}
|
||||
return baderror.WrapH2(err)
|
||||
return wrapError(err)
|
||||
}
|
||||
|
||||
func wrapError(err error) error {
|
||||
err = baderror.WrapH2(err)
|
||||
if WrapError != nil {
|
||||
err = WrapError(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *naiveH2Conn) Close() error {
|
||||
|
||||
@@ -124,4 +124,5 @@ func init() {
|
||||
|
||||
return quicListener, nil
|
||||
}
|
||||
naive.WrapError = qtls.WrapError
|
||||
}
|
||||
|
||||
@@ -70,6 +70,10 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route
|
||||
|
||||
func (r *Router) Initialize(rules []option.Rule, ruleSets []option.RuleSet) error {
|
||||
for i, options := range rules {
|
||||
err := R.ValidateNoNestedRuleActions(options)
|
||||
if err != nil {
|
||||
return E.Cause(err, "parse rule[", i, "]")
|
||||
}
|
||||
rule, err := R.NewRule(r.ctx, r.logger, options, false)
|
||||
if err != nil {
|
||||
return E.Cause(err, "parse rule[", i, "]")
|
||||
|
||||
126
route/rule/match_state.go
Normal file
126
route/rule/match_state.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package rule
|
||||
|
||||
import "github.com/sagernet/sing-box/adapter"
|
||||
|
||||
type ruleMatchState uint8
|
||||
|
||||
const (
|
||||
ruleMatchSourceAddress ruleMatchState = 1 << iota
|
||||
ruleMatchSourcePort
|
||||
ruleMatchDestinationAddress
|
||||
ruleMatchDestinationPort
|
||||
)
|
||||
|
||||
type ruleMatchStateSet uint16
|
||||
|
||||
func singleRuleMatchState(state ruleMatchState) ruleMatchStateSet {
|
||||
return 1 << state
|
||||
}
|
||||
|
||||
func emptyRuleMatchState() ruleMatchStateSet {
|
||||
return singleRuleMatchState(0)
|
||||
}
|
||||
|
||||
func (s ruleMatchStateSet) isEmpty() bool {
|
||||
return s == 0
|
||||
}
|
||||
|
||||
func (s ruleMatchStateSet) contains(state ruleMatchState) bool {
|
||||
return s&(1<<state) != 0
|
||||
}
|
||||
|
||||
func (s ruleMatchStateSet) add(state ruleMatchState) ruleMatchStateSet {
|
||||
return s | singleRuleMatchState(state)
|
||||
}
|
||||
|
||||
func (s ruleMatchStateSet) merge(other ruleMatchStateSet) ruleMatchStateSet {
|
||||
return s | other
|
||||
}
|
||||
|
||||
func (s ruleMatchStateSet) combine(other ruleMatchStateSet) ruleMatchStateSet {
|
||||
if s.isEmpty() || other.isEmpty() {
|
||||
return 0
|
||||
}
|
||||
var combined ruleMatchStateSet
|
||||
for left := ruleMatchState(0); left < 16; left++ {
|
||||
if !s.contains(left) {
|
||||
continue
|
||||
}
|
||||
for right := ruleMatchState(0); right < 16; right++ {
|
||||
if !other.contains(right) {
|
||||
continue
|
||||
}
|
||||
combined = combined.add(left | right)
|
||||
}
|
||||
}
|
||||
return combined
|
||||
}
|
||||
|
||||
func (s ruleMatchStateSet) withBase(base ruleMatchState) ruleMatchStateSet {
|
||||
if s.isEmpty() {
|
||||
return 0
|
||||
}
|
||||
var withBase ruleMatchStateSet
|
||||
for state := ruleMatchState(0); state < 16; state++ {
|
||||
if !s.contains(state) {
|
||||
continue
|
||||
}
|
||||
withBase = withBase.add(state | base)
|
||||
}
|
||||
return withBase
|
||||
}
|
||||
|
||||
func (s ruleMatchStateSet) filter(allowed func(ruleMatchState) bool) ruleMatchStateSet {
|
||||
var filtered ruleMatchStateSet
|
||||
for state := ruleMatchState(0); state < 16; state++ {
|
||||
if !s.contains(state) {
|
||||
continue
|
||||
}
|
||||
if allowed(state) {
|
||||
filtered = filtered.add(state)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
type ruleStateMatcher interface {
|
||||
matchStates(metadata *adapter.InboundContext) ruleMatchStateSet
|
||||
}
|
||||
|
||||
type ruleStateMatcherWithBase interface {
|
||||
matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet
|
||||
}
|
||||
|
||||
func matchHeadlessRuleStates(rule adapter.HeadlessRule, metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return matchHeadlessRuleStatesWithBase(rule, metadata, 0)
|
||||
}
|
||||
|
||||
func matchHeadlessRuleStatesWithBase(rule adapter.HeadlessRule, metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
|
||||
if matcher, isStateMatcher := rule.(ruleStateMatcherWithBase); isStateMatcher {
|
||||
return matcher.matchStatesWithBase(metadata, base)
|
||||
}
|
||||
if matcher, isStateMatcher := rule.(ruleStateMatcher); isStateMatcher {
|
||||
return matcher.matchStates(metadata).withBase(base)
|
||||
}
|
||||
if rule.Match(metadata) {
|
||||
return emptyRuleMatchState().withBase(base)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func matchRuleItemStates(item RuleItem, metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return matchRuleItemStatesWithBase(item, metadata, 0)
|
||||
}
|
||||
|
||||
func matchRuleItemStatesWithBase(item RuleItem, metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
|
||||
if matcher, isStateMatcher := item.(ruleStateMatcherWithBase); isStateMatcher {
|
||||
return matcher.matchStatesWithBase(metadata, base)
|
||||
}
|
||||
if matcher, isStateMatcher := item.(ruleStateMatcher); isStateMatcher {
|
||||
return matcher.matchStates(metadata).withBase(base)
|
||||
}
|
||||
if item.Match(metadata) {
|
||||
return emptyRuleMatchState().withBase(base)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
76
route/rule/nested_action.go
Normal file
76
route/rule/nested_action.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
const (
|
||||
routeRuleActionNestedUnsupportedMessage = "rule action is not supported in nested rules"
|
||||
dnsRuleActionNestedUnsupportedMessage = "DNS rule action is not supported in nested rules"
|
||||
)
|
||||
|
||||
func ValidateNoNestedRuleActions(rule option.Rule) error {
|
||||
return validateNoNestedRuleActions(rule, false)
|
||||
}
|
||||
|
||||
func ValidateNoNestedDNSRuleActions(rule option.DNSRule) error {
|
||||
return validateNoNestedDNSRuleActions(rule, false)
|
||||
}
|
||||
|
||||
func validateNoNestedRuleActions(rule option.Rule, nested bool) error {
|
||||
if nested && ruleHasConfiguredAction(rule) {
|
||||
return E.New(routeRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
if rule.Type != C.RuleTypeLogical {
|
||||
return nil
|
||||
}
|
||||
for i, subRule := range rule.LogicalOptions.Rules {
|
||||
err := validateNoNestedRuleActions(subRule, true)
|
||||
if err != nil {
|
||||
return E.Cause(err, "sub rule[", i, "]")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateNoNestedDNSRuleActions(rule option.DNSRule, nested bool) error {
|
||||
if nested && dnsRuleHasConfiguredAction(rule) {
|
||||
return E.New(dnsRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
if rule.Type != C.RuleTypeLogical {
|
||||
return nil
|
||||
}
|
||||
for i, subRule := range rule.LogicalOptions.Rules {
|
||||
err := validateNoNestedDNSRuleActions(subRule, true)
|
||||
if err != nil {
|
||||
return E.Cause(err, "sub rule[", i, "]")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ruleHasConfiguredAction(rule option.Rule) bool {
|
||||
switch rule.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
return !reflect.DeepEqual(rule.DefaultOptions.RuleAction, option.RuleAction{})
|
||||
case C.RuleTypeLogical:
|
||||
return !reflect.DeepEqual(rule.LogicalOptions.RuleAction, option.RuleAction{})
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func dnsRuleHasConfiguredAction(rule option.DNSRule) bool {
|
||||
switch rule.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
return !reflect.DeepEqual(rule.DefaultOptions.DNSRuleAction, option.DNSRuleAction{})
|
||||
case C.RuleTypeLogical:
|
||||
return !reflect.DeepEqual(rule.LogicalOptions.DNSRuleAction, option.DNSRuleAction{})
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
157
route/rule/nested_action_test.go
Normal file
157
route/rule/nested_action_test.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewRulePreservesImplicitTopLevelDefaultAction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var options option.Rule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"domain": "example.com"
|
||||
}`), &options)
|
||||
require.NoError(t, err)
|
||||
|
||||
rule, err := NewRule(context.Background(), log.NewNOPFactory().NewLogger("router"), options, false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, rule.Action())
|
||||
require.Equal(t, C.RuleActionTypeRoute, rule.Action().Type())
|
||||
}
|
||||
|
||||
func TestNewRuleAllowsNestedRuleWithoutAction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var options option.Rule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{"domain": "example.com"}
|
||||
]
|
||||
}`), &options)
|
||||
require.NoError(t, err)
|
||||
|
||||
rule, err := NewRule(context.Background(), log.NewNOPFactory().NewLogger("router"), options, false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, rule.Action())
|
||||
require.Equal(t, C.RuleActionTypeRoute, rule.Action().Type())
|
||||
}
|
||||
|
||||
func TestNewRuleRejectsNestedRuleAction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := NewRule(context.Background(), log.NewNOPFactory().NewLogger("router"), option.Rule{
|
||||
Type: C.RuleTypeLogical,
|
||||
LogicalOptions: option.LogicalRule{
|
||||
RawLogicalRule: option.RawLogicalRule{
|
||||
Mode: C.LogicalTypeAnd,
|
||||
Rules: []option.Rule{{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultRule{
|
||||
RuleAction: option.RuleAction{
|
||||
Action: C.RuleActionTypeRoute,
|
||||
RouteOptions: option.RouteActionOptions{
|
||||
Outbound: "direct",
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}, false)
|
||||
require.ErrorContains(t, err, routeRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
|
||||
func TestNewDNSRulePreservesImplicitTopLevelDefaultAction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var options option.DNSRule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"domain": "example.com"
|
||||
}`), &options)
|
||||
require.NoError(t, err)
|
||||
|
||||
rule, err := NewDNSRule(context.Background(), log.NewNOPFactory().NewLogger("dns"), options, false, false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, rule.Action())
|
||||
require.Equal(t, C.RuleActionTypeRoute, rule.Action().Type())
|
||||
}
|
||||
|
||||
func TestNewDNSRuleAllowsNestedRuleWithoutAction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var options option.DNSRule
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{"domain": "example.com"}
|
||||
]
|
||||
}`), &options)
|
||||
require.NoError(t, err)
|
||||
|
||||
rule, err := NewDNSRule(context.Background(), log.NewNOPFactory().NewLogger("dns"), options, false, false)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, rule.Action())
|
||||
require.Equal(t, C.RuleActionTypeRoute, rule.Action().Type())
|
||||
}
|
||||
|
||||
func TestNewDNSRuleRejectsNestedRuleAction(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := NewDNSRule(context.Background(), log.NewNOPFactory().NewLogger("dns"), option.DNSRule{
|
||||
Type: C.RuleTypeLogical,
|
||||
LogicalOptions: option.LogicalDNSRule{
|
||||
RawLogicalDNSRule: option.RawLogicalDNSRule{
|
||||
Mode: C.LogicalTypeAnd,
|
||||
Rules: []option.DNSRule{{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultDNSRule{
|
||||
DNSRuleAction: option.DNSRuleAction{
|
||||
Action: C.RuleActionTypeRoute,
|
||||
RouteOptions: option.DNSRouteActionOptions{
|
||||
Server: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
DNSRuleAction: option.DNSRuleAction{
|
||||
Action: C.RuleActionTypeRoute,
|
||||
RouteOptions: option.DNSRouteActionOptions{
|
||||
Server: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true, false)
|
||||
require.ErrorContains(t, err, dnsRuleActionNestedUnsupportedMessage)
|
||||
}
|
||||
|
||||
func TestNewDNSRuleRejectsReplyRejectMethod(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := NewDNSRule(context.Background(), log.NewNOPFactory().NewLogger("dns"), option.DNSRule{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultDNSRule{
|
||||
RawDefaultDNSRule: option.RawDefaultDNSRule{
|
||||
Domain: []string{"example.com"},
|
||||
},
|
||||
DNSRuleAction: option.DNSRuleAction{
|
||||
Action: C.RuleActionTypeReject,
|
||||
RejectOptions: option.RejectActionOptions{
|
||||
Method: C.RuleActionRejectMethodReply,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, false, false)
|
||||
require.ErrorContains(t, err, "reject method `reply` is not supported for DNS rules")
|
||||
}
|
||||
@@ -52,88 +52,120 @@ func (r *abstractDefaultRule) Close() error {
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||
return !r.matchStates(metadata).isEmpty()
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) destinationIPCIDRMatchesSource(metadata *adapter.InboundContext) bool {
|
||||
return metadata.IPCIDRMatchSource && len(r.destinationIPCIDRItems) > 0
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) destinationIPCIDRMatchesDestination(metadata *adapter.InboundContext) bool {
|
||||
return !metadata.IPCIDRMatchSource && len(r.destinationIPCIDRItems) > 0
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) requiresSourceAddressMatch(metadata *adapter.InboundContext) bool {
|
||||
return len(r.sourceAddressItems) > 0 || r.destinationIPCIDRMatchesSource(metadata)
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) requiresDestinationAddressMatch(metadata *adapter.InboundContext) bool {
|
||||
return len(r.destinationAddressItems) > 0 || r.destinationIPCIDRMatchesDestination(metadata)
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.matchStatesWithBase(metadata, 0)
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) matchStatesWithBase(metadata *adapter.InboundContext, inheritedBase ruleMatchState) ruleMatchStateSet {
|
||||
if len(r.allItems) == 0 {
|
||||
return true
|
||||
return emptyRuleMatchState().withBase(inheritedBase)
|
||||
}
|
||||
|
||||
if len(r.sourceAddressItems) > 0 && !metadata.SourceAddressMatch {
|
||||
evaluationBase := inheritedBase
|
||||
if r.invert {
|
||||
evaluationBase = 0
|
||||
}
|
||||
baseState := evaluationBase
|
||||
if len(r.sourceAddressItems) > 0 {
|
||||
metadata.DidMatch = true
|
||||
for _, item := range r.sourceAddressItems {
|
||||
if item.Match(metadata) {
|
||||
metadata.SourceAddressMatch = true
|
||||
break
|
||||
}
|
||||
if matchAnyItem(r.sourceAddressItems, metadata) {
|
||||
baseState |= ruleMatchSourceAddress
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.sourcePortItems) > 0 && !metadata.SourcePortMatch {
|
||||
if r.destinationIPCIDRMatchesSource(metadata) && !baseState.has(ruleMatchSourceAddress) {
|
||||
metadata.DidMatch = true
|
||||
for _, item := range r.sourcePortItems {
|
||||
if item.Match(metadata) {
|
||||
metadata.SourcePortMatch = true
|
||||
break
|
||||
}
|
||||
if matchAnyItem(r.destinationIPCIDRItems, metadata) {
|
||||
baseState |= ruleMatchSourceAddress
|
||||
}
|
||||
} else if r.destinationIPCIDRMatchesSource(metadata) {
|
||||
metadata.DidMatch = true
|
||||
}
|
||||
if len(r.sourcePortItems) > 0 {
|
||||
metadata.DidMatch = true
|
||||
if matchAnyItem(r.sourcePortItems, metadata) {
|
||||
baseState |= ruleMatchSourcePort
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.destinationAddressItems) > 0 && !metadata.DestinationAddressMatch {
|
||||
if len(r.destinationAddressItems) > 0 {
|
||||
metadata.DidMatch = true
|
||||
for _, item := range r.destinationAddressItems {
|
||||
if item.Match(metadata) {
|
||||
metadata.DestinationAddressMatch = true
|
||||
break
|
||||
}
|
||||
if matchAnyItem(r.destinationAddressItems, metadata) {
|
||||
baseState |= ruleMatchDestinationAddress
|
||||
}
|
||||
}
|
||||
|
||||
if !metadata.IgnoreDestinationIPCIDRMatch && len(r.destinationIPCIDRItems) > 0 && !metadata.DestinationAddressMatch {
|
||||
if r.destinationIPCIDRMatchesDestination(metadata) && !baseState.has(ruleMatchDestinationAddress) {
|
||||
metadata.DidMatch = true
|
||||
for _, item := range r.destinationIPCIDRItems {
|
||||
if item.Match(metadata) {
|
||||
metadata.DestinationAddressMatch = true
|
||||
break
|
||||
}
|
||||
if matchAnyItem(r.destinationIPCIDRItems, metadata) {
|
||||
baseState |= ruleMatchDestinationAddress
|
||||
}
|
||||
} else if r.destinationIPCIDRMatchesDestination(metadata) {
|
||||
metadata.DidMatch = true
|
||||
}
|
||||
if len(r.destinationPortItems) > 0 {
|
||||
metadata.DidMatch = true
|
||||
if matchAnyItem(r.destinationPortItems, metadata) {
|
||||
baseState |= ruleMatchDestinationPort
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.destinationPortItems) > 0 && !metadata.DestinationPortMatch {
|
||||
metadata.DidMatch = true
|
||||
for _, item := range r.destinationPortItems {
|
||||
if item.Match(metadata) {
|
||||
metadata.DestinationPortMatch = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, item := range r.items {
|
||||
metadata.DidMatch = true
|
||||
if !item.Match(metadata) {
|
||||
return r.invert
|
||||
return r.invertedFailure(inheritedBase)
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.sourceAddressItems) > 0 && !metadata.SourceAddressMatch {
|
||||
return r.invert
|
||||
var stateSet ruleMatchStateSet
|
||||
if r.ruleSetItem != nil {
|
||||
metadata.DidMatch = true
|
||||
stateSet = matchRuleItemStatesWithBase(r.ruleSetItem, metadata, baseState)
|
||||
} else {
|
||||
stateSet = singleRuleMatchState(baseState)
|
||||
}
|
||||
|
||||
if len(r.sourcePortItems) > 0 && !metadata.SourcePortMatch {
|
||||
return r.invert
|
||||
}
|
||||
|
||||
if ((!metadata.IgnoreDestinationIPCIDRMatch && len(r.destinationIPCIDRItems) > 0) || len(r.destinationAddressItems) > 0) && !metadata.DestinationAddressMatch {
|
||||
return r.invert
|
||||
}
|
||||
|
||||
if len(r.destinationPortItems) > 0 && !metadata.DestinationPortMatch {
|
||||
return r.invert
|
||||
}
|
||||
|
||||
if !metadata.DidMatch {
|
||||
stateSet = stateSet.filter(func(state ruleMatchState) bool {
|
||||
if r.requiresSourceAddressMatch(metadata) && !state.has(ruleMatchSourceAddress) {
|
||||
return false
|
||||
}
|
||||
if len(r.sourcePortItems) > 0 && !state.has(ruleMatchSourcePort) {
|
||||
return false
|
||||
}
|
||||
if r.requiresDestinationAddressMatch(metadata) && !state.has(ruleMatchDestinationAddress) {
|
||||
return false
|
||||
}
|
||||
if len(r.destinationPortItems) > 0 && !state.has(ruleMatchDestinationPort) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if stateSet.isEmpty() {
|
||||
return r.invertedFailure(inheritedBase)
|
||||
}
|
||||
if r.invert {
|
||||
return 0
|
||||
}
|
||||
return stateSet
|
||||
}
|
||||
|
||||
return !r.invert
|
||||
func (r *abstractDefaultRule) invertedFailure(base ruleMatchState) ruleMatchStateSet {
|
||||
if r.invert {
|
||||
return emptyRuleMatchState().withBase(base)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) Action() adapter.RuleAction {
|
||||
@@ -191,17 +223,50 @@ func (r *abstractLogicalRule) Close() error {
|
||||
}
|
||||
|
||||
func (r *abstractLogicalRule) Match(metadata *adapter.InboundContext) bool {
|
||||
if r.mode == C.LogicalTypeAnd {
|
||||
return common.All(r.rules, func(it adapter.HeadlessRule) bool {
|
||||
metadata.ResetRuleCache()
|
||||
return it.Match(metadata)
|
||||
}) != r.invert
|
||||
} else {
|
||||
return common.Any(r.rules, func(it adapter.HeadlessRule) bool {
|
||||
metadata.ResetRuleCache()
|
||||
return it.Match(metadata)
|
||||
}) != r.invert
|
||||
return !r.matchStates(metadata).isEmpty()
|
||||
}
|
||||
|
||||
func (r *abstractLogicalRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.matchStatesWithBase(metadata, 0)
|
||||
}
|
||||
|
||||
func (r *abstractLogicalRule) matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
|
||||
evaluationBase := base
|
||||
if r.invert {
|
||||
evaluationBase = 0
|
||||
}
|
||||
var stateSet ruleMatchStateSet
|
||||
if r.mode == C.LogicalTypeAnd {
|
||||
stateSet = emptyRuleMatchState().withBase(evaluationBase)
|
||||
for _, rule := range r.rules {
|
||||
nestedMetadata := *metadata
|
||||
nestedMetadata.ResetRuleCache()
|
||||
nestedStateSet := matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, evaluationBase)
|
||||
if nestedStateSet.isEmpty() {
|
||||
if r.invert {
|
||||
return emptyRuleMatchState().withBase(base)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
stateSet = stateSet.combine(nestedStateSet)
|
||||
}
|
||||
} else {
|
||||
for _, rule := range r.rules {
|
||||
nestedMetadata := *metadata
|
||||
nestedMetadata.ResetRuleCache()
|
||||
stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, evaluationBase))
|
||||
}
|
||||
if stateSet.isEmpty() {
|
||||
if r.invert {
|
||||
return emptyRuleMatchState().withBase(base)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
if r.invert {
|
||||
return 0
|
||||
}
|
||||
return stateSet
|
||||
}
|
||||
|
||||
func (r *abstractLogicalRule) Action() adapter.RuleAction {
|
||||
@@ -222,3 +287,13 @@ func (r *abstractLogicalRule) String() string {
|
||||
return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")"
|
||||
}
|
||||
}
|
||||
|
||||
func matchAnyItem(items []RuleItem, metadata *adapter.InboundContext) bool {
|
||||
return common.Any(items, func(it RuleItem) bool {
|
||||
return it.Match(metadata)
|
||||
})
|
||||
}
|
||||
|
||||
func (s ruleMatchState) has(target ruleMatchState) bool {
|
||||
return s&target != 0
|
||||
}
|
||||
|
||||
@@ -78,9 +78,9 @@ func newRuleSetOnlyRule(ruleSetMatched bool, invert bool) *DefaultRule {
|
||||
}
|
||||
return &DefaultRule{
|
||||
abstractDefaultRule: abstractDefaultRule{
|
||||
items: []RuleItem{ruleSetItem},
|
||||
allItems: []RuleItem{ruleSetItem},
|
||||
invert: invert,
|
||||
ruleSetItem: ruleSetItem,
|
||||
allItems: []RuleItem{ruleSetItem},
|
||||
invert: invert,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +132,16 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction)
|
||||
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),
|
||||
},
|
||||
}
|
||||
case C.RuleActionTypeEvaluate:
|
||||
return &RuleActionEvaluate{
|
||||
Server: action.RouteOptions.Server,
|
||||
RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{
|
||||
Strategy: C.DomainStrategy(action.RouteOptions.Strategy),
|
||||
DisableCache: action.RouteOptions.DisableCache,
|
||||
RewriteTTL: action.RouteOptions.RewriteTTL,
|
||||
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),
|
||||
},
|
||||
}
|
||||
case C.RuleActionTypeRouteOptions:
|
||||
return &RuleActionDNSRouteOptions{
|
||||
Strategy: C.DomainStrategy(action.RouteOptionsOptions.Strategy),
|
||||
@@ -266,18 +276,35 @@ func (r *RuleActionDNSRoute) Type() string {
|
||||
}
|
||||
|
||||
func (r *RuleActionDNSRoute) String() string {
|
||||
return formatDNSRouteAction("route", r.Server, r.RuleActionDNSRouteOptions)
|
||||
}
|
||||
|
||||
type RuleActionEvaluate struct {
|
||||
Server string
|
||||
RuleActionDNSRouteOptions
|
||||
}
|
||||
|
||||
func (r *RuleActionEvaluate) Type() string {
|
||||
return C.RuleActionTypeEvaluate
|
||||
}
|
||||
|
||||
func (r *RuleActionEvaluate) String() string {
|
||||
return formatDNSRouteAction("evaluate", r.Server, r.RuleActionDNSRouteOptions)
|
||||
}
|
||||
|
||||
func formatDNSRouteAction(action string, server string, options RuleActionDNSRouteOptions) string {
|
||||
var descriptions []string
|
||||
descriptions = append(descriptions, r.Server)
|
||||
if r.DisableCache {
|
||||
descriptions = append(descriptions, server)
|
||||
if options.DisableCache {
|
||||
descriptions = append(descriptions, "disable-cache")
|
||||
}
|
||||
if r.RewriteTTL != nil {
|
||||
descriptions = append(descriptions, F.ToString("rewrite-ttl=", *r.RewriteTTL))
|
||||
if options.RewriteTTL != nil {
|
||||
descriptions = append(descriptions, F.ToString("rewrite-ttl=", *options.RewriteTTL))
|
||||
}
|
||||
if r.ClientSubnet.IsValid() {
|
||||
descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet))
|
||||
if options.ClientSubnet.IsValid() {
|
||||
descriptions = append(descriptions, F.ToString("client-subnet=", options.ClientSubnet))
|
||||
}
|
||||
return F.ToString("route(", strings.Join(descriptions, ","), ")")
|
||||
return F.ToString(action, "(", strings.Join(descriptions, ","), ")")
|
||||
}
|
||||
|
||||
type RuleActionDNSRouteOptions struct {
|
||||
|
||||
@@ -47,6 +47,10 @@ type DefaultRule struct {
|
||||
abstractDefaultRule
|
||||
}
|
||||
|
||||
func (r *DefaultRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.abstractDefaultRule.matchStates(metadata)
|
||||
}
|
||||
|
||||
type RuleItem interface {
|
||||
Match(metadata *adapter.InboundContext) bool
|
||||
String() string
|
||||
@@ -285,7 +289,7 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
|
||||
matchSource = true
|
||||
}
|
||||
item := NewRuleSetItem(router, options.RuleSet, matchSource, false)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.ruleSetItem = item
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
return rule, nil
|
||||
@@ -297,6 +301,10 @@ type LogicalRule struct {
|
||||
abstractLogicalRule
|
||||
}
|
||||
|
||||
func (r *LogicalRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.abstractLogicalRule.matchStates(metadata)
|
||||
}
|
||||
|
||||
func NewLogicalRule(ctx context.Context, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) {
|
||||
action, err := NewRuleAction(ctx, logger, options.RuleAction)
|
||||
if err != nil {
|
||||
@@ -318,6 +326,10 @@ func NewLogicalRule(ctx context.Context, logger log.ContextLogger, options optio
|
||||
return nil, E.New("unknown logical mode: ", options.Mode)
|
||||
}
|
||||
for i, subOptions := range options.Rules {
|
||||
err = validateNoNestedRuleActions(subOptions, true)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "sub rule[", i, "]")
|
||||
}
|
||||
subRule, err := NewRule(ctx, logger, subOptions, false)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "sub rule[", i, "]")
|
||||
|
||||
@@ -5,54 +5,84 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func NewDNSRule(ctx context.Context, logger log.ContextLogger, options option.DNSRule, checkServer bool) (adapter.DNSRule, error) {
|
||||
func NewDNSRule(ctx context.Context, logger log.ContextLogger, options option.DNSRule, checkServer bool, legacyDNSMode bool) (adapter.DNSRule, error) {
|
||||
switch options.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
if !options.DefaultOptions.IsValid() {
|
||||
return nil, E.New("missing conditions")
|
||||
}
|
||||
if !checkServer && options.DefaultOptions.Action == C.RuleActionTypeEvaluate {
|
||||
return nil, E.New(options.DefaultOptions.Action, " is only allowed on top-level DNS rules")
|
||||
}
|
||||
err := validateDNSRuleAction(options.DefaultOptions.DNSRuleAction)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch options.DefaultOptions.Action {
|
||||
case "", C.RuleActionTypeRoute:
|
||||
case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate:
|
||||
if options.DefaultOptions.RouteOptions.Server == "" && checkServer {
|
||||
return nil, E.New("missing server field")
|
||||
}
|
||||
}
|
||||
return NewDefaultDNSRule(ctx, logger, options.DefaultOptions)
|
||||
return NewDefaultDNSRule(ctx, logger, options.DefaultOptions, legacyDNSMode)
|
||||
case C.RuleTypeLogical:
|
||||
if !options.LogicalOptions.IsValid() {
|
||||
return nil, E.New("missing conditions")
|
||||
}
|
||||
if !checkServer && options.LogicalOptions.Action == C.RuleActionTypeEvaluate {
|
||||
return nil, E.New(options.LogicalOptions.Action, " is only allowed on top-level DNS rules")
|
||||
}
|
||||
err := validateDNSRuleAction(options.LogicalOptions.DNSRuleAction)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch options.LogicalOptions.Action {
|
||||
case "", C.RuleActionTypeRoute:
|
||||
case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate:
|
||||
if options.LogicalOptions.RouteOptions.Server == "" && checkServer {
|
||||
return nil, E.New("missing server field")
|
||||
}
|
||||
}
|
||||
return NewLogicalDNSRule(ctx, logger, options.LogicalOptions)
|
||||
return NewLogicalDNSRule(ctx, logger, options.LogicalOptions, legacyDNSMode)
|
||||
default:
|
||||
return nil, E.New("unknown rule type: ", options.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func validateDNSRuleAction(action option.DNSRuleAction) error {
|
||||
if action.Action == C.RuleActionTypeReject && action.RejectOptions.Method == C.RuleActionRejectMethodReply {
|
||||
return E.New("reject method `reply` is not supported for DNS rules")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ adapter.DNSRule = (*DefaultDNSRule)(nil)
|
||||
|
||||
type DefaultDNSRule struct {
|
||||
abstractDefaultRule
|
||||
matchResponse bool
|
||||
}
|
||||
|
||||
func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) {
|
||||
func (r *DefaultDNSRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.abstractDefaultRule.matchStates(metadata)
|
||||
}
|
||||
|
||||
func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options option.DefaultDNSRule, legacyDNSMode bool) (*DefaultDNSRule, error) {
|
||||
rule := &DefaultDNSRule{
|
||||
abstractDefaultRule: abstractDefaultRule{
|
||||
invert: options.Invert,
|
||||
action: NewDNSRuleAction(logger, options.DNSRuleAction),
|
||||
},
|
||||
matchResponse: options.MatchResponse,
|
||||
}
|
||||
if len(options.Inbound) > 0 {
|
||||
item := NewInboundRule(options.Inbound)
|
||||
@@ -112,7 +142,7 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
|
||||
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.Geosite) > 0 {
|
||||
if len(options.Geosite) > 0 { //nolint:staticcheck
|
||||
return nil, E.New("geosite database is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0")
|
||||
}
|
||||
if len(options.SourceGeoIP) > 0 {
|
||||
@@ -147,11 +177,36 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
|
||||
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if options.IPAcceptAny {
|
||||
if options.IPAcceptAny { //nolint:staticcheck
|
||||
if legacyDNSMode {
|
||||
deprecated.Report(ctx, deprecated.OptionIPAcceptAny)
|
||||
} else {
|
||||
return nil, E.New("ip_accept_any is removed when legacyDNSMode is disabled, use ip_cidr with match_response")
|
||||
}
|
||||
item := NewIPAcceptAnyItem()
|
||||
rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if options.ResponseRcode != nil {
|
||||
item := NewDNSResponseRCodeItem(int(*options.ResponseRcode))
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.ResponseAnswer) > 0 {
|
||||
item := NewDNSResponseRecordItem("response_answer", options.ResponseAnswer, dnsResponseAnswers)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.ResponseNs) > 0 {
|
||||
item := NewDNSResponseRecordItem("response_ns", options.ResponseNs, dnsResponseNS)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.ResponseExtra) > 0 {
|
||||
item := NewDNSResponseRecordItem("response_extra", options.ResponseExtra, dnsResponseExtra)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.SourcePort) > 0 {
|
||||
item := NewPortItem(true, options.SourcePort)
|
||||
rule.sourcePortItems = append(rule.sourcePortItems, item)
|
||||
@@ -280,8 +335,15 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
|
||||
if options.RuleSetIPCIDRMatchSource {
|
||||
matchSource = true
|
||||
}
|
||||
item := NewRuleSetItem(router, options.RuleSet, matchSource, options.RuleSetIPCIDRAcceptEmpty)
|
||||
rule.items = append(rule.items, item)
|
||||
if options.RuleSetIPCIDRAcceptEmpty { //nolint:staticcheck
|
||||
if legacyDNSMode {
|
||||
deprecated.Report(ctx, deprecated.OptionRuleSetIPCIDRAcceptEmpty)
|
||||
} else {
|
||||
return nil, E.New("rule_set_ip_cidr_accept_empty is removed when legacyDNSMode is disabled")
|
||||
}
|
||||
}
|
||||
item := NewRuleSetItem(router, options.RuleSet, matchSource, options.RuleSetIPCIDRAcceptEmpty) //nolint:staticcheck
|
||||
rule.ruleSetItem = item
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
return rule, nil
|
||||
@@ -295,12 +357,9 @@ func (r *DefaultDNSRule) WithAddressLimit() bool {
|
||||
if len(r.destinationIPCIDRItems) > 0 {
|
||||
return true
|
||||
}
|
||||
for _, rawRule := range r.items {
|
||||
ruleSet, isRuleSet := rawRule.(*RuleSetItem)
|
||||
if !isRuleSet {
|
||||
continue
|
||||
}
|
||||
if ruleSet.ContainsDestinationIPCIDRRule() {
|
||||
if r.ruleSetItem != nil {
|
||||
ruleSet, isRuleSet := r.ruleSetItem.(*RuleSetItem)
|
||||
if isRuleSet && ruleSet.ContainsDestinationIPCIDRRule() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -308,15 +367,44 @@ func (r *DefaultDNSRule) WithAddressLimit() bool {
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) Match(metadata *adapter.InboundContext) bool {
|
||||
metadata.IgnoreDestinationIPCIDRMatch = true
|
||||
defer func() {
|
||||
metadata.IgnoreDestinationIPCIDRMatch = false
|
||||
}()
|
||||
return r.abstractDefaultRule.Match(metadata)
|
||||
return !r.matchStatesForMatch(metadata).isEmpty()
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) MatchAddressLimit(metadata *adapter.InboundContext) bool {
|
||||
return r.abstractDefaultRule.Match(metadata)
|
||||
func (r *DefaultDNSRule) LegacyPreMatch(metadata *adapter.InboundContext) bool {
|
||||
if r.matchResponse {
|
||||
return !r.legacyMatchStatesForMatch(metadata).isEmpty()
|
||||
}
|
||||
return !r.abstractDefaultRule.legacyMatchStates(metadata).isEmpty()
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) matchStatesForMatch(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.matchStatesForMatchWithMissingResponse(metadata, true)
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) legacyMatchStatesForMatch(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.matchStatesForMatchWithMissingResponse(metadata, false)
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) matchStatesForMatchWithMissingResponse(metadata *adapter.InboundContext, ordinaryFailure bool) ruleMatchStateSet {
|
||||
if r.matchResponse {
|
||||
if metadata.DNSResponse == nil {
|
||||
if ordinaryFailure {
|
||||
return r.abstractDefaultRule.invertedFailure(0)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
matchMetadata := *metadata
|
||||
matchMetadata.DestinationAddressMatchFromResponse = true
|
||||
return r.abstractDefaultRule.matchStates(&matchMetadata)
|
||||
}
|
||||
return r.abstractDefaultRule.matchStates(metadata)
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) MatchAddressLimit(metadata *adapter.InboundContext, response *dns.Msg) bool {
|
||||
matchMetadata := *metadata
|
||||
matchMetadata.DNSResponse = response
|
||||
matchMetadata.DestinationAddressMatchFromResponse = true
|
||||
return !r.abstractDefaultRule.matchStates(&matchMetadata).isEmpty()
|
||||
}
|
||||
|
||||
var _ adapter.DNSRule = (*LogicalDNSRule)(nil)
|
||||
@@ -325,7 +413,57 @@ type LogicalDNSRule struct {
|
||||
abstractLogicalRule
|
||||
}
|
||||
|
||||
func NewLogicalDNSRule(ctx context.Context, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) {
|
||||
func (r *LogicalDNSRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.abstractLogicalRule.matchStates(metadata)
|
||||
}
|
||||
|
||||
func matchDNSHeadlessRuleStatesForMatch(rule adapter.HeadlessRule, metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
switch typedRule := rule.(type) {
|
||||
case *DefaultDNSRule:
|
||||
return typedRule.matchStatesForMatch(metadata)
|
||||
case *LogicalDNSRule:
|
||||
return typedRule.matchStatesForMatch(metadata)
|
||||
default:
|
||||
return matchHeadlessRuleStates(typedRule, metadata)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) matchStatesForMatch(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
var stateSet ruleMatchStateSet
|
||||
if r.mode == C.LogicalTypeAnd {
|
||||
stateSet = emptyRuleMatchState()
|
||||
for _, rule := range r.rules {
|
||||
nestedMetadata := *metadata
|
||||
nestedMetadata.ResetRuleCache()
|
||||
nestedStateSet := matchDNSHeadlessRuleStatesForMatch(rule, &nestedMetadata)
|
||||
if nestedStateSet.isEmpty() {
|
||||
if r.invert {
|
||||
return emptyRuleMatchState()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
stateSet = stateSet.combine(nestedStateSet)
|
||||
}
|
||||
} else {
|
||||
for _, rule := range r.rules {
|
||||
nestedMetadata := *metadata
|
||||
nestedMetadata.ResetRuleCache()
|
||||
stateSet = stateSet.merge(matchDNSHeadlessRuleStatesForMatch(rule, &nestedMetadata))
|
||||
}
|
||||
if stateSet.isEmpty() {
|
||||
if r.invert {
|
||||
return emptyRuleMatchState()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
if r.invert {
|
||||
return 0
|
||||
}
|
||||
return stateSet
|
||||
}
|
||||
|
||||
func NewLogicalDNSRule(ctx context.Context, logger log.ContextLogger, options option.LogicalDNSRule, legacyDNSMode bool) (*LogicalDNSRule, error) {
|
||||
r := &LogicalDNSRule{
|
||||
abstractLogicalRule: abstractLogicalRule{
|
||||
rules: make([]adapter.HeadlessRule, len(options.Rules)),
|
||||
@@ -342,7 +480,11 @@ func NewLogicalDNSRule(ctx context.Context, logger log.ContextLogger, options op
|
||||
return nil, E.New("unknown logical mode: ", options.Mode)
|
||||
}
|
||||
for i, subRule := range options.Rules {
|
||||
rule, err := NewDNSRule(ctx, logger, subRule, false)
|
||||
err := validateNoNestedDNSRuleActions(subRule, true)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "sub rule[", i, "]")
|
||||
}
|
||||
rule, err := NewDNSRule(ctx, logger, subRule, false, legacyDNSMode)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "sub rule[", i, "]")
|
||||
}
|
||||
@@ -372,29 +514,16 @@ func (r *LogicalDNSRule) WithAddressLimit() bool {
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool {
|
||||
if r.mode == C.LogicalTypeAnd {
|
||||
return common.All(r.rules, func(it adapter.HeadlessRule) bool {
|
||||
metadata.ResetRuleCache()
|
||||
return it.(adapter.DNSRule).Match(metadata)
|
||||
}) != r.invert
|
||||
} else {
|
||||
return common.Any(r.rules, func(it adapter.HeadlessRule) bool {
|
||||
metadata.ResetRuleCache()
|
||||
return it.(adapter.DNSRule).Match(metadata)
|
||||
}) != r.invert
|
||||
}
|
||||
return !r.matchStatesForMatch(metadata).isEmpty()
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) MatchAddressLimit(metadata *adapter.InboundContext) bool {
|
||||
if r.mode == C.LogicalTypeAnd {
|
||||
return common.All(r.rules, func(it adapter.HeadlessRule) bool {
|
||||
metadata.ResetRuleCache()
|
||||
return it.(adapter.DNSRule).MatchAddressLimit(metadata)
|
||||
}) != r.invert
|
||||
} else {
|
||||
return common.Any(r.rules, func(it adapter.HeadlessRule) bool {
|
||||
metadata.ResetRuleCache()
|
||||
return it.(adapter.DNSRule).MatchAddressLimit(metadata)
|
||||
}) != r.invert
|
||||
}
|
||||
func (r *LogicalDNSRule) LegacyPreMatch(metadata *adapter.InboundContext) bool {
|
||||
return !r.abstractLogicalRule.legacyMatchStates(metadata).isEmpty()
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) MatchAddressLimit(metadata *adapter.InboundContext, response *dns.Msg) bool {
|
||||
matchMetadata := *metadata
|
||||
matchMetadata.DNSResponse = response
|
||||
matchMetadata.DestinationAddressMatchFromResponse = true
|
||||
return !r.abstractLogicalRule.matchStates(&matchMetadata).isEmpty()
|
||||
}
|
||||
|
||||
754
route/rule/rule_dns_legacy.go
Normal file
754
route/rule/rule_dns_legacy.go
Normal file
@@ -0,0 +1,754 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
type legacyResponseLiteralKind uint8
|
||||
|
||||
const (
|
||||
legacyLiteralRequireEmpty legacyResponseLiteralKind = iota
|
||||
legacyLiteralRequireNonEmpty
|
||||
legacyLiteralRequireSet
|
||||
legacyLiteralForbidSet
|
||||
)
|
||||
|
||||
type legacyResponseLiteral struct {
|
||||
kind legacyResponseLiteralKind
|
||||
ipSet *netipx.IPSet
|
||||
}
|
||||
|
||||
type legacyResponseFormulaKind uint8
|
||||
|
||||
const (
|
||||
legacyFormulaFalse legacyResponseFormulaKind = iota
|
||||
legacyFormulaTrue
|
||||
legacyFormulaLiteral
|
||||
legacyFormulaAnd
|
||||
legacyFormulaOr
|
||||
)
|
||||
|
||||
type legacyResponseFormula struct {
|
||||
kind legacyResponseFormulaKind
|
||||
literal legacyResponseLiteral
|
||||
children []legacyResponseFormula
|
||||
}
|
||||
|
||||
type legacyResponseConstraint struct {
|
||||
requireEmpty bool
|
||||
requireNonEmpty bool
|
||||
requiredSets []*netipx.IPSet
|
||||
forbiddenSet *netipx.IPSet
|
||||
}
|
||||
|
||||
const (
|
||||
legacyRuleMatchDeferredDestinationAddress ruleMatchState = 1 << 4
|
||||
legacyRuleMatchStateCount = 32
|
||||
)
|
||||
|
||||
type legacyRuleMatchStateSet [legacyRuleMatchStateCount]legacyResponseFormula
|
||||
|
||||
var (
|
||||
legacyAllIPSet = func() *netipx.IPSet {
|
||||
var builder netipx.IPSetBuilder
|
||||
builder.Complement()
|
||||
return common.Must1(builder.IPSet())
|
||||
}()
|
||||
legacyNonPublicIPSet = func() *netipx.IPSet {
|
||||
var builder netipx.IPSetBuilder
|
||||
for _, prefix := range []string{
|
||||
"0.0.0.0/32",
|
||||
"10.0.0.0/8",
|
||||
"127.0.0.0/8",
|
||||
"169.254.0.0/16",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16",
|
||||
"224.0.0.0/4",
|
||||
"::/128",
|
||||
"::1/128",
|
||||
"fc00::/7",
|
||||
"fe80::/10",
|
||||
"ff00::/8",
|
||||
} {
|
||||
builder.AddPrefix(netip.MustParsePrefix(prefix))
|
||||
}
|
||||
return common.Must1(builder.IPSet())
|
||||
}()
|
||||
)
|
||||
|
||||
func legacyFalseFormula() legacyResponseFormula {
|
||||
return legacyResponseFormula{}
|
||||
}
|
||||
|
||||
func legacyTrueFormula() legacyResponseFormula {
|
||||
return legacyResponseFormula{kind: legacyFormulaTrue}
|
||||
}
|
||||
|
||||
func legacyLiteralFormula(literal legacyResponseLiteral) legacyResponseFormula {
|
||||
return legacyResponseFormula{
|
||||
kind: legacyFormulaLiteral,
|
||||
literal: literal,
|
||||
}
|
||||
}
|
||||
|
||||
func (f legacyResponseFormula) isFalse() bool {
|
||||
return f.kind == legacyFormulaFalse
|
||||
}
|
||||
|
||||
func (f legacyResponseFormula) isTrue() bool {
|
||||
return f.kind == legacyFormulaTrue
|
||||
}
|
||||
|
||||
func (f legacyResponseFormula) or(other legacyResponseFormula) legacyResponseFormula {
|
||||
return legacyOrFormulas(f, other)
|
||||
}
|
||||
|
||||
func (f legacyResponseFormula) and(other legacyResponseFormula) legacyResponseFormula {
|
||||
return legacyAndFormulas(f, other)
|
||||
}
|
||||
|
||||
func (f legacyResponseFormula) not() legacyResponseFormula {
|
||||
switch f.kind {
|
||||
case legacyFormulaFalse:
|
||||
return legacyTrueFormula()
|
||||
case legacyFormulaTrue:
|
||||
return legacyFalseFormula()
|
||||
case legacyFormulaLiteral:
|
||||
return legacyLiteralFormula(legacyNegateResponseLiteral(f.literal))
|
||||
case legacyFormulaAnd:
|
||||
negated := make([]legacyResponseFormula, 0, len(f.children))
|
||||
for _, child := range f.children {
|
||||
negated = append(negated, child.not())
|
||||
}
|
||||
return legacyOrFormulas(negated...)
|
||||
case legacyFormulaOr:
|
||||
negated := make([]legacyResponseFormula, 0, len(f.children))
|
||||
for _, child := range f.children {
|
||||
negated = append(negated, child.not())
|
||||
}
|
||||
return legacyAndFormulas(negated...)
|
||||
default:
|
||||
panic("unknown legacy response formula kind")
|
||||
}
|
||||
}
|
||||
|
||||
func legacyNegateResponseLiteral(literal legacyResponseLiteral) legacyResponseLiteral {
|
||||
switch literal.kind {
|
||||
case legacyLiteralRequireEmpty:
|
||||
return legacyResponseLiteral{kind: legacyLiteralRequireNonEmpty}
|
||||
case legacyLiteralRequireNonEmpty:
|
||||
return legacyResponseLiteral{kind: legacyLiteralRequireEmpty}
|
||||
case legacyLiteralRequireSet:
|
||||
return legacyResponseLiteral{kind: legacyLiteralForbidSet, ipSet: literal.ipSet}
|
||||
case legacyLiteralForbidSet:
|
||||
return legacyResponseLiteral{kind: legacyLiteralRequireSet, ipSet: literal.ipSet}
|
||||
default:
|
||||
panic("unknown legacy response literal kind")
|
||||
}
|
||||
}
|
||||
|
||||
func legacyOrFormulas(formulas ...legacyResponseFormula) legacyResponseFormula {
|
||||
children := make([]legacyResponseFormula, 0, len(formulas))
|
||||
for _, formula := range formulas {
|
||||
if formula.isFalse() {
|
||||
continue
|
||||
}
|
||||
if formula.isTrue() {
|
||||
return legacyTrueFormula()
|
||||
}
|
||||
if formula.kind == legacyFormulaOr {
|
||||
children = append(children, formula.children...)
|
||||
continue
|
||||
}
|
||||
children = append(children, formula)
|
||||
}
|
||||
switch len(children) {
|
||||
case 0:
|
||||
return legacyFalseFormula()
|
||||
case 1:
|
||||
return children[0]
|
||||
default:
|
||||
return legacyResponseFormula{
|
||||
kind: legacyFormulaOr,
|
||||
children: children,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func legacyAndFormulas(formulas ...legacyResponseFormula) legacyResponseFormula {
|
||||
children := make([]legacyResponseFormula, 0, len(formulas))
|
||||
for _, formula := range formulas {
|
||||
if formula.isFalse() {
|
||||
return legacyFalseFormula()
|
||||
}
|
||||
if formula.isTrue() {
|
||||
continue
|
||||
}
|
||||
if formula.kind == legacyFormulaAnd {
|
||||
children = append(children, formula.children...)
|
||||
continue
|
||||
}
|
||||
children = append(children, formula)
|
||||
}
|
||||
switch len(children) {
|
||||
case 0:
|
||||
return legacyTrueFormula()
|
||||
case 1:
|
||||
return children[0]
|
||||
}
|
||||
result := legacyResponseFormula{
|
||||
kind: legacyFormulaAnd,
|
||||
children: children,
|
||||
}
|
||||
if !result.satisfiable() {
|
||||
return legacyFalseFormula()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (f legacyResponseFormula) satisfiable() bool {
|
||||
return legacyResponseFormulasSatisfiable(legacyResponseConstraint{}, []legacyResponseFormula{f})
|
||||
}
|
||||
|
||||
func legacyResponseFormulasSatisfiable(constraint legacyResponseConstraint, formulas []legacyResponseFormula) bool {
|
||||
stack := append(make([]legacyResponseFormula, 0, len(formulas)), formulas...)
|
||||
var disjunctions []legacyResponseFormula
|
||||
for len(stack) > 0 {
|
||||
formula := stack[len(stack)-1]
|
||||
stack = stack[:len(stack)-1]
|
||||
switch formula.kind {
|
||||
case legacyFormulaFalse:
|
||||
return false
|
||||
case legacyFormulaTrue:
|
||||
continue
|
||||
case legacyFormulaLiteral:
|
||||
var ok bool
|
||||
constraint, ok = constraint.withLiteral(formula.literal)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
case legacyFormulaAnd:
|
||||
stack = append(stack, formula.children...)
|
||||
case legacyFormulaOr:
|
||||
if len(formula.children) == 0 {
|
||||
return false
|
||||
}
|
||||
disjunctions = append(disjunctions, formula)
|
||||
default:
|
||||
panic("unknown legacy response formula kind")
|
||||
}
|
||||
}
|
||||
if len(disjunctions) == 0 {
|
||||
return true
|
||||
}
|
||||
bestIndex := 0
|
||||
for i := 1; i < len(disjunctions); i++ {
|
||||
if len(disjunctions[i].children) < len(disjunctions[bestIndex].children) {
|
||||
bestIndex = i
|
||||
}
|
||||
}
|
||||
selected := disjunctions[bestIndex]
|
||||
remaining := make([]legacyResponseFormula, 0, len(disjunctions)-1)
|
||||
remaining = append(remaining, disjunctions[:bestIndex]...)
|
||||
remaining = append(remaining, disjunctions[bestIndex+1:]...)
|
||||
for _, child := range selected.children {
|
||||
nextFormulas := make([]legacyResponseFormula, 0, len(remaining)+1)
|
||||
nextFormulas = append(nextFormulas, remaining...)
|
||||
nextFormulas = append(nextFormulas, child)
|
||||
if legacyResponseFormulasSatisfiable(constraint, nextFormulas) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c legacyResponseConstraint) withLiteral(literal legacyResponseLiteral) (legacyResponseConstraint, bool) {
|
||||
switch literal.kind {
|
||||
case legacyLiteralRequireEmpty:
|
||||
c.requireEmpty = true
|
||||
case legacyLiteralRequireNonEmpty:
|
||||
c.requireNonEmpty = true
|
||||
case legacyLiteralRequireSet:
|
||||
requiredSets := make([]*netipx.IPSet, len(c.requiredSets)+1)
|
||||
copy(requiredSets, c.requiredSets)
|
||||
requiredSets[len(c.requiredSets)] = literal.ipSet
|
||||
c.requiredSets = requiredSets
|
||||
case legacyLiteralForbidSet:
|
||||
c.forbiddenSet = legacyUnionIPSets(c.forbiddenSet, literal.ipSet)
|
||||
default:
|
||||
panic("unknown legacy response literal kind")
|
||||
}
|
||||
return c, c.satisfiable()
|
||||
}
|
||||
|
||||
func (c legacyResponseConstraint) satisfiable() bool {
|
||||
if c.requireEmpty && (c.requireNonEmpty || len(c.requiredSets) > 0) {
|
||||
return false
|
||||
}
|
||||
if c.requireEmpty {
|
||||
return true
|
||||
}
|
||||
for _, required := range c.requiredSets {
|
||||
if !legacyIPSetHasAllowedIP(required, c.forbiddenSet) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if c.requireNonEmpty && len(c.requiredSets) == 0 {
|
||||
return legacyIPSetHasAllowedIP(legacyAllIPSet, c.forbiddenSet)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func legacyUnionIPSets(left *netipx.IPSet, right *netipx.IPSet) *netipx.IPSet {
|
||||
if left == nil {
|
||||
return right
|
||||
}
|
||||
if right == nil {
|
||||
return left
|
||||
}
|
||||
var builder netipx.IPSetBuilder
|
||||
builder.AddSet(left)
|
||||
builder.AddSet(right)
|
||||
return common.Must1(builder.IPSet())
|
||||
}
|
||||
|
||||
func legacyIPSetHasAllowedIP(required *netipx.IPSet, forbidden *netipx.IPSet) bool {
|
||||
if required == nil {
|
||||
required = legacyAllIPSet
|
||||
}
|
||||
if forbidden == nil {
|
||||
return len(required.Ranges()) > 0
|
||||
}
|
||||
builder := netipx.IPSetBuilder{}
|
||||
builder.AddSet(required)
|
||||
builder.RemoveSet(forbidden)
|
||||
remaining := common.Must1(builder.IPSet())
|
||||
return len(remaining.Ranges()) > 0
|
||||
}
|
||||
|
||||
func legacySingleRuleMatchState(state ruleMatchState) legacyRuleMatchStateSet {
|
||||
return legacySingleRuleMatchStateWithFormula(state, legacyTrueFormula())
|
||||
}
|
||||
|
||||
func legacySingleRuleMatchStateWithFormula(state ruleMatchState, formula legacyResponseFormula) legacyRuleMatchStateSet {
|
||||
var stateSet legacyRuleMatchStateSet
|
||||
if !formula.isFalse() {
|
||||
stateSet[state] = formula
|
||||
}
|
||||
return stateSet
|
||||
}
|
||||
|
||||
func (s legacyRuleMatchStateSet) isEmpty() bool {
|
||||
for _, formula := range s {
|
||||
if !formula.isFalse() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s legacyRuleMatchStateSet) merge(other legacyRuleMatchStateSet) legacyRuleMatchStateSet {
|
||||
var merged legacyRuleMatchStateSet
|
||||
for state := ruleMatchState(0); state < legacyRuleMatchStateCount; state++ {
|
||||
merged[state] = s[state].or(other[state])
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
func (s legacyRuleMatchStateSet) combine(other legacyRuleMatchStateSet) legacyRuleMatchStateSet {
|
||||
if s.isEmpty() || other.isEmpty() {
|
||||
return legacyRuleMatchStateSet{}
|
||||
}
|
||||
var combined legacyRuleMatchStateSet
|
||||
for left := ruleMatchState(0); left < legacyRuleMatchStateCount; left++ {
|
||||
if s[left].isFalse() {
|
||||
continue
|
||||
}
|
||||
for right := ruleMatchState(0); right < legacyRuleMatchStateCount; right++ {
|
||||
if other[right].isFalse() {
|
||||
continue
|
||||
}
|
||||
combined[left|right] = combined[left|right].or(s[left].and(other[right]))
|
||||
}
|
||||
}
|
||||
return combined
|
||||
}
|
||||
|
||||
func (s legacyRuleMatchStateSet) withBase(base ruleMatchState) legacyRuleMatchStateSet {
|
||||
if s.isEmpty() {
|
||||
return legacyRuleMatchStateSet{}
|
||||
}
|
||||
var withBase legacyRuleMatchStateSet
|
||||
for state := ruleMatchState(0); state < legacyRuleMatchStateCount; state++ {
|
||||
if s[state].isFalse() {
|
||||
continue
|
||||
}
|
||||
withBase[state|base] = withBase[state|base].or(s[state])
|
||||
}
|
||||
return withBase
|
||||
}
|
||||
|
||||
func (s legacyRuleMatchStateSet) filter(allowed func(ruleMatchState) bool) legacyRuleMatchStateSet {
|
||||
var filtered legacyRuleMatchStateSet
|
||||
for state := ruleMatchState(0); state < legacyRuleMatchStateCount; state++ {
|
||||
if s[state].isFalse() {
|
||||
continue
|
||||
}
|
||||
if allowed(state) {
|
||||
filtered[state] = s[state]
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func (s legacyRuleMatchStateSet) addBit(bit ruleMatchState) legacyRuleMatchStateSet {
|
||||
var withBit legacyRuleMatchStateSet
|
||||
for state := ruleMatchState(0); state < legacyRuleMatchStateCount; state++ {
|
||||
if s[state].isFalse() {
|
||||
continue
|
||||
}
|
||||
withBit[state|bit] = withBit[state|bit].or(s[state])
|
||||
}
|
||||
return withBit
|
||||
}
|
||||
|
||||
func (s legacyRuleMatchStateSet) branchOnBit(bit ruleMatchState, condition legacyResponseFormula) legacyRuleMatchStateSet {
|
||||
if condition.isFalse() {
|
||||
return s
|
||||
}
|
||||
if condition.isTrue() {
|
||||
return s.addBit(bit)
|
||||
}
|
||||
var branched legacyRuleMatchStateSet
|
||||
conditionFalse := condition.not()
|
||||
for state := ruleMatchState(0); state < legacyRuleMatchStateCount; state++ {
|
||||
if s[state].isFalse() {
|
||||
continue
|
||||
}
|
||||
if state.has(bit) {
|
||||
branched[state] = branched[state].or(s[state])
|
||||
continue
|
||||
}
|
||||
branched[state] = branched[state].or(s[state].and(conditionFalse))
|
||||
branched[state|bit] = branched[state|bit].or(s[state].and(condition))
|
||||
}
|
||||
return branched
|
||||
}
|
||||
|
||||
func (s legacyRuleMatchStateSet) andFormula(formula legacyResponseFormula) legacyRuleMatchStateSet {
|
||||
if formula.isFalse() || s.isEmpty() {
|
||||
return legacyRuleMatchStateSet{}
|
||||
}
|
||||
if formula.isTrue() {
|
||||
return s
|
||||
}
|
||||
var result legacyRuleMatchStateSet
|
||||
for state := ruleMatchState(0); state < legacyRuleMatchStateCount; state++ {
|
||||
if s[state].isFalse() {
|
||||
continue
|
||||
}
|
||||
result[state] = s[state].and(formula)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s legacyRuleMatchStateSet) anyFormula() legacyResponseFormula {
|
||||
var formula legacyResponseFormula
|
||||
for _, stateFormula := range s {
|
||||
formula = formula.or(stateFormula)
|
||||
}
|
||||
return formula
|
||||
}
|
||||
|
||||
type legacyRuleStateMatcher interface {
|
||||
legacyMatchStates(metadata *adapter.InboundContext) legacyRuleMatchStateSet
|
||||
}
|
||||
|
||||
type legacyRuleStateMatcherWithBase interface {
|
||||
legacyMatchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) legacyRuleMatchStateSet
|
||||
}
|
||||
|
||||
func legacyMatchHeadlessRuleStates(rule adapter.HeadlessRule, metadata *adapter.InboundContext) legacyRuleMatchStateSet {
|
||||
return legacyMatchHeadlessRuleStatesWithBase(rule, metadata, 0)
|
||||
}
|
||||
|
||||
func legacyMatchHeadlessRuleStatesWithBase(rule adapter.HeadlessRule, metadata *adapter.InboundContext, base ruleMatchState) legacyRuleMatchStateSet {
|
||||
if matcher, loaded := rule.(legacyRuleStateMatcherWithBase); loaded {
|
||||
return matcher.legacyMatchStatesWithBase(metadata, base)
|
||||
}
|
||||
if matcher, loaded := rule.(legacyRuleStateMatcher); loaded {
|
||||
return matcher.legacyMatchStates(metadata).withBase(base)
|
||||
}
|
||||
if rule.Match(metadata) {
|
||||
return legacySingleRuleMatchState(base)
|
||||
}
|
||||
return legacyRuleMatchStateSet{}
|
||||
}
|
||||
|
||||
func legacyMatchRuleItemStatesWithBase(item RuleItem, metadata *adapter.InboundContext, base ruleMatchState) legacyRuleMatchStateSet {
|
||||
if matcher, loaded := item.(legacyRuleStateMatcherWithBase); loaded {
|
||||
return matcher.legacyMatchStatesWithBase(metadata, base)
|
||||
}
|
||||
if matcher, loaded := item.(legacyRuleStateMatcher); loaded {
|
||||
return matcher.legacyMatchStates(metadata).withBase(base)
|
||||
}
|
||||
if item.Match(metadata) {
|
||||
return legacySingleRuleMatchState(base)
|
||||
}
|
||||
return legacyRuleMatchStateSet{}
|
||||
}
|
||||
|
||||
func (r *DefaultHeadlessRule) legacyMatchStates(metadata *adapter.InboundContext) legacyRuleMatchStateSet {
|
||||
return r.abstractDefaultRule.legacyMatchStates(metadata)
|
||||
}
|
||||
|
||||
func (r *LogicalHeadlessRule) legacyMatchStates(metadata *adapter.InboundContext) legacyRuleMatchStateSet {
|
||||
return r.abstractLogicalRule.legacyMatchStates(metadata)
|
||||
}
|
||||
|
||||
func (r *RuleSetItem) legacyMatchStates(metadata *adapter.InboundContext) legacyRuleMatchStateSet {
|
||||
return r.legacyMatchStatesWithBase(metadata, 0)
|
||||
}
|
||||
|
||||
func (r *RuleSetItem) legacyMatchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) legacyRuleMatchStateSet {
|
||||
var stateSet legacyRuleMatchStateSet
|
||||
for _, ruleSet := range r.setList {
|
||||
nestedMetadata := *metadata
|
||||
nestedMetadata.ResetRuleMatchCache()
|
||||
nestedMetadata.IPCIDRMatchSource = r.ipCidrMatchSource
|
||||
nestedMetadata.IPCIDRAcceptEmpty = r.ipCidrAcceptEmpty
|
||||
stateSet = stateSet.merge(legacyMatchHeadlessRuleStatesWithBase(ruleSet, &nestedMetadata, base))
|
||||
}
|
||||
return stateSet
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) legacyMatchStates(metadata *adapter.InboundContext) legacyRuleMatchStateSet {
|
||||
return s.legacyMatchStatesWithBase(metadata, 0)
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) legacyMatchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) legacyRuleMatchStateSet {
|
||||
var stateSet legacyRuleMatchStateSet
|
||||
for _, rule := range s.rules {
|
||||
nestedMetadata := *metadata
|
||||
nestedMetadata.ResetRuleMatchCache()
|
||||
stateSet = stateSet.merge(legacyMatchHeadlessRuleStatesWithBase(rule, &nestedMetadata, base))
|
||||
}
|
||||
return stateSet
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) legacyMatchStates(metadata *adapter.InboundContext) legacyRuleMatchStateSet {
|
||||
return s.legacyMatchStatesWithBase(metadata, 0)
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) legacyMatchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) legacyRuleMatchStateSet {
|
||||
var stateSet legacyRuleMatchStateSet
|
||||
for _, rule := range s.rules {
|
||||
nestedMetadata := *metadata
|
||||
nestedMetadata.ResetRuleMatchCache()
|
||||
stateSet = stateSet.merge(legacyMatchHeadlessRuleStatesWithBase(rule, &nestedMetadata, base))
|
||||
}
|
||||
return stateSet
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) legacyMatchStates(metadata *adapter.InboundContext) legacyRuleMatchStateSet {
|
||||
return r.legacyMatchStatesWithBase(metadata, 0)
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) legacyMatchStatesWithBase(metadata *adapter.InboundContext, inheritedBase ruleMatchState) legacyRuleMatchStateSet {
|
||||
if len(r.allItems) == 0 {
|
||||
return legacySingleRuleMatchState(inheritedBase)
|
||||
}
|
||||
evaluationBase := inheritedBase
|
||||
if r.invert {
|
||||
evaluationBase = 0
|
||||
}
|
||||
stateSet := legacySingleRuleMatchState(evaluationBase)
|
||||
if len(r.sourceAddressItems) > 0 {
|
||||
metadata.DidMatch = true
|
||||
if matchAnyItem(r.sourceAddressItems, metadata) {
|
||||
stateSet = stateSet.addBit(ruleMatchSourceAddress)
|
||||
}
|
||||
}
|
||||
if r.destinationIPCIDRMatchesSource(metadata) {
|
||||
metadata.DidMatch = true
|
||||
stateSet = stateSet.branchOnBit(ruleMatchSourceAddress, legacyDestinationIPFormula(r.destinationIPCIDRItems, metadata))
|
||||
}
|
||||
if len(r.sourcePortItems) > 0 {
|
||||
metadata.DidMatch = true
|
||||
if matchAnyItem(r.sourcePortItems, metadata) {
|
||||
stateSet = stateSet.addBit(ruleMatchSourcePort)
|
||||
}
|
||||
}
|
||||
if len(r.destinationAddressItems) > 0 {
|
||||
metadata.DidMatch = true
|
||||
if matchAnyItem(r.destinationAddressItems, metadata) {
|
||||
stateSet = stateSet.addBit(ruleMatchDestinationAddress)
|
||||
}
|
||||
}
|
||||
if r.legacyDestinationIPCIDRMatchesDestination(metadata) {
|
||||
metadata.DidMatch = true
|
||||
stateSet = stateSet.branchOnBit(legacyRuleMatchDeferredDestinationAddress, legacyDestinationIPFormula(r.destinationIPCIDRItems, metadata))
|
||||
}
|
||||
if len(r.destinationPortItems) > 0 {
|
||||
metadata.DidMatch = true
|
||||
if matchAnyItem(r.destinationPortItems, metadata) {
|
||||
stateSet = stateSet.addBit(ruleMatchDestinationPort)
|
||||
}
|
||||
}
|
||||
for _, item := range r.items {
|
||||
metadata.DidMatch = true
|
||||
if !item.Match(metadata) {
|
||||
if r.invert {
|
||||
return legacySingleRuleMatchState(inheritedBase)
|
||||
}
|
||||
return legacyRuleMatchStateSet{}
|
||||
}
|
||||
}
|
||||
if r.ruleSetItem != nil {
|
||||
metadata.DidMatch = true
|
||||
var merged legacyRuleMatchStateSet
|
||||
for state := ruleMatchState(0); state < legacyRuleMatchStateCount; state++ {
|
||||
if stateSet[state].isFalse() {
|
||||
continue
|
||||
}
|
||||
nestedStateSet := legacyMatchRuleItemStatesWithBase(r.ruleSetItem, metadata, state)
|
||||
merged = merged.merge(nestedStateSet.andFormula(stateSet[state]))
|
||||
}
|
||||
stateSet = merged
|
||||
}
|
||||
stateSet = stateSet.filter(func(state ruleMatchState) bool {
|
||||
if r.legacyRequiresSourceAddressMatch(metadata) && !state.has(ruleMatchSourceAddress) {
|
||||
return false
|
||||
}
|
||||
if len(r.sourcePortItems) > 0 && !state.has(ruleMatchSourcePort) {
|
||||
return false
|
||||
}
|
||||
if r.legacyRequiresDestinationAddressMatch(metadata) && !state.has(ruleMatchDestinationAddress) {
|
||||
return false
|
||||
}
|
||||
if r.legacyRequiresDeferredDestinationAddressMatch(metadata) && !state.has(legacyRuleMatchDeferredDestinationAddress) {
|
||||
return false
|
||||
}
|
||||
if len(r.destinationPortItems) > 0 && !state.has(ruleMatchDestinationPort) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if r.invert {
|
||||
return legacySingleRuleMatchStateWithFormula(inheritedBase, stateSet.anyFormula().not())
|
||||
}
|
||||
return stateSet
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) legacyRequiresSourceAddressMatch(metadata *adapter.InboundContext) bool {
|
||||
return len(r.sourceAddressItems) > 0 || r.destinationIPCIDRMatchesSource(metadata)
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) legacyDestinationIPCIDRMatchesDestination(metadata *adapter.InboundContext) bool {
|
||||
return !metadata.IPCIDRMatchSource && len(r.destinationIPCIDRItems) > 0
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) legacyRequiresDestinationAddressMatch(metadata *adapter.InboundContext) bool {
|
||||
return len(r.destinationAddressItems) > 0
|
||||
}
|
||||
|
||||
func (r *abstractDefaultRule) legacyRequiresDeferredDestinationAddressMatch(metadata *adapter.InboundContext) bool {
|
||||
return r.legacyDestinationIPCIDRMatchesDestination(metadata)
|
||||
}
|
||||
|
||||
func (r *abstractLogicalRule) legacyMatchStates(metadata *adapter.InboundContext) legacyRuleMatchStateSet {
|
||||
return r.legacyMatchStatesWithBase(metadata, 0)
|
||||
}
|
||||
|
||||
func (r *abstractLogicalRule) legacyMatchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) legacyRuleMatchStateSet {
|
||||
evaluationBase := base
|
||||
if r.invert {
|
||||
evaluationBase = 0
|
||||
}
|
||||
var stateSet legacyRuleMatchStateSet
|
||||
if r.mode == C.LogicalTypeAnd {
|
||||
stateSet = legacySingleRuleMatchState(evaluationBase)
|
||||
for _, rule := range r.rules {
|
||||
nestedMetadata := *metadata
|
||||
nestedMetadata.ResetRuleCache()
|
||||
stateSet = stateSet.combine(legacyMatchHeadlessRuleStatesWithBase(rule, &nestedMetadata, evaluationBase))
|
||||
if stateSet.isEmpty() && !r.invert {
|
||||
return legacyRuleMatchStateSet{}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, rule := range r.rules {
|
||||
nestedMetadata := *metadata
|
||||
nestedMetadata.ResetRuleCache()
|
||||
stateSet = stateSet.merge(legacyMatchHeadlessRuleStatesWithBase(rule, &nestedMetadata, evaluationBase))
|
||||
}
|
||||
}
|
||||
if r.invert {
|
||||
return legacySingleRuleMatchStateWithFormula(base, stateSet.anyFormula().not())
|
||||
}
|
||||
return stateSet
|
||||
}
|
||||
|
||||
func legacyDestinationIPFormula(items []RuleItem, metadata *adapter.InboundContext) legacyResponseFormula {
|
||||
if legacyDestinationIPResolved(metadata) {
|
||||
if matchAnyItem(items, metadata) {
|
||||
return legacyTrueFormula()
|
||||
}
|
||||
return legacyFalseFormula()
|
||||
}
|
||||
var formula legacyResponseFormula
|
||||
for _, rawItem := range items {
|
||||
switch item := rawItem.(type) {
|
||||
case *IPCIDRItem:
|
||||
if item.isSource || metadata.IPCIDRMatchSource {
|
||||
if item.Match(metadata) {
|
||||
return legacyTrueFormula()
|
||||
}
|
||||
continue
|
||||
}
|
||||
formula = formula.or(legacyLiteralFormula(legacyResponseLiteral{
|
||||
kind: legacyLiteralRequireSet,
|
||||
ipSet: item.ipSet,
|
||||
}))
|
||||
if metadata.IPCIDRAcceptEmpty {
|
||||
formula = formula.or(legacyLiteralFormula(legacyResponseLiteral{
|
||||
kind: legacyLiteralRequireEmpty,
|
||||
}))
|
||||
}
|
||||
case *IPIsPrivateItem:
|
||||
if item.isSource {
|
||||
if item.Match(metadata) {
|
||||
return legacyTrueFormula()
|
||||
}
|
||||
continue
|
||||
}
|
||||
formula = formula.or(legacyLiteralFormula(legacyResponseLiteral{
|
||||
kind: legacyLiteralRequireSet,
|
||||
ipSet: legacyNonPublicIPSet,
|
||||
}))
|
||||
case *IPAcceptAnyItem:
|
||||
formula = formula.or(legacyLiteralFormula(legacyResponseLiteral{
|
||||
kind: legacyLiteralRequireNonEmpty,
|
||||
}))
|
||||
default:
|
||||
if rawItem.Match(metadata) {
|
||||
return legacyTrueFormula()
|
||||
}
|
||||
}
|
||||
}
|
||||
return formula
|
||||
}
|
||||
|
||||
func legacyDestinationIPResolved(metadata *adapter.InboundContext) bool {
|
||||
return metadata.IPCIDRMatchSource ||
|
||||
metadata.DestinationAddressMatchFromResponse ||
|
||||
metadata.DNSResponse != nil ||
|
||||
metadata.Destination.IsIP() ||
|
||||
len(metadata.DestinationAddresses) > 0
|
||||
}
|
||||
@@ -34,6 +34,10 @@ type DefaultHeadlessRule struct {
|
||||
abstractDefaultRule
|
||||
}
|
||||
|
||||
func (r *DefaultHeadlessRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.abstractDefaultRule.matchStates(metadata)
|
||||
}
|
||||
|
||||
func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessRule) (*DefaultHeadlessRule, error) {
|
||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||
rule := &DefaultHeadlessRule{
|
||||
@@ -41,6 +45,11 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR
|
||||
invert: options.Invert,
|
||||
},
|
||||
}
|
||||
if len(options.QueryType) > 0 {
|
||||
item := NewQueryTypeItem(options.QueryType)
|
||||
rule.items = append(rule.items, item)
|
||||
rule.allItems = append(rule.allItems, item)
|
||||
}
|
||||
if len(options.Network) > 0 {
|
||||
item := NewNetworkItem(options.Network)
|
||||
rule.items = append(rule.items, item)
|
||||
@@ -199,6 +208,10 @@ type LogicalHeadlessRule struct {
|
||||
abstractLogicalRule
|
||||
}
|
||||
|
||||
func (r *LogicalHeadlessRule) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.abstractLogicalRule.matchStates(metadata)
|
||||
}
|
||||
|
||||
func NewLogicalHeadlessRule(ctx context.Context, options option.LogicalHeadlessRule) (*LogicalHeadlessRule, error) {
|
||||
r := &LogicalHeadlessRule{
|
||||
abstractLogicalRule{
|
||||
|
||||
@@ -76,11 +76,26 @@ func (r *IPCIDRItem) Match(metadata *adapter.InboundContext) bool {
|
||||
if r.isSource || metadata.IPCIDRMatchSource {
|
||||
return r.ipSet.Contains(metadata.Source.Addr)
|
||||
}
|
||||
if metadata.DestinationAddressMatchFromResponse {
|
||||
addresses := metadata.DNSResponseAddressesForMatch()
|
||||
if len(addresses) == 0 {
|
||||
// Legacy rule_set_ip_cidr_accept_empty only applies when the DNS response
|
||||
// does not expose any address answers for matching.
|
||||
return metadata.IPCIDRAcceptEmpty
|
||||
}
|
||||
for _, address := range addresses {
|
||||
if r.ipSet.Contains(address) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if metadata.Destination.IsIP() {
|
||||
return r.ipSet.Contains(metadata.Destination.Addr)
|
||||
}
|
||||
if len(metadata.DestinationAddresses) > 0 {
|
||||
for _, address := range metadata.DestinationAddresses {
|
||||
addresses := metadata.DestinationAddresses
|
||||
if len(addresses) > 0 {
|
||||
for _, address := range addresses {
|
||||
if r.ipSet.Contains(address) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ func NewIPAcceptAnyItem() *IPAcceptAnyItem {
|
||||
}
|
||||
|
||||
func (r *IPAcceptAnyItem) Match(metadata *adapter.InboundContext) bool {
|
||||
if metadata.DestinationAddressMatchFromResponse {
|
||||
return len(metadata.DNSResponseAddressesForMatch()) > 0
|
||||
}
|
||||
return len(metadata.DestinationAddresses) > 0
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
@@ -18,21 +16,24 @@ func NewIPIsPrivateItem(isSource bool) *IPIsPrivateItem {
|
||||
}
|
||||
|
||||
func (r *IPIsPrivateItem) Match(metadata *adapter.InboundContext) bool {
|
||||
var destination netip.Addr
|
||||
if r.isSource {
|
||||
destination = metadata.Source.Addr
|
||||
} else {
|
||||
destination = metadata.Destination.Addr
|
||||
return !N.IsPublicAddr(metadata.Source.Addr)
|
||||
}
|
||||
if destination.IsValid() {
|
||||
return !N.IsPublicAddr(destination)
|
||||
}
|
||||
if !r.isSource {
|
||||
for _, destinationAddress := range metadata.DestinationAddresses {
|
||||
if metadata.DestinationAddressMatchFromResponse {
|
||||
for _, destinationAddress := range metadata.DNSResponseAddressesForMatch() {
|
||||
if !N.IsPublicAddr(destinationAddress) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if metadata.Destination.Addr.IsValid() {
|
||||
return !N.IsPublicAddr(metadata.Destination.Addr)
|
||||
}
|
||||
for _, destinationAddress := range metadata.DestinationAddresses {
|
||||
if !N.IsPublicAddr(destinationAddress) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
25
route/rule/rule_item_response_rcode.go
Normal file
25
route/rule/rule_item_response_rcode.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"github.com/miekg/dns"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
var _ RuleItem = (*DNSResponseRCodeItem)(nil)
|
||||
|
||||
type DNSResponseRCodeItem struct {
|
||||
rcode int
|
||||
}
|
||||
|
||||
func NewDNSResponseRCodeItem(rcode int) *DNSResponseRCodeItem {
|
||||
return &DNSResponseRCodeItem{rcode: rcode}
|
||||
}
|
||||
|
||||
func (r *DNSResponseRCodeItem) Match(metadata *adapter.InboundContext) bool {
|
||||
return metadata.DNSResponse != nil && metadata.DNSResponse.Rcode == r.rcode
|
||||
}
|
||||
|
||||
func (r *DNSResponseRCodeItem) String() string {
|
||||
return F.ToString("response_rcode=", dns.RcodeToString[r.rcode])
|
||||
}
|
||||
63
route/rule/rule_item_response_record.go
Normal file
63
route/rule/rule_item_response_record.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
var _ RuleItem = (*DNSResponseRecordItem)(nil)
|
||||
|
||||
type DNSResponseRecordItem struct {
|
||||
field string
|
||||
records []option.DNSRecordOptions
|
||||
selector func(*dns.Msg) []dns.RR
|
||||
}
|
||||
|
||||
func NewDNSResponseRecordItem(field string, records []option.DNSRecordOptions, selector func(*dns.Msg) []dns.RR) *DNSResponseRecordItem {
|
||||
return &DNSResponseRecordItem{
|
||||
field: field,
|
||||
records: records,
|
||||
selector: selector,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *DNSResponseRecordItem) Match(metadata *adapter.InboundContext) bool {
|
||||
if metadata.DNSResponse == nil {
|
||||
return false
|
||||
}
|
||||
records := r.selector(metadata.DNSResponse)
|
||||
for _, expected := range r.records {
|
||||
for _, record := range records {
|
||||
if expected.Match(record) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *DNSResponseRecordItem) String() string {
|
||||
descriptions := make([]string, 0, len(r.records))
|
||||
for _, record := range r.records {
|
||||
if record.RR != nil {
|
||||
descriptions = append(descriptions, record.RR.String())
|
||||
}
|
||||
}
|
||||
return r.field + "=[" + strings.Join(descriptions, " ") + "]"
|
||||
}
|
||||
|
||||
func dnsResponseAnswers(message *dns.Msg) []dns.RR {
|
||||
return message.Answer
|
||||
}
|
||||
|
||||
func dnsResponseNS(message *dns.Msg) []dns.RR {
|
||||
return message.Ns
|
||||
}
|
||||
|
||||
func dnsResponseExtra(message *dns.Msg) []dns.RR {
|
||||
return message.Extra
|
||||
}
|
||||
@@ -29,9 +29,11 @@ func NewRuleSetItem(router adapter.Router, tagList []string, ipCIDRMatchSource b
|
||||
}
|
||||
|
||||
func (r *RuleSetItem) Start() error {
|
||||
_ = r.Close()
|
||||
for _, tag := range r.tagList {
|
||||
ruleSet, loaded := r.router.RuleSet(tag)
|
||||
if !loaded {
|
||||
_ = r.Close()
|
||||
return E.New("rule-set not found: ", tag)
|
||||
}
|
||||
ruleSet.IncRef()
|
||||
@@ -40,17 +42,33 @@ func (r *RuleSetItem) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RuleSetItem) Close() error {
|
||||
for _, ruleSet := range r.setList {
|
||||
ruleSet.DecRef()
|
||||
}
|
||||
clear(r.setList)
|
||||
r.setList = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RuleSetItem) Match(metadata *adapter.InboundContext) bool {
|
||||
return !r.matchStates(metadata).isEmpty()
|
||||
}
|
||||
|
||||
func (r *RuleSetItem) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return r.matchStatesWithBase(metadata, 0)
|
||||
}
|
||||
|
||||
func (r *RuleSetItem) matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
|
||||
var stateSet ruleMatchStateSet
|
||||
for _, ruleSet := range r.setList {
|
||||
nestedMetadata := *metadata
|
||||
nestedMetadata.ResetRuleMatchCache()
|
||||
nestedMetadata.IPCIDRMatchSource = r.ipCidrMatchSource
|
||||
nestedMetadata.IPCIDRAcceptEmpty = r.ipCidrAcceptEmpty
|
||||
if ruleSet.Match(&nestedMetadata) {
|
||||
return true
|
||||
}
|
||||
stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(ruleSet, &nestedMetadata, base))
|
||||
}
|
||||
return false
|
||||
return stateSet
|
||||
}
|
||||
|
||||
func (r *RuleSetItem) ContainsDestinationIPCIDRRule() bool {
|
||||
|
||||
139
route/rule/rule_item_rule_set_test.go
Normal file
139
route/rule/rule_item_rule_set_test.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-tun"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
type ruleSetItemTestRouter struct {
|
||||
ruleSets map[string]adapter.RuleSet
|
||||
}
|
||||
|
||||
func (r *ruleSetItemTestRouter) Start(adapter.StartStage) error { return nil }
|
||||
func (r *ruleSetItemTestRouter) Close() error { return nil }
|
||||
func (r *ruleSetItemTestRouter) PreMatch(adapter.InboundContext, tun.DirectRouteContext, time.Duration, bool) (tun.DirectRouteDestination, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (r *ruleSetItemTestRouter) RouteConnection(context.Context, net.Conn, adapter.InboundContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ruleSetItemTestRouter) RoutePacketConnection(context.Context, N.PacketConn, adapter.InboundContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ruleSetItemTestRouter) RouteConnectionEx(context.Context, net.Conn, adapter.InboundContext, N.CloseHandlerFunc) {
|
||||
}
|
||||
|
||||
func (r *ruleSetItemTestRouter) RoutePacketConnectionEx(context.Context, N.PacketConn, adapter.InboundContext, N.CloseHandlerFunc) {
|
||||
}
|
||||
|
||||
func (r *ruleSetItemTestRouter) RuleSet(tag string) (adapter.RuleSet, bool) {
|
||||
ruleSet, loaded := r.ruleSets[tag]
|
||||
return ruleSet, loaded
|
||||
}
|
||||
func (r *ruleSetItemTestRouter) Rules() []adapter.Rule { return nil }
|
||||
func (r *ruleSetItemTestRouter) NeedFindProcess() bool { return false }
|
||||
func (r *ruleSetItemTestRouter) NeedFindNeighbor() bool { return false }
|
||||
func (r *ruleSetItemTestRouter) NeighborResolver() adapter.NeighborResolver { return nil }
|
||||
func (r *ruleSetItemTestRouter) AppendTracker(adapter.ConnectionTracker) {}
|
||||
func (r *ruleSetItemTestRouter) ResetNetwork() {}
|
||||
|
||||
type countingRuleSet struct {
|
||||
name string
|
||||
refs atomic.Int32
|
||||
}
|
||||
|
||||
func (s *countingRuleSet) Name() string { return s.name }
|
||||
func (s *countingRuleSet) StartContext(context.Context, *adapter.HTTPStartContext) error { return nil }
|
||||
func (s *countingRuleSet) PostStart() error { return nil }
|
||||
func (s *countingRuleSet) Metadata() adapter.RuleSetMetadata { return adapter.RuleSetMetadata{} }
|
||||
func (s *countingRuleSet) ExtractIPSet() []*netipx.IPSet { return nil }
|
||||
func (s *countingRuleSet) IncRef() { s.refs.Add(1) }
|
||||
func (s *countingRuleSet) DecRef() {
|
||||
if s.refs.Add(-1) < 0 {
|
||||
panic("rule-set: negative refs")
|
||||
}
|
||||
}
|
||||
func (s *countingRuleSet) Cleanup() {}
|
||||
func (s *countingRuleSet) RegisterCallback(adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
|
||||
return nil
|
||||
}
|
||||
func (s *countingRuleSet) UnregisterCallback(*list.Element[adapter.RuleSetUpdateCallback]) {}
|
||||
func (s *countingRuleSet) Close() error { return nil }
|
||||
func (s *countingRuleSet) Match(*adapter.InboundContext) bool { return true }
|
||||
func (s *countingRuleSet) String() string { return s.name }
|
||||
func (s *countingRuleSet) RefCount() int32 { return s.refs.Load() }
|
||||
|
||||
func TestRuleSetItemCloseReleasesRefs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
firstSet := &countingRuleSet{name: "first"}
|
||||
secondSet := &countingRuleSet{name: "second"}
|
||||
item := NewRuleSetItem(&ruleSetItemTestRouter{
|
||||
ruleSets: map[string]adapter.RuleSet{
|
||||
"first": firstSet,
|
||||
"second": secondSet,
|
||||
},
|
||||
}, []string{"first", "second"}, false, false)
|
||||
|
||||
require.NoError(t, item.Start())
|
||||
require.EqualValues(t, 1, firstSet.RefCount())
|
||||
require.EqualValues(t, 1, secondSet.RefCount())
|
||||
|
||||
require.NoError(t, item.Close())
|
||||
require.Zero(t, firstSet.RefCount())
|
||||
require.Zero(t, secondSet.RefCount())
|
||||
|
||||
require.NoError(t, item.Close())
|
||||
require.Zero(t, firstSet.RefCount())
|
||||
require.Zero(t, secondSet.RefCount())
|
||||
}
|
||||
|
||||
func TestRuleSetItemStartRollbackOnFailure(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
firstSet := &countingRuleSet{name: "first"}
|
||||
item := NewRuleSetItem(&ruleSetItemTestRouter{
|
||||
ruleSets: map[string]adapter.RuleSet{
|
||||
"first": firstSet,
|
||||
},
|
||||
}, []string{"first", "missing"}, false, false)
|
||||
|
||||
err := item.Start()
|
||||
require.ErrorContains(t, err, "rule-set not found: missing")
|
||||
require.Zero(t, firstSet.RefCount())
|
||||
require.Empty(t, item.setList)
|
||||
}
|
||||
|
||||
func TestRuleSetItemRestartKeepsBalancedRefs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
firstSet := &countingRuleSet{name: "first"}
|
||||
item := NewRuleSetItem(&ruleSetItemTestRouter{
|
||||
ruleSets: map[string]adapter.RuleSet{
|
||||
"first": firstSet,
|
||||
},
|
||||
}, []string{"first"}, false, false)
|
||||
|
||||
require.NoError(t, item.Start())
|
||||
require.EqualValues(t, 1, firstSet.RefCount())
|
||||
|
||||
require.NoError(t, item.Start())
|
||||
require.EqualValues(t, 1, firstSet.RefCount())
|
||||
|
||||
require.NoError(t, item.Close())
|
||||
require.Zero(t, firstSet.RefCount())
|
||||
}
|
||||
@@ -69,3 +69,7 @@ func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool {
|
||||
func isIPCIDRHeadlessRule(rule option.DefaultHeadlessRule) bool {
|
||||
return len(rule.IPCIDR) > 0 || rule.IPSet != nil
|
||||
}
|
||||
|
||||
func isDNSQueryTypeHeadlessRule(rule option.DefaultHeadlessRule) bool {
|
||||
return len(rule.QueryType) > 0
|
||||
}
|
||||
|
||||
@@ -141,6 +141,7 @@ func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error {
|
||||
metadata.ContainsProcessRule = HasHeadlessRule(headlessRules, isProcessHeadlessRule)
|
||||
metadata.ContainsWIFIRule = HasHeadlessRule(headlessRules, isWIFIHeadlessRule)
|
||||
metadata.ContainsIPCIDRRule = HasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
|
||||
metadata.ContainsDNSQueryTypeRule = HasHeadlessRule(headlessRules, isDNSQueryTypeHeadlessRule)
|
||||
s.access.Lock()
|
||||
s.rules = rules
|
||||
s.metadata = metadata
|
||||
@@ -202,12 +203,19 @@ func (s *LocalRuleSet) Close() error {
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool {
|
||||
return !s.matchStates(metadata).isEmpty()
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return s.matchStatesWithBase(metadata, 0)
|
||||
}
|
||||
|
||||
func (s *LocalRuleSet) matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
|
||||
var stateSet ruleMatchStateSet
|
||||
for _, rule := range s.rules {
|
||||
nestedMetadata := *metadata
|
||||
nestedMetadata.ResetRuleMatchCache()
|
||||
if rule.Match(&nestedMetadata) {
|
||||
return true
|
||||
}
|
||||
stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, base))
|
||||
}
|
||||
return false
|
||||
return stateSet
|
||||
}
|
||||
|
||||
@@ -193,6 +193,7 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
|
||||
s.metadata.ContainsProcessRule = HasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
||||
s.metadata.ContainsWIFIRule = HasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
||||
s.metadata.ContainsIPCIDRRule = HasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
||||
s.metadata.ContainsDNSQueryTypeRule = HasHeadlessRule(plainRuleSet.Rules, isDNSQueryTypeHeadlessRule)
|
||||
s.rules = rules
|
||||
callbacks := s.callbacks.Array()
|
||||
s.access.Unlock()
|
||||
@@ -322,12 +323,19 @@ func (s *RemoteRuleSet) Close() error {
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool {
|
||||
return !s.matchStates(metadata).isEmpty()
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) matchStates(metadata *adapter.InboundContext) ruleMatchStateSet {
|
||||
return s.matchStatesWithBase(metadata, 0)
|
||||
}
|
||||
|
||||
func (s *RemoteRuleSet) matchStatesWithBase(metadata *adapter.InboundContext, base ruleMatchState) ruleMatchStateSet {
|
||||
var stateSet ruleMatchStateSet
|
||||
for _, rule := range s.rules {
|
||||
nestedMetadata := *metadata
|
||||
nestedMetadata.ResetRuleMatchCache()
|
||||
if rule.Match(&nestedMetadata) {
|
||||
return true
|
||||
}
|
||||
stateSet = stateSet.merge(matchHeadlessRuleStatesWithBase(rule, &nestedMetadata, base))
|
||||
}
|
||||
return false
|
||||
return stateSet
|
||||
}
|
||||
|
||||
1391
route/rule/rule_set_semantics_test.go
Normal file
1391
route/rule/rule_set_semantics_test.go
Normal file
File diff suppressed because it is too large
Load Diff
120
test/go.mod
120
test/go.mod
@@ -7,18 +7,18 @@ require github.com/sagernet/sing-box v0.0.0
|
||||
replace github.com/sagernet/sing-box => ../
|
||||
|
||||
require (
|
||||
github.com/docker/docker v28.0.0+incompatible
|
||||
github.com/docker/docker v27.3.1+incompatible
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/gofrs/uuid/v5 v5.4.0
|
||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4
|
||||
github.com/sagernet/sing v0.8.3-0.20260315153529-ed51f65fbfde
|
||||
github.com/sagernet/sing-quic v0.6.0
|
||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.2
|
||||
github.com/sagernet/sing v0.8.0-beta.16
|
||||
github.com/sagernet/sing-quic v0.6.0-beta.11
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||
github.com/spyzhov/ajson v0.9.4
|
||||
github.com/stretchr/testify v1.11.1
|
||||
go.uber.org/goleak v1.3.0
|
||||
golang.org/x/net v0.50.0
|
||||
golang.org/x/net v0.48.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -28,17 +28,16 @@ require (
|
||||
github.com/akutz/memconn v0.1.0 // indirect
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0 // indirect
|
||||
github.com/anthropics/anthropic-sdk-go v1.19.0 // indirect
|
||||
github.com/anytls/sing-anytls v0.0.11 // indirect
|
||||
github.com/caddyserver/certmagic v0.25.2 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.5 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/caddyserver/certmagic v0.25.0 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||
github.com/coder/websocket v1.8.14 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
|
||||
github.com/cretz/bine v0.2.0 // indirect
|
||||
github.com/database64128/netx-go v0.1.1 // indirect
|
||||
github.com/database64128/tfo-go/v2 v2.3.2 // indirect
|
||||
github.com/database64128/tfo-go/v2 v2.3.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
@@ -49,7 +48,7 @@ require (
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/gaissmai/bart v0.18.0 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.5 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.3 // indirect
|
||||
github.com/go-chi/render v1.0.3 // indirect
|
||||
github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
@@ -57,7 +56,7 @@ require (
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
||||
github.com/godbus/dbus/v5 v5.2.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
@@ -66,26 +65,26 @@ require (
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
|
||||
github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91 // indirect
|
||||
github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167 // indirect
|
||||
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
|
||||
github.com/keybase/go-keychain v0.0.1 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/libdns/acmedns v0.5.0 // indirect
|
||||
github.com/libdns/alidns v1.0.6 // indirect
|
||||
github.com/libdns/alidns v1.0.6-beta.3 // indirect
|
||||
github.com/libdns/cloudflare v0.2.2 // indirect
|
||||
github.com/libdns/libdns v1.1.1 // indirect
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
|
||||
github.com/mdlayher/netlink v1.9.0 // indirect
|
||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
|
||||
github.com/mdlayher/socket v0.5.1 // indirect
|
||||
github.com/metacubex/utls v1.8.4 // indirect
|
||||
github.com/mholt/acmez/v3 v3.1.6 // indirect
|
||||
github.com/miekg/dns v1.1.72 // indirect
|
||||
github.com/mholt/acmez/v3 v3.1.4 // indirect
|
||||
github.com/miekg/dns v1.1.69 // indirect
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/openai/openai-go/v3 v3.26.0 // indirect
|
||||
github.com/openai/openai-go/v3 v3.15.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
@@ -97,48 +96,41 @@ require (
|
||||
github.com/safchain/ethtool v0.3.0 // indirect
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
|
||||
github.com/sagernet/cors v1.2.1 // indirect
|
||||
github.com/sagernet/cronet-go v0.0.0-20260309100020-c128886ff3fc // indirect
|
||||
github.com/sagernet/cronet-go/all v0.0.0-20260309100020-c128886ff3fc // indirect
|
||||
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260309101654-0cbdcfddded9 // indirect
|
||||
github.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287 // indirect
|
||||
github.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287 // indirect
|
||||
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f // indirect
|
||||
github.com/sagernet/fswatch v0.1.1 // indirect
|
||||
github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 // indirect
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||
github.com/sagernet/sing-mux v0.3.4 // indirect
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 // indirect
|
||||
github.com/sagernet/sing-tun v0.8.7-0.20260323120017-8eb4e8acfc2d // indirect
|
||||
github.com/sagernet/sing-tun v0.8.0-beta.17 // indirect
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 // indirect
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1 // indirect
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7 // indirect
|
||||
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c // indirect
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 // indirect
|
||||
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 // indirect
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
|
||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
|
||||
@@ -157,29 +149,29 @@ require (
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect
|
||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.1 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/oauth2 v0.34.0 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/oauth2 v0.32.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/term v0.40.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/term v0.38.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/grpc v1.79.1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect
|
||||
google.golang.org/grpc v1.77.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.5.1 // indirect
|
||||
|
||||
264
test/go.sum
264
test/go.sum
@@ -1,5 +1,3 @@
|
||||
code.pfad.fr/check v1.1.0 h1:GWvjdzhSEgHvEHe2uJujDcpmZoySKuHQNrZMfzfO0bE=
|
||||
code.pfad.fr/check v1.1.0/go.mod h1:NiUH13DtYsb7xp5wll0U4SXx7KhXQVCtRgdC96IPfoM=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
@@ -14,18 +12,16 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAfT7CoSYSac11PY=
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q=
|
||||
github.com/anthropics/anthropic-sdk-go v1.19.0 h1:mO6E+ffSzLRvR/YUH9KJC0uGw0uV8GjISIuzem//3KE=
|
||||
github.com/anthropics/anthropic-sdk-go v1.19.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
|
||||
github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc=
|
||||
github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
|
||||
github.com/caddyserver/certmagic v0.25.2 h1:D7xcS7ggX/WEY54x0czj7ioTkmDWKIgxtIi2OcQclUc=
|
||||
github.com/caddyserver/certmagic v0.25.2/go.mod h1:llW/CvsNmza8S6hmsuggsZeiX+uS27dkqY27wDIuBWg=
|
||||
github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE=
|
||||
github.com/caddyserver/zerossl v0.1.5/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/caddyserver/certmagic v0.25.0 h1:VMleO/XA48gEWes5l+Fh6tRWo9bHkhwAEhx63i+F5ic=
|
||||
github.com/caddyserver/certmagic v0.25.0/go.mod h1:m9yB7Mud24OQbPHOiipAoyKPn9pKHhpSJxXR1jydBxA=
|
||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
||||
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
|
||||
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
|
||||
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||
@@ -38,8 +34,8 @@ github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
||||
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
||||
github.com/database64128/netx-go v0.1.1 h1:dT5LG7Gs7zFZBthFBbzWE6K8wAHjSNAaK7wCYZT7NzM=
|
||||
github.com/database64128/netx-go v0.1.1/go.mod h1:LNlYVipaYkQArRFDNNJ02VkNV+My9A5XR/IGS7sIBQc=
|
||||
github.com/database64128/tfo-go/v2 v2.3.2 h1:UhZMKiMq3swZGUiETkLBDzQnZBPSAeBMClpJGlnJ5Fw=
|
||||
github.com/database64128/tfo-go/v2 v2.3.2/go.mod h1:GC3uB5oa4beGpCUbRb2ZOWP73bJJFmMyAVgQSO7r724=
|
||||
github.com/database64128/tfo-go/v2 v2.3.1 h1:EGE+ELd5/AQ0X6YBlQ9RgKs8+kciNhgN3d8lRvfEJQw=
|
||||
github.com/database64128/tfo-go/v2 v2.3.1/go.mod h1:k9wcpg/8i5zenspBkc9jUEYehpZZccBnCElzOJB++bU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -47,10 +43,8 @@ github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbww
|
||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=
|
||||
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
|
||||
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/docker/docker v28.0.0+incompatible h1:Olh0KS820sJ7nPsBKChVhk5pzqcwDR15fumfAd/p9hM=
|
||||
github.com/docker/docker v28.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
|
||||
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
@@ -69,12 +63,10 @@ github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo=
|
||||
github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY=
|
||||
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
|
||||
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
|
||||
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced h1:Q311OHjMh/u5E2TITc++WlTP5We0xNseRMkHDyvhW7I=
|
||||
github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@@ -88,8 +80,8 @@ github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
||||
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||
github.com/godbus/dbus/v5 v5.2.1 h1:I4wwMdWSkmI57ewd+elNGwLRf2/dtSaFz1DujfWYvOk=
|
||||
github.com/godbus/dbus/v5 v5.2.1/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
|
||||
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
@@ -112,8 +104,8 @@ github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8
|
||||
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
|
||||
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91 h1:u9i04mGE3iliBh0EFuWaKsmcwrLacqGmq1G3XoaM7gY=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91/go.mod h1:qfvBmyDNp+/liLEYWRvqny/PEz9hGe2Dz833eXILSmo=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167 h1:MEufgJohwIjFi2n3eJv4c/8UdRLQVUwPwSWQPoER+eU=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20251020182700-175e84fbb167/go.mod h1:qfvBmyDNp+/liLEYWRvqny/PEz9hGe2Dz833eXILSmo=
|
||||
github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=
|
||||
github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
|
||||
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
|
||||
@@ -128,30 +120,26 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU=
|
||||
github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk=
|
||||
github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U=
|
||||
github.com/letsencrypt/pebble/v2 v2.10.0/go.mod h1:Sk8cmUIPcIdv2nINo+9PB4L+ZBhzY+F9A1a/h/xmWiQ=
|
||||
github.com/libdns/acmedns v0.5.0 h1:5pRtmUj4Lb/QkNJSl1xgOGBUJTWW7RjpNaIhjpDXjPE=
|
||||
github.com/libdns/acmedns v0.5.0/go.mod h1:X7UAFP1Ep9NpTwWpVlrZzJLR7epynAy0wrIxSPFgKjQ=
|
||||
github.com/libdns/alidns v1.0.6 h1:/Ii428ty6WHFJmE24rZxq2taq++gh7rf9jhgLfp8PmM=
|
||||
github.com/libdns/alidns v1.0.6/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec=
|
||||
github.com/libdns/alidns v1.0.6-beta.3 h1:KAmb7FQ1tRzKsaAUGa7ZpGKAMRANwg7+1c7tUbSELq8=
|
||||
github.com/libdns/alidns v1.0.6-beta.3/go.mod h1:RECwyQ88e9VqQVtSrvX76o1ux3gQUKGzMgxICi+u7Ec=
|
||||
github.com/libdns/cloudflare v0.2.2 h1:XWHv+C1dDcApqazlh08Q6pjytYLgR2a+Y3xrXFu0vsI=
|
||||
github.com/libdns/cloudflare v0.2.2/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60=
|
||||
github.com/libdns/libdns v1.1.1 h1:wPrHrXILoSHKWJKGd0EiAVmiJbFShguILTg9leS/P/U=
|
||||
github.com/libdns/libdns v1.1.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/mdlayher/netlink v1.9.0 h1:G8+GLq2x3v4D4MVIqDdNUhTUC7TKiCy/6MDkmItfKco=
|
||||
github.com/mdlayher/netlink v1.9.0/go.mod h1:YBnl5BXsCoRuwBjKKlZ+aYmEoq0r12FDA/3JC+94KDg=
|
||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg=
|
||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o=
|
||||
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
|
||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||
github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg=
|
||||
github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
|
||||
github.com/mholt/acmez/v3 v3.1.6 h1:eGVQNObP0pBN4sxqrXeg7MYqTOWyoiYpQqITVWlrevk=
|
||||
github.com/mholt/acmez/v3 v3.1.6/go.mod h1:5nTPosTGosLxF3+LU4ygbgMRFDhbAVpqMI4+a4aHLBY=
|
||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/mholt/acmez/v3 v3.1.4 h1:DyzZe/RnAzT3rpZj/2Ii5xZpiEvvYk3cQEN/RmqxwFQ=
|
||||
github.com/mholt/acmez/v3 v3.1.4/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||
github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc=
|
||||
github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g=
|
||||
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
@@ -162,8 +150,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/openai/openai-go/v3 v3.26.0 h1:bRt6H/ozMNt/dDkN4gobnLqaEGrRGBzmbVs0xxJEnQE=
|
||||
github.com/openai/openai-go/v3 v3.26.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
|
||||
github.com/openai/openai-go/v3 v3.15.0 h1:hk99rM7YPz+M99/5B/zOQcVwFRLLMdprVGx1vaZ8XMo=
|
||||
github.com/openai/openai-go/v3 v3.15.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
@@ -189,68 +177,54 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
|
||||
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
||||
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
||||
github.com/sagernet/cronet-go v0.0.0-20260309100020-c128886ff3fc h1:YK7PwJT0irRAEui9ASdXSxcE2BOVQipWMF/A1Ogt+7c=
|
||||
github.com/sagernet/cronet-go v0.0.0-20260309100020-c128886ff3fc/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw=
|
||||
github.com/sagernet/cronet-go/all v0.0.0-20260309100020-c128886ff3fc h1:EJPHOqk23IuBsTjXK9OXqkNxPbKOBWKRmviQoCcriAs=
|
||||
github.com/sagernet/cronet-go/all v0.0.0-20260309100020-c128886ff3fc/go.mod h1:8aty0RW96DrJSMWXO6bRPMBJEjuqq5JWiOIi4bCRzFA=
|
||||
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260309101654-0cbdcfddded9 h1:Qi0IKBpoPP3qZqIXuOKMsT2dv+l/MLWMyBHDMLRw2EA=
|
||||
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
|
||||
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260309101654-0cbdcfddded9 h1:p+wCMjOhj46SpSD/AJeTGgkCcbyA76FyH631XZatyU8=
|
||||
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM=
|
||||
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260309101654-0cbdcfddded9 h1:Y7lWrZwEhC/HX8Pb5C92CrQihuaE7hrHmWB2ykst3iQ=
|
||||
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc=
|
||||
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260309101654-0cbdcfddded9 h1:3Ggy5wiyjA6t+aVVPnXlSEIVj9zkxd4ybH3NsvsNefs=
|
||||
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ=
|
||||
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260309101654-0cbdcfddded9 h1:DuFTCnZloblY+7olXiZoRdueWfxi34EV5UheTFKM2rA=
|
||||
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs=
|
||||
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260309101654-0cbdcfddded9 h1:x/6T2gjpLw9yNdCVR6xBlzMUzED9fxNFNt6U6A6SOh8=
|
||||
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0=
|
||||
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260309101654-0cbdcfddded9 h1:Lx9PExM70rg8aNxPm0JPeSr5SWC3yFiCz4wIq86ugx8=
|
||||
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0=
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260309101654-0cbdcfddded9 h1:BTEpw7/vKR9BNBsHebfpiGHDCPpjVJ3vLIbHNU3VUfM=
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4=
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260309101654-0cbdcfddded9 h1:hdEph9nQXRnKwc/lIDwo15rmzbC6znXF5jJWHPN1Fiw=
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo=
|
||||
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260309101654-0cbdcfddded9 h1:Iq++oYV7dtRJHTpu8yclHJdn+1oj2t1e84/YpdXYWW8=
|
||||
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ=
|
||||
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260309101654-0cbdcfddded9 h1:Y43fuLL8cgwRHpEKwxh0O3vYp7g/SZGvbkJj3cQ6USA=
|
||||
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU=
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260309101654-0cbdcfddded9 h1:bX2GJmF0VCC+tBrVAa49YEsmJ4A9dLmwoA6DJUxRtCY=
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI=
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260309101654-0cbdcfddded9 h1:gQTR/2azUCInE0r3kmesZT9xu+x801+BmtDY0d0Tw9Y=
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260309101654-0cbdcfddded9 h1:X4mP3jlYvxgrKpZLOKMmc/O8T5/zP83/23pgfQOc3tY=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260309101654-0cbdcfddded9 h1:c6xj2nXr/65EDiRFddUKQIBQ/b/lAPoH8WFYlgadaPc=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260309101654-0cbdcfddded9 h1:ahbl7yjOvGVVNUwk9TcQk+xejVfoYAYFRlhWnby0/YM=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260309101654-0cbdcfddded9 h1:JC5Zv5+J85da6g5G56VhdaK53fmo6Os2q/wWi5QlxOw=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow=
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260309101654-0cbdcfddded9 h1:4bt7Go588BoM4VjNYMxx0MrvbwlFQn3DdRDCM7BmkRo=
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:Wt5uFdU3tnmm8YzobYewwdF7Mt6SucRQg6xeTNWC3Tk=
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260309101654-0cbdcfddded9 h1:E1z0BeLUh8EZfCjIyS9BrfCocZrt+0KPS0bzop3Sxf4=
|
||||
github.com/sagernet/cronet-go/lib/linux_loong64_musl v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:lyIF6wKBLwWa5ZXaAKbAoewewl+yCHo2iYev39Mbj4E=
|
||||
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260309101654-0cbdcfddded9 h1:d8ejxRHO7Vi9JqR/6DxR7RyI/swA2JfDWATR4T7otBw=
|
||||
github.com/sagernet/cronet-go/lib/linux_mips64le v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:H46PnSTTZNcZokLLiDeMDaHiS1l14PH3tzWi0eykjD8=
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260309101654-0cbdcfddded9 h1:iUDVEVu3RxL5ArPIY72BesbuX5zQ1la/ZFwKpQcGc5c=
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:RBhSUDAKWq7fswtV4nQUQhuaTLcX3ettR7teA7/yf2w=
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260309101654-0cbdcfddded9 h1:xB6ikOC/R3n3hjy68EJ0sbZhH4vwEhd6JM9jZ1U2SVY=
|
||||
github.com/sagernet/cronet-go/lib/linux_mipsle_musl v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:wRzoIOGG4xbpp3Gh3triLKwMwYriScXzFtunLYhY4w0=
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260309101654-0cbdcfddded9 h1:mBOuLCPOOMMq8N1+dUM5FqZclqga1+u6fAbPqQcbIhc=
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:LNiZXmWil1OPwKCheqQjtakZlJuKGFz+iv2eGF76Hhs=
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260309101654-0cbdcfddded9 h1:cwPyDfj+ZNFE7kvcWbayQJyeC/KQA16HTXOxgHphL0w=
|
||||
github.com/sagernet/cronet-go/lib/linux_riscv64_musl v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:YFDGKTkpkJGc5+hnX/RYosZyTWg9h+68VB55fYRRLYc=
|
||||
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260309101654-0cbdcfddded9 h1:Zk9zG8kt3mXAboclUXQlvvxKQuhnI8u5NdDEl8uotNY=
|
||||
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4=
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260309101654-0cbdcfddded9 h1:Lu05srGqddQRMnl1MZtGAReln2yJljeGx9b1IadlMJ8=
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc=
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260309101654-0cbdcfddded9 h1:Tk9bDywUmOtc0iMjjCVIwMlAQNsxCy+bK+bTNA0OaBE=
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc=
|
||||
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260309101654-0cbdcfddded9 h1:tQqDQw3tEHdQpt7NTdAwF3UvZ3CjNIj/IJKMRFmm388=
|
||||
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8=
|
||||
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260309101654-0cbdcfddded9 h1:biUIbI2YxUrcQikEfS/bwPA8NsHp/WO+VZUG4morUmE=
|
||||
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw=
|
||||
github.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287 h1:0BYNmr0ptjsII948U0oBFmrbo4qEaCFcrE2JPRg3Zlk=
|
||||
github.com/sagernet/cronet-go v0.0.0-20260117110918-dc1cda1fe287/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw=
|
||||
github.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287 h1:ghxhYSBQpzkakqWqJDvXr/Zmxe0WjTjKuALEGbjGiGY=
|
||||
github.com/sagernet/cronet-go/all v0.0.0-20260117110918-dc1cda1fe287/go.mod h1:M+4ZjPhLJXIvoxcQsbDofmc19Wrig59hZ+hLvj6S3To=
|
||||
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f h1:8jZbZ4KBTdcXDFLwUBNQt5Xci6ZuAKh255S8TwuBCaM=
|
||||
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260117110516-f21660bef13f/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
|
||||
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f h1:tG0hCx+0u5zca7qQ7AMkcv4DCrBG/DKW1ggs/P+BRRI=
|
||||
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM=
|
||||
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f h1:ZXp5hKJIA7iJ52ZShJCKMQEPLpp/7dDIVZmPGV9Il40=
|
||||
github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20260117110516-f21660bef13f/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc=
|
||||
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f h1:gL7H8HS8s38adz4/HZtRHh79qMwsbLTRRPz4GQ9LcWI=
|
||||
github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ=
|
||||
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f h1:Dchgc0pAY5Jwb5lzUlE+1nhHIzqLx+YOurXLHgvWd/0=
|
||||
github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs=
|
||||
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f h1:+MOLSQoduuKDxF410i1LcSPaQGaiP0eZb0INvMlmjM4=
|
||||
github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0=
|
||||
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f h1:lIZna05Vn6n8k21p8OpSUnhwGm+E57PrMjiI4ZUfMSg=
|
||||
github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0=
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f h1:B2aFQ5CRHI20t8YsEizvtguS5W2QfK7D5XV/NzTIxPE=
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4=
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f h1:qpSwJ1rFGYCfJDenNCZoWYjoG7N+xEa6ke+E7/JO1i4=
|
||||
github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo=
|
||||
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f h1:cx7Ipg0tSvTDjS4maMEYz4vuzz93BMPAysmZ1YLrz80=
|
||||
github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20260117110516-f21660bef13f/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ=
|
||||
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f h1:4jOHuUiBxD8pJEpBBVQfJqyLmxjpd3t4MLRzU7YLFyg=
|
||||
github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU=
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f h1:OpXBa2WlRU+Mam9oRe9Nn4/zf7gQ+qiBTNK8A5RwbfQ=
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI=
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f h1:nJpGFi+6hI85tl4zoyNFEnFEQ5+xEV5gyvsUoMvd8g0=
|
||||
github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f h1:SEy2rpmgOJgrqcEryJI/RSnqUWIsEsp0cfYoA8y21jc=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20260117110516-f21660bef13f/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f h1:EW2TuFMLm0iBGqRZtuGwIZdeYmDtDsDmRcRRJQOMxUo=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f h1:3U5woxrNCkzfv1+UX+mVoWh1228AE1qAiMG02F9oFbY=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f h1:YwFTfuWG3mmctroeDYtFZ6LHjGsedVO+5wInYbbUuUY=
|
||||
github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20260117110516-f21660bef13f/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow=
|
||||
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f h1:r4V0ddPCRLgGu0VdgR3aUsO9NjpmyjAf+h+3oTD9D6E=
|
||||
github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4=
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f h1:B8yf4gFvEYUnwWmtVK9sdwUsflYZ387MhYmlOP2ohFQ=
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc=
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f h1:9YyaMg4rO1/jIgrxmNb0LKH+X7frSYWfX2pFgW5JUVM=
|
||||
github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20260117110516-f21660bef13f/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc=
|
||||
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f h1:B0fnGu0sh9yT/9JDN5u/GqThGoOzNN/daOAuGWFLXEk=
|
||||
github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8=
|
||||
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f h1:lxPcIXKSSI5JDhc7rx/6yufISWM4vtBS2FY9PavWQTs=
|
||||
github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260117110516-f21660bef13f/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw=
|
||||
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
||||
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
||||
github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 h1:SUPFNB+vSP4RBPrSEgNII+HkfqC8hKMpYLodom4o4EU=
|
||||
@@ -259,30 +233,30 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o=
|
||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
|
||||
github.com/sagernet/sing v0.8.3-0.20260315153529-ed51f65fbfde h1:RNQzlpnsXIuu1HGts/fIzJ1PR7RhrzaNlU52MDyiX1c=
|
||||
github.com/sagernet/sing v0.8.3-0.20260315153529-ed51f65fbfde/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.2 h1:hJUL+HtxEOjxsa0CsucbBVqI/AMS4k52NwNU637zmdw=
|
||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.2/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
|
||||
github.com/sagernet/sing v0.8.0-beta.16 h1:Fe+6E9VHYky9Mx4cf0ugbZPWDcXRflpAu7JQ5bWXvaA=
|
||||
github.com/sagernet/sing v0.8.0-beta.16/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s=
|
||||
github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
|
||||
github.com/sagernet/sing-quic v0.6.0 h1:dhrFnP45wgVKEOT1EvtsToxdzRnHIDIAgj6WHV9pLyM=
|
||||
github.com/sagernet/sing-quic v0.6.0/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8=
|
||||
github.com/sagernet/sing-quic v0.6.0-beta.11 h1:eUusxITKKRedhWC2ScUYFUvD96h/QfbKLaS3N6/7in4=
|
||||
github.com/sagernet/sing-quic v0.6.0-beta.11/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||
github.com/sagernet/sing-tun v0.8.7-0.20260323120017-8eb4e8acfc2d h1:vi0j6301f6H8t2GYgAC2PA2AdnGdMwkP34B4+N03Qt4=
|
||||
github.com/sagernet/sing-tun v0.8.7-0.20260323120017-8eb4e8acfc2d/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs=
|
||||
github.com/sagernet/sing-tun v0.8.0-beta.17 h1:6DdbNXeTFYj8Tb4FCh8Mp2boA3rVY6VNqzTOObj7Xis=
|
||||
github.com/sagernet/sing-tun v0.8.0-beta.17/go.mod h1:+HAK/y9GZljdT0KYKMYDR8MjjqnqDDQZYp5ZZQoRzS8=
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8=
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7 h1:8zc1Aph1+ElqF9/7aSPkO0o4vTd+AfQC+CO324mLWGg=
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc=
|
||||
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c h1:f9cXNB+IOOPnR8DOLMTpr42jf7naxh5Un5Y09BBf5Cg=
|
||||
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0=
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6 h1:eYz/OpMqWCvO2++iw3dEuzrlfC2xv78GdlGvprIM6O8=
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.6/go.mod h1:m87GAn4UcesHQF3leaPFEINZETO5za1LGn1GJdNDgNc=
|
||||
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 h1:E2tZFeg9mGYGQ7E7BbxMv1cU35HxwgRm6tPKI2Pp7DA=
|
||||
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0=
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
@@ -338,20 +312,20 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
@@ -370,26 +344,26 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
|
||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
|
||||
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
|
||||
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
|
||||
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -405,24 +379,24 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -433,19 +407,17 @@ golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -229,12 +229,13 @@ func (e *Endpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
||||
}
|
||||
|
||||
func (e *Endpoint) Close() error {
|
||||
if e.device != nil {
|
||||
e.device.Close()
|
||||
}
|
||||
if e.pauseCallback != nil {
|
||||
e.pause.UnregisterCallback(e.pauseCallback)
|
||||
}
|
||||
if e.device != nil {
|
||||
e.device.Down()
|
||||
e.device.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user