mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
9 Commits
f4de7d622a
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d82be7d86b | ||
|
|
64a5cc88e1 | ||
|
|
71626beaf8 | ||
|
|
004a967e72 | ||
|
|
3ab8ba76d0 | ||
|
|
1edc8fd855 | ||
|
|
22a8661858 | ||
|
|
52aa5716be | ||
|
|
2c24e82257 |
@@ -4,7 +4,6 @@
|
||||
--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
33
.github/detect_track.sh
vendored
@@ -1,33 +0,0 @@
|
||||
#!/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,6 +19,7 @@ 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
|
||||
@@ -259,13 +260,13 @@ jobs:
|
||||
fi
|
||||
echo "ref=$ref"
|
||||
echo "ref=$ref" >> $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
|
||||
if [[ $ref == *"-"* ]]; then
|
||||
latest=latest-beta
|
||||
else
|
||||
latest=latest
|
||||
fi
|
||||
echo "latest=$latest"
|
||||
echo "latest=$latest" >> $GITHUB_OUTPUT
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
@@ -285,11 +286,11 @@ jobs:
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
-t "${{ env.REGISTRY_IMAGE }}:${{ env.DOCKER_TAG }}" \
|
||||
-t "${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}" \
|
||||
-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 }}:${{ env.DOCKER_TAG }}
|
||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.latest }}
|
||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.ref.outputs.ref }}
|
||||
|
||||
16
.github/workflows/linux.yml
vendored
16
.github/workflows/linux.yml
vendored
@@ -11,6 +11,11 @@ on:
|
||||
description: "Version name"
|
||||
required: true
|
||||
type: string
|
||||
forceBeta:
|
||||
description: "Force beta"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
@@ -18,6 +23,7 @@ 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 }}
|
||||
@@ -162,8 +168,14 @@ jobs:
|
||||
- name: Set mtime
|
||||
run: |-
|
||||
TZ=UTC touch -t '197001010000' dist/sing-box
|
||||
- name: Detect track
|
||||
run: bash .github/detect_track.sh
|
||||
- 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: 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(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)
|
||||
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)
|
||||
ClearCache()
|
||||
}
|
||||
|
||||
@@ -72,6 +72,11 @@ 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,8 +10,6 @@ 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 {
|
||||
@@ -81,16 +79,14 @@ type InboundContext struct {
|
||||
FallbackNetworkType []C.InterfaceType
|
||||
FallbackDelay time.Duration
|
||||
|
||||
DestinationAddresses []netip.Addr
|
||||
DNSResponse *dns.Msg
|
||||
DestinationAddressMatchFromResponse bool
|
||||
SourceGeoIPCode string
|
||||
GeoIPCode string
|
||||
ProcessInfo *ConnectionOwner
|
||||
SourceMACAddress net.HardwareAddr
|
||||
SourceHostname string
|
||||
QueryType uint16
|
||||
FakeIP bool
|
||||
DestinationAddresses []netip.Addr
|
||||
SourceGeoIPCode string
|
||||
GeoIPCode string
|
||||
ProcessInfo *ConnectionOwner
|
||||
SourceMACAddress net.HardwareAddr
|
||||
SourceHostname string
|
||||
QueryType uint16
|
||||
FakeIP bool
|
||||
|
||||
// rule cache
|
||||
|
||||
@@ -119,51 +115,6 @@ 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:
|
||||
addr := M.AddrFromIP(record.A)
|
||||
if addr.IsValid() {
|
||||
addresses = append(addresses, addr)
|
||||
}
|
||||
case *dns.AAAA:
|
||||
addr := M.AddrFromIP(record.AAAA)
|
||||
if addr.IsValid() {
|
||||
addresses = append(addresses, addr)
|
||||
}
|
||||
case *dns.HTTPS:
|
||||
for _, value := range record.SVCB.Value {
|
||||
switch hint := value.(type) {
|
||||
case *dns.SVCBIPv4Hint:
|
||||
for _, ip := range hint.Hint {
|
||||
addr := M.AddrFromIP(ip).Unmap()
|
||||
if addr.IsValid() {
|
||||
addresses = append(addresses, addr)
|
||||
}
|
||||
}
|
||||
case *dns.SVCBIPv6Hint:
|
||||
for _, ip := range hint.Hint {
|
||||
addr := M.AddrFromIP(ip)
|
||||
if addr.IsValid() {
|
||||
addresses = append(addresses, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
|
||||
type inboundContextKey struct{}
|
||||
|
||||
func WithContext(ctx context.Context, inboundContext *InboundContext) context.Context {
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDNSResponseAddressesUnmapsHTTPSIPv4Hints(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ipv4Hint := net.ParseIP("1.1.1.1")
|
||||
require.NotNil(t, ipv4Hint)
|
||||
|
||||
response := &dns.Msg{
|
||||
MsgHdr: dns.MsgHdr{
|
||||
Response: true,
|
||||
Rcode: dns.RcodeSuccess,
|
||||
},
|
||||
Answer: []dns.RR{
|
||||
&dns.HTTPS{
|
||||
SVCB: dns.SVCB{
|
||||
Hdr: dns.RR_Header{
|
||||
Name: dns.Fqdn("example.com"),
|
||||
Rrtype: dns.TypeHTTPS,
|
||||
Class: dns.ClassINET,
|
||||
Ttl: 60,
|
||||
},
|
||||
Priority: 1,
|
||||
Target: ".",
|
||||
Value: []dns.SVCBKeyValue{
|
||||
&dns.SVCBIPv4Hint{Hint: []net.IP{ipv4Hint}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
addresses := DNSResponseAddresses(response)
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("1.1.1.1")}, addresses)
|
||||
require.True(t, addresses[0].Is4())
|
||||
}
|
||||
@@ -66,16 +66,10 @@ type RuleSet interface {
|
||||
|
||||
type RuleSetUpdateCallback func(it RuleSet)
|
||||
|
||||
type DNSRuleSetUpdateValidator interface {
|
||||
ValidateRuleSetMetadataUpdate(tag string, metadata RuleSetMetadata) error
|
||||
}
|
||||
|
||||
// ip_version is not a headless-rule item, so ContainsIPVersionRule is intentionally absent.
|
||||
type RuleSetMetadata struct {
|
||||
ContainsProcessRule bool
|
||||
ContainsWIFIRule bool
|
||||
ContainsIPCIDRRule bool
|
||||
ContainsDNSQueryTypeRule bool
|
||||
ContainsProcessRule bool
|
||||
ContainsWIFIRule bool
|
||||
ContainsIPCIDRRule bool
|
||||
}
|
||||
type HTTPStartContext struct {
|
||||
ctx context.Context
|
||||
|
||||
@@ -2,8 +2,6 @@ package adapter
|
||||
|
||||
import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type HeadlessRule interface {
|
||||
@@ -20,9 +18,8 @@ type Rule interface {
|
||||
|
||||
type DNSRule interface {
|
||||
Rule
|
||||
LegacyPreMatch(metadata *InboundContext) bool
|
||||
WithAddressLimit() bool
|
||||
MatchAddressLimit(metadata *InboundContext, response *dns.Msg) bool
|
||||
MatchAddressLimit(metadata *InboundContext) bool
|
||||
}
|
||||
|
||||
type RuleAction interface {
|
||||
@@ -32,7 +29,7 @@ type RuleAction interface {
|
||||
|
||||
func IsFinalAction(action RuleAction) bool {
|
||||
switch action.Type() {
|
||||
case C.RuleActionTypeSniff, C.RuleActionTypeResolve, C.RuleActionTypeEvaluate:
|
||||
case C.RuleActionTypeSniff, C.RuleActionTypeResolve:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
|
||||
8
box.go
8
box.go
@@ -171,7 +171,10 @@ func New(options Options) (*Box, error) {
|
||||
|
||||
var internalServices []adapter.LifecycleService
|
||||
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
||||
if C.IsAndroid || C.IsDarwin || certificateOptions.Store != "" {
|
||||
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
|
||||
len(certificateOptions.Certificate) > 0 ||
|
||||
len(certificateOptions.CertificatePath) > 0 ||
|
||||
len(certificateOptions.CertificateDirectoryPath) > 0 {
|
||||
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -196,7 +199,6 @@ func New(options Options) (*Box, error) {
|
||||
service.MustRegister[adapter.CertificateProviderManager](ctx, certificateProviderManager)
|
||||
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||
service.MustRegister[adapter.DNSRuleSetUpdateValidator](ctx, dnsRouter)
|
||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize network manager")
|
||||
@@ -484,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.network, s.connection, s.router, s.dnsRouter)
|
||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Submodule clients/android updated: 4f0826b94d...82ffed6cd7
Submodule clients/apple updated: ffbf405b52...bcdd385b79
@@ -1,122 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/networkquality"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
commandNetworkQualityFlagConfigURL string
|
||||
commandNetworkQualityFlagSerial bool
|
||||
commandNetworkQualityFlagMaxRuntime int
|
||||
)
|
||||
|
||||
var commandNetworkQuality = &cobra.Command{
|
||||
Use: "networkquality",
|
||||
Short: "Run a network quality test",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := runNetworkQuality()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandNetworkQuality.Flags().StringVar(
|
||||
&commandNetworkQualityFlagConfigURL,
|
||||
"config-url", "",
|
||||
"Network quality test config URL (default: Apple mensura)",
|
||||
)
|
||||
commandNetworkQuality.Flags().BoolVar(
|
||||
&commandNetworkQualityFlagSerial,
|
||||
"serial", false,
|
||||
"Run download and upload tests sequentially instead of in parallel",
|
||||
)
|
||||
commandNetworkQuality.Flags().IntVar(
|
||||
&commandNetworkQualityFlagMaxRuntime,
|
||||
"max-runtime", int(networkquality.DefaultMaxRuntime/time.Second),
|
||||
"Network quality maximum runtime in seconds",
|
||||
)
|
||||
commandTools.AddCommand(commandNetworkQuality)
|
||||
}
|
||||
|
||||
func runNetworkQuality() error {
|
||||
instance, err := createPreStartedClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer instance.Close()
|
||||
|
||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpClient := networkquality.NewHTTPClient(dialer)
|
||||
defer httpClient.CloseIdleConnections()
|
||||
|
||||
fmt.Fprintln(os.Stderr, "==== NETWORK QUALITY TEST ====")
|
||||
|
||||
result, err := networkquality.Run(networkquality.Options{
|
||||
ConfigURL: commandNetworkQualityFlagConfigURL,
|
||||
HTTPClient: httpClient,
|
||||
Serial: commandNetworkQualityFlagSerial,
|
||||
MaxRuntime: time.Duration(commandNetworkQualityFlagMaxRuntime) * time.Second,
|
||||
Context: globalCtx,
|
||||
OnProgress: func(p networkquality.Progress) {
|
||||
if !commandNetworkQualityFlagSerial && p.Phase != networkquality.PhaseIdle {
|
||||
fmt.Fprintf(os.Stderr, "\rDownload: %s RPM: %d Upload: %s RPM: %d",
|
||||
formatBitrate(p.DownloadCapacity), p.DownloadRPM,
|
||||
formatBitrate(p.UploadCapacity), p.UploadRPM)
|
||||
return
|
||||
}
|
||||
switch networkquality.Phase(p.Phase) {
|
||||
case networkquality.PhaseIdle:
|
||||
if p.IdleLatencyMs > 0 {
|
||||
fmt.Fprintf(os.Stderr, "\rIdle Latency: %d ms", p.IdleLatencyMs)
|
||||
} else {
|
||||
fmt.Fprint(os.Stderr, "\rMeasuring idle latency...")
|
||||
}
|
||||
case networkquality.PhaseDownload:
|
||||
fmt.Fprintf(os.Stderr, "\rDownload: %s RPM: %d",
|
||||
formatBitrate(p.DownloadCapacity), p.DownloadRPM)
|
||||
case networkquality.PhaseUpload:
|
||||
fmt.Fprintf(os.Stderr, "\rUpload: %s RPM: %d",
|
||||
formatBitrate(p.UploadCapacity), p.UploadRPM)
|
||||
}
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr)
|
||||
fmt.Fprintln(os.Stderr, strings.Repeat("-", 40))
|
||||
fmt.Fprintf(os.Stderr, "Idle Latency: %d ms\n", result.IdleLatencyMs)
|
||||
fmt.Fprintf(os.Stderr, "Download Capacity: %-20s Accuracy: %s\n", formatBitrate(result.DownloadCapacity), result.DownloadCapacityAccuracy)
|
||||
fmt.Fprintf(os.Stderr, "Upload Capacity: %-20s Accuracy: %s\n", formatBitrate(result.UploadCapacity), result.UploadCapacityAccuracy)
|
||||
fmt.Fprintf(os.Stderr, "Download Responsiveness: %-20s Accuracy: %s\n", fmt.Sprintf("%d RPM", result.DownloadRPM), result.DownloadRPMAccuracy)
|
||||
fmt.Fprintf(os.Stderr, "Upload Responsiveness: %-20s Accuracy: %s\n", fmt.Sprintf("%d RPM", result.UploadRPM), result.UploadRPMAccuracy)
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatBitrate(bps int64) string {
|
||||
switch {
|
||||
case bps >= 1_000_000_000:
|
||||
return fmt.Sprintf("%.1f Gbps", float64(bps)/1_000_000_000)
|
||||
case bps >= 1_000_000:
|
||||
return fmt.Sprintf("%.1f Mbps", float64(bps)/1_000_000)
|
||||
case bps >= 1_000:
|
||||
return fmt.Sprintf("%.1f Kbps", float64(bps)/1_000)
|
||||
default:
|
||||
return fmt.Sprintf("%d bps", bps)
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package networkquality
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
sBufio "github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
// NewHTTPClient creates an http.Client that dials through the given dialer.
|
||||
// The dialer should already handle DNS resolution if needed.
|
||||
func NewHTTPClient(dialer N.Dialer) *http.Client {
|
||||
transport := &http.Transport{
|
||||
ForceAttemptHTTP2: true,
|
||||
TLSHandshakeTimeout: C.TCPTimeout,
|
||||
}
|
||||
if dialer != nil {
|
||||
transport.DialContext = func(ctx context.Context, network string, addr string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
}
|
||||
}
|
||||
return &http.Client{Transport: transport}
|
||||
}
|
||||
|
||||
func baseTransportFromClient(client *http.Client) (*http.Transport, error) {
|
||||
if client == nil {
|
||||
return nil, E.New("http client is nil")
|
||||
}
|
||||
if client.Transport == nil {
|
||||
return http.DefaultTransport.(*http.Transport).Clone(), nil
|
||||
}
|
||||
transport, ok := client.Transport.(*http.Transport)
|
||||
if !ok {
|
||||
return nil, E.New("http client transport must be *http.Transport")
|
||||
}
|
||||
return transport.Clone(), nil
|
||||
}
|
||||
|
||||
func newMeasurementClient(
|
||||
baseClient *http.Client,
|
||||
connectEndpoint string,
|
||||
singleConnection bool,
|
||||
disableKeepAlives bool,
|
||||
readCounters []N.CountFunc,
|
||||
writeCounters []N.CountFunc,
|
||||
) (*http.Client, error) {
|
||||
transport, err := baseTransportFromClient(baseClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transport.DisableCompression = true
|
||||
transport.DisableKeepAlives = disableKeepAlives
|
||||
if singleConnection {
|
||||
transport.MaxConnsPerHost = 1
|
||||
transport.MaxIdleConnsPerHost = 1
|
||||
transport.MaxIdleConns = 1
|
||||
}
|
||||
|
||||
baseDialContext := transport.DialContext
|
||||
if baseDialContext == nil {
|
||||
dialer := &net.Dialer{}
|
||||
baseDialContext = dialer.DialContext
|
||||
}
|
||||
connectEndpoint = strings.TrimSpace(connectEndpoint)
|
||||
transport.DialContext = func(ctx context.Context, network string, addr string) (net.Conn, error) {
|
||||
dialAddr := addr
|
||||
if connectEndpoint != "" {
|
||||
dialAddr = rewriteDialAddress(addr, connectEndpoint)
|
||||
}
|
||||
conn, dialErr := baseDialContext(ctx, network, dialAddr)
|
||||
if dialErr != nil {
|
||||
return nil, dialErr
|
||||
}
|
||||
if len(readCounters) > 0 || len(writeCounters) > 0 {
|
||||
return sBufio.NewCounterConn(conn, readCounters, writeCounters), nil
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
return &http.Client{
|
||||
Transport: transport,
|
||||
CheckRedirect: baseClient.CheckRedirect,
|
||||
Jar: baseClient.Jar,
|
||||
Timeout: baseClient.Timeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func rewriteDialAddress(addr string, connectEndpoint string) string {
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return addr
|
||||
}
|
||||
endpointHost, endpointPort, err := net.SplitHostPort(connectEndpoint)
|
||||
if err == nil {
|
||||
host = endpointHost
|
||||
if endpointPort != "" {
|
||||
port = endpointPort
|
||||
}
|
||||
} else if connectEndpoint != "" {
|
||||
host = connectEndpoint
|
||||
}
|
||||
return net.JoinHostPort(host, port)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,18 +15,19 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
DNSTypeLegacy = "legacy"
|
||||
DNSTypeUDP = "udp"
|
||||
DNSTypeTCP = "tcp"
|
||||
DNSTypeTLS = "tls"
|
||||
DNSTypeHTTPS = "https"
|
||||
DNSTypeQUIC = "quic"
|
||||
DNSTypeHTTP3 = "h3"
|
||||
DNSTypeLocal = "local"
|
||||
DNSTypeHosts = "hosts"
|
||||
DNSTypeFakeIP = "fakeip"
|
||||
DNSTypeDHCP = "dhcp"
|
||||
DNSTypeTailscale = "tailscale"
|
||||
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"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -29,8 +29,6 @@ const (
|
||||
const (
|
||||
RuleActionTypeRoute = "route"
|
||||
RuleActionTypeRouteOptions = "route-options"
|
||||
RuleActionTypeEvaluate = "evaluate"
|
||||
RuleActionTypeRespond = "respond"
|
||||
RuleActionTypeDirect = "direct"
|
||||
RuleActionTypeBypass = "bypass"
|
||||
RuleActionTypeReject = "reject"
|
||||
|
||||
@@ -87,17 +87,12 @@ func (s *StartedService) newInstance(profileContent string, overrideOptions *Ove
|
||||
}
|
||||
}
|
||||
}
|
||||
if s.oomKillerEnabled {
|
||||
if s.oomKiller && C.IsIos {
|
||||
if !common.Any(options.Services, func(it option.Service) bool {
|
||||
return it.Type == C.TypeOOMKiller
|
||||
}) {
|
||||
oomOptions := &option.OOMKillerServiceOptions{
|
||||
KillerDisabled: s.oomKillerDisabled,
|
||||
MemoryLimitOverride: s.oomMemoryLimit,
|
||||
}
|
||||
options.Services = append(options.Services, option.Service{
|
||||
Type: C.TypeOOMKiller,
|
||||
Options: oomOptions,
|
||||
Type: C.TypeOOMKiller,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,5 @@ type PlatformHandler interface {
|
||||
ServiceReload() error
|
||||
SystemProxyStatus() (*SystemProxyStatus, error)
|
||||
SetSystemProxyEnabled(enabled bool) error
|
||||
TriggerNativeCrash() error
|
||||
WriteDebugMessage(message string)
|
||||
}
|
||||
|
||||
@@ -8,15 +8,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/common/networkquality"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/protocol/group"
|
||||
"github.com/sagernet/sing-box/service/oomkiller"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/batch"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -27,8 +24,6 @@ import (
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
@@ -37,12 +32,10 @@ var _ StartedServiceServer = (*StartedService)(nil)
|
||||
type StartedService struct {
|
||||
ctx context.Context
|
||||
// platform adapter.PlatformInterface
|
||||
handler PlatformHandler
|
||||
debug bool
|
||||
logMaxLines int
|
||||
oomKillerEnabled bool
|
||||
oomKillerDisabled bool
|
||||
oomMemoryLimit uint64
|
||||
handler PlatformHandler
|
||||
debug bool
|
||||
logMaxLines int
|
||||
oomKiller bool
|
||||
// workingDirectory string
|
||||
// tempDirectory string
|
||||
// userID int
|
||||
@@ -71,12 +64,10 @@ type StartedService struct {
|
||||
type ServiceOptions struct {
|
||||
Context context.Context
|
||||
// Platform adapter.PlatformInterface
|
||||
Handler PlatformHandler
|
||||
Debug bool
|
||||
LogMaxLines int
|
||||
OOMKillerEnabled bool
|
||||
OOMKillerDisabled bool
|
||||
OOMMemoryLimit uint64
|
||||
Handler PlatformHandler
|
||||
Debug bool
|
||||
LogMaxLines int
|
||||
OOMKiller bool
|
||||
// WorkingDirectory string
|
||||
// TempDirectory string
|
||||
// UserID int
|
||||
@@ -88,12 +79,10 @@ func NewStartedService(options ServiceOptions) *StartedService {
|
||||
s := &StartedService{
|
||||
ctx: options.Context,
|
||||
// platform: options.Platform,
|
||||
handler: options.Handler,
|
||||
debug: options.Debug,
|
||||
logMaxLines: options.LogMaxLines,
|
||||
oomKillerEnabled: options.OOMKillerEnabled,
|
||||
oomKillerDisabled: options.OOMKillerDisabled,
|
||||
oomMemoryLimit: options.OOMMemoryLimit,
|
||||
handler: options.Handler,
|
||||
debug: options.Debug,
|
||||
logMaxLines: options.LogMaxLines,
|
||||
oomKiller: options.OOMKiller,
|
||||
// workingDirectory: options.WorkingDirectory,
|
||||
// tempDirectory: options.TempDirectory,
|
||||
// userID: options.UserID,
|
||||
@@ -696,41 +685,6 @@ func (s *StartedService) SetSystemProxyEnabled(ctx context.Context, request *Set
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (s *StartedService) TriggerDebugCrash(ctx context.Context, request *DebugCrashRequest) (*emptypb.Empty, error) {
|
||||
if !s.debug {
|
||||
return nil, status.Error(codes.PermissionDenied, "debug crash trigger unavailable")
|
||||
}
|
||||
if request == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "missing debug crash request")
|
||||
}
|
||||
switch request.Type {
|
||||
case DebugCrashRequest_GO:
|
||||
time.AfterFunc(200*time.Millisecond, func() {
|
||||
panic("debug go crash")
|
||||
})
|
||||
case DebugCrashRequest_NATIVE:
|
||||
err := s.handler.TriggerNativeCrash()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, status.Error(codes.InvalidArgument, "unknown debug crash type")
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) TriggerOOMReport(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
instance := s.Instance()
|
||||
if instance == nil {
|
||||
return nil, status.Error(codes.FailedPrecondition, "service not started")
|
||||
}
|
||||
reporter := service.FromContext[oomkiller.OOMReporter](instance.ctx)
|
||||
if reporter == nil {
|
||||
return nil, status.Error(codes.Unavailable, "OOM reporter not available")
|
||||
}
|
||||
return &emptypb.Empty{}, reporter.WriteReport(memory.Total())
|
||||
}
|
||||
|
||||
func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[ConnectionEvents]) error {
|
||||
err := s.waitForStarted(server.Context())
|
||||
if err != nil {
|
||||
@@ -1065,12 +1019,9 @@ func (s *StartedService) GetDeprecatedWarnings(ctx context.Context, empty *empty
|
||||
return &DeprecatedWarnings{
|
||||
Warnings: common.Map(notes, func(it deprecated.Note) *DeprecatedWarning {
|
||||
return &DeprecatedWarning{
|
||||
Message: it.Message(),
|
||||
Impending: it.Impending(),
|
||||
MigrationLink: it.MigrationLink,
|
||||
Description: it.Description,
|
||||
DeprecatedVersion: it.DeprecatedVersion,
|
||||
ScheduledVersion: it.ScheduledVersion,
|
||||
Message: it.Message(),
|
||||
Impending: it.Impending(),
|
||||
MigrationLink: it.MigrationLink,
|
||||
}
|
||||
}),
|
||||
}, nil
|
||||
@@ -1082,149 +1033,6 @@ func (s *StartedService) GetStartedAt(ctx context.Context, empty *emptypb.Empty)
|
||||
return &StartedAt{StartedAt: s.startedAt.UnixMilli()}, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) ListOutbounds(ctx context.Context, _ *emptypb.Empty) (*OutboundList, error) {
|
||||
s.serviceAccess.RLock()
|
||||
if s.serviceStatus.Status != ServiceStatus_STARTED {
|
||||
s.serviceAccess.RUnlock()
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
boxService := s.instance
|
||||
s.serviceAccess.RUnlock()
|
||||
historyStorage := boxService.urlTestHistoryStorage
|
||||
outbounds := boxService.instance.Outbound().Outbounds()
|
||||
var list OutboundList
|
||||
for _, ob := range outbounds {
|
||||
item := &GroupItem{
|
||||
Tag: ob.Tag(),
|
||||
Type: ob.Type(),
|
||||
}
|
||||
if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(ob)); history != nil {
|
||||
item.UrlTestTime = history.Time.Unix()
|
||||
item.UrlTestDelay = int32(history.Delay)
|
||||
}
|
||||
list.Outbounds = append(list.Outbounds, item)
|
||||
}
|
||||
return &list, nil
|
||||
}
|
||||
|
||||
func (s *StartedService) SubscribeOutbounds(_ *emptypb.Empty, server grpc.ServerStreamingServer[OutboundList]) error {
|
||||
err := s.waitForStarted(server.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
subscription, done, err := s.urlTestObserver.Subscribe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.urlTestObserver.UnSubscribe(subscription)
|
||||
for {
|
||||
s.serviceAccess.RLock()
|
||||
if s.serviceStatus.Status != ServiceStatus_STARTED {
|
||||
s.serviceAccess.RUnlock()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
boxService := s.instance
|
||||
s.serviceAccess.RUnlock()
|
||||
historyStorage := boxService.urlTestHistoryStorage
|
||||
outbounds := boxService.instance.Outbound().Outbounds()
|
||||
var list OutboundList
|
||||
for _, ob := range outbounds {
|
||||
item := &GroupItem{
|
||||
Tag: ob.Tag(),
|
||||
Type: ob.Type(),
|
||||
}
|
||||
if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(ob)); history != nil {
|
||||
item.UrlTestTime = history.Time.Unix()
|
||||
item.UrlTestDelay = int32(history.Delay)
|
||||
}
|
||||
list.Outbounds = append(list.Outbounds, item)
|
||||
}
|
||||
err = server.Send(&list)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-subscription:
|
||||
case <-s.ctx.Done():
|
||||
return s.ctx.Err()
|
||||
case <-server.Context().Done():
|
||||
return server.Context().Err()
|
||||
case <-done:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *StartedService) StartNetworkQualityTest(
|
||||
request *NetworkQualityTestRequest,
|
||||
server grpc.ServerStreamingServer[NetworkQualityTestProgress],
|
||||
) error {
|
||||
err := s.waitForStarted(server.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.serviceAccess.RLock()
|
||||
boxService := s.instance
|
||||
s.serviceAccess.RUnlock()
|
||||
|
||||
var outbound adapter.Outbound
|
||||
if request.OutboundTag == "" {
|
||||
outbound = boxService.instance.Outbound().Default()
|
||||
} else {
|
||||
var loaded bool
|
||||
outbound, loaded = boxService.instance.Outbound().Outbound(request.OutboundTag)
|
||||
if !loaded {
|
||||
return E.New("outbound not found: ", request.OutboundTag)
|
||||
}
|
||||
}
|
||||
|
||||
resolvedDialer := dialer.NewResolveDialer(boxService.ctx, outbound, true, "", adapter.DNSQueryOptions{}, 0)
|
||||
httpClient := networkquality.NewHTTPClient(resolvedDialer)
|
||||
defer httpClient.CloseIdleConnections()
|
||||
|
||||
result, nqErr := networkquality.Run(networkquality.Options{
|
||||
ConfigURL: request.ConfigURL,
|
||||
HTTPClient: httpClient,
|
||||
Serial: request.Serial,
|
||||
MaxRuntime: time.Duration(request.MaxRuntimeSeconds) * time.Second,
|
||||
Context: server.Context(),
|
||||
OnProgress: func(p networkquality.Progress) {
|
||||
_ = server.Send(&NetworkQualityTestProgress{
|
||||
Phase: int32(p.Phase),
|
||||
DownloadCapacity: p.DownloadCapacity,
|
||||
UploadCapacity: p.UploadCapacity,
|
||||
DownloadRPM: p.DownloadRPM,
|
||||
UploadRPM: p.UploadRPM,
|
||||
IdleLatencyMs: p.IdleLatencyMs,
|
||||
ElapsedMs: p.ElapsedMs,
|
||||
DownloadCapacityAccuracy: int32(p.DownloadCapacityAccuracy),
|
||||
UploadCapacityAccuracy: int32(p.UploadCapacityAccuracy),
|
||||
DownloadRPMAccuracy: int32(p.DownloadRPMAccuracy),
|
||||
UploadRPMAccuracy: int32(p.UploadRPMAccuracy),
|
||||
})
|
||||
},
|
||||
})
|
||||
if nqErr != nil {
|
||||
return server.Send(&NetworkQualityTestProgress{
|
||||
IsFinal: true,
|
||||
Error: nqErr.Error(),
|
||||
})
|
||||
}
|
||||
return server.Send(&NetworkQualityTestProgress{
|
||||
Phase: int32(networkquality.PhaseDone),
|
||||
DownloadCapacity: result.DownloadCapacity,
|
||||
UploadCapacity: result.UploadCapacity,
|
||||
DownloadRPM: result.DownloadRPM,
|
||||
UploadRPM: result.UploadRPM,
|
||||
IdleLatencyMs: result.IdleLatencyMs,
|
||||
IsFinal: true,
|
||||
DownloadCapacityAccuracy: int32(result.DownloadCapacityAccuracy),
|
||||
UploadCapacityAccuracy: int32(result.UploadCapacityAccuracy),
|
||||
DownloadRPMAccuracy: int32(result.DownloadRPMAccuracy),
|
||||
UploadRPMAccuracy: int32(result.UploadRPMAccuracy),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *StartedService) mustEmbedUnimplementedStartedServiceServer() {
|
||||
}
|
||||
|
||||
|
||||
@@ -182,52 +182,6 @@ func (ServiceStatus_Type) EnumDescriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
type DebugCrashRequest_Type int32
|
||||
|
||||
const (
|
||||
DebugCrashRequest_GO DebugCrashRequest_Type = 0
|
||||
DebugCrashRequest_NATIVE DebugCrashRequest_Type = 1
|
||||
)
|
||||
|
||||
// Enum value maps for DebugCrashRequest_Type.
|
||||
var (
|
||||
DebugCrashRequest_Type_name = map[int32]string{
|
||||
0: "GO",
|
||||
1: "NATIVE",
|
||||
}
|
||||
DebugCrashRequest_Type_value = map[string]int32{
|
||||
"GO": 0,
|
||||
"NATIVE": 1,
|
||||
}
|
||||
)
|
||||
|
||||
func (x DebugCrashRequest_Type) Enum() *DebugCrashRequest_Type {
|
||||
p := new(DebugCrashRequest_Type)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x DebugCrashRequest_Type) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (DebugCrashRequest_Type) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_daemon_started_service_proto_enumTypes[3].Descriptor()
|
||||
}
|
||||
|
||||
func (DebugCrashRequest_Type) Type() protoreflect.EnumType {
|
||||
return &file_daemon_started_service_proto_enumTypes[3]
|
||||
}
|
||||
|
||||
func (x DebugCrashRequest_Type) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DebugCrashRequest_Type.Descriptor instead.
|
||||
func (DebugCrashRequest_Type) EnumDescriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{16, 0}
|
||||
}
|
||||
|
||||
type ServiceStatus struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Status ServiceStatus_Type `protobuf:"varint,1,opt,name=status,proto3,enum=daemon.ServiceStatus_Type" json:"status,omitempty"`
|
||||
@@ -1108,50 +1062,6 @@ func (x *SetSystemProxyEnabledRequest) GetEnabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type DebugCrashRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Type DebugCrashRequest_Type `protobuf:"varint,1,opt,name=type,proto3,enum=daemon.DebugCrashRequest_Type" json:"type,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *DebugCrashRequest) Reset() {
|
||||
*x = DebugCrashRequest{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[16]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *DebugCrashRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*DebugCrashRequest) ProtoMessage() {}
|
||||
|
||||
func (x *DebugCrashRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[16]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use DebugCrashRequest.ProtoReflect.Descriptor instead.
|
||||
func (*DebugCrashRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{16}
|
||||
}
|
||||
|
||||
func (x *DebugCrashRequest) GetType() DebugCrashRequest_Type {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return DebugCrashRequest_GO
|
||||
}
|
||||
|
||||
type SubscribeConnectionsRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Interval int64 `protobuf:"varint,1,opt,name=interval,proto3" json:"interval,omitempty"`
|
||||
@@ -1161,7 +1071,7 @@ type SubscribeConnectionsRequest struct {
|
||||
|
||||
func (x *SubscribeConnectionsRequest) Reset() {
|
||||
*x = SubscribeConnectionsRequest{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[17]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[16]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1173,7 +1083,7 @@ func (x *SubscribeConnectionsRequest) String() string {
|
||||
func (*SubscribeConnectionsRequest) ProtoMessage() {}
|
||||
|
||||
func (x *SubscribeConnectionsRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[17]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[16]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1186,7 +1096,7 @@ func (x *SubscribeConnectionsRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use SubscribeConnectionsRequest.ProtoReflect.Descriptor instead.
|
||||
func (*SubscribeConnectionsRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{17}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{16}
|
||||
}
|
||||
|
||||
func (x *SubscribeConnectionsRequest) GetInterval() int64 {
|
||||
@@ -1210,7 +1120,7 @@ type ConnectionEvent struct {
|
||||
|
||||
func (x *ConnectionEvent) Reset() {
|
||||
*x = ConnectionEvent{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[18]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[17]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1222,7 +1132,7 @@ func (x *ConnectionEvent) String() string {
|
||||
func (*ConnectionEvent) ProtoMessage() {}
|
||||
|
||||
func (x *ConnectionEvent) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[18]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[17]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1235,7 +1145,7 @@ func (x *ConnectionEvent) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ConnectionEvent.ProtoReflect.Descriptor instead.
|
||||
func (*ConnectionEvent) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{18}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{17}
|
||||
}
|
||||
|
||||
func (x *ConnectionEvent) GetType() ConnectionEventType {
|
||||
@@ -1290,7 +1200,7 @@ type ConnectionEvents struct {
|
||||
|
||||
func (x *ConnectionEvents) Reset() {
|
||||
*x = ConnectionEvents{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[19]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[18]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1302,7 +1212,7 @@ func (x *ConnectionEvents) String() string {
|
||||
func (*ConnectionEvents) ProtoMessage() {}
|
||||
|
||||
func (x *ConnectionEvents) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[19]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[18]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1315,7 +1225,7 @@ func (x *ConnectionEvents) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ConnectionEvents.ProtoReflect.Descriptor instead.
|
||||
func (*ConnectionEvents) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{19}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{18}
|
||||
}
|
||||
|
||||
func (x *ConnectionEvents) GetEvents() []*ConnectionEvent {
|
||||
@@ -1362,7 +1272,7 @@ type Connection struct {
|
||||
|
||||
func (x *Connection) Reset() {
|
||||
*x = Connection{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[20]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[19]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1374,7 +1284,7 @@ func (x *Connection) String() string {
|
||||
func (*Connection) ProtoMessage() {}
|
||||
|
||||
func (x *Connection) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[20]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[19]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1387,7 +1297,7 @@ func (x *Connection) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Connection.ProtoReflect.Descriptor instead.
|
||||
func (*Connection) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{20}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{19}
|
||||
}
|
||||
|
||||
func (x *Connection) GetId() string {
|
||||
@@ -1557,7 +1467,7 @@ type ProcessInfo struct {
|
||||
|
||||
func (x *ProcessInfo) Reset() {
|
||||
*x = ProcessInfo{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[21]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[20]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1569,7 +1479,7 @@ func (x *ProcessInfo) String() string {
|
||||
func (*ProcessInfo) ProtoMessage() {}
|
||||
|
||||
func (x *ProcessInfo) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[21]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[20]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1582,7 +1492,7 @@ func (x *ProcessInfo) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ProcessInfo.ProtoReflect.Descriptor instead.
|
||||
func (*ProcessInfo) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{21}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{20}
|
||||
}
|
||||
|
||||
func (x *ProcessInfo) GetProcessId() uint32 {
|
||||
@@ -1629,7 +1539,7 @@ type CloseConnectionRequest struct {
|
||||
|
||||
func (x *CloseConnectionRequest) Reset() {
|
||||
*x = CloseConnectionRequest{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[22]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[21]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1641,7 +1551,7 @@ func (x *CloseConnectionRequest) String() string {
|
||||
func (*CloseConnectionRequest) ProtoMessage() {}
|
||||
|
||||
func (x *CloseConnectionRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[22]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[21]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1654,7 +1564,7 @@ func (x *CloseConnectionRequest) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use CloseConnectionRequest.ProtoReflect.Descriptor instead.
|
||||
func (*CloseConnectionRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{22}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{21}
|
||||
}
|
||||
|
||||
func (x *CloseConnectionRequest) GetId() string {
|
||||
@@ -1673,7 +1583,7 @@ type DeprecatedWarnings struct {
|
||||
|
||||
func (x *DeprecatedWarnings) Reset() {
|
||||
*x = DeprecatedWarnings{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[23]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[22]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1685,7 +1595,7 @@ func (x *DeprecatedWarnings) String() string {
|
||||
func (*DeprecatedWarnings) ProtoMessage() {}
|
||||
|
||||
func (x *DeprecatedWarnings) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[23]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[22]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1698,7 +1608,7 @@ func (x *DeprecatedWarnings) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use DeprecatedWarnings.ProtoReflect.Descriptor instead.
|
||||
func (*DeprecatedWarnings) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{23}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{22}
|
||||
}
|
||||
|
||||
func (x *DeprecatedWarnings) GetWarnings() []*DeprecatedWarning {
|
||||
@@ -1709,20 +1619,17 @@ func (x *DeprecatedWarnings) GetWarnings() []*DeprecatedWarning {
|
||||
}
|
||||
|
||||
type DeprecatedWarning struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
Impending bool `protobuf:"varint,2,opt,name=impending,proto3" json:"impending,omitempty"`
|
||||
MigrationLink string `protobuf:"bytes,3,opt,name=migrationLink,proto3" json:"migrationLink,omitempty"`
|
||||
Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"`
|
||||
DeprecatedVersion string `protobuf:"bytes,5,opt,name=deprecatedVersion,proto3" json:"deprecatedVersion,omitempty"`
|
||||
ScheduledVersion string `protobuf:"bytes,6,opt,name=scheduledVersion,proto3" json:"scheduledVersion,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
Impending bool `protobuf:"varint,2,opt,name=impending,proto3" json:"impending,omitempty"`
|
||||
MigrationLink string `protobuf:"bytes,3,opt,name=migrationLink,proto3" json:"migrationLink,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *DeprecatedWarning) Reset() {
|
||||
*x = DeprecatedWarning{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[24]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[23]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1734,7 +1641,7 @@ func (x *DeprecatedWarning) String() string {
|
||||
func (*DeprecatedWarning) ProtoMessage() {}
|
||||
|
||||
func (x *DeprecatedWarning) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[24]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[23]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1747,7 +1654,7 @@ func (x *DeprecatedWarning) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use DeprecatedWarning.ProtoReflect.Descriptor instead.
|
||||
func (*DeprecatedWarning) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{24}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{23}
|
||||
}
|
||||
|
||||
func (x *DeprecatedWarning) GetMessage() string {
|
||||
@@ -1771,27 +1678,6 @@ func (x *DeprecatedWarning) GetMigrationLink() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DeprecatedWarning) GetDescription() string {
|
||||
if x != nil {
|
||||
return x.Description
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DeprecatedWarning) GetDeprecatedVersion() string {
|
||||
if x != nil {
|
||||
return x.DeprecatedVersion
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *DeprecatedWarning) GetScheduledVersion() string {
|
||||
if x != nil {
|
||||
return x.ScheduledVersion
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type StartedAt struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
StartedAt int64 `protobuf:"varint,1,opt,name=startedAt,proto3" json:"startedAt,omitempty"`
|
||||
@@ -1801,7 +1687,7 @@ type StartedAt struct {
|
||||
|
||||
func (x *StartedAt) Reset() {
|
||||
*x = StartedAt{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[25]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[24]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1813,7 +1699,7 @@ func (x *StartedAt) String() string {
|
||||
func (*StartedAt) ProtoMessage() {}
|
||||
|
||||
func (x *StartedAt) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[25]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[24]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1826,7 +1712,7 @@ func (x *StartedAt) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use StartedAt.ProtoReflect.Descriptor instead.
|
||||
func (*StartedAt) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{25}
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{24}
|
||||
}
|
||||
|
||||
func (x *StartedAt) GetStartedAt() int64 {
|
||||
@@ -1836,258 +1722,6 @@ func (x *StartedAt) GetStartedAt() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
type OutboundList struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Outbounds []*GroupItem `protobuf:"bytes,1,rep,name=outbounds,proto3" json:"outbounds,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *OutboundList) Reset() {
|
||||
*x = OutboundList{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[26]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *OutboundList) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*OutboundList) ProtoMessage() {}
|
||||
|
||||
func (x *OutboundList) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[26]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use OutboundList.ProtoReflect.Descriptor instead.
|
||||
func (*OutboundList) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{26}
|
||||
}
|
||||
|
||||
func (x *OutboundList) GetOutbounds() []*GroupItem {
|
||||
if x != nil {
|
||||
return x.Outbounds
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NetworkQualityTestRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
ConfigURL string `protobuf:"bytes,1,opt,name=configURL,proto3" json:"configURL,omitempty"`
|
||||
OutboundTag string `protobuf:"bytes,2,opt,name=outboundTag,proto3" json:"outboundTag,omitempty"`
|
||||
Serial bool `protobuf:"varint,3,opt,name=serial,proto3" json:"serial,omitempty"`
|
||||
MaxRuntimeSeconds int32 `protobuf:"varint,4,opt,name=maxRuntimeSeconds,proto3" json:"maxRuntimeSeconds,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestRequest) Reset() {
|
||||
*x = NetworkQualityTestRequest{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[27]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*NetworkQualityTestRequest) ProtoMessage() {}
|
||||
|
||||
func (x *NetworkQualityTestRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[27]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use NetworkQualityTestRequest.ProtoReflect.Descriptor instead.
|
||||
func (*NetworkQualityTestRequest) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{27}
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestRequest) GetConfigURL() string {
|
||||
if x != nil {
|
||||
return x.ConfigURL
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestRequest) GetOutboundTag() string {
|
||||
if x != nil {
|
||||
return x.OutboundTag
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestRequest) GetSerial() bool {
|
||||
if x != nil {
|
||||
return x.Serial
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestRequest) GetMaxRuntimeSeconds() int32 {
|
||||
if x != nil {
|
||||
return x.MaxRuntimeSeconds
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type NetworkQualityTestProgress struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Phase int32 `protobuf:"varint,1,opt,name=phase,proto3" json:"phase,omitempty"`
|
||||
DownloadCapacity int64 `protobuf:"varint,2,opt,name=downloadCapacity,proto3" json:"downloadCapacity,omitempty"`
|
||||
UploadCapacity int64 `protobuf:"varint,3,opt,name=uploadCapacity,proto3" json:"uploadCapacity,omitempty"`
|
||||
DownloadRPM int32 `protobuf:"varint,4,opt,name=downloadRPM,proto3" json:"downloadRPM,omitempty"`
|
||||
UploadRPM int32 `protobuf:"varint,5,opt,name=uploadRPM,proto3" json:"uploadRPM,omitempty"`
|
||||
IdleLatencyMs int32 `protobuf:"varint,6,opt,name=idleLatencyMs,proto3" json:"idleLatencyMs,omitempty"`
|
||||
ElapsedMs int64 `protobuf:"varint,7,opt,name=elapsedMs,proto3" json:"elapsedMs,omitempty"`
|
||||
IsFinal bool `protobuf:"varint,8,opt,name=isFinal,proto3" json:"isFinal,omitempty"`
|
||||
Error string `protobuf:"bytes,9,opt,name=error,proto3" json:"error,omitempty"`
|
||||
DownloadCapacityAccuracy int32 `protobuf:"varint,10,opt,name=downloadCapacityAccuracy,proto3" json:"downloadCapacityAccuracy,omitempty"`
|
||||
UploadCapacityAccuracy int32 `protobuf:"varint,11,opt,name=uploadCapacityAccuracy,proto3" json:"uploadCapacityAccuracy,omitempty"`
|
||||
DownloadRPMAccuracy int32 `protobuf:"varint,12,opt,name=downloadRPMAccuracy,proto3" json:"downloadRPMAccuracy,omitempty"`
|
||||
UploadRPMAccuracy int32 `protobuf:"varint,13,opt,name=uploadRPMAccuracy,proto3" json:"uploadRPMAccuracy,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestProgress) Reset() {
|
||||
*x = NetworkQualityTestProgress{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[28]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestProgress) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*NetworkQualityTestProgress) ProtoMessage() {}
|
||||
|
||||
func (x *NetworkQualityTestProgress) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[28]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use NetworkQualityTestProgress.ProtoReflect.Descriptor instead.
|
||||
func (*NetworkQualityTestProgress) Descriptor() ([]byte, []int) {
|
||||
return file_daemon_started_service_proto_rawDescGZIP(), []int{28}
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestProgress) GetPhase() int32 {
|
||||
if x != nil {
|
||||
return x.Phase
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestProgress) GetDownloadCapacity() int64 {
|
||||
if x != nil {
|
||||
return x.DownloadCapacity
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestProgress) GetUploadCapacity() int64 {
|
||||
if x != nil {
|
||||
return x.UploadCapacity
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestProgress) GetDownloadRPM() int32 {
|
||||
if x != nil {
|
||||
return x.DownloadRPM
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestProgress) GetUploadRPM() int32 {
|
||||
if x != nil {
|
||||
return x.UploadRPM
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestProgress) GetIdleLatencyMs() int32 {
|
||||
if x != nil {
|
||||
return x.IdleLatencyMs
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestProgress) GetElapsedMs() int64 {
|
||||
if x != nil {
|
||||
return x.ElapsedMs
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestProgress) GetIsFinal() bool {
|
||||
if x != nil {
|
||||
return x.IsFinal
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestProgress) GetError() string {
|
||||
if x != nil {
|
||||
return x.Error
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestProgress) GetDownloadCapacityAccuracy() int32 {
|
||||
if x != nil {
|
||||
return x.DownloadCapacityAccuracy
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestProgress) GetUploadCapacityAccuracy() int32 {
|
||||
if x != nil {
|
||||
return x.UploadCapacityAccuracy
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestProgress) GetDownloadRPMAccuracy() int32 {
|
||||
if x != nil {
|
||||
return x.DownloadRPMAccuracy
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *NetworkQualityTestProgress) GetUploadRPMAccuracy() int32 {
|
||||
if x != nil {
|
||||
return x.UploadRPMAccuracy
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Log_Message struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Level LogLevel `protobuf:"varint,1,opt,name=level,proto3,enum=daemon.LogLevel" json:"level,omitempty"`
|
||||
@@ -2098,7 +1732,7 @@ type Log_Message struct {
|
||||
|
||||
func (x *Log_Message) Reset() {
|
||||
*x = Log_Message{}
|
||||
mi := &file_daemon_started_service_proto_msgTypes[29]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[25]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -2110,7 +1744,7 @@ func (x *Log_Message) String() string {
|
||||
func (*Log_Message) ProtoMessage() {}
|
||||
|
||||
func (x *Log_Message) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_daemon_started_service_proto_msgTypes[29]
|
||||
mi := &file_daemon_started_service_proto_msgTypes[25]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -2211,13 +1845,7 @@ const file_daemon_started_service_proto_rawDesc = "" +
|
||||
"\tavailable\x18\x01 \x01(\bR\tavailable\x12\x18\n" +
|
||||
"\aenabled\x18\x02 \x01(\bR\aenabled\"8\n" +
|
||||
"\x1cSetSystemProxyEnabledRequest\x12\x18\n" +
|
||||
"\aenabled\x18\x01 \x01(\bR\aenabled\"c\n" +
|
||||
"\x11DebugCrashRequest\x122\n" +
|
||||
"\x04type\x18\x01 \x01(\x0e2\x1e.daemon.DebugCrashRequest.TypeR\x04type\"\x1a\n" +
|
||||
"\x04Type\x12\x06\n" +
|
||||
"\x02GO\x10\x00\x12\n" +
|
||||
"\n" +
|
||||
"\x06NATIVE\x10\x01\"9\n" +
|
||||
"\aenabled\x18\x01 \x01(\bR\aenabled\"9\n" +
|
||||
"\x1bSubscribeConnectionsRequest\x12\x1a\n" +
|
||||
"\binterval\x18\x01 \x01(\x03R\binterval\"\xea\x01\n" +
|
||||
"\x0fConnectionEvent\x12/\n" +
|
||||
@@ -2266,38 +1894,13 @@ const file_daemon_started_service_proto_rawDesc = "" +
|
||||
"\x16CloseConnectionRequest\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\tR\x02id\"K\n" +
|
||||
"\x12DeprecatedWarnings\x125\n" +
|
||||
"\bwarnings\x18\x01 \x03(\v2\x19.daemon.DeprecatedWarningR\bwarnings\"\xed\x01\n" +
|
||||
"\bwarnings\x18\x01 \x03(\v2\x19.daemon.DeprecatedWarningR\bwarnings\"q\n" +
|
||||
"\x11DeprecatedWarning\x12\x18\n" +
|
||||
"\amessage\x18\x01 \x01(\tR\amessage\x12\x1c\n" +
|
||||
"\timpending\x18\x02 \x01(\bR\timpending\x12$\n" +
|
||||
"\rmigrationLink\x18\x03 \x01(\tR\rmigrationLink\x12 \n" +
|
||||
"\vdescription\x18\x04 \x01(\tR\vdescription\x12,\n" +
|
||||
"\x11deprecatedVersion\x18\x05 \x01(\tR\x11deprecatedVersion\x12*\n" +
|
||||
"\x10scheduledVersion\x18\x06 \x01(\tR\x10scheduledVersion\")\n" +
|
||||
"\rmigrationLink\x18\x03 \x01(\tR\rmigrationLink\")\n" +
|
||||
"\tStartedAt\x12\x1c\n" +
|
||||
"\tstartedAt\x18\x01 \x01(\x03R\tstartedAt\"?\n" +
|
||||
"\fOutboundList\x12/\n" +
|
||||
"\toutbounds\x18\x01 \x03(\v2\x11.daemon.GroupItemR\toutbounds\"\xa1\x01\n" +
|
||||
"\x19NetworkQualityTestRequest\x12\x1c\n" +
|
||||
"\tconfigURL\x18\x01 \x01(\tR\tconfigURL\x12 \n" +
|
||||
"\voutboundTag\x18\x02 \x01(\tR\voutboundTag\x12\x16\n" +
|
||||
"\x06serial\x18\x03 \x01(\bR\x06serial\x12,\n" +
|
||||
"\x11maxRuntimeSeconds\x18\x04 \x01(\x05R\x11maxRuntimeSeconds\"\x8e\x04\n" +
|
||||
"\x1aNetworkQualityTestProgress\x12\x14\n" +
|
||||
"\x05phase\x18\x01 \x01(\x05R\x05phase\x12*\n" +
|
||||
"\x10downloadCapacity\x18\x02 \x01(\x03R\x10downloadCapacity\x12&\n" +
|
||||
"\x0euploadCapacity\x18\x03 \x01(\x03R\x0euploadCapacity\x12 \n" +
|
||||
"\vdownloadRPM\x18\x04 \x01(\x05R\vdownloadRPM\x12\x1c\n" +
|
||||
"\tuploadRPM\x18\x05 \x01(\x05R\tuploadRPM\x12$\n" +
|
||||
"\ridleLatencyMs\x18\x06 \x01(\x05R\ridleLatencyMs\x12\x1c\n" +
|
||||
"\telapsedMs\x18\a \x01(\x03R\telapsedMs\x12\x18\n" +
|
||||
"\aisFinal\x18\b \x01(\bR\aisFinal\x12\x14\n" +
|
||||
"\x05error\x18\t \x01(\tR\x05error\x12:\n" +
|
||||
"\x18downloadCapacityAccuracy\x18\n" +
|
||||
" \x01(\x05R\x18downloadCapacityAccuracy\x126\n" +
|
||||
"\x16uploadCapacityAccuracy\x18\v \x01(\x05R\x16uploadCapacityAccuracy\x120\n" +
|
||||
"\x13downloadRPMAccuracy\x18\f \x01(\x05R\x13downloadRPMAccuracy\x12,\n" +
|
||||
"\x11uploadRPMAccuracy\x18\r \x01(\x05R\x11uploadRPMAccuracy*U\n" +
|
||||
"\tstartedAt\x18\x01 \x01(\x03R\tstartedAt*U\n" +
|
||||
"\bLogLevel\x12\t\n" +
|
||||
"\x05PANIC\x10\x00\x12\t\n" +
|
||||
"\x05FATAL\x10\x01\x12\t\n" +
|
||||
@@ -2309,7 +1912,7 @@ const file_daemon_started_service_proto_rawDesc = "" +
|
||||
"\x13ConnectionEventType\x12\x18\n" +
|
||||
"\x14CONNECTION_EVENT_NEW\x10\x00\x12\x1b\n" +
|
||||
"\x17CONNECTION_EVENT_UPDATE\x10\x01\x12\x1b\n" +
|
||||
"\x17CONNECTION_EVENT_CLOSED\x10\x022\xe4\x0e\n" +
|
||||
"\x17CONNECTION_EVENT_CLOSED\x10\x022\xe5\v\n" +
|
||||
"\x0eStartedService\x12=\n" +
|
||||
"\vStopService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12?\n" +
|
||||
"\rReloadService\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12K\n" +
|
||||
@@ -2326,17 +1929,12 @@ const file_daemon_started_service_proto_rawDesc = "" +
|
||||
"\x0eSelectOutbound\x12\x1d.daemon.SelectOutboundRequest\x1a\x16.google.protobuf.Empty\"\x00\x12I\n" +
|
||||
"\x0eSetGroupExpand\x12\x1d.daemon.SetGroupExpandRequest\x1a\x16.google.protobuf.Empty\"\x00\x12K\n" +
|
||||
"\x14GetSystemProxyStatus\x12\x16.google.protobuf.Empty\x1a\x19.daemon.SystemProxyStatus\"\x00\x12W\n" +
|
||||
"\x15SetSystemProxyEnabled\x12$.daemon.SetSystemProxyEnabledRequest\x1a\x16.google.protobuf.Empty\"\x00\x12H\n" +
|
||||
"\x11TriggerDebugCrash\x12\x19.daemon.DebugCrashRequest\x1a\x16.google.protobuf.Empty\"\x00\x12D\n" +
|
||||
"\x10TriggerOOMReport\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12Y\n" +
|
||||
"\x15SetSystemProxyEnabled\x12$.daemon.SetSystemProxyEnabledRequest\x1a\x16.google.protobuf.Empty\"\x00\x12Y\n" +
|
||||
"\x14SubscribeConnections\x12#.daemon.SubscribeConnectionsRequest\x1a\x18.daemon.ConnectionEvents\"\x000\x01\x12K\n" +
|
||||
"\x0fCloseConnection\x12\x1e.daemon.CloseConnectionRequest\x1a\x16.google.protobuf.Empty\"\x00\x12G\n" +
|
||||
"\x13CloseAllConnections\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\"\x00\x12M\n" +
|
||||
"\x15GetDeprecatedWarnings\x12\x16.google.protobuf.Empty\x1a\x1a.daemon.DeprecatedWarnings\"\x00\x12;\n" +
|
||||
"\fGetStartedAt\x12\x16.google.protobuf.Empty\x1a\x11.daemon.StartedAt\"\x00\x12?\n" +
|
||||
"\rListOutbounds\x12\x16.google.protobuf.Empty\x1a\x14.daemon.OutboundList\"\x00\x12F\n" +
|
||||
"\x12SubscribeOutbounds\x12\x16.google.protobuf.Empty\x1a\x14.daemon.OutboundList\"\x000\x01\x12d\n" +
|
||||
"\x17StartNetworkQualityTest\x12!.daemon.NetworkQualityTestRequest\x1a\".daemon.NetworkQualityTestProgress\"\x000\x01B%Z#github.com/sagernet/sing-box/daemonb\x06proto3"
|
||||
"\fGetStartedAt\x12\x16.google.protobuf.Empty\x1a\x11.daemon.StartedAt\"\x00B%Z#github.com/sagernet/sing-box/daemonb\x06proto3"
|
||||
|
||||
var (
|
||||
file_daemon_started_service_proto_rawDescOnce sync.Once
|
||||
@@ -2351,118 +1949,101 @@ func file_daemon_started_service_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var (
|
||||
file_daemon_started_service_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
|
||||
file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 30)
|
||||
file_daemon_started_service_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
|
||||
file_daemon_started_service_proto_msgTypes = make([]protoimpl.MessageInfo, 26)
|
||||
file_daemon_started_service_proto_goTypes = []any{
|
||||
(LogLevel)(0), // 0: daemon.LogLevel
|
||||
(ConnectionEventType)(0), // 1: daemon.ConnectionEventType
|
||||
(ServiceStatus_Type)(0), // 2: daemon.ServiceStatus.Type
|
||||
(DebugCrashRequest_Type)(0), // 3: daemon.DebugCrashRequest.Type
|
||||
(*ServiceStatus)(nil), // 4: daemon.ServiceStatus
|
||||
(*ReloadServiceRequest)(nil), // 5: daemon.ReloadServiceRequest
|
||||
(*SubscribeStatusRequest)(nil), // 6: daemon.SubscribeStatusRequest
|
||||
(*Log)(nil), // 7: daemon.Log
|
||||
(*DefaultLogLevel)(nil), // 8: daemon.DefaultLogLevel
|
||||
(*Status)(nil), // 9: daemon.Status
|
||||
(*Groups)(nil), // 10: daemon.Groups
|
||||
(*Group)(nil), // 11: daemon.Group
|
||||
(*GroupItem)(nil), // 12: daemon.GroupItem
|
||||
(*URLTestRequest)(nil), // 13: daemon.URLTestRequest
|
||||
(*SelectOutboundRequest)(nil), // 14: daemon.SelectOutboundRequest
|
||||
(*SetGroupExpandRequest)(nil), // 15: daemon.SetGroupExpandRequest
|
||||
(*ClashMode)(nil), // 16: daemon.ClashMode
|
||||
(*ClashModeStatus)(nil), // 17: daemon.ClashModeStatus
|
||||
(*SystemProxyStatus)(nil), // 18: daemon.SystemProxyStatus
|
||||
(*SetSystemProxyEnabledRequest)(nil), // 19: daemon.SetSystemProxyEnabledRequest
|
||||
(*DebugCrashRequest)(nil), // 20: daemon.DebugCrashRequest
|
||||
(*SubscribeConnectionsRequest)(nil), // 21: daemon.SubscribeConnectionsRequest
|
||||
(*ConnectionEvent)(nil), // 22: daemon.ConnectionEvent
|
||||
(*ConnectionEvents)(nil), // 23: daemon.ConnectionEvents
|
||||
(*Connection)(nil), // 24: daemon.Connection
|
||||
(*ProcessInfo)(nil), // 25: daemon.ProcessInfo
|
||||
(*CloseConnectionRequest)(nil), // 26: daemon.CloseConnectionRequest
|
||||
(*DeprecatedWarnings)(nil), // 27: daemon.DeprecatedWarnings
|
||||
(*DeprecatedWarning)(nil), // 28: daemon.DeprecatedWarning
|
||||
(*StartedAt)(nil), // 29: daemon.StartedAt
|
||||
(*OutboundList)(nil), // 30: daemon.OutboundList
|
||||
(*NetworkQualityTestRequest)(nil), // 31: daemon.NetworkQualityTestRequest
|
||||
(*NetworkQualityTestProgress)(nil), // 32: daemon.NetworkQualityTestProgress
|
||||
(*Log_Message)(nil), // 33: daemon.Log.Message
|
||||
(*emptypb.Empty)(nil), // 34: google.protobuf.Empty
|
||||
(*ServiceStatus)(nil), // 3: daemon.ServiceStatus
|
||||
(*ReloadServiceRequest)(nil), // 4: daemon.ReloadServiceRequest
|
||||
(*SubscribeStatusRequest)(nil), // 5: daemon.SubscribeStatusRequest
|
||||
(*Log)(nil), // 6: daemon.Log
|
||||
(*DefaultLogLevel)(nil), // 7: daemon.DefaultLogLevel
|
||||
(*Status)(nil), // 8: daemon.Status
|
||||
(*Groups)(nil), // 9: daemon.Groups
|
||||
(*Group)(nil), // 10: daemon.Group
|
||||
(*GroupItem)(nil), // 11: daemon.GroupItem
|
||||
(*URLTestRequest)(nil), // 12: daemon.URLTestRequest
|
||||
(*SelectOutboundRequest)(nil), // 13: daemon.SelectOutboundRequest
|
||||
(*SetGroupExpandRequest)(nil), // 14: daemon.SetGroupExpandRequest
|
||||
(*ClashMode)(nil), // 15: daemon.ClashMode
|
||||
(*ClashModeStatus)(nil), // 16: daemon.ClashModeStatus
|
||||
(*SystemProxyStatus)(nil), // 17: daemon.SystemProxyStatus
|
||||
(*SetSystemProxyEnabledRequest)(nil), // 18: daemon.SetSystemProxyEnabledRequest
|
||||
(*SubscribeConnectionsRequest)(nil), // 19: daemon.SubscribeConnectionsRequest
|
||||
(*ConnectionEvent)(nil), // 20: daemon.ConnectionEvent
|
||||
(*ConnectionEvents)(nil), // 21: daemon.ConnectionEvents
|
||||
(*Connection)(nil), // 22: daemon.Connection
|
||||
(*ProcessInfo)(nil), // 23: daemon.ProcessInfo
|
||||
(*CloseConnectionRequest)(nil), // 24: daemon.CloseConnectionRequest
|
||||
(*DeprecatedWarnings)(nil), // 25: daemon.DeprecatedWarnings
|
||||
(*DeprecatedWarning)(nil), // 26: daemon.DeprecatedWarning
|
||||
(*StartedAt)(nil), // 27: daemon.StartedAt
|
||||
(*Log_Message)(nil), // 28: daemon.Log.Message
|
||||
(*emptypb.Empty)(nil), // 29: google.protobuf.Empty
|
||||
}
|
||||
)
|
||||
|
||||
var file_daemon_started_service_proto_depIdxs = []int32{
|
||||
2, // 0: daemon.ServiceStatus.status:type_name -> daemon.ServiceStatus.Type
|
||||
33, // 1: daemon.Log.messages:type_name -> daemon.Log.Message
|
||||
28, // 1: daemon.Log.messages:type_name -> daemon.Log.Message
|
||||
0, // 2: daemon.DefaultLogLevel.level:type_name -> daemon.LogLevel
|
||||
11, // 3: daemon.Groups.group:type_name -> daemon.Group
|
||||
12, // 4: daemon.Group.items:type_name -> daemon.GroupItem
|
||||
3, // 5: daemon.DebugCrashRequest.type:type_name -> daemon.DebugCrashRequest.Type
|
||||
1, // 6: daemon.ConnectionEvent.type:type_name -> daemon.ConnectionEventType
|
||||
24, // 7: daemon.ConnectionEvent.connection:type_name -> daemon.Connection
|
||||
22, // 8: daemon.ConnectionEvents.events:type_name -> daemon.ConnectionEvent
|
||||
25, // 9: daemon.Connection.processInfo:type_name -> daemon.ProcessInfo
|
||||
28, // 10: daemon.DeprecatedWarnings.warnings:type_name -> daemon.DeprecatedWarning
|
||||
12, // 11: daemon.OutboundList.outbounds:type_name -> daemon.GroupItem
|
||||
0, // 12: daemon.Log.Message.level:type_name -> daemon.LogLevel
|
||||
34, // 13: daemon.StartedService.StopService:input_type -> google.protobuf.Empty
|
||||
34, // 14: daemon.StartedService.ReloadService:input_type -> google.protobuf.Empty
|
||||
34, // 15: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty
|
||||
34, // 16: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty
|
||||
34, // 17: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty
|
||||
34, // 18: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty
|
||||
6, // 19: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest
|
||||
34, // 20: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty
|
||||
34, // 21: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty
|
||||
34, // 22: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty
|
||||
16, // 23: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode
|
||||
13, // 24: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest
|
||||
14, // 25: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest
|
||||
15, // 26: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest
|
||||
34, // 27: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty
|
||||
19, // 28: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest
|
||||
20, // 29: daemon.StartedService.TriggerDebugCrash:input_type -> daemon.DebugCrashRequest
|
||||
34, // 30: daemon.StartedService.TriggerOOMReport:input_type -> google.protobuf.Empty
|
||||
21, // 31: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest
|
||||
26, // 32: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest
|
||||
34, // 33: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty
|
||||
34, // 34: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty
|
||||
34, // 35: daemon.StartedService.GetStartedAt:input_type -> google.protobuf.Empty
|
||||
34, // 36: daemon.StartedService.ListOutbounds:input_type -> google.protobuf.Empty
|
||||
34, // 37: daemon.StartedService.SubscribeOutbounds:input_type -> google.protobuf.Empty
|
||||
31, // 38: daemon.StartedService.StartNetworkQualityTest:input_type -> daemon.NetworkQualityTestRequest
|
||||
34, // 39: daemon.StartedService.StopService:output_type -> google.protobuf.Empty
|
||||
34, // 40: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty
|
||||
4, // 41: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus
|
||||
7, // 42: daemon.StartedService.SubscribeLog:output_type -> daemon.Log
|
||||
8, // 43: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel
|
||||
34, // 44: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty
|
||||
9, // 45: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status
|
||||
10, // 46: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups
|
||||
17, // 47: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus
|
||||
16, // 48: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode
|
||||
34, // 49: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty
|
||||
34, // 50: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty
|
||||
34, // 51: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty
|
||||
34, // 52: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty
|
||||
18, // 53: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus
|
||||
34, // 54: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty
|
||||
34, // 55: daemon.StartedService.TriggerDebugCrash:output_type -> google.protobuf.Empty
|
||||
34, // 56: daemon.StartedService.TriggerOOMReport:output_type -> google.protobuf.Empty
|
||||
23, // 57: daemon.StartedService.SubscribeConnections:output_type -> daemon.ConnectionEvents
|
||||
34, // 58: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty
|
||||
34, // 59: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty
|
||||
27, // 60: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings
|
||||
29, // 61: daemon.StartedService.GetStartedAt:output_type -> daemon.StartedAt
|
||||
30, // 62: daemon.StartedService.ListOutbounds:output_type -> daemon.OutboundList
|
||||
30, // 63: daemon.StartedService.SubscribeOutbounds:output_type -> daemon.OutboundList
|
||||
32, // 64: daemon.StartedService.StartNetworkQualityTest:output_type -> daemon.NetworkQualityTestProgress
|
||||
39, // [39:65] is the sub-list for method output_type
|
||||
13, // [13:39] is the sub-list for method input_type
|
||||
13, // [13:13] is the sub-list for extension type_name
|
||||
13, // [13:13] is the sub-list for extension extendee
|
||||
0, // [0:13] is the sub-list for field type_name
|
||||
10, // 3: daemon.Groups.group:type_name -> daemon.Group
|
||||
11, // 4: daemon.Group.items:type_name -> daemon.GroupItem
|
||||
1, // 5: daemon.ConnectionEvent.type:type_name -> daemon.ConnectionEventType
|
||||
22, // 6: daemon.ConnectionEvent.connection:type_name -> daemon.Connection
|
||||
20, // 7: daemon.ConnectionEvents.events:type_name -> daemon.ConnectionEvent
|
||||
23, // 8: daemon.Connection.processInfo:type_name -> daemon.ProcessInfo
|
||||
26, // 9: daemon.DeprecatedWarnings.warnings:type_name -> daemon.DeprecatedWarning
|
||||
0, // 10: daemon.Log.Message.level:type_name -> daemon.LogLevel
|
||||
29, // 11: daemon.StartedService.StopService:input_type -> google.protobuf.Empty
|
||||
29, // 12: daemon.StartedService.ReloadService:input_type -> google.protobuf.Empty
|
||||
29, // 13: daemon.StartedService.SubscribeServiceStatus:input_type -> google.protobuf.Empty
|
||||
29, // 14: daemon.StartedService.SubscribeLog:input_type -> google.protobuf.Empty
|
||||
29, // 15: daemon.StartedService.GetDefaultLogLevel:input_type -> google.protobuf.Empty
|
||||
29, // 16: daemon.StartedService.ClearLogs:input_type -> google.protobuf.Empty
|
||||
5, // 17: daemon.StartedService.SubscribeStatus:input_type -> daemon.SubscribeStatusRequest
|
||||
29, // 18: daemon.StartedService.SubscribeGroups:input_type -> google.protobuf.Empty
|
||||
29, // 19: daemon.StartedService.GetClashModeStatus:input_type -> google.protobuf.Empty
|
||||
29, // 20: daemon.StartedService.SubscribeClashMode:input_type -> google.protobuf.Empty
|
||||
15, // 21: daemon.StartedService.SetClashMode:input_type -> daemon.ClashMode
|
||||
12, // 22: daemon.StartedService.URLTest:input_type -> daemon.URLTestRequest
|
||||
13, // 23: daemon.StartedService.SelectOutbound:input_type -> daemon.SelectOutboundRequest
|
||||
14, // 24: daemon.StartedService.SetGroupExpand:input_type -> daemon.SetGroupExpandRequest
|
||||
29, // 25: daemon.StartedService.GetSystemProxyStatus:input_type -> google.protobuf.Empty
|
||||
18, // 26: daemon.StartedService.SetSystemProxyEnabled:input_type -> daemon.SetSystemProxyEnabledRequest
|
||||
19, // 27: daemon.StartedService.SubscribeConnections:input_type -> daemon.SubscribeConnectionsRequest
|
||||
24, // 28: daemon.StartedService.CloseConnection:input_type -> daemon.CloseConnectionRequest
|
||||
29, // 29: daemon.StartedService.CloseAllConnections:input_type -> google.protobuf.Empty
|
||||
29, // 30: daemon.StartedService.GetDeprecatedWarnings:input_type -> google.protobuf.Empty
|
||||
29, // 31: daemon.StartedService.GetStartedAt:input_type -> google.protobuf.Empty
|
||||
29, // 32: daemon.StartedService.StopService:output_type -> google.protobuf.Empty
|
||||
29, // 33: daemon.StartedService.ReloadService:output_type -> google.protobuf.Empty
|
||||
3, // 34: daemon.StartedService.SubscribeServiceStatus:output_type -> daemon.ServiceStatus
|
||||
6, // 35: daemon.StartedService.SubscribeLog:output_type -> daemon.Log
|
||||
7, // 36: daemon.StartedService.GetDefaultLogLevel:output_type -> daemon.DefaultLogLevel
|
||||
29, // 37: daemon.StartedService.ClearLogs:output_type -> google.protobuf.Empty
|
||||
8, // 38: daemon.StartedService.SubscribeStatus:output_type -> daemon.Status
|
||||
9, // 39: daemon.StartedService.SubscribeGroups:output_type -> daemon.Groups
|
||||
16, // 40: daemon.StartedService.GetClashModeStatus:output_type -> daemon.ClashModeStatus
|
||||
15, // 41: daemon.StartedService.SubscribeClashMode:output_type -> daemon.ClashMode
|
||||
29, // 42: daemon.StartedService.SetClashMode:output_type -> google.protobuf.Empty
|
||||
29, // 43: daemon.StartedService.URLTest:output_type -> google.protobuf.Empty
|
||||
29, // 44: daemon.StartedService.SelectOutbound:output_type -> google.protobuf.Empty
|
||||
29, // 45: daemon.StartedService.SetGroupExpand:output_type -> google.protobuf.Empty
|
||||
17, // 46: daemon.StartedService.GetSystemProxyStatus:output_type -> daemon.SystemProxyStatus
|
||||
29, // 47: daemon.StartedService.SetSystemProxyEnabled:output_type -> google.protobuf.Empty
|
||||
21, // 48: daemon.StartedService.SubscribeConnections:output_type -> daemon.ConnectionEvents
|
||||
29, // 49: daemon.StartedService.CloseConnection:output_type -> google.protobuf.Empty
|
||||
29, // 50: daemon.StartedService.CloseAllConnections:output_type -> google.protobuf.Empty
|
||||
25, // 51: daemon.StartedService.GetDeprecatedWarnings:output_type -> daemon.DeprecatedWarnings
|
||||
27, // 52: daemon.StartedService.GetStartedAt:output_type -> daemon.StartedAt
|
||||
32, // [32:53] is the sub-list for method output_type
|
||||
11, // [11:32] is the sub-list for method input_type
|
||||
11, // [11:11] is the sub-list for extension type_name
|
||||
11, // [11:11] is the sub-list for extension extendee
|
||||
0, // [0:11] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_daemon_started_service_proto_init() }
|
||||
@@ -2475,8 +2056,8 @@ func file_daemon_started_service_proto_init() {
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_started_service_proto_rawDesc), len(file_daemon_started_service_proto_rawDesc)),
|
||||
NumEnums: 4,
|
||||
NumMessages: 30,
|
||||
NumEnums: 3,
|
||||
NumMessages: 26,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@@ -26,18 +26,12 @@ service StartedService {
|
||||
|
||||
rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {}
|
||||
rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {}
|
||||
rpc TriggerDebugCrash(DebugCrashRequest) returns(google.protobuf.Empty) {}
|
||||
rpc TriggerOOMReport(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
||||
|
||||
rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream ConnectionEvents) {}
|
||||
rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {}
|
||||
rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
||||
rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {}
|
||||
rpc GetStartedAt(google.protobuf.Empty) returns(StartedAt) {}
|
||||
|
||||
rpc ListOutbounds(google.protobuf.Empty) returns (OutboundList) {}
|
||||
rpc SubscribeOutbounds(google.protobuf.Empty) returns (stream OutboundList) {}
|
||||
rpc StartNetworkQualityTest(NetworkQualityTestRequest) returns (stream NetworkQualityTestProgress) {}
|
||||
}
|
||||
|
||||
message ServiceStatus {
|
||||
@@ -147,15 +141,6 @@ message SetSystemProxyEnabledRequest {
|
||||
bool enabled = 1;
|
||||
}
|
||||
|
||||
message DebugCrashRequest {
|
||||
enum Type {
|
||||
GO = 0;
|
||||
NATIVE = 1;
|
||||
}
|
||||
|
||||
Type type = 1;
|
||||
}
|
||||
|
||||
message SubscribeConnectionsRequest {
|
||||
int64 interval = 1;
|
||||
}
|
||||
@@ -225,38 +210,8 @@ message DeprecatedWarning {
|
||||
string message = 1;
|
||||
bool impending = 2;
|
||||
string migrationLink = 3;
|
||||
string description = 4;
|
||||
string deprecatedVersion = 5;
|
||||
string scheduledVersion = 6;
|
||||
}
|
||||
|
||||
message StartedAt {
|
||||
int64 startedAt = 1;
|
||||
}
|
||||
|
||||
message OutboundList {
|
||||
repeated GroupItem outbounds = 1;
|
||||
}
|
||||
|
||||
message NetworkQualityTestRequest {
|
||||
string configURL = 1;
|
||||
string outboundTag = 2;
|
||||
bool serial = 3;
|
||||
int32 maxRuntimeSeconds = 4;
|
||||
}
|
||||
|
||||
message NetworkQualityTestProgress {
|
||||
int32 phase = 1;
|
||||
int64 downloadCapacity = 2;
|
||||
int64 uploadCapacity = 3;
|
||||
int32 downloadRPM = 4;
|
||||
int32 uploadRPM = 5;
|
||||
int32 idleLatencyMs = 6;
|
||||
int64 elapsedMs = 7;
|
||||
bool isFinal = 8;
|
||||
string error = 9;
|
||||
int32 downloadCapacityAccuracy = 10;
|
||||
int32 uploadCapacityAccuracy = 11;
|
||||
int32 downloadRPMAccuracy = 12;
|
||||
int32 uploadRPMAccuracy = 13;
|
||||
}
|
||||
}
|
||||
@@ -15,32 +15,27 @@ import (
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
StartedService_StopService_FullMethodName = "/daemon.StartedService/StopService"
|
||||
StartedService_ReloadService_FullMethodName = "/daemon.StartedService/ReloadService"
|
||||
StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus"
|
||||
StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog"
|
||||
StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel"
|
||||
StartedService_ClearLogs_FullMethodName = "/daemon.StartedService/ClearLogs"
|
||||
StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus"
|
||||
StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups"
|
||||
StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus"
|
||||
StartedService_SubscribeClashMode_FullMethodName = "/daemon.StartedService/SubscribeClashMode"
|
||||
StartedService_SetClashMode_FullMethodName = "/daemon.StartedService/SetClashMode"
|
||||
StartedService_URLTest_FullMethodName = "/daemon.StartedService/URLTest"
|
||||
StartedService_SelectOutbound_FullMethodName = "/daemon.StartedService/SelectOutbound"
|
||||
StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand"
|
||||
StartedService_GetSystemProxyStatus_FullMethodName = "/daemon.StartedService/GetSystemProxyStatus"
|
||||
StartedService_SetSystemProxyEnabled_FullMethodName = "/daemon.StartedService/SetSystemProxyEnabled"
|
||||
StartedService_TriggerDebugCrash_FullMethodName = "/daemon.StartedService/TriggerDebugCrash"
|
||||
StartedService_TriggerOOMReport_FullMethodName = "/daemon.StartedService/TriggerOOMReport"
|
||||
StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections"
|
||||
StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection"
|
||||
StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections"
|
||||
StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings"
|
||||
StartedService_GetStartedAt_FullMethodName = "/daemon.StartedService/GetStartedAt"
|
||||
StartedService_ListOutbounds_FullMethodName = "/daemon.StartedService/ListOutbounds"
|
||||
StartedService_SubscribeOutbounds_FullMethodName = "/daemon.StartedService/SubscribeOutbounds"
|
||||
StartedService_StartNetworkQualityTest_FullMethodName = "/daemon.StartedService/StartNetworkQualityTest"
|
||||
StartedService_StopService_FullMethodName = "/daemon.StartedService/StopService"
|
||||
StartedService_ReloadService_FullMethodName = "/daemon.StartedService/ReloadService"
|
||||
StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus"
|
||||
StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog"
|
||||
StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel"
|
||||
StartedService_ClearLogs_FullMethodName = "/daemon.StartedService/ClearLogs"
|
||||
StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus"
|
||||
StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups"
|
||||
StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus"
|
||||
StartedService_SubscribeClashMode_FullMethodName = "/daemon.StartedService/SubscribeClashMode"
|
||||
StartedService_SetClashMode_FullMethodName = "/daemon.StartedService/SetClashMode"
|
||||
StartedService_URLTest_FullMethodName = "/daemon.StartedService/URLTest"
|
||||
StartedService_SelectOutbound_FullMethodName = "/daemon.StartedService/SelectOutbound"
|
||||
StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand"
|
||||
StartedService_GetSystemProxyStatus_FullMethodName = "/daemon.StartedService/GetSystemProxyStatus"
|
||||
StartedService_SetSystemProxyEnabled_FullMethodName = "/daemon.StartedService/SetSystemProxyEnabled"
|
||||
StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections"
|
||||
StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection"
|
||||
StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections"
|
||||
StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings"
|
||||
StartedService_GetStartedAt_FullMethodName = "/daemon.StartedService/GetStartedAt"
|
||||
)
|
||||
|
||||
// StartedServiceClient is the client API for StartedService service.
|
||||
@@ -63,16 +58,11 @@ type StartedServiceClient interface {
|
||||
SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error)
|
||||
SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
TriggerDebugCrash(ctx context.Context, in *DebugCrashRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
TriggerOOMReport(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error)
|
||||
CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error)
|
||||
GetStartedAt(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StartedAt, error)
|
||||
ListOutbounds(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*OutboundList, error)
|
||||
SubscribeOutbounds(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OutboundList], error)
|
||||
StartNetworkQualityTest(ctx context.Context, in *NetworkQualityTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NetworkQualityTestProgress], error)
|
||||
}
|
||||
|
||||
type startedServiceClient struct {
|
||||
@@ -288,26 +278,6 @@ func (c *startedServiceClient) SetSystemProxyEnabled(ctx context.Context, in *Se
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) TriggerDebugCrash(ctx context.Context, in *DebugCrashRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, StartedService_TriggerDebugCrash_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) TriggerOOMReport(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, StartedService_TriggerOOMReport_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...)
|
||||
@@ -367,54 +337,6 @@ func (c *startedServiceClient) GetStartedAt(ctx context.Context, in *emptypb.Emp
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) ListOutbounds(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*OutboundList, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(OutboundList)
|
||||
err := c.cc.Invoke(ctx, StartedService_ListOutbounds_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *startedServiceClient) SubscribeOutbounds(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OutboundList], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[6], StartedService_SubscribeOutbounds_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[emptypb.Empty, OutboundList]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeOutboundsClient = grpc.ServerStreamingClient[OutboundList]
|
||||
|
||||
func (c *startedServiceClient) StartNetworkQualityTest(ctx context.Context, in *NetworkQualityTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NetworkQualityTestProgress], error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[7], StartedService_StartNetworkQualityTest_FullMethodName, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &grpc.GenericClientStream[NetworkQualityTestRequest, NetworkQualityTestProgress]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_StartNetworkQualityTestClient = grpc.ServerStreamingClient[NetworkQualityTestProgress]
|
||||
|
||||
// StartedServiceServer is the server API for StartedService service.
|
||||
// All implementations must embed UnimplementedStartedServiceServer
|
||||
// for forward compatibility.
|
||||
@@ -435,16 +357,11 @@ type StartedServiceServer interface {
|
||||
SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error)
|
||||
GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error)
|
||||
SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error)
|
||||
TriggerDebugCrash(context.Context, *DebugCrashRequest) (*emptypb.Empty, error)
|
||||
TriggerOOMReport(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||
SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error
|
||||
CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error)
|
||||
CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||
GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error)
|
||||
GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error)
|
||||
ListOutbounds(context.Context, *emptypb.Empty) (*OutboundList, error)
|
||||
SubscribeOutbounds(*emptypb.Empty, grpc.ServerStreamingServer[OutboundList]) error
|
||||
StartNetworkQualityTest(*NetworkQualityTestRequest, grpc.ServerStreamingServer[NetworkQualityTestProgress]) error
|
||||
mustEmbedUnimplementedStartedServiceServer()
|
||||
}
|
||||
|
||||
@@ -519,14 +436,6 @@ func (UnimplementedStartedServiceServer) SetSystemProxyEnabled(context.Context,
|
||||
return nil, status.Error(codes.Unimplemented, "method SetSystemProxyEnabled not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) TriggerDebugCrash(context.Context, *DebugCrashRequest) (*emptypb.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method TriggerDebugCrash not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) TriggerOOMReport(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method TriggerOOMReport not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error {
|
||||
return status.Error(codes.Unimplemented, "method SubscribeConnections not implemented")
|
||||
}
|
||||
@@ -546,18 +455,6 @@ func (UnimplementedStartedServiceServer) GetDeprecatedWarnings(context.Context,
|
||||
func (UnimplementedStartedServiceServer) GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method GetStartedAt not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) ListOutbounds(context.Context, *emptypb.Empty) (*OutboundList, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method ListOutbounds not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) SubscribeOutbounds(*emptypb.Empty, grpc.ServerStreamingServer[OutboundList]) error {
|
||||
return status.Error(codes.Unimplemented, "method SubscribeOutbounds not implemented")
|
||||
}
|
||||
|
||||
func (UnimplementedStartedServiceServer) StartNetworkQualityTest(*NetworkQualityTestRequest, grpc.ServerStreamingServer[NetworkQualityTestProgress]) error {
|
||||
return status.Error(codes.Unimplemented, "method StartNetworkQualityTest not implemented")
|
||||
}
|
||||
func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {}
|
||||
func (UnimplementedStartedServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
@@ -832,42 +729,6 @@ func _StartedService_SetSystemProxyEnabled_Handler(srv interface{}, ctx context.
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_TriggerDebugCrash_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DebugCrashRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).TriggerDebugCrash(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_TriggerDebugCrash_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).TriggerDebugCrash(ctx, req.(*DebugCrashRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_TriggerOOMReport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).TriggerOOMReport(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_TriggerOOMReport_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).TriggerOOMReport(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(SubscribeConnectionsRequest)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
@@ -951,46 +812,6 @@ func _StartedService_GetStartedAt_Handler(srv interface{}, ctx context.Context,
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_ListOutbounds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(emptypb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StartedServiceServer).ListOutbounds(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StartedService_ListOutbounds_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StartedServiceServer).ListOutbounds(ctx, req.(*emptypb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _StartedService_SubscribeOutbounds_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(emptypb.Empty)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(StartedServiceServer).SubscribeOutbounds(m, &grpc.GenericServerStream[emptypb.Empty, OutboundList]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_SubscribeOutboundsServer = grpc.ServerStreamingServer[OutboundList]
|
||||
|
||||
func _StartedService_StartNetworkQualityTest_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(NetworkQualityTestRequest)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(StartedServiceServer).StartNetworkQualityTest(m, &grpc.GenericServerStream[NetworkQualityTestRequest, NetworkQualityTestProgress]{ServerStream: stream})
|
||||
}
|
||||
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type StartedService_StartNetworkQualityTestServer = grpc.ServerStreamingServer[NetworkQualityTestProgress]
|
||||
|
||||
// StartedService_ServiceDesc is the grpc.ServiceDesc for StartedService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
@@ -1042,14 +863,6 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "SetSystemProxyEnabled",
|
||||
Handler: _StartedService_SetSystemProxyEnabled_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "TriggerDebugCrash",
|
||||
Handler: _StartedService_TriggerDebugCrash_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "TriggerOOMReport",
|
||||
Handler: _StartedService_TriggerOOMReport_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CloseConnection",
|
||||
Handler: _StartedService_CloseConnection_Handler,
|
||||
@@ -1066,10 +879,6 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "GetStartedAt",
|
||||
Handler: _StartedService_GetStartedAt_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListOutbounds",
|
||||
Handler: _StartedService_ListOutbounds_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
@@ -1102,16 +911,6 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{
|
||||
Handler: _StartedService_SubscribeConnections_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "SubscribeOutbounds",
|
||||
Handler: _StartedService_SubscribeOutbounds_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
{
|
||||
StreamName: "StartNetworkQualityTest",
|
||||
Handler: _StartedService_StartNetworkQualityTest_Handler,
|
||||
ServerStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "daemon/started_service.proto",
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -13,6 +14,7 @@ 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"
|
||||
@@ -107,7 +109,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(response *dns.Msg) bool) (*dns.Msg, error) {
|
||||
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) {
|
||||
if len(message.Question) == 0 {
|
||||
if c.logger != nil {
|
||||
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
||||
@@ -237,10 +239,13 @@ 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(response)
|
||||
rejected = !responseChecker(MessageToAddresses(response))
|
||||
}
|
||||
if rejected {
|
||||
if !disableCache && c.rdrc != nil {
|
||||
@@ -310,7 +315,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(response *dns.Msg) bool) ([]netip.Addr, error) {
|
||||
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||
domain = FqdnToDomain(domain)
|
||||
dnsName := dns.Fqdn(domain)
|
||||
var strategy C.DomainStrategy
|
||||
@@ -395,7 +400,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(response *dns.Msg) bool) ([]netip.Addr, error) {
|
||||
func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name string, qType uint16, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||
question := dns.Question{
|
||||
Name: name,
|
||||
Qtype: qType,
|
||||
@@ -510,7 +515,25 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
|
||||
}
|
||||
|
||||
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
||||
return adapter.DNSResponseAddresses(response)
|
||||
if response == nil || response.Rcode != dns.RcodeSuccess {
|
||||
return nil
|
||||
}
|
||||
addresses := make([]netip.Addr, 0, len(response.Answer))
|
||||
for _, rawAnswer := range response.Answer {
|
||||
switch answer := rawAnswer.(type) {
|
||||
case *dns.A:
|
||||
addresses = append(addresses, M.AddrFromIP(answer.A))
|
||||
case *dns.AAAA:
|
||||
addresses = append(addresses, M.AddrFromIP(answer.AAAA))
|
||||
case *dns.HTTPS:
|
||||
for _, value := range answer.SVCB.Value {
|
||||
if value.Key() == dns.SVCB_IPV4HINT || value.Key() == dns.SVCB_IPV6HINT {
|
||||
addresses = append(addresses, common.Map(strings.Split(value.String(), ","), M.ParseAddr)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
|
||||
func wrapError(err error) error {
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json/badoption"
|
||||
|
||||
mDNS "github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestReproLookupWithRulesUsesRequestStrategy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
|
||||
var qTypes []uint16
|
||||
router := newTestRouter(t, nil, &fakeDNSTransportManager{
|
||||
defaultTransport: defaultTransport,
|
||||
transports: map[string]adapter.DNSTransport{
|
||||
"default": defaultTransport,
|
||||
},
|
||||
}, &fakeDNSClient{
|
||||
exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
qTypes = append(qTypes, message.Question[0].Qtype)
|
||||
if message.Question[0].Qtype == mDNS.TypeA {
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2.2.2.2")}, 60), nil
|
||||
}
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2001:db8::1")}, 60), nil
|
||||
},
|
||||
})
|
||||
|
||||
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{
|
||||
Strategy: C.DomainStrategyIPv4Only,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []uint16{mDNS.TypeA}, qTypes)
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("2.2.2.2")}, addresses)
|
||||
}
|
||||
|
||||
func TestReproLogicalMatchResponseIPCIDR(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
transportManager := &fakeDNSTransportManager{
|
||||
defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP},
|
||||
transports: map[string]adapter.DNSTransport{
|
||||
"upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP},
|
||||
"selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP},
|
||||
"default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP},
|
||||
},
|
||||
}
|
||||
client := &fakeDNSClient{
|
||||
exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
switch transport.Tag() {
|
||||
case "upstream":
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil
|
||||
case "selected":
|
||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("8.8.8.8")}, 60), nil
|
||||
default:
|
||||
return nil, E.New("unexpected transport")
|
||||
}
|
||||
},
|
||||
}
|
||||
rules := []option.DNSRule{
|
||||
{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultDNSRule{
|
||||
RawDefaultDNSRule: option.RawDefaultDNSRule{
|
||||
Domain: badoption.Listable[string]{"example.com"},
|
||||
},
|
||||
DNSRuleAction: option.DNSRuleAction{
|
||||
Action: C.RuleActionTypeEvaluate,
|
||||
RouteOptions: option.DNSRouteActionOptions{Server: "upstream"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: C.RuleTypeLogical,
|
||||
LogicalOptions: option.LogicalDNSRule{
|
||||
RawLogicalDNSRule: option.RawLogicalDNSRule{
|
||||
Mode: C.LogicalTypeOr,
|
||||
Rules: []option.DNSRule{{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultDNSRule{
|
||||
RawDefaultDNSRule: option.RawDefaultDNSRule{
|
||||
MatchResponse: true,
|
||||
IPCIDR: badoption.Listable[string]{"1.1.1.0/24"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
DNSRuleAction: option.DNSRuleAction{
|
||||
Action: C.RuleActionTypeRoute,
|
||||
RouteOptions: option.DNSRouteActionOptions{Server: "selected"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
router := newTestRouter(t, rules, transportManager, client)
|
||||
|
||||
response, err := router.Exchange(context.Background(), &mDNS.Msg{
|
||||
Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)},
|
||||
}, adapter.DNSQueryOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("8.8.8.8")}, MessageToAddresses(response))
|
||||
}
|
||||
792
dns/router.go
792
dns/router.go
@@ -5,13 +5,11 @@ import (
|
||||
"errors"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||
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"
|
||||
R "github.com/sagernet/sing-box/route/rule"
|
||||
@@ -21,7 +19,6 @@ import (
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"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"
|
||||
"github.com/sagernet/sing/service"
|
||||
@@ -29,10 +26,7 @@ import (
|
||||
mDNS "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
var (
|
||||
_ adapter.DNSRouter = (*Router)(nil)
|
||||
_ adapter.DNSRuleSetUpdateValidator = (*Router)(nil)
|
||||
)
|
||||
var _ adapter.DNSRouter = (*Router)(nil)
|
||||
|
||||
type Router struct {
|
||||
ctx context.Context
|
||||
@@ -40,15 +34,10 @@ type Router struct {
|
||||
transport adapter.DNSTransportManager
|
||||
outbound adapter.OutboundManager
|
||||
client adapter.DNSClient
|
||||
rawRules []option.DNSRule
|
||||
rules []adapter.DNSRule
|
||||
defaultDomainStrategy C.DomainStrategy
|
||||
dnsReverseMapping freelru.Cache[netip.Addr, string]
|
||||
platformInterface adapter.PlatformInterface
|
||||
legacyDNSMode bool
|
||||
rulesAccess sync.RWMutex
|
||||
started bool
|
||||
closing bool
|
||||
}
|
||||
|
||||
func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOptions) *Router {
|
||||
@@ -57,7 +46,6 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
|
||||
logger: logFactory.NewLogger("dns"),
|
||||
transport: service.FromContext[adapter.DNSTransportManager](ctx),
|
||||
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||
rawRules: make([]option.DNSRule, 0, len(options.Rules)),
|
||||
rules: make([]adapter.DNSRule, 0, len(options.Rules)),
|
||||
defaultDomainStrategy: C.DomainStrategy(options.Strategy),
|
||||
}
|
||||
@@ -86,12 +74,13 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
|
||||
}
|
||||
|
||||
func (r *Router) Initialize(rules []option.DNSRule) error {
|
||||
r.rawRules = append(r.rawRules[:0], rules...)
|
||||
newRules, _, _, err := r.buildRules(false)
|
||||
if err != nil {
|
||||
return err
|
||||
for i, ruleOptions := range rules {
|
||||
dnsRule, err := R.NewDNSRule(r.ctx, r.logger, ruleOptions, true)
|
||||
if err != nil {
|
||||
return E.Cause(err, "parse dns rule[", i, "]")
|
||||
}
|
||||
r.rules = append(r.rules, dnsRule)
|
||||
}
|
||||
closeRules(newRules)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -103,146 +92,32 @@ func (r *Router) Start(stage adapter.StartStage) error {
|
||||
r.client.Start()
|
||||
monitor.Finish()
|
||||
|
||||
monitor.Start("initialize DNS rules")
|
||||
newRules, legacyDNSMode, modeFlags, err := r.buildRules(true)
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.rulesAccess.Lock()
|
||||
if r.closing {
|
||||
r.rulesAccess.Unlock()
|
||||
closeRules(newRules)
|
||||
return nil
|
||||
}
|
||||
r.rules = newRules
|
||||
r.legacyDNSMode = legacyDNSMode
|
||||
r.started = true
|
||||
r.rulesAccess.Unlock()
|
||||
if legacyDNSMode && common.Any(newRules, func(rule adapter.DNSRule) bool { return rule.WithAddressLimit() }) {
|
||||
deprecated.Report(r.ctx, deprecated.OptionLegacyDNSAddressFilter)
|
||||
}
|
||||
if legacyDNSMode && modeFlags.neededFromStrategy {
|
||||
deprecated.Report(r.ctx, deprecated.OptionLegacyDNSRuleStrategy)
|
||||
for i, rule := range r.rules {
|
||||
monitor.Start("initialize DNS rule[", i, "]")
|
||||
err := rule.Start()
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
return E.Cause(err, "initialize DNS rule[", i, "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Router) Close() error {
|
||||
r.rulesAccess.Lock()
|
||||
if r.closing {
|
||||
r.rulesAccess.Unlock()
|
||||
return nil
|
||||
monitor := taskmonitor.New(r.logger, C.StopTimeout)
|
||||
var err error
|
||||
for i, rule := range r.rules {
|
||||
monitor.Start("close dns rule[", i, "]")
|
||||
err = E.Append(err, rule.Close(), func(err error) error {
|
||||
return E.Cause(err, "close dns rule[", i, "]")
|
||||
})
|
||||
monitor.Finish()
|
||||
}
|
||||
r.closing = true
|
||||
runtimeRules := r.rules
|
||||
r.rules = nil
|
||||
r.rulesAccess.Unlock()
|
||||
closeRules(runtimeRules)
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Router) buildRules(startRules bool) ([]adapter.DNSRule, bool, dnsRuleModeFlags, error) {
|
||||
for i, ruleOptions := range r.rawRules {
|
||||
err := R.ValidateNoNestedDNSRuleActions(ruleOptions)
|
||||
if err != nil {
|
||||
return nil, false, dnsRuleModeFlags{}, E.Cause(err, "parse dns rule[", i, "]")
|
||||
}
|
||||
}
|
||||
router := service.FromContext[adapter.Router](r.ctx)
|
||||
legacyDNSMode, modeFlags, err := resolveLegacyDNSMode(router, r.rawRules, nil)
|
||||
if err != nil {
|
||||
return nil, false, dnsRuleModeFlags{}, err
|
||||
}
|
||||
if !legacyDNSMode {
|
||||
err = validateLegacyDNSModeDisabledRules(r.rawRules)
|
||||
if err != nil {
|
||||
return nil, false, dnsRuleModeFlags{}, err
|
||||
}
|
||||
}
|
||||
err = validateEvaluateFakeIPRules(r.rawRules, r.transport)
|
||||
if err != nil {
|
||||
return nil, false, dnsRuleModeFlags{}, err
|
||||
}
|
||||
newRules := make([]adapter.DNSRule, 0, len(r.rawRules))
|
||||
for i, ruleOptions := range r.rawRules {
|
||||
var dnsRule adapter.DNSRule
|
||||
dnsRule, err = R.NewDNSRule(r.ctx, r.logger, ruleOptions, true, legacyDNSMode)
|
||||
if err != nil {
|
||||
closeRules(newRules)
|
||||
return nil, false, dnsRuleModeFlags{}, E.Cause(err, "parse dns rule[", i, "]")
|
||||
}
|
||||
newRules = append(newRules, dnsRule)
|
||||
}
|
||||
if startRules {
|
||||
for i, rule := range newRules {
|
||||
err = rule.Start()
|
||||
if err != nil {
|
||||
closeRules(newRules)
|
||||
return nil, false, dnsRuleModeFlags{}, E.Cause(err, "initialize DNS rule[", i, "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
return newRules, legacyDNSMode, modeFlags, nil
|
||||
}
|
||||
|
||||
func closeRules(rules []adapter.DNSRule) {
|
||||
for _, rule := range rules {
|
||||
_ = rule.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) ValidateRuleSetMetadataUpdate(tag string, metadata adapter.RuleSetMetadata) error {
|
||||
if len(r.rawRules) == 0 {
|
||||
return nil
|
||||
}
|
||||
router := service.FromContext[adapter.Router](r.ctx)
|
||||
if router == nil {
|
||||
return E.New("router service not found")
|
||||
}
|
||||
overrides := map[string]adapter.RuleSetMetadata{
|
||||
tag: metadata,
|
||||
}
|
||||
r.rulesAccess.RLock()
|
||||
started := r.started
|
||||
legacyDNSMode := r.legacyDNSMode
|
||||
closing := r.closing
|
||||
r.rulesAccess.RUnlock()
|
||||
if closing {
|
||||
return nil
|
||||
}
|
||||
if !started {
|
||||
candidateLegacyDNSMode, _, err := resolveLegacyDNSMode(router, r.rawRules, overrides)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !candidateLegacyDNSMode {
|
||||
return validateLegacyDNSModeDisabledRules(r.rawRules)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
candidateLegacyDNSMode, flags, err := resolveLegacyDNSMode(router, r.rawRules, overrides)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if legacyDNSMode {
|
||||
if !candidateLegacyDNSMode && flags.disabled {
|
||||
err := validateLegacyDNSModeDisabledRules(r.rawRules)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return E.New(deprecated.OptionLegacyDNSAddressFilter.MessageWithLink())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if candidateLegacyDNSMode {
|
||||
return E.New(deprecated.OptionLegacyDNSAddressFilter.MessageWithLink())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Router) matchDNS(ctx context.Context, rules []adapter.DNSRule, allowFakeIP bool, ruleIndex int, isAddressQuery bool, options *adapter.DNSQueryOptions) (adapter.DNSTransport, adapter.DNSRule, int) {
|
||||
func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int, isAddressQuery bool, options *adapter.DNSQueryOptions) (adapter.DNSTransport, adapter.DNSRule, int) {
|
||||
metadata := adapter.ContextFrom(ctx)
|
||||
if metadata == nil {
|
||||
panic("no context")
|
||||
@@ -251,18 +126,22 @@ func (r *Router) matchDNS(ctx context.Context, rules []adapter.DNSRule, allowFak
|
||||
if ruleIndex != -1 {
|
||||
currentRuleIndex = ruleIndex + 1
|
||||
}
|
||||
for ; currentRuleIndex < len(rules); currentRuleIndex++ {
|
||||
currentRule := rules[currentRuleIndex]
|
||||
for ; currentRuleIndex < len(r.rules); currentRuleIndex++ {
|
||||
currentRule := r.rules[currentRuleIndex]
|
||||
if currentRule.WithAddressLimit() && !isAddressQuery {
|
||||
continue
|
||||
}
|
||||
metadata.ResetRuleCache()
|
||||
metadata.DestinationAddressMatchFromResponse = false
|
||||
if currentRule.LegacyPreMatch(metadata) {
|
||||
if ruleDescription := currentRule.String(); ruleDescription != "" {
|
||||
r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action())
|
||||
if currentRule.Match(metadata) {
|
||||
displayRuleIndex := currentRuleIndex
|
||||
if displayRuleIndex != -1 {
|
||||
displayRuleIndex += displayRuleIndex + 1
|
||||
}
|
||||
ruleDescription := currentRule.String()
|
||||
if ruleDescription != "" {
|
||||
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] ", currentRule, " => ", currentRule.Action())
|
||||
} else {
|
||||
r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action())
|
||||
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
||||
}
|
||||
switch action := currentRule.Action().(type) {
|
||||
case *R.RuleActionDNSRoute:
|
||||
@@ -287,6 +166,14 @@ func (r *Router) matchDNS(ctx context.Context, rules []adapter.DNSRule, allowFak
|
||||
if action.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = action.ClientSubnet
|
||||
}
|
||||
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||
if options.Strategy == C.DomainStrategyAsIS {
|
||||
options.Strategy = legacyTransport.LegacyStrategy()
|
||||
}
|
||||
if !options.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||
}
|
||||
}
|
||||
return transport, currentRule, currentRuleIndex
|
||||
case *R.RuleActionDNSRouteOptions:
|
||||
if action.Strategy != C.DomainStrategyAsIS {
|
||||
@@ -309,272 +196,17 @@ func (r *Router) matchDNS(ctx context.Context, rules []adapter.DNSRule, allowFak
|
||||
}
|
||||
}
|
||||
transport := r.transport.Default()
|
||||
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||
if options.Strategy == C.DomainStrategyAsIS {
|
||||
options.Strategy = legacyTransport.LegacyStrategy()
|
||||
}
|
||||
if !options.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||
}
|
||||
}
|
||||
return transport, nil, -1
|
||||
}
|
||||
|
||||
func (r *Router) applyDNSRouteOptions(options *adapter.DNSQueryOptions, routeOptions R.RuleActionDNSRouteOptions) {
|
||||
// Strategy is intentionally skipped here. A non-default DNS rule action strategy
|
||||
// forces legacy mode via resolveLegacyDNSMode, so this path is only reachable
|
||||
// when strategy remains at its default value.
|
||||
if routeOptions.DisableCache {
|
||||
options.DisableCache = true
|
||||
}
|
||||
if routeOptions.RewriteTTL != nil {
|
||||
options.RewriteTTL = routeOptions.RewriteTTL
|
||||
}
|
||||
if routeOptions.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = routeOptions.ClientSubnet
|
||||
}
|
||||
}
|
||||
|
||||
type dnsRouteStatus uint8
|
||||
|
||||
const (
|
||||
dnsRouteStatusMissing dnsRouteStatus = iota
|
||||
dnsRouteStatusSkipped
|
||||
dnsRouteStatusResolved
|
||||
)
|
||||
|
||||
func (r *Router) resolveDNSRoute(server string, routeOptions R.RuleActionDNSRouteOptions, allowFakeIP bool, options *adapter.DNSQueryOptions) (adapter.DNSTransport, dnsRouteStatus) {
|
||||
transport, loaded := r.transport.Transport(server)
|
||||
if !loaded {
|
||||
return nil, dnsRouteStatusMissing
|
||||
}
|
||||
isFakeIP := transport.Type() == C.DNSTypeFakeIP
|
||||
if isFakeIP && !allowFakeIP {
|
||||
return transport, dnsRouteStatusSkipped
|
||||
}
|
||||
r.applyDNSRouteOptions(options, routeOptions)
|
||||
if isFakeIP {
|
||||
options.DisableCache = true
|
||||
}
|
||||
return transport, dnsRouteStatusResolved
|
||||
}
|
||||
|
||||
func (r *Router) logRuleMatch(ctx context.Context, ruleIndex int, currentRule adapter.DNSRule) {
|
||||
if ruleDescription := currentRule.String(); ruleDescription != "" {
|
||||
r.logger.DebugContext(ctx, "match[", ruleIndex, "] ", currentRule, " => ", currentRule.Action())
|
||||
} else {
|
||||
r.logger.DebugContext(ctx, "match[", ruleIndex, "] => ", currentRule.Action())
|
||||
}
|
||||
}
|
||||
|
||||
type exchangeWithRulesResult struct {
|
||||
response *mDNS.Msg
|
||||
transport adapter.DNSTransport
|
||||
rejectAction *R.RuleActionReject
|
||||
err error
|
||||
}
|
||||
|
||||
const dnsRespondMissingResponseMessage = "respond action requires an evaluated response from a preceding evaluate action"
|
||||
|
||||
func (r *Router) exchangeWithRules(ctx context.Context, rules []adapter.DNSRule, message *mDNS.Msg, options adapter.DNSQueryOptions, allowFakeIP bool) exchangeWithRulesResult {
|
||||
metadata := adapter.ContextFrom(ctx)
|
||||
if metadata == nil {
|
||||
panic("no context")
|
||||
}
|
||||
effectiveOptions := options
|
||||
var evaluatedResponse *mDNS.Msg
|
||||
var evaluatedTransport adapter.DNSTransport
|
||||
for currentRuleIndex, currentRule := range rules {
|
||||
metadata.ResetRuleCache()
|
||||
metadata.DNSResponse = evaluatedResponse
|
||||
metadata.DestinationAddressMatchFromResponse = false
|
||||
if !currentRule.Match(metadata) {
|
||||
continue
|
||||
}
|
||||
r.logRuleMatch(ctx, currentRuleIndex, currentRule)
|
||||
switch action := currentRule.Action().(type) {
|
||||
case *R.RuleActionDNSRouteOptions:
|
||||
r.applyDNSRouteOptions(&effectiveOptions, *action)
|
||||
case *R.RuleActionEvaluate:
|
||||
queryOptions := effectiveOptions
|
||||
transport, loaded := r.transport.Transport(action.Server)
|
||||
if !loaded {
|
||||
r.logger.ErrorContext(ctx, "transport not found: ", action.Server)
|
||||
evaluatedResponse = nil
|
||||
evaluatedTransport = nil
|
||||
continue
|
||||
}
|
||||
r.applyDNSRouteOptions(&queryOptions, action.RuleActionDNSRouteOptions)
|
||||
exchangeOptions := queryOptions
|
||||
if exchangeOptions.Strategy == C.DomainStrategyAsIS {
|
||||
exchangeOptions.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, exchangeOptions, nil)
|
||||
if err != nil {
|
||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
||||
evaluatedResponse = nil
|
||||
evaluatedTransport = nil
|
||||
continue
|
||||
}
|
||||
evaluatedResponse = response
|
||||
evaluatedTransport = transport
|
||||
case *R.RuleActionRespond:
|
||||
if evaluatedResponse == nil {
|
||||
return exchangeWithRulesResult{
|
||||
err: E.New(dnsRespondMissingResponseMessage),
|
||||
}
|
||||
}
|
||||
return exchangeWithRulesResult{
|
||||
response: evaluatedResponse,
|
||||
transport: evaluatedTransport,
|
||||
}
|
||||
case *R.RuleActionDNSRoute:
|
||||
queryOptions := effectiveOptions
|
||||
transport, status := r.resolveDNSRoute(action.Server, action.RuleActionDNSRouteOptions, allowFakeIP, &queryOptions)
|
||||
switch status {
|
||||
case dnsRouteStatusMissing:
|
||||
r.logger.ErrorContext(ctx, "transport not found: ", action.Server)
|
||||
continue
|
||||
case dnsRouteStatusSkipped:
|
||||
continue
|
||||
}
|
||||
exchangeOptions := queryOptions
|
||||
if exchangeOptions.Strategy == C.DomainStrategyAsIS {
|
||||
exchangeOptions.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, exchangeOptions, nil)
|
||||
return exchangeWithRulesResult{
|
||||
response: response,
|
||||
transport: transport,
|
||||
err: err,
|
||||
}
|
||||
case *R.RuleActionReject:
|
||||
switch action.Method {
|
||||
case C.RuleActionRejectMethodDefault:
|
||||
return exchangeWithRulesResult{
|
||||
response: &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
Id: message.Id,
|
||||
Rcode: mDNS.RcodeRefused,
|
||||
Response: true,
|
||||
},
|
||||
Question: []mDNS.Question{message.Question[0]},
|
||||
},
|
||||
rejectAction: action,
|
||||
}
|
||||
case C.RuleActionRejectMethodDrop:
|
||||
return exchangeWithRulesResult{
|
||||
rejectAction: action,
|
||||
err: tun.ErrDrop,
|
||||
}
|
||||
}
|
||||
case *R.RuleActionPredefined:
|
||||
return exchangeWithRulesResult{
|
||||
response: action.Response(message),
|
||||
}
|
||||
}
|
||||
}
|
||||
transport := r.transport.Default()
|
||||
exchangeOptions := effectiveOptions
|
||||
if exchangeOptions.Strategy == C.DomainStrategyAsIS {
|
||||
exchangeOptions.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, exchangeOptions, nil)
|
||||
return exchangeWithRulesResult{
|
||||
response: response,
|
||||
transport: transport,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) resolveLookupStrategy(options adapter.DNSQueryOptions) C.DomainStrategy {
|
||||
if options.LookupStrategy != C.DomainStrategyAsIS {
|
||||
return options.LookupStrategy
|
||||
}
|
||||
if options.Strategy != C.DomainStrategyAsIS {
|
||||
return options.Strategy
|
||||
}
|
||||
return r.defaultDomainStrategy
|
||||
}
|
||||
|
||||
func withLookupQueryMetadata(ctx context.Context, qType uint16) context.Context {
|
||||
ctx, metadata := adapter.ExtendContext(ctx)
|
||||
metadata.QueryType = qType
|
||||
metadata.IPVersion = 0
|
||||
switch qType {
|
||||
case mDNS.TypeA:
|
||||
metadata.IPVersion = 4
|
||||
case mDNS.TypeAAAA:
|
||||
metadata.IPVersion = 6
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func filterAddressesByQueryType(addresses []netip.Addr, qType uint16) []netip.Addr {
|
||||
switch qType {
|
||||
case mDNS.TypeA:
|
||||
return common.Filter(addresses, func(address netip.Addr) bool {
|
||||
return address.Is4()
|
||||
})
|
||||
case mDNS.TypeAAAA:
|
||||
return common.Filter(addresses, func(address netip.Addr) bool {
|
||||
return address.Is6()
|
||||
})
|
||||
default:
|
||||
return addresses
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) lookupWithRules(ctx context.Context, rules []adapter.DNSRule, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, error) {
|
||||
strategy := r.resolveLookupStrategy(options)
|
||||
lookupOptions := options
|
||||
if strategy != C.DomainStrategyAsIS {
|
||||
lookupOptions.Strategy = strategy
|
||||
}
|
||||
if strategy == C.DomainStrategyIPv4Only {
|
||||
return r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeA, lookupOptions)
|
||||
}
|
||||
if strategy == C.DomainStrategyIPv6Only {
|
||||
return r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeAAAA, lookupOptions)
|
||||
}
|
||||
var (
|
||||
response4 []netip.Addr
|
||||
response6 []netip.Addr
|
||||
)
|
||||
var group task.Group
|
||||
group.Append("exchange4", func(ctx context.Context) error {
|
||||
result, err := r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeA, lookupOptions)
|
||||
response4 = result
|
||||
return err
|
||||
})
|
||||
group.Append("exchange6", func(ctx context.Context) error {
|
||||
result, err := r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeAAAA, lookupOptions)
|
||||
response6 = result
|
||||
return err
|
||||
})
|
||||
err := group.Run(ctx)
|
||||
if len(response4) == 0 && len(response6) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return sortAddresses(response4, response6, strategy), nil
|
||||
}
|
||||
|
||||
func (r *Router) lookupWithRulesType(ctx context.Context, rules []adapter.DNSRule, domain string, qType uint16, options adapter.DNSQueryOptions) ([]netip.Addr, error) {
|
||||
request := &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
RecursionDesired: true,
|
||||
},
|
||||
Question: []mDNS.Question{{
|
||||
Name: mDNS.Fqdn(domain),
|
||||
Qtype: qType,
|
||||
Qclass: mDNS.ClassINET,
|
||||
}},
|
||||
}
|
||||
exchangeResult := r.exchangeWithRules(withLookupQueryMetadata(ctx, qType), rules, request, options, false)
|
||||
if exchangeResult.rejectAction != nil {
|
||||
return nil, exchangeResult.rejectAction.Error(ctx)
|
||||
}
|
||||
if exchangeResult.err != nil {
|
||||
return nil, exchangeResult.err
|
||||
}
|
||||
if exchangeResult.response.Rcode != mDNS.RcodeSuccess {
|
||||
return nil, RcodeError(exchangeResult.response.Rcode)
|
||||
}
|
||||
return filterAddressesByQueryType(MessageToAddresses(exchangeResult.response), qType), nil
|
||||
}
|
||||
|
||||
func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapter.DNSQueryOptions) (*mDNS.Msg, error) {
|
||||
if len(message.Question) != 1 {
|
||||
r.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
||||
@@ -588,13 +220,6 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
}
|
||||
return &responseMessage, nil
|
||||
}
|
||||
r.rulesAccess.RLock()
|
||||
defer r.rulesAccess.RUnlock()
|
||||
if r.closing {
|
||||
return nil, E.New("dns router closed")
|
||||
}
|
||||
rules := r.rules
|
||||
legacyDNSMode := r.legacyDNSMode
|
||||
r.logger.DebugContext(ctx, "exchange ", FormatQuestion(message.Question[0].String()))
|
||||
var (
|
||||
response *mDNS.Msg
|
||||
@@ -605,8 +230,6 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
ctx, metadata = adapter.ExtendContext(ctx)
|
||||
metadata.Destination = M.Socksaddr{}
|
||||
metadata.QueryType = message.Question[0].Qtype
|
||||
metadata.DNSResponse = nil
|
||||
metadata.DestinationAddressMatchFromResponse = false
|
||||
switch metadata.QueryType {
|
||||
case mDNS.TypeA:
|
||||
metadata.IPVersion = 4
|
||||
@@ -616,13 +239,18 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
metadata.Domain = FqdnToDomain(message.Question[0].Name)
|
||||
if options.Transport != nil {
|
||||
transport = options.Transport
|
||||
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||
if options.Strategy == C.DomainStrategyAsIS {
|
||||
options.Strategy = legacyTransport.LegacyStrategy()
|
||||
}
|
||||
if !options.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||
}
|
||||
}
|
||||
if options.Strategy == C.DomainStrategyAsIS {
|
||||
options.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
response, err = r.client.Exchange(ctx, transport, message, options, nil)
|
||||
} else if !legacyDNSMode {
|
||||
exchangeResult := r.exchangeWithRules(ctx, rules, message, options, true)
|
||||
response, transport, err = exchangeResult.response, exchangeResult.transport, exchangeResult.err
|
||||
} else {
|
||||
var (
|
||||
rule adapter.DNSRule
|
||||
@@ -632,7 +260,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
for {
|
||||
dnsCtx := adapter.OverrideContext(ctx)
|
||||
dnsOptions := options
|
||||
transport, rule, ruleIndex = r.matchDNS(ctx, rules, true, ruleIndex, isAddressQuery(message), &dnsOptions)
|
||||
transport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message), &dnsOptions)
|
||||
if rule != nil {
|
||||
switch action := rule.Action().(type) {
|
||||
case *R.RuleActionReject:
|
||||
@@ -650,9 +278,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
return nil, tun.ErrDrop
|
||||
}
|
||||
case *R.RuleActionPredefined:
|
||||
err = nil
|
||||
response = action.Response(message)
|
||||
goto done
|
||||
return action.Response(message), nil
|
||||
}
|
||||
}
|
||||
responseCheck := addressLimitResponseCheck(rule, metadata)
|
||||
@@ -680,7 +306,6 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
break
|
||||
}
|
||||
}
|
||||
done:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -700,13 +325,6 @@ done:
|
||||
}
|
||||
|
||||
func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, error) {
|
||||
r.rulesAccess.RLock()
|
||||
defer r.rulesAccess.RUnlock()
|
||||
if r.closing {
|
||||
return nil, E.New("dns router closed")
|
||||
}
|
||||
rules := r.rules
|
||||
legacyDNSMode := r.legacyDNSMode
|
||||
var (
|
||||
responseAddrs []netip.Addr
|
||||
err error
|
||||
@@ -720,8 +338,6 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
|
||||
} else if errors.Is(err, ErrResponseRejected) {
|
||||
r.logger.DebugContext(ctx, "response rejected for ", domain)
|
||||
} else if R.IsRejected(err) {
|
||||
r.logger.DebugContext(ctx, "lookup rejected for ", domain)
|
||||
} else {
|
||||
r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
||||
}
|
||||
@@ -734,16 +350,20 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
ctx, metadata := adapter.ExtendContext(ctx)
|
||||
metadata.Destination = M.Socksaddr{}
|
||||
metadata.Domain = FqdnToDomain(domain)
|
||||
metadata.DNSResponse = nil
|
||||
metadata.DestinationAddressMatchFromResponse = false
|
||||
if options.Transport != nil {
|
||||
transport := options.Transport
|
||||
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||
if options.Strategy == C.DomainStrategyAsIS {
|
||||
options.Strategy = legacyTransport.LegacyStrategy()
|
||||
}
|
||||
if !options.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||
}
|
||||
}
|
||||
if options.Strategy == C.DomainStrategyAsIS {
|
||||
options.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
responseAddrs, err = r.client.Lookup(ctx, transport, domain, options, nil)
|
||||
} else if !legacyDNSMode {
|
||||
responseAddrs, err = r.lookupWithRules(ctx, rules, domain, options)
|
||||
} else {
|
||||
var (
|
||||
transport adapter.DNSTransport
|
||||
@@ -754,7 +374,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
for {
|
||||
dnsCtx := adapter.OverrideContext(ctx)
|
||||
dnsOptions := options
|
||||
transport, rule, ruleIndex = r.matchDNS(ctx, rules, false, ruleIndex, true, &dnsOptions)
|
||||
transport, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true, &dnsOptions)
|
||||
if rule != nil {
|
||||
switch action := rule.Action().(type) {
|
||||
case *R.RuleActionReject:
|
||||
@@ -805,14 +425,15 @@ func isAddressQuery(message *mDNS.Msg) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func addressLimitResponseCheck(rule adapter.DNSRule, metadata *adapter.InboundContext) func(response *mDNS.Msg) bool {
|
||||
func addressLimitResponseCheck(rule adapter.DNSRule, metadata *adapter.InboundContext) func(responseAddrs []netip.Addr) bool {
|
||||
if rule == nil || !rule.WithAddressLimit() {
|
||||
return nil
|
||||
}
|
||||
responseMetadata := *metadata
|
||||
return func(response *mDNS.Msg) bool {
|
||||
return func(responseAddrs []netip.Addr) bool {
|
||||
checkMetadata := responseMetadata
|
||||
return rule.MatchAddressLimit(&checkMetadata, response)
|
||||
checkMetadata.DestinationAddresses = responseAddrs
|
||||
return rule.MatchAddressLimit(&checkMetadata)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -837,268 +458,3 @@ func (r *Router) ResetNetwork() {
|
||||
transport.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func defaultRuleNeedsLegacyDNSModeFromAddressFilter(rule option.DefaultDNSRule) bool {
|
||||
if rule.IPAcceptAny || rule.RuleSetIPCIDRAcceptEmpty { //nolint:staticcheck
|
||||
return true
|
||||
}
|
||||
return !rule.MatchResponse && (len(rule.IPCIDR) > 0 || rule.IPIsPrivate)
|
||||
}
|
||||
|
||||
func hasResponseMatchFields(rule option.DefaultDNSRule) bool {
|
||||
return rule.ResponseRcode != nil ||
|
||||
len(rule.ResponseAnswer) > 0 ||
|
||||
len(rule.ResponseNs) > 0 ||
|
||||
len(rule.ResponseExtra) > 0
|
||||
}
|
||||
|
||||
func defaultRuleDisablesLegacyDNSMode(rule option.DefaultDNSRule) bool {
|
||||
return rule.MatchResponse ||
|
||||
hasResponseMatchFields(rule) ||
|
||||
rule.Action == C.RuleActionTypeEvaluate ||
|
||||
rule.Action == C.RuleActionTypeRespond ||
|
||||
rule.IPVersion > 0 ||
|
||||
len(rule.QueryType) > 0
|
||||
}
|
||||
|
||||
type dnsRuleModeFlags struct {
|
||||
disabled bool
|
||||
needed bool
|
||||
neededFromStrategy bool
|
||||
}
|
||||
|
||||
func (f *dnsRuleModeFlags) merge(other dnsRuleModeFlags) {
|
||||
f.disabled = f.disabled || other.disabled
|
||||
f.needed = f.needed || other.needed
|
||||
f.neededFromStrategy = f.neededFromStrategy || other.neededFromStrategy
|
||||
}
|
||||
|
||||
func resolveLegacyDNSMode(router adapter.Router, rules []option.DNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) (bool, dnsRuleModeFlags, error) {
|
||||
flags, err := dnsRuleModeRequirements(router, rules, metadataOverrides)
|
||||
if err != nil {
|
||||
return false, flags, err
|
||||
}
|
||||
if flags.disabled && flags.neededFromStrategy {
|
||||
return false, flags, E.New(deprecated.OptionLegacyDNSRuleStrategy.MessageWithLink())
|
||||
}
|
||||
if flags.disabled {
|
||||
return false, flags, nil
|
||||
}
|
||||
return flags.needed, flags, nil
|
||||
}
|
||||
|
||||
func dnsRuleModeRequirements(router adapter.Router, rules []option.DNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) (dnsRuleModeFlags, error) {
|
||||
var flags dnsRuleModeFlags
|
||||
for i, rule := range rules {
|
||||
ruleFlags, err := dnsRuleModeRequirementsInRule(router, rule, metadataOverrides)
|
||||
if err != nil {
|
||||
return dnsRuleModeFlags{}, E.Cause(err, "dns rule[", i, "]")
|
||||
}
|
||||
flags.merge(ruleFlags)
|
||||
}
|
||||
return flags, nil
|
||||
}
|
||||
|
||||
func dnsRuleModeRequirementsInRule(router adapter.Router, rule option.DNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) (dnsRuleModeFlags, error) {
|
||||
switch rule.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
return dnsRuleModeRequirementsInDefaultRule(router, rule.DefaultOptions, metadataOverrides)
|
||||
case C.RuleTypeLogical:
|
||||
flags := dnsRuleModeFlags{
|
||||
disabled: dnsRuleActionType(rule) == C.RuleActionTypeEvaluate || dnsRuleActionType(rule) == C.RuleActionTypeRespond,
|
||||
neededFromStrategy: dnsRuleActionHasStrategy(rule.LogicalOptions.DNSRuleAction),
|
||||
}
|
||||
flags.needed = flags.neededFromStrategy
|
||||
for i, subRule := range rule.LogicalOptions.Rules {
|
||||
subFlags, err := dnsRuleModeRequirementsInRule(router, subRule, metadataOverrides)
|
||||
if err != nil {
|
||||
return dnsRuleModeFlags{}, E.Cause(err, "sub rule[", i, "]")
|
||||
}
|
||||
flags.merge(subFlags)
|
||||
}
|
||||
return flags, nil
|
||||
default:
|
||||
return dnsRuleModeFlags{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func dnsRuleModeRequirementsInDefaultRule(router adapter.Router, rule option.DefaultDNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) (dnsRuleModeFlags, error) {
|
||||
flags := dnsRuleModeFlags{
|
||||
disabled: defaultRuleDisablesLegacyDNSMode(rule),
|
||||
neededFromStrategy: dnsRuleActionHasStrategy(rule.DNSRuleAction),
|
||||
}
|
||||
flags.needed = defaultRuleNeedsLegacyDNSModeFromAddressFilter(rule) || flags.neededFromStrategy
|
||||
if len(rule.RuleSet) == 0 {
|
||||
return flags, nil
|
||||
}
|
||||
if router == nil {
|
||||
return dnsRuleModeFlags{}, E.New("router service not found")
|
||||
}
|
||||
for _, tag := range rule.RuleSet {
|
||||
metadata, err := lookupDNSRuleSetMetadata(router, tag, metadataOverrides)
|
||||
if err != nil {
|
||||
return dnsRuleModeFlags{}, err
|
||||
}
|
||||
// ip_version is not a headless-rule item, so ContainsIPVersionRule is intentionally absent.
|
||||
flags.disabled = flags.disabled || metadata.ContainsDNSQueryTypeRule
|
||||
if !rule.RuleSetIPCIDRMatchSource && metadata.ContainsIPCIDRRule {
|
||||
flags.needed = true
|
||||
}
|
||||
}
|
||||
return flags, nil
|
||||
}
|
||||
|
||||
func lookupDNSRuleSetMetadata(router adapter.Router, tag string, metadataOverrides map[string]adapter.RuleSetMetadata) (adapter.RuleSetMetadata, error) {
|
||||
if metadataOverrides != nil {
|
||||
if metadata, loaded := metadataOverrides[tag]; loaded {
|
||||
return metadata, nil
|
||||
}
|
||||
}
|
||||
ruleSet, loaded := router.RuleSet(tag)
|
||||
if !loaded {
|
||||
return adapter.RuleSetMetadata{}, E.New("rule-set not found: ", tag)
|
||||
}
|
||||
return ruleSet.Metadata(), nil
|
||||
}
|
||||
|
||||
func referencedDNSRuleSetTags(rules []option.DNSRule) []string {
|
||||
tagMap := make(map[string]bool)
|
||||
var walkRule func(rule option.DNSRule)
|
||||
walkRule = func(rule option.DNSRule) {
|
||||
switch rule.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
for _, tag := range rule.DefaultOptions.RuleSet {
|
||||
tagMap[tag] = true
|
||||
}
|
||||
case C.RuleTypeLogical:
|
||||
for _, subRule := range rule.LogicalOptions.Rules {
|
||||
walkRule(subRule)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, rule := range rules {
|
||||
walkRule(rule)
|
||||
}
|
||||
tags := make([]string, 0, len(tagMap))
|
||||
for tag := range tagMap {
|
||||
if tag != "" {
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
func validateLegacyDNSModeDisabledRules(rules []option.DNSRule) error {
|
||||
var seenEvaluate bool
|
||||
for i, rule := range rules {
|
||||
requiresPriorEvaluate, err := validateLegacyDNSModeDisabledRuleTree(rule)
|
||||
if err != nil {
|
||||
return E.Cause(err, "validate dns rule[", i, "]")
|
||||
}
|
||||
if requiresPriorEvaluate && !seenEvaluate {
|
||||
return E.New("dns rule[", i, "]: response-based matching requires a preceding evaluate action")
|
||||
}
|
||||
if dnsRuleActionType(rule) == C.RuleActionTypeEvaluate {
|
||||
seenEvaluate = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateEvaluateFakeIPRules(rules []option.DNSRule, transportManager adapter.DNSTransportManager) error {
|
||||
if transportManager == nil {
|
||||
return nil
|
||||
}
|
||||
for i, rule := range rules {
|
||||
if dnsRuleActionType(rule) != C.RuleActionTypeEvaluate {
|
||||
continue
|
||||
}
|
||||
server := dnsRuleActionServer(rule)
|
||||
if server == "" {
|
||||
continue
|
||||
}
|
||||
transport, loaded := transportManager.Transport(server)
|
||||
if !loaded || transport.Type() != C.DNSTypeFakeIP {
|
||||
continue
|
||||
}
|
||||
return E.New("dns rule[", i, "]: evaluate action cannot use fakeip server: ", server)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateLegacyDNSModeDisabledRuleTree(rule option.DNSRule) (bool, error) {
|
||||
switch rule.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
return validateLegacyDNSModeDisabledDefaultRule(rule.DefaultOptions)
|
||||
case C.RuleTypeLogical:
|
||||
requiresPriorEvaluate := dnsRuleActionType(rule) == C.RuleActionTypeRespond
|
||||
for i, subRule := range rule.LogicalOptions.Rules {
|
||||
subRequiresPriorEvaluate, err := validateLegacyDNSModeDisabledRuleTree(subRule)
|
||||
if err != nil {
|
||||
return false, E.Cause(err, "sub rule[", i, "]")
|
||||
}
|
||||
requiresPriorEvaluate = requiresPriorEvaluate || subRequiresPriorEvaluate
|
||||
}
|
||||
return requiresPriorEvaluate, nil
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func validateLegacyDNSModeDisabledDefaultRule(rule option.DefaultDNSRule) (bool, error) {
|
||||
hasResponseRecords := hasResponseMatchFields(rule)
|
||||
if (hasResponseRecords || len(rule.IPCIDR) > 0 || rule.IPIsPrivate) && !rule.MatchResponse {
|
||||
return false, E.New("Response Match Fields (ip_cidr, ip_is_private, response_rcode, response_answer, response_ns, response_extra) require match_response to be enabled")
|
||||
}
|
||||
// Intentionally do not reject rule_set here. A referenced rule set may mix
|
||||
// destination-IP predicates with pre-response predicates such as domain items.
|
||||
// When match_response is false, those destination-IP branches fail closed during
|
||||
// pre-response evaluation instead of consuming DNS response state, while sibling
|
||||
// non-response branches remain matchable.
|
||||
if rule.IPAcceptAny { //nolint:staticcheck
|
||||
return false, E.New(deprecated.OptionIPAcceptAny.MessageWithLink())
|
||||
}
|
||||
if rule.RuleSetIPCIDRAcceptEmpty { //nolint:staticcheck
|
||||
return false, E.New(deprecated.OptionRuleSetIPCIDRAcceptEmpty.MessageWithLink())
|
||||
}
|
||||
return rule.MatchResponse || rule.Action == C.RuleActionTypeRespond, nil
|
||||
}
|
||||
|
||||
func dnsRuleActionHasStrategy(action option.DNSRuleAction) bool {
|
||||
switch action.Action {
|
||||
case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate:
|
||||
return C.DomainStrategy(action.RouteOptions.Strategy) != C.DomainStrategyAsIS
|
||||
case C.RuleActionTypeRouteOptions:
|
||||
return C.DomainStrategy(action.RouteOptionsOptions.Strategy) != C.DomainStrategyAsIS
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func dnsRuleActionType(rule option.DNSRule) string {
|
||||
switch rule.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
if rule.DefaultOptions.Action == "" {
|
||||
return C.RuleActionTypeRoute
|
||||
}
|
||||
return rule.DefaultOptions.Action
|
||||
case C.RuleTypeLogical:
|
||||
if rule.LogicalOptions.Action == "" {
|
||||
return C.RuleActionTypeRoute
|
||||
}
|
||||
return rule.LogicalOptions.Action
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func dnsRuleActionServer(rule option.DNSRule) string {
|
||||
switch rule.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
return rule.DefaultOptions.RouteOptions.Server
|
||||
case C.RuleTypeLogical:
|
||||
return rule.LogicalOptions.RouteOptions.Server
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
2547
dns/router_test.go
2547
dns/router_test.go
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,21 @@
|
||||
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 {
|
||||
@@ -27,6 +35,8 @@ func NewTransportAdapterWithLocalOptions(transportType string, transportTag stri
|
||||
transportType: transportType,
|
||||
transportTag: transportTag,
|
||||
dependencies: dependencies,
|
||||
strategy: C.DomainStrategy(localOptions.LegacyStrategy),
|
||||
clientSubnet: localOptions.LegacyClientSubnet,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,10 +45,15 @@ func NewTransportAdapterWithRemoteOptions(transportType string, transportTag str
|
||||
if remoteOptions.DomainResolver != nil && remoteOptions.DomainResolver.Server != "" {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,3 +68,11 @@ 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,25 +2,104 @@ 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) {
|
||||
return dialer.NewWithOptions(dialer.Options{
|
||||
Context: ctx,
|
||||
Options: options.DialerOptions,
|
||||
DirectResolver: true,
|
||||
})
|
||||
if options.LegacyDefaultDialer {
|
||||
return dialer.NewDefaultOutbound(ctx), nil
|
||||
} else {
|
||||
return dialer.NewWithOptions(dialer.Options{
|
||||
Context: ctx,
|
||||
Options: options.DialerOptions,
|
||||
DirectResolver: true,
|
||||
LegacyDNSDialer: options.Legacy,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) {
|
||||
return dialer.NewWithOptions(dialer.Options{
|
||||
Context: ctx,
|
||||
Options: options.DialerOptions,
|
||||
RemoteIsDomain: options.ServerIsDomain(),
|
||||
DirectResolver: true,
|
||||
})
|
||||
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,
|
||||
})
|
||||
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,57 +2,13 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.14.0-alpha.9
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.13.6
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.14.0-alpha.8
|
||||
|
||||
* Add BBR profile and hop interval randomization for Hysteria2 **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [Hysteria2 Inbound](/configuration/inbound/hysteria2/#bbr_profile) and [Hysteria2 Outbound](/configuration/outbound/hysteria2/#bbr_profile).
|
||||
|
||||
#### 1.14.0-alpha.8
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.13.5
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.14.0-alpha.7
|
||||
|
||||
* 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**:
|
||||
#### 1.13.4-beta.1
|
||||
|
||||
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).
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.13.3
|
||||
|
||||
@@ -752,7 +708,7 @@ DNS servers are refactored for better performance and scalability.
|
||||
|
||||
See [DNS server](/configuration/dns/server/).
|
||||
|
||||
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-server-formats).
|
||||
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-servers).
|
||||
|
||||
Compatibility for old formats will be removed in sing-box 1.14.0.
|
||||
|
||||
@@ -1222,7 +1178,7 @@ DNS servers are refactored for better performance and scalability.
|
||||
|
||||
See [DNS server](/configuration/dns/server/).
|
||||
|
||||
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-server-formats).
|
||||
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-servers).
|
||||
|
||||
Compatibility for old formats will be removed in sing-box 1.14.0.
|
||||
|
||||
@@ -2058,7 +2014,7 @@ See [Migration](/migration/#process_path-format-update-on-windows).
|
||||
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS
|
||||
if using this method.
|
||||
|
||||
See [Legacy Address Filter Fields](/configuration/dns/rule#legacy-address-filter-fields).
|
||||
See [Address Filter Fields](/configuration/dns/rule#address-filter-fields).
|
||||
|
||||
[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.
|
||||
|
||||
@@ -2072,7 +2028,7 @@ the [Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users
|
||||
**5**:
|
||||
|
||||
The new feature allows you to cache the check results of
|
||||
[Legacy Address Filter Fields](/configuration/dns/rule/#legacy-address-filter-fields) until expiration.
|
||||
[Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields) until expiration.
|
||||
|
||||
**6**:
|
||||
|
||||
@@ -2253,7 +2209,7 @@ See [TUN](/configuration/inbound/tun) inbound.
|
||||
**1**:
|
||||
|
||||
The new feature allows you to cache the check results of
|
||||
[Legacy Address Filter Fields](/configuration/dns/rule/#legacy-address-filter-fields) until expiration.
|
||||
[Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields) until expiration.
|
||||
|
||||
#### 1.9.0-alpha.7
|
||||
|
||||
@@ -2300,7 +2256,7 @@ See [Migration](/migration/#process_path-format-update-on-windows).
|
||||
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS
|
||||
if using this method.
|
||||
|
||||
See [Legacy Address Filter Fields](/configuration/dns/rule#legacy-address-filter-fields).
|
||||
See [Address Filter Fields](/configuration/dns/rule#address-filter-fields).
|
||||
|
||||
[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
icon: material/note-remove
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "Removed in sing-box 1.14.0"
|
||||
!!! failure "Deprecated in sing-box 1.12.0"
|
||||
|
||||
Legacy fake-ip configuration is deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-server-formats).
|
||||
Legacy fake-ip configuration is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
|
||||
|
||||
### Structure
|
||||
|
||||
@@ -26,6 +26,6 @@ Enable FakeIP service.
|
||||
|
||||
IPv4 address range for FakeIP.
|
||||
|
||||
#### inet6_range
|
||||
#### inet6_address
|
||||
|
||||
IPv6 address range for FakeIP.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
icon: material/note-remove
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "已在 sing-box 1.14.0 移除"
|
||||
!!! failure "已在 sing-box 1.12.0 废弃"
|
||||
|
||||
旧的 fake-ip 配置已在 sing-box 1.12.0 废弃且已在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
||||
旧的 fake-ip 配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
||||
|
||||
### 结构
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ icon: material/alert-decagram
|
||||
|----------|---------------------------------|
|
||||
| `server` | List of [DNS Server](./server/) |
|
||||
| `rules` | List of [DNS Rule](./rule/) |
|
||||
| `fakeip` | :material-note-remove: [FakeIP](./fakeip/) |
|
||||
| `fakeip` | [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 overridden by `servers.[].client_subnet` or `rules.[].client_subnet`.
|
||||
Can be overrides by `servers.[].client_subnet` or `rules.[].client_subnet`.
|
||||
|
||||
@@ -88,6 +88,6 @@ LRU 缓存容量。
|
||||
|
||||
可以被 `servers.[].client_subnet` 或 `rules.[].client_subnet` 覆盖。
|
||||
|
||||
#### fakeip :material-note-remove:
|
||||
#### fakeip
|
||||
|
||||
[FakeIP](./fakeip/) 设置。
|
||||
|
||||
@@ -5,14 +5,7 @@ icon: material/alert-decagram
|
||||
!!! quote "Changes in sing-box 1.14.0"
|
||||
|
||||
:material-plus: [source_mac_address](#source_mac_address)
|
||||
:material-plus: [source_hostname](#source_hostname)
|
||||
:material-plus: [match_response](#match_response)
|
||||
:material-delete-clock: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
||||
:material-delete-clock: [ip_accept_any](#ip_accept_any)
|
||||
: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_hostname](#source_hostname)
|
||||
|
||||
!!! quote "Changes in sing-box 1.13.0"
|
||||
|
||||
@@ -101,6 +94,12 @@ 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
|
||||
],
|
||||
@@ -172,16 +171,7 @@ icon: material/alert-decagram
|
||||
"geosite-cn"
|
||||
],
|
||||
"rule_set_ip_cidr_match_source": 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": [],
|
||||
"rule_set_ip_cidr_accept_empty": false,
|
||||
"invert": false,
|
||||
"outbound": [
|
||||
"direct"
|
||||
@@ -190,9 +180,7 @@ 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"
|
||||
@@ -232,7 +220,7 @@ icon: material/alert-decagram
|
||||
(`source_port` || `source_port_range`) &&
|
||||
`other fields`
|
||||
|
||||
Additionally, each branch inside an included rule-set can be considered merged into the outer rule, while different branches keep OR semantics.
|
||||
Additionally, included rule-sets can be considered merged rather than as a single rule sub-item.
|
||||
|
||||
#### inbound
|
||||
|
||||
@@ -489,19 +477,6 @@ Make `ip_cidr` rule items in rule-sets match the source IP.
|
||||
|
||||
Make `ip_cidr` rule items in rule-sets match the source IP.
|
||||
|
||||
#### match_response
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
Enable response-based matching. When enabled, this rule matches against the evaluated response
|
||||
(set by a preceding [`evaluate`](/configuration/dns/rule_action/#evaluate) action)
|
||||
instead of only matching the original query.
|
||||
|
||||
The evaluated response can also be returned directly by a later [`respond`](/configuration/dns/rule_action/#respond) action.
|
||||
|
||||
Required for Response Match Fields (`response_rcode`, `response_answer`, `response_ns`, `response_extra`).
|
||||
Also required for `ip_cidr` and `ip_is_private` when used with `evaluate` or Response Match Fields.
|
||||
|
||||
#### invert
|
||||
|
||||
Invert match result.
|
||||
@@ -546,12 +521,7 @@ See [DNS Rule Actions](../rule_action/) for details.
|
||||
|
||||
Moved to [DNS Rule Action](../rule_action#route).
|
||||
|
||||
### Legacy Address Filter Fields
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.14.0"
|
||||
|
||||
Legacy Address Filter Fields are deprecated and will be removed in sing-box 1.16.0,
|
||||
check [Migration](/migration/#migrate-address-filter-fields-to-response-matching).
|
||||
### 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.
|
||||
|
||||
@@ -577,73 +547,24 @@ Match GeoIP with query response.
|
||||
|
||||
Match IP CIDR with query response.
|
||||
|
||||
As a Legacy Address Filter Field, deprecated. Use with `match_response` instead,
|
||||
check [Migration](/migration/#migrate-address-filter-fields-to-response-matching).
|
||||
|
||||
#### ip_is_private
|
||||
|
||||
!!! question "Since sing-box 1.9.0"
|
||||
|
||||
Match private IP with query response.
|
||||
|
||||
As a Legacy Address Filter Field, deprecated. Use with `match_response` instead,
|
||||
check [Migration](/migration/#migrate-address-filter-fields-to-response-matching).
|
||||
|
||||
#### rule_set_ip_cidr_accept_empty
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.14.0"
|
||||
|
||||
`rule_set_ip_cidr_accept_empty` is deprecated and will be removed in sing-box 1.16.0,
|
||||
check [Migration](/migration/#migrate-address-filter-fields-to-response-matching).
|
||||
|
||||
Make `ip_cidr` rules in rule-sets accept empty query response.
|
||||
|
||||
#### 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,
|
||||
check [Migration](/migration/#migrate-address-filter-fields-to-response-matching).
|
||||
|
||||
Match any IP with query response.
|
||||
|
||||
### Response Match Fields
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
Match fields for the evaluated response. Require `match_response` to be set to `true`
|
||||
and a preceding rule with [`evaluate`](/configuration/dns/rule_action/#evaluate) action to populate the response.
|
||||
|
||||
That evaluated response may also be returned directly by a later [`respond`](/configuration/dns/rule_action/#respond) action.
|
||||
|
||||
#### response_rcode
|
||||
|
||||
Match DNS response code.
|
||||
|
||||
Accepted values are the same as in the [predefined action rcode](/configuration/dns/rule_action/#rcode).
|
||||
|
||||
#### response_answer
|
||||
|
||||
Match DNS answer records.
|
||||
|
||||
Record format is the same as in [predefined action answer](/configuration/dns/rule_action/#answer).
|
||||
|
||||
#### response_ns
|
||||
|
||||
Match DNS name server records.
|
||||
|
||||
Record format is the same as in [predefined action ns](/configuration/dns/rule_action/#ns).
|
||||
|
||||
#### response_extra
|
||||
|
||||
Match DNS extra records.
|
||||
|
||||
Record format is the same as in [predefined action extra](/configuration/dns/rule_action/#extra).
|
||||
|
||||
### Logical Fields
|
||||
|
||||
#### type
|
||||
@@ -656,4 +577,4 @@ Record format is the same as in [predefined action extra](/configuration/dns/rul
|
||||
|
||||
#### rules
|
||||
|
||||
Included rules.
|
||||
Included rules.
|
||||
@@ -5,14 +5,7 @@ icon: material/alert-decagram
|
||||
!!! quote "sing-box 1.14.0 中的更改"
|
||||
|
||||
:material-plus: [source_mac_address](#source_mac_address)
|
||||
:material-plus: [source_hostname](#source_hostname)
|
||||
:material-plus: [match_response](#match_response)
|
||||
:material-delete-clock: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
||||
:material-delete-clock: [ip_accept_any](#ip_accept_any)
|
||||
: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_hostname](#source_hostname)
|
||||
|
||||
!!! quote "sing-box 1.13.0 中的更改"
|
||||
|
||||
@@ -101,6 +94,12 @@ 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
|
||||
],
|
||||
@@ -172,16 +171,7 @@ icon: material/alert-decagram
|
||||
"geosite-cn"
|
||||
],
|
||||
"rule_set_ip_cidr_match_source": 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": [],
|
||||
"rule_set_ip_cidr_accept_empty": false,
|
||||
"invert": false,
|
||||
"outbound": [
|
||||
"direct"
|
||||
@@ -190,9 +180,6 @@ 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"
|
||||
@@ -232,7 +219,7 @@ icon: material/alert-decagram
|
||||
(`source_port` || `source_port_range`) &&
|
||||
`other fields`
|
||||
|
||||
另外,引用规则集中的每个分支都可视为与外层规则合并,不同分支之间仍保持 OR 语义。
|
||||
另外,引用的规则集可视为被合并,而不是作为一个单独的规则子项。
|
||||
|
||||
#### inbound
|
||||
|
||||
@@ -489,17 +476,6 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
使规则集中的 `ip_cidr` 规则匹配源 IP。
|
||||
|
||||
#### match_response
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
启用响应匹配。启用后,此规则将匹配已评估的响应(由前序 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作设置),而不仅是匹配原始查询。
|
||||
|
||||
该已评估的响应也可以被后续的 [`respond`](/zh/configuration/dns/rule_action/#respond) 动作直接返回。
|
||||
|
||||
响应匹配字段(`response_rcode`、`response_answer`、`response_ns`、`response_extra`)需要此选项。
|
||||
当与 `evaluate` 或响应匹配字段一起使用时,`ip_cidr` 和 `ip_is_private` 也需要此选项。
|
||||
|
||||
#### invert
|
||||
|
||||
反选匹配结果。
|
||||
@@ -544,12 +520,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
已移动到 [DNS 规则动作](../rule_action#route).
|
||||
|
||||
### 旧版地址筛选字段
|
||||
|
||||
!!! failure "已在 sing-box 1.14.0 废弃"
|
||||
|
||||
旧版地址筛选字段已废弃,且将在 sing-box 1.16.0 中被移除,
|
||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
||||
### 地址筛选字段
|
||||
|
||||
仅对地址请求 (A/AAAA/HTTPS) 生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。
|
||||
|
||||
@@ -576,73 +547,24 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
与查询响应匹配 IP CIDR。
|
||||
|
||||
作为旧版地址筛选字段已废弃。请改为配合 `match_response` 使用,
|
||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
||||
|
||||
#### ip_is_private
|
||||
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
|
||||
与查询响应匹配非公开 IP。
|
||||
|
||||
作为旧版地址筛选字段已废弃。请改为配合 `match_response` 使用,
|
||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
||||
#### ip_accept_any
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
匹配任意 IP。
|
||||
|
||||
#### 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 中被移除,
|
||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
||||
|
||||
使规则集中的 `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 中被移除,
|
||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
||||
|
||||
匹配任意 IP。
|
||||
|
||||
### 响应匹配字段
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
已评估的响应的匹配字段。需要将 `match_response` 设为 `true`,
|
||||
且需要前序规则使用 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作来填充响应。
|
||||
|
||||
该已评估的响应也可以被后续的 [`respond`](/zh/configuration/dns/rule_action/#respond) 动作直接返回。
|
||||
|
||||
#### response_rcode
|
||||
|
||||
匹配 DNS 响应码。
|
||||
|
||||
接受的值与 [predefined 动作 rcode](/zh/configuration/dns/rule_action/#rcode) 中相同。
|
||||
|
||||
#### response_answer
|
||||
|
||||
匹配 DNS 应答记录。
|
||||
|
||||
记录格式与 [predefined 动作 answer](/zh/configuration/dns/rule_action/#answer) 中相同。
|
||||
|
||||
#### response_ns
|
||||
|
||||
匹配 DNS 名称服务器记录。
|
||||
|
||||
记录格式与 [predefined 动作 ns](/zh/configuration/dns/rule_action/#ns) 中相同。
|
||||
|
||||
#### response_extra
|
||||
|
||||
匹配 DNS 额外记录。
|
||||
|
||||
记录格式与 [predefined 动作 extra](/zh/configuration/dns/rule_action/#extra) 中相同。
|
||||
|
||||
### 逻辑字段
|
||||
|
||||
#### type
|
||||
@@ -659,4 +581,4 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
==必填==
|
||||
|
||||
包括的规则。
|
||||
包括的规则。
|
||||
@@ -2,12 +2,6 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.14.0"
|
||||
|
||||
:material-delete-clock: [strategy](#strategy)
|
||||
:material-plus: [evaluate](#evaluate)
|
||||
:material-plus: [respond](#respond)
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
:material-plus: [strategy](#strategy)
|
||||
@@ -40,11 +34,7 @@ Tag of target server.
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.14.0"
|
||||
|
||||
`strategy` is deprecated in sing-box 1.14.0 and will be removed in sing-box 1.16.0.
|
||||
|
||||
Set domain strategy for this query. Deprecated, check [Migration](/migration/#migrate-dns-rule-action-strategy-to-rule-items).
|
||||
Set domain strategy for this query.
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
@@ -62,68 +52,7 @@ Append a `edns0-subnet` OPT extra record with the specified IP prefix to every q
|
||||
|
||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||
|
||||
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 evaluated response for subsequent rules
|
||||
to match against using [`match_response`](/configuration/dns/rule/#match_response) and response fields.
|
||||
Unlike `route`, it does **not** terminate rule evaluation.
|
||||
|
||||
Only allowed on top-level DNS rules (not inside logical sub-rules).
|
||||
Rules that use [`match_response`](/configuration/dns/rule/#match_response) or Response Match Fields
|
||||
require a preceding top-level rule with `evaluate` action. A rule's own `evaluate` action
|
||||
does not satisfy this requirement, because matching happens before the action runs.
|
||||
|
||||
#### server
|
||||
|
||||
==Required==
|
||||
|
||||
Tag of target server.
|
||||
|
||||
#### disable_cache
|
||||
|
||||
Disable cache and save cache in this query.
|
||||
|
||||
#### rewrite_ttl
|
||||
|
||||
Rewrite TTL in DNS responses.
|
||||
|
||||
#### client_subnet
|
||||
|
||||
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
|
||||
|
||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||
|
||||
Will override `dns.client_subnet`.
|
||||
|
||||
### respond
|
||||
|
||||
!!! question "Since sing-box 1.14.0"
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "respond"
|
||||
}
|
||||
```
|
||||
|
||||
`respond` terminates rule evaluation and returns the evaluated response from a preceding [`evaluate`](/configuration/dns/rule_action/#evaluate) action.
|
||||
|
||||
This action does not send a new DNS query and has no extra options.
|
||||
|
||||
Only allowed after a preceding top-level `evaluate` rule. If the action is reached without an evaluated response at runtime, the request fails with an error instead of falling through to later rules.
|
||||
Will overrides `dns.client_subnet`.
|
||||
|
||||
### route-options
|
||||
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.14.0 中的更改"
|
||||
|
||||
:material-delete-clock: [strategy](#strategy)
|
||||
:material-plus: [evaluate](#evaluate)
|
||||
:material-plus: [respond](#respond)
|
||||
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
:material-plus: [strategy](#strategy)
|
||||
@@ -40,11 +34,7 @@ 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 中被移除。
|
||||
|
||||
为此查询设置域名策略。已废弃,参阅[迁移指南](/zh/migration/#迁移-dns-规则动作-strategy-到规则项)。
|
||||
为此查询设置域名策略。
|
||||
|
||||
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||
|
||||
@@ -64,65 +54,6 @@ 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 规则中使用(不可在逻辑子规则内部使用)。
|
||||
使用 [`match_response`](/zh/configuration/dns/rule/#match_response) 或响应匹配字段的规则,
|
||||
需要位于更早的顶层 `evaluate` 规则之后。规则自身的 `evaluate` 动作不能满足这个条件,
|
||||
因为匹配发生在动作执行之前。
|
||||
|
||||
#### server
|
||||
|
||||
==必填==
|
||||
|
||||
目标 DNS 服务器的标签。
|
||||
|
||||
#### disable_cache
|
||||
|
||||
在此查询中禁用缓存。
|
||||
|
||||
#### rewrite_ttl
|
||||
|
||||
重写 DNS 回应中的 TTL。
|
||||
|
||||
#### client_subnet
|
||||
|
||||
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
||||
|
||||
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
|
||||
|
||||
将覆盖 `dns.client_subnet`.
|
||||
|
||||
### respond
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "respond"
|
||||
}
|
||||
```
|
||||
|
||||
`respond` 会终止规则评估,并直接返回前序 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作保存的已评估的响应。
|
||||
|
||||
此动作不会发起新的 DNS 查询,也没有额外选项。
|
||||
|
||||
只能用于前面已有顶层 `evaluate` 规则的场景。如果运行时命中该动作时没有已评估的响应,则请求会直接返回错误,而不是继续匹配后续规则。
|
||||
|
||||
### route-options
|
||||
|
||||
```json
|
||||
@@ -153,7 +84,7 @@ icon: material/new-box
|
||||
- `default`: 返回 REFUSED。
|
||||
- `drop`: 丢弃请求。
|
||||
|
||||
默认使用 `default`。
|
||||
默认使用 `defualt`。
|
||||
|
||||
#### no_drop
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ The type of the DNS server.
|
||||
|
||||
| Type | Format |
|
||||
|-----------------|---------------------------|
|
||||
| empty (default) | :material-note-remove: [Legacy](./legacy/) |
|
||||
| empty (default) | [Legacy](./legacy/) |
|
||||
| `local` | [Local](./local/) |
|
||||
| `hosts` | [Hosts](./hosts/) |
|
||||
| `tcp` | [TCP](./tcp/) |
|
||||
|
||||
@@ -29,7 +29,7 @@ DNS 服务器的类型。
|
||||
|
||||
| 类型 | 格式 |
|
||||
|-----------------|---------------------------|
|
||||
| empty (default) | :material-note-remove: [Legacy](./legacy/) |
|
||||
| empty (default) | [Legacy](./legacy/) |
|
||||
| `local` | [Local](./local/) |
|
||||
| `hosts` | [Hosts](./hosts/) |
|
||||
| `tcp` | [TCP](./tcp/) |
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
icon: material/note-remove
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "Removed in sing-box 1.14.0"
|
||||
!!! failure "Deprecated in sing-box 1.12.0"
|
||||
|
||||
Legacy DNS servers are deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-server-formats).
|
||||
Legacy DNS servers is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
|
||||
|
||||
!!! quote "Changes in sing-box 1.9.0"
|
||||
|
||||
@@ -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 overridden by `rules.[].client_subnet`.
|
||||
Can be overrides by `rules.[].client_subnet`.
|
||||
|
||||
Will override `dns.client_subnet`.
|
||||
Will overrides `dns.client_subnet`.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
icon: material/note-remove
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "已在 sing-box 1.14.0 移除"
|
||||
!!! failure "Deprecated in sing-box 1.12.0"
|
||||
|
||||
旧的 DNS 服务器配置已在 sing-box 1.12.0 废弃且已在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
||||
旧的 DNS 服务器配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
||||
|
||||
!!! quote "sing-box 1.9.0 中的更改"
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ Store fakeip in the cache file
|
||||
|
||||
Store rejected DNS response cache in the cache file
|
||||
|
||||
The check results of [Legacy Address Filter Fields](/configuration/dns/rule/#legacy-address-filter-fields)
|
||||
The check results of [Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields)
|
||||
will be cached until expiration.
|
||||
|
||||
#### rdrc_timeout
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
将拒绝的 DNS 响应缓存存储在缓存文件中。
|
||||
|
||||
[旧版地址筛选字段](/zh/configuration/dns/rule/#旧版地址筛选字段) 的检查结果将被缓存至过期。
|
||||
[地址筛选 DNS 规则项](/zh/configuration/dns/rule/#地址筛选字段) 的检查结果将被缓存至过期。
|
||||
|
||||
#### rdrc_timeout
|
||||
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
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)
|
||||
@@ -35,7 +31,6 @@ icon: material/alert-decagram
|
||||
"ignore_client_bandwidth": false,
|
||||
"tls": {},
|
||||
"masquerade": "", // or {}
|
||||
"bbr_profile": "",
|
||||
"brutal_debug": false
|
||||
}
|
||||
```
|
||||
@@ -146,14 +141,6 @@ 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,10 +2,6 @@
|
||||
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)
|
||||
@@ -35,7 +31,6 @@ icon: material/alert-decagram
|
||||
"ignore_client_bandwidth": false,
|
||||
"tls": {},
|
||||
"masquerade": "", // 或 {}
|
||||
"bbr_profile": "",
|
||||
"brutal_debug": false
|
||||
}
|
||||
```
|
||||
@@ -143,14 +138,6 @@ HTTP3 服务器认证失败时的行为 (对象配置)。
|
||||
|
||||
固定响应内容。
|
||||
|
||||
#### bbr_profile
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
BBR 拥塞控制算法配置,可选 `conservative` `standard` `aggressive`。
|
||||
|
||||
默认使用 `standard`。
|
||||
|
||||
#### brutal_debug
|
||||
|
||||
启用 Hysteria Brutal CC 的调试信息日志记录。
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
!!! quote "Changes in sing-box 1.14.0"
|
||||
|
||||
:material-plus: [hop_interval_max](#hop_interval_max)
|
||||
:material-plus: [bbr_profile](#bbr_profile)
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-plus: [server_ports](#server_ports)
|
||||
@@ -14,14 +9,13 @@
|
||||
{
|
||||
"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": {
|
||||
@@ -31,9 +25,8 @@
|
||||
"password": "goofy_ahh_password",
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"bbr_profile": "",
|
||||
"brutal_debug": false,
|
||||
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
```
|
||||
@@ -82,14 +75,6 @@ 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.
|
||||
@@ -124,14 +109,6 @@ 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,8 +1,3 @@
|
||||
!!! quote "sing-box 1.14.0 中的更改"
|
||||
|
||||
:material-plus: [hop_interval_max](#hop_interval_max)
|
||||
:material-plus: [bbr_profile](#bbr_profile)
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-plus: [server_ports](#server_ports)
|
||||
@@ -21,7 +16,6 @@
|
||||
"2080:3000"
|
||||
],
|
||||
"hop_interval": "",
|
||||
"hop_interval_max": "",
|
||||
"up_mbps": 100,
|
||||
"down_mbps": 100,
|
||||
"obfs": {
|
||||
@@ -31,9 +25,8 @@
|
||||
"password": "goofy_ahh_password",
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"bbr_profile": "",
|
||||
"brutal_debug": false,
|
||||
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
```
|
||||
@@ -80,14 +73,6 @@
|
||||
|
||||
默认使用 `30s`。
|
||||
|
||||
#### hop_interval_max
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
最大端口跳跃间隔,用于随机化。
|
||||
|
||||
如果设置,实际跳跃间隔将在 `hop_interval` 和 `hop_interval_max` 之间随机选择。
|
||||
|
||||
#### up_mbps, down_mbps
|
||||
|
||||
最大带宽。
|
||||
@@ -122,14 +107,6 @@ 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 overridden by `outbound.domain_resolver`.
|
||||
Can be overrides 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 overridden by `outbound.network_strategy`.
|
||||
Can be overrides by `outbound.network_strategy`.
|
||||
|
||||
Conflicts with `default_interface`.
|
||||
|
||||
|
||||
@@ -210,7 +210,7 @@ icon: material/new-box
|
||||
(`source_port` || `source_port_range`) &&
|
||||
`other fields`
|
||||
|
||||
Additionally, each branch inside an included rule-set can be considered merged into the outer rule, while different branches keep OR semantics.
|
||||
Additionally, included rule-sets can be considered merged rather than as a single rule sub-item.
|
||||
|
||||
#### 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 override `dns.client_subnet`.
|
||||
Will overrides `dns.client_subnet`.
|
||||
|
||||
@@ -14,43 +14,14 @@ check [Migration](../migration/#migrate-inline-acme-to-certificate-provider).
|
||||
|
||||
Old fields will be removed in sing-box 1.16.0.
|
||||
|
||||
#### Legacy `strategy` DNS rule action option
|
||||
|
||||
Legacy `strategy` DNS rule action option is deprecated,
|
||||
check [Migration](../migration/#migrate-dns-rule-action-strategy-to-rule-items).
|
||||
|
||||
Old fields will be removed in sing-box 1.16.0.
|
||||
|
||||
#### Legacy `ip_accept_any` DNS rule item
|
||||
|
||||
Legacy `ip_accept_any` DNS rule item is deprecated,
|
||||
check [Migration](../migration/#migrate-address-filter-fields-to-response-matching).
|
||||
|
||||
Old fields will be removed in sing-box 1.16.0.
|
||||
|
||||
#### Legacy `rule_set_ip_cidr_accept_empty` DNS rule item
|
||||
|
||||
Legacy `rule_set_ip_cidr_accept_empty` DNS rule item is deprecated,
|
||||
check [Migration](../migration/#migrate-address-filter-fields-to-response-matching).
|
||||
|
||||
Old fields will be removed in sing-box 1.16.0.
|
||||
|
||||
#### Legacy Address Filter Fields in DNS rules
|
||||
|
||||
Legacy Address Filter Fields (`ip_cidr`, `ip_is_private` without `match_response`)
|
||||
in DNS rules are deprecated,
|
||||
check [Migration](../migration/#migrate-address-filter-fields-to-response-matching).
|
||||
|
||||
Old behavior will be removed in sing-box 1.16.0.
|
||||
|
||||
## 1.12.0
|
||||
|
||||
#### Legacy DNS server formats
|
||||
|
||||
DNS servers are refactored,
|
||||
check [Migration](../migration/#migrate-to-new-dns-server-formats).
|
||||
check [Migration](../migration/#migrate-to-new-dns-servers).
|
||||
|
||||
Old formats were removed in sing-box 1.14.0.
|
||||
Compatibility for old formats will be removed in sing-box 1.14.0.
|
||||
|
||||
#### `outbound` DNS rule item
|
||||
|
||||
|
||||
@@ -14,34 +14,6 @@ TLS 中的内联 ACME 选项(`tls.acme`)已废弃,
|
||||
|
||||
旧字段将在 sing-box 1.16.0 中被移除。
|
||||
|
||||
#### 旧版 DNS 规则动作 `strategy` 选项
|
||||
|
||||
旧版 DNS 规则动作 `strategy` 选项已废弃,
|
||||
参阅[迁移指南](/zh/migration/#迁移-dns-规则动作-strategy-到规则项)。
|
||||
|
||||
旧字段将在 sing-box 1.16.0 中被移除。
|
||||
|
||||
#### 旧版 `ip_accept_any` DNS 规则项
|
||||
|
||||
旧版 `ip_accept_any` DNS 规则项已废弃,
|
||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
||||
|
||||
旧字段将在 sing-box 1.16.0 中被移除。
|
||||
|
||||
#### 旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项
|
||||
|
||||
旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项已废弃,
|
||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
||||
|
||||
旧字段将在 sing-box 1.16.0 中被移除。
|
||||
|
||||
#### 旧版地址筛选字段 (DNS 规则)
|
||||
|
||||
旧版地址筛选字段(不使用 `match_response` 的 `ip_cidr`、`ip_is_private`)已废弃,
|
||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
||||
|
||||
旧行为将在 sing-box 1.16.0 中被移除。
|
||||
|
||||
## 1.12.0
|
||||
|
||||
#### 旧的 DNS 服务器格式
|
||||
@@ -49,7 +21,7 @@ TLS 中的内联 ACME 选项(`tls.acme`)已废弃,
|
||||
DNS 服务器已重构,
|
||||
参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式).
|
||||
|
||||
旧格式已在 sing-box 1.14.0 中被移除。
|
||||
对旧格式的兼容性将在 sing-box 1.14.0 中被移除。
|
||||
|
||||
#### `outbound` DNS 规则项
|
||||
|
||||
|
||||
@@ -79,111 +79,6 @@ See [ACME](/configuration/shared/certificate-provider/acme/) for fields newly ad
|
||||
}
|
||||
```
|
||||
|
||||
### Migrate DNS rule action strategy to rule items
|
||||
|
||||
Legacy `strategy` DNS rule action option is deprecated.
|
||||
|
||||
In sing-box 1.14.0, internal domain resolution (Lookup) now splits A and AAAA queries
|
||||
at the rule level, so each query type is evaluated independently through the full rule chain.
|
||||
Use `ip_version` or `query_type` rule items to control which query types a rule matches.
|
||||
|
||||
!!! info "References"
|
||||
|
||||
[DNS Rule](/configuration/dns/rule/) /
|
||||
[DNS Rule Action](/configuration/dns/rule_action/)
|
||||
|
||||
=== ":material-card-remove: Deprecated"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"rules": [
|
||||
{
|
||||
"domain_suffix": ".cn",
|
||||
"action": "route",
|
||||
"server": "local",
|
||||
"strategy": "ipv4_only"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-card-multiple: New"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"rules": [
|
||||
{
|
||||
"domain_suffix": ".cn",
|
||||
"ip_version": 4,
|
||||
"action": "route",
|
||||
"server": "local"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Migrate address filter fields to response matching
|
||||
|
||||
Legacy Address Filter Fields (`ip_cidr`, `ip_is_private` without `match_response`) in DNS rules are deprecated,
|
||||
along with Legacy `ip_accept_any` and Legacy `rule_set_ip_cidr_accept_empty` DNS rule items.
|
||||
|
||||
In sing-box 1.14.0, use the [`evaluate`](/configuration/dns/rule_action/#evaluate) action
|
||||
to fetch a DNS response, then match against it explicitly with `match_response`.
|
||||
|
||||
!!! info "References"
|
||||
|
||||
[DNS Rule](/configuration/dns/rule/) /
|
||||
[DNS Rule Action](/configuration/dns/rule_action/#evaluate)
|
||||
|
||||
=== ":material-card-remove: Deprecated"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"rules": [
|
||||
{
|
||||
"rule_set": "geoip-cn",
|
||||
"action": "route",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"action": "route",
|
||||
"server": "remote"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-card-multiple: New"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"rules": [
|
||||
{
|
||||
"action": "evaluate",
|
||||
"server": "remote"
|
||||
},
|
||||
{
|
||||
"match_response": true,
|
||||
"rule_set": "geoip-cn",
|
||||
"action": "route",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"action": "route",
|
||||
"server": "remote"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 1.12.0
|
||||
|
||||
### Migrate to new DNS server formats
|
||||
|
||||
@@ -79,111 +79,6 @@ sing-box 1.14.0 新增字段参阅 [ACME](/zh/configuration/shared/certificate-p
|
||||
}
|
||||
```
|
||||
|
||||
### 迁移 DNS 规则动作 strategy 到规则项
|
||||
|
||||
旧版 DNS 规则动作 `strategy` 选项已废弃。
|
||||
|
||||
在 sing-box 1.14.0 中,内部域名解析(Lookup)现在在规则层拆分 A 和 AAAA 查询,
|
||||
每种查询类型独立通过完整的规则链评估。
|
||||
请使用 `ip_version` 或 `query_type` 规则项来控制规则匹配的查询类型。
|
||||
|
||||
!!! info "参考"
|
||||
|
||||
[DNS 规则](/zh/configuration/dns/rule/) /
|
||||
[DNS 规则动作](/zh/configuration/dns/rule_action/)
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"rules": [
|
||||
{
|
||||
"domain_suffix": ".cn",
|
||||
"action": "route",
|
||||
"server": "local",
|
||||
"strategy": "ipv4_only"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"rules": [
|
||||
{
|
||||
"domain_suffix": ".cn",
|
||||
"ip_version": 4,
|
||||
"action": "route",
|
||||
"server": "local"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 迁移地址筛选字段到响应匹配
|
||||
|
||||
旧版地址筛选字段(不使用 `match_response` 的 `ip_cidr`、`ip_is_private`)已废弃,
|
||||
旧版 `ip_accept_any` 和旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项也已废弃。
|
||||
|
||||
在 sing-box 1.14.0 中,请使用 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作
|
||||
获取 DNS 响应,然后通过 `match_response` 显式匹配。
|
||||
|
||||
!!! info "参考"
|
||||
|
||||
[DNS 规则](/zh/configuration/dns/rule/) /
|
||||
[DNS 规则动作](/zh/configuration/dns/rule_action/#evaluate)
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"rules": [
|
||||
{
|
||||
"rule_set": "geoip-cn",
|
||||
"action": "route",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"action": "route",
|
||||
"server": "remote"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"rules": [
|
||||
{
|
||||
"action": "evaluate",
|
||||
"server": "remote"
|
||||
},
|
||||
{
|
||||
"match_response": true,
|
||||
"rule_set": "geoip-cn",
|
||||
"action": "route",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"action": "route",
|
||||
"server": "remote"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 1.12.0
|
||||
|
||||
### 迁移到新的 DNS 服务器格式
|
||||
|
||||
@@ -57,6 +57,24 @@ func (n Note) MessageWithLink() string {
|
||||
}
|
||||
}
|
||||
|
||||
var OptionLegacyDNSTransport = Note{
|
||||
Name: "legacy-dns-transport",
|
||||
Description: "legacy DNS servers",
|
||||
DeprecatedVersion: "1.12.0",
|
||||
ScheduledVersion: "1.14.0",
|
||||
EnvName: "LEGACY_DNS_SERVERS",
|
||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats",
|
||||
}
|
||||
|
||||
var OptionLegacyDNSFakeIPOptions = Note{
|
||||
Name: "legacy-dns-fakeip-options",
|
||||
Description: "legacy DNS fakeip options",
|
||||
DeprecatedVersion: "1.12.0",
|
||||
ScheduledVersion: "1.14.0",
|
||||
EnvName: "LEGACY_DNS_FAKEIP_OPTIONS",
|
||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats",
|
||||
}
|
||||
|
||||
var OptionOutboundDNSRuleItem = Note{
|
||||
Name: "outbound-dns-rule-item",
|
||||
Description: "outbound DNS rule item",
|
||||
@@ -93,49 +111,11 @@ 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: "Legacy `ip_accept_any` DNS rule item",
|
||||
DeprecatedVersion: "1.14.0",
|
||||
ScheduledVersion: "1.16.0",
|
||||
EnvName: "DNS_RULE_IP_ACCEPT_ANY",
|
||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-address-filter-fields-to-response-matching",
|
||||
}
|
||||
|
||||
var OptionRuleSetIPCIDRAcceptEmpty = Note{
|
||||
Name: "dns-rule-rule-set-ip-cidr-accept-empty",
|
||||
Description: "Legacy `rule_set_ip_cidr_accept_empty` DNS rule item",
|
||||
DeprecatedVersion: "1.14.0",
|
||||
ScheduledVersion: "1.16.0",
|
||||
EnvName: "DNS_RULE_RULE_SET_IP_CIDR_ACCEPT_EMPTY",
|
||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-address-filter-fields-to-response-matching",
|
||||
}
|
||||
|
||||
var OptionLegacyDNSAddressFilter = Note{
|
||||
Name: "legacy-dns-address-filter",
|
||||
Description: "Legacy Address Filter Fields in DNS rules",
|
||||
DeprecatedVersion: "1.14.0",
|
||||
ScheduledVersion: "1.16.0",
|
||||
EnvName: "LEGACY_DNS_ADDRESS_FILTER",
|
||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-address-filter-fields-to-response-matching",
|
||||
}
|
||||
|
||||
var OptionLegacyDNSRuleStrategy = Note{
|
||||
Name: "legacy-dns-rule-strategy",
|
||||
Description: "Legacy `strategy` DNS rule action option",
|
||||
DeprecatedVersion: "1.14.0",
|
||||
ScheduledVersion: "1.16.0",
|
||||
EnvName: "LEGACY_DNS_RULE_STRATEGY",
|
||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-dns-rule-action-strategy-to-rule-items",
|
||||
}
|
||||
|
||||
var Options = []Note{
|
||||
OptionLegacyDNSTransport,
|
||||
OptionLegacyDNSFakeIPOptions,
|
||||
OptionOutboundDNSRuleItem,
|
||||
OptionMissingDomainResolver,
|
||||
OptionLegacyDomainStrategyOptions,
|
||||
OptionInlineACME,
|
||||
OptionIPAcceptAny,
|
||||
OptionRuleSetIPCIDRAcceptEmpty,
|
||||
OptionLegacyDNSAddressFilter,
|
||||
OptionLegacyDNSRuleStrategy,
|
||||
}
|
||||
|
||||
@@ -6,5 +6,4 @@ const (
|
||||
CommandGroup
|
||||
CommandClashMode
|
||||
CommandConnections
|
||||
CommandOutbounds
|
||||
)
|
||||
|
||||
@@ -47,7 +47,6 @@ type CommandClientHandler interface {
|
||||
WriteLogs(messageList LogIterator)
|
||||
WriteStatus(message *StatusMessage)
|
||||
WriteGroups(message OutboundGroupIterator)
|
||||
WriteOutbounds(message OutboundGroupItemIterator)
|
||||
InitializeClashMode(modeList StringIterator, currentMode string)
|
||||
UpdateClashMode(newMode string)
|
||||
WriteConnectionEvents(events *ConnectionEvents)
|
||||
@@ -244,8 +243,6 @@ func (c *CommandClient) dispatchCommands() error {
|
||||
go c.handleClashModeStream()
|
||||
case CommandConnections:
|
||||
go c.handleConnectionsStream()
|
||||
case CommandOutbounds:
|
||||
go c.handleOutboundsStream()
|
||||
default:
|
||||
return E.New("unknown command: ", command)
|
||||
}
|
||||
@@ -459,25 +456,6 @@ func (c *CommandClient) handleConnectionsStream() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandClient) handleOutboundsStream() {
|
||||
client, ctx := c.getStreamContext()
|
||||
|
||||
stream, err := client.SubscribeOutbounds(ctx, &emptypb.Empty{})
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
list, err := stream.Recv()
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
}
|
||||
c.handler.WriteOutbounds(outboundGroupItemListFromGRPC(list))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommandClient) SelectOutbound(groupTag string, outboundTag string) error {
|
||||
_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {
|
||||
return client.SelectOutbound(context.Background(), &daemon.SelectOutboundRequest{
|
||||
@@ -562,31 +540,6 @@ func (c *CommandClient) SetSystemProxyEnabled(isEnabled bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) TriggerGoCrash() error {
|
||||
_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {
|
||||
return client.TriggerDebugCrash(context.Background(), &daemon.DebugCrashRequest{
|
||||
Type: daemon.DebugCrashRequest_GO,
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) TriggerNativeCrash() error {
|
||||
_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {
|
||||
return client.TriggerDebugCrash(context.Background(), &daemon.DebugCrashRequest{
|
||||
Type: daemon.DebugCrashRequest_NATIVE,
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) TriggerOOMReport() error {
|
||||
_, err := callWithResult(c, func(client daemon.StartedServiceClient) (*emptypb.Empty, error) {
|
||||
return client.TriggerOOMReport(context.Background(), &emptypb.Empty{})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) {
|
||||
return callWithResult(c, func(client daemon.StartedServiceClient) (DeprecatedNoteIterator, error) {
|
||||
warnings, err := client.GetDeprecatedWarnings(context.Background(), &emptypb.Empty{})
|
||||
@@ -596,10 +549,8 @@ func (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) {
|
||||
var notes []*DeprecatedNote
|
||||
for _, warning := range warnings.Warnings {
|
||||
notes = append(notes, &DeprecatedNote{
|
||||
Description: warning.Description,
|
||||
DeprecatedVersion: warning.DeprecatedVersion,
|
||||
ScheduledVersion: warning.ScheduledVersion,
|
||||
MigrationLink: warning.MigrationLink,
|
||||
Description: warning.Message,
|
||||
MigrationLink: warning.MigrationLink,
|
||||
})
|
||||
}
|
||||
return newIterator(notes), nil
|
||||
@@ -625,78 +576,3 @@ func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error {
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *CommandClient) ListOutbounds() (OutboundGroupItemIterator, error) {
|
||||
return callWithResult(c, func(client daemon.StartedServiceClient) (OutboundGroupItemIterator, error) {
|
||||
list, err := client.ListOutbounds(context.Background(), &emptypb.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return outboundGroupItemListFromGRPC(list), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (c *CommandClient) StartNetworkQualityTest(configURL string, outboundTag string, handler NetworkQualityTestHandler) error {
|
||||
return c.StartNetworkQualityTestWithSerialAndRuntime(
|
||||
configURL,
|
||||
outboundTag,
|
||||
false,
|
||||
NetworkQualityDefaultMaxRuntimeSeconds,
|
||||
handler,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *CommandClient) StartNetworkQualityTestWithSerial(configURL string, outboundTag string, serial bool, handler NetworkQualityTestHandler) error {
|
||||
return c.StartNetworkQualityTestWithSerialAndRuntime(
|
||||
configURL,
|
||||
outboundTag,
|
||||
serial,
|
||||
NetworkQualityDefaultMaxRuntimeSeconds,
|
||||
handler,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *CommandClient) StartNetworkQualityTestWithSerialAndRuntime(configURL string, outboundTag string, serial bool, maxRuntimeSeconds int32, handler NetworkQualityTestHandler) error {
|
||||
client, err := c.getClientForCall()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.standalone {
|
||||
defer c.closeConnection()
|
||||
}
|
||||
stream, err := client.StartNetworkQualityTest(context.Background(), &daemon.NetworkQualityTestRequest{
|
||||
ConfigURL: configURL,
|
||||
OutboundTag: outboundTag,
|
||||
Serial: serial,
|
||||
MaxRuntimeSeconds: maxRuntimeSeconds,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
event, recvErr := stream.Recv()
|
||||
if recvErr != nil {
|
||||
handler.OnError(recvErr.Error())
|
||||
return recvErr
|
||||
}
|
||||
if event.IsFinal {
|
||||
if event.Error != "" {
|
||||
handler.OnError(event.Error)
|
||||
} else {
|
||||
handler.OnResult(&NetworkQualityResult{
|
||||
DownloadCapacity: event.DownloadCapacity,
|
||||
UploadCapacity: event.UploadCapacity,
|
||||
DownloadRPM: event.DownloadRPM,
|
||||
UploadRPM: event.UploadRPM,
|
||||
IdleLatencyMs: event.IdleLatencyMs,
|
||||
DownloadCapacityAccuracy: event.DownloadCapacityAccuracy,
|
||||
UploadCapacityAccuracy: event.UploadCapacityAccuracy,
|
||||
DownloadRPMAccuracy: event.DownloadRPMAccuracy,
|
||||
UploadRPMAccuracy: event.UploadRPMAccuracy,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
handler.OnProgress(networkQualityProgressFromGRPC(event))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ type CommandServerHandler interface {
|
||||
ServiceReload() error
|
||||
GetSystemProxyStatus() (*SystemProxyStatus, error)
|
||||
SetSystemProxyEnabled(enabled bool) error
|
||||
TriggerNativeCrash() error
|
||||
WriteDebugMessage(message string)
|
||||
}
|
||||
|
||||
@@ -58,12 +57,10 @@ func NewCommandServer(handler CommandServerHandler, platformInterface PlatformIn
|
||||
server.StartedService = daemon.NewStartedService(daemon.ServiceOptions{
|
||||
Context: ctx,
|
||||
// Platform: platformWrapper,
|
||||
Handler: (*platformHandler)(server),
|
||||
Debug: sDebug,
|
||||
LogMaxLines: sLogMaxLines,
|
||||
OOMKillerEnabled: sOOMKillerEnabled,
|
||||
OOMKillerDisabled: sOOMKillerDisabled,
|
||||
OOMMemoryLimit: uint64(sOOMMemoryLimit),
|
||||
Handler: (*platformHandler)(server),
|
||||
Debug: sDebug,
|
||||
LogMaxLines: sLogMaxLines,
|
||||
OOMKiller: memoryLimitEnabled,
|
||||
// WorkingDirectory: sWorkingPath,
|
||||
// TempDirectory: sTempPath,
|
||||
// UserID: sUserID,
|
||||
@@ -173,16 +170,11 @@ type OverrideOptions struct {
|
||||
}
|
||||
|
||||
func (s *CommandServer) StartOrReloadService(configContent string, options *OverrideOptions) error {
|
||||
saveConfigSnapshot(configContent)
|
||||
err := s.StartedService.StartOrReloadService(configContent, &daemon.OverrideOptions{
|
||||
return s.StartedService.StartOrReloadService(configContent, &daemon.OverrideOptions{
|
||||
AutoRedirect: options.AutoRedirect,
|
||||
IncludePackage: iteratorToArray(options.IncludePackage),
|
||||
ExcludePackage: iteratorToArray(options.ExcludePackage),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CommandServer) CloseService() error {
|
||||
@@ -279,10 +271,6 @@ func (h *platformHandler) SetSystemProxyEnabled(enabled bool) error {
|
||||
return (*CommandServer)(h).handler.SetSystemProxyEnabled(enabled)
|
||||
}
|
||||
|
||||
func (h *platformHandler) TriggerNativeCrash() error {
|
||||
return (*CommandServer)(h).handler.TriggerNativeCrash()
|
||||
}
|
||||
|
||||
func (h *platformHandler) WriteDebugMessage(message string) {
|
||||
(*CommandServer)(h).handler.WriteDebugMessage(message)
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
package libbox
|
||||
|
||||
import "github.com/sagernet/sing-box/daemon"
|
||||
|
||||
type NetworkQualityProgress struct {
|
||||
Phase int32
|
||||
DownloadCapacity int64
|
||||
UploadCapacity int64
|
||||
DownloadRPM int32
|
||||
UploadRPM int32
|
||||
IdleLatencyMs int32
|
||||
ElapsedMs int64
|
||||
IsFinal bool
|
||||
Error string
|
||||
DownloadCapacityAccuracy int32
|
||||
UploadCapacityAccuracy int32
|
||||
DownloadRPMAccuracy int32
|
||||
UploadRPMAccuracy int32
|
||||
}
|
||||
|
||||
type NetworkQualityResult struct {
|
||||
DownloadCapacity int64
|
||||
UploadCapacity int64
|
||||
DownloadRPM int32
|
||||
UploadRPM int32
|
||||
IdleLatencyMs int32
|
||||
DownloadCapacityAccuracy int32
|
||||
UploadCapacityAccuracy int32
|
||||
DownloadRPMAccuracy int32
|
||||
UploadRPMAccuracy int32
|
||||
}
|
||||
|
||||
type NetworkQualityTestHandler interface {
|
||||
OnProgress(progress *NetworkQualityProgress)
|
||||
OnResult(result *NetworkQualityResult)
|
||||
OnError(message string)
|
||||
}
|
||||
|
||||
func outboundGroupItemListFromGRPC(list *daemon.OutboundList) OutboundGroupItemIterator {
|
||||
if list == nil || len(list.Outbounds) == 0 {
|
||||
return newIterator([]*OutboundGroupItem{})
|
||||
}
|
||||
var items []*OutboundGroupItem
|
||||
for _, ob := range list.Outbounds {
|
||||
items = append(items, &OutboundGroupItem{
|
||||
Tag: ob.Tag,
|
||||
Type: ob.Type,
|
||||
URLTestTime: ob.UrlTestTime,
|
||||
URLTestDelay: ob.UrlTestDelay,
|
||||
})
|
||||
}
|
||||
return newIterator(items)
|
||||
}
|
||||
|
||||
func networkQualityProgressFromGRPC(event *daemon.NetworkQualityTestProgress) *NetworkQualityProgress {
|
||||
return &NetworkQualityProgress{
|
||||
Phase: event.Phase,
|
||||
DownloadCapacity: event.DownloadCapacity,
|
||||
UploadCapacity: event.UploadCapacity,
|
||||
DownloadRPM: event.DownloadRPM,
|
||||
UploadRPM: event.UploadRPM,
|
||||
IdleLatencyMs: event.IdleLatencyMs,
|
||||
ElapsedMs: event.ElapsedMs,
|
||||
IsFinal: event.IsFinal,
|
||||
Error: event.Error,
|
||||
DownloadCapacityAccuracy: event.DownloadCapacityAccuracy,
|
||||
UploadCapacityAccuracy: event.UploadCapacityAccuracy,
|
||||
DownloadRPMAccuracy: event.DownloadRPMAccuracy,
|
||||
UploadRPMAccuracy: event.UploadRPMAccuracy,
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/service/oomkiller"
|
||||
tun "github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -23,8 +22,6 @@ import (
|
||||
"github.com/sagernet/sing/service/filemanager"
|
||||
)
|
||||
|
||||
var sOOMReporter oomkiller.OOMReporter
|
||||
|
||||
func baseContext(platformInterface PlatformInterface) context.Context {
|
||||
dnsRegistry := include.DNSTransportRegistry()
|
||||
if platformInterface != nil {
|
||||
@@ -36,9 +33,6 @@ func baseContext(platformInterface PlatformInterface) context.Context {
|
||||
}
|
||||
ctx := context.Background()
|
||||
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
|
||||
if sOOMReporter != nil {
|
||||
ctx = service.ContextWith[oomkiller.OOMReporter](ctx, sOOMReporter)
|
||||
}
|
||||
return box.Context(ctx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry(), include.CertificateProviderRegistry())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package libbox
|
||||
|
||||
import "time"
|
||||
|
||||
func TriggerGoPanic() {
|
||||
time.AfterFunc(200*time.Millisecond, func() {
|
||||
panic("debug go crash")
|
||||
})
|
||||
}
|
||||
@@ -52,11 +52,6 @@ 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 (
|
||||
@@ -244,31 +239,3 @@ 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
|
||||
}
|
||||
|
||||
@@ -1,390 +0,0 @@
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package oomprofile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
tagProfile_SampleType = 1
|
||||
tagProfile_Sample = 2
|
||||
tagProfile_Mapping = 3
|
||||
tagProfile_Location = 4
|
||||
tagProfile_Function = 5
|
||||
tagProfile_StringTable = 6
|
||||
tagProfile_TimeNanos = 9
|
||||
tagProfile_PeriodType = 11
|
||||
tagProfile_Period = 12
|
||||
tagProfile_DefaultSampleType = 14
|
||||
|
||||
tagValueType_Type = 1
|
||||
tagValueType_Unit = 2
|
||||
|
||||
tagSample_Location = 1
|
||||
tagSample_Value = 2
|
||||
tagSample_Label = 3
|
||||
|
||||
tagLabel_Key = 1
|
||||
tagLabel_Str = 2
|
||||
tagLabel_Num = 3
|
||||
|
||||
tagMapping_ID = 1
|
||||
tagMapping_Start = 2
|
||||
tagMapping_Limit = 3
|
||||
tagMapping_Offset = 4
|
||||
tagMapping_Filename = 5
|
||||
tagMapping_BuildID = 6
|
||||
tagMapping_HasFunctions = 7
|
||||
tagMapping_HasFilenames = 8
|
||||
tagMapping_HasLineNumbers = 9
|
||||
tagMapping_HasInlineFrames = 10
|
||||
|
||||
tagLocation_ID = 1
|
||||
tagLocation_MappingID = 2
|
||||
tagLocation_Address = 3
|
||||
tagLocation_Line = 4
|
||||
|
||||
tagLine_FunctionID = 1
|
||||
tagLine_Line = 2
|
||||
|
||||
tagFunction_ID = 1
|
||||
tagFunction_Name = 2
|
||||
tagFunction_SystemName = 3
|
||||
tagFunction_Filename = 4
|
||||
tagFunction_StartLine = 5
|
||||
)
|
||||
|
||||
type memMap struct {
|
||||
start uintptr
|
||||
end uintptr
|
||||
offset uint64
|
||||
file string
|
||||
buildID string
|
||||
funcs symbolizeFlag
|
||||
fake bool
|
||||
}
|
||||
|
||||
type symbolizeFlag uint8
|
||||
|
||||
const (
|
||||
lookupTried symbolizeFlag = 1 << iota
|
||||
lookupFailed
|
||||
)
|
||||
|
||||
func newProfileBuilder(w io.Writer) *profileBuilder {
|
||||
builder := &profileBuilder{
|
||||
start: time.Now(),
|
||||
w: w,
|
||||
strings: []string{""},
|
||||
stringMap: map[string]int{"": 0},
|
||||
locs: map[uintptr]locInfo{},
|
||||
funcs: map[string]int{},
|
||||
}
|
||||
builder.readMapping()
|
||||
return builder
|
||||
}
|
||||
|
||||
func (b *profileBuilder) stringIndex(s string) int64 {
|
||||
id, ok := b.stringMap[s]
|
||||
if !ok {
|
||||
id = len(b.strings)
|
||||
b.strings = append(b.strings, s)
|
||||
b.stringMap[s] = id
|
||||
}
|
||||
return int64(id)
|
||||
}
|
||||
|
||||
func (b *profileBuilder) flush() {
|
||||
const dataFlush = 4096
|
||||
if b.err != nil || b.pb.nest != 0 || len(b.pb.data) <= dataFlush {
|
||||
return
|
||||
}
|
||||
|
||||
_, b.err = b.w.Write(b.pb.data)
|
||||
b.pb.data = b.pb.data[:0]
|
||||
}
|
||||
|
||||
func (b *profileBuilder) pbValueType(tag int, typ string, unit string) {
|
||||
start := b.pb.startMessage()
|
||||
b.pb.int64(tagValueType_Type, b.stringIndex(typ))
|
||||
b.pb.int64(tagValueType_Unit, b.stringIndex(unit))
|
||||
b.pb.endMessage(tag, start)
|
||||
}
|
||||
|
||||
func (b *profileBuilder) pbSample(values []int64, locs []uint64, labels func()) {
|
||||
start := b.pb.startMessage()
|
||||
b.pb.int64s(tagSample_Value, values)
|
||||
b.pb.uint64s(tagSample_Location, locs)
|
||||
if labels != nil {
|
||||
labels()
|
||||
}
|
||||
b.pb.endMessage(tagProfile_Sample, start)
|
||||
b.flush()
|
||||
}
|
||||
|
||||
func (b *profileBuilder) pbLabel(tag int, key string, str string, num int64) {
|
||||
start := b.pb.startMessage()
|
||||
b.pb.int64Opt(tagLabel_Key, b.stringIndex(key))
|
||||
b.pb.int64Opt(tagLabel_Str, b.stringIndex(str))
|
||||
b.pb.int64Opt(tagLabel_Num, num)
|
||||
b.pb.endMessage(tag, start)
|
||||
}
|
||||
|
||||
func (b *profileBuilder) pbLine(tag int, funcID uint64, line int64) {
|
||||
start := b.pb.startMessage()
|
||||
b.pb.uint64Opt(tagLine_FunctionID, funcID)
|
||||
b.pb.int64Opt(tagLine_Line, line)
|
||||
b.pb.endMessage(tag, start)
|
||||
}
|
||||
|
||||
func (b *profileBuilder) pbMapping(tag int, id uint64, base uint64, limit uint64, offset uint64, file string, buildID string, hasFuncs bool) {
|
||||
start := b.pb.startMessage()
|
||||
b.pb.uint64Opt(tagMapping_ID, id)
|
||||
b.pb.uint64Opt(tagMapping_Start, base)
|
||||
b.pb.uint64Opt(tagMapping_Limit, limit)
|
||||
b.pb.uint64Opt(tagMapping_Offset, offset)
|
||||
b.pb.int64Opt(tagMapping_Filename, b.stringIndex(file))
|
||||
b.pb.int64Opt(tagMapping_BuildID, b.stringIndex(buildID))
|
||||
if hasFuncs {
|
||||
b.pb.bool(tagMapping_HasFunctions, true)
|
||||
}
|
||||
b.pb.endMessage(tag, start)
|
||||
}
|
||||
|
||||
func (b *profileBuilder) build() error {
|
||||
if b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
|
||||
b.pb.int64Opt(tagProfile_TimeNanos, b.start.UnixNano())
|
||||
for i, mapping := range b.mem {
|
||||
hasFunctions := mapping.funcs == lookupTried
|
||||
b.pbMapping(tagProfile_Mapping, uint64(i+1), uint64(mapping.start), uint64(mapping.end), mapping.offset, mapping.file, mapping.buildID, hasFunctions)
|
||||
}
|
||||
b.pb.strings(tagProfile_StringTable, b.strings)
|
||||
if b.err != nil {
|
||||
return b.err
|
||||
}
|
||||
_, err := b.w.Write(b.pb.data)
|
||||
return err
|
||||
}
|
||||
|
||||
func allFrames(addr uintptr) ([]runtime.Frame, symbolizeFlag) {
|
||||
frames := runtime.CallersFrames([]uintptr{addr})
|
||||
frame, more := frames.Next()
|
||||
if frame.Function == "runtime.goexit" {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
result := lookupTried
|
||||
if frame.PC == 0 || frame.Function == "" || frame.File == "" || frame.Line == 0 {
|
||||
result |= lookupFailed
|
||||
}
|
||||
if frame.PC == 0 {
|
||||
frame.PC = addr - 1
|
||||
}
|
||||
|
||||
ret := []runtime.Frame{frame}
|
||||
for frame.Function != "runtime.goexit" && more {
|
||||
frame, more = frames.Next()
|
||||
ret = append(ret, frame)
|
||||
}
|
||||
return ret, result
|
||||
}
|
||||
|
||||
type locInfo struct {
|
||||
id uint64
|
||||
|
||||
pcs []uintptr
|
||||
|
||||
firstPCFrames []runtime.Frame
|
||||
firstPCSymbolizeResult symbolizeFlag
|
||||
}
|
||||
|
||||
func (b *profileBuilder) appendLocsForStack(locs []uint64, stk []uintptr) []uint64 {
|
||||
b.deck.reset()
|
||||
origStk := stk
|
||||
stk = runtimeExpandFinalInlineFrame(stk)
|
||||
|
||||
for len(stk) > 0 {
|
||||
addr := stk[0]
|
||||
if loc, ok := b.locs[addr]; ok {
|
||||
if len(b.deck.pcs) > 0 {
|
||||
if b.deck.tryAdd(addr, loc.firstPCFrames, loc.firstPCSymbolizeResult) {
|
||||
stk = stk[1:]
|
||||
continue
|
||||
}
|
||||
}
|
||||
if id := b.emitLocation(); id > 0 {
|
||||
locs = append(locs, id)
|
||||
}
|
||||
locs = append(locs, loc.id)
|
||||
if len(loc.pcs) > len(stk) {
|
||||
panic(fmt.Sprintf("stack too short to match cached location; stk = %#x, loc.pcs = %#x, original stk = %#x", stk, loc.pcs, origStk))
|
||||
}
|
||||
stk = stk[len(loc.pcs):]
|
||||
continue
|
||||
}
|
||||
|
||||
frames, symbolizeResult := allFrames(addr)
|
||||
if len(frames) == 0 {
|
||||
if id := b.emitLocation(); id > 0 {
|
||||
locs = append(locs, id)
|
||||
}
|
||||
stk = stk[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
if b.deck.tryAdd(addr, frames, symbolizeResult) {
|
||||
stk = stk[1:]
|
||||
continue
|
||||
}
|
||||
if id := b.emitLocation(); id > 0 {
|
||||
locs = append(locs, id)
|
||||
}
|
||||
|
||||
if loc, ok := b.locs[addr]; ok {
|
||||
locs = append(locs, loc.id)
|
||||
stk = stk[len(loc.pcs):]
|
||||
} else {
|
||||
b.deck.tryAdd(addr, frames, symbolizeResult)
|
||||
stk = stk[1:]
|
||||
}
|
||||
}
|
||||
if id := b.emitLocation(); id > 0 {
|
||||
locs = append(locs, id)
|
||||
}
|
||||
return locs
|
||||
}
|
||||
|
||||
type pcDeck struct {
|
||||
pcs []uintptr
|
||||
frames []runtime.Frame
|
||||
symbolizeResult symbolizeFlag
|
||||
|
||||
firstPCFrames int
|
||||
firstPCSymbolizeResult symbolizeFlag
|
||||
}
|
||||
|
||||
func (d *pcDeck) reset() {
|
||||
d.pcs = d.pcs[:0]
|
||||
d.frames = d.frames[:0]
|
||||
d.symbolizeResult = 0
|
||||
d.firstPCFrames = 0
|
||||
d.firstPCSymbolizeResult = 0
|
||||
}
|
||||
|
||||
func (d *pcDeck) tryAdd(pc uintptr, frames []runtime.Frame, symbolizeResult symbolizeFlag) bool {
|
||||
if existing := len(d.frames); existing > 0 {
|
||||
newFrame := frames[0]
|
||||
last := d.frames[existing-1]
|
||||
if last.Func != nil {
|
||||
return false
|
||||
}
|
||||
if last.Entry == 0 || newFrame.Entry == 0 {
|
||||
return false
|
||||
}
|
||||
if last.Entry != newFrame.Entry {
|
||||
return false
|
||||
}
|
||||
if runtimeFrameSymbolName(&last) == runtimeFrameSymbolName(&newFrame) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
d.pcs = append(d.pcs, pc)
|
||||
d.frames = append(d.frames, frames...)
|
||||
d.symbolizeResult |= symbolizeResult
|
||||
if len(d.pcs) == 1 {
|
||||
d.firstPCFrames = len(d.frames)
|
||||
d.firstPCSymbolizeResult = symbolizeResult
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *profileBuilder) emitLocation() uint64 {
|
||||
if len(b.deck.pcs) == 0 {
|
||||
return 0
|
||||
}
|
||||
defer b.deck.reset()
|
||||
|
||||
addr := b.deck.pcs[0]
|
||||
firstFrame := b.deck.frames[0]
|
||||
|
||||
type newFunc struct {
|
||||
id uint64
|
||||
name string
|
||||
file string
|
||||
startLine int64
|
||||
}
|
||||
|
||||
newFuncs := make([]newFunc, 0, 8)
|
||||
id := uint64(len(b.locs)) + 1
|
||||
b.locs[addr] = locInfo{
|
||||
id: id,
|
||||
pcs: append([]uintptr{}, b.deck.pcs...),
|
||||
firstPCFrames: append([]runtime.Frame{}, b.deck.frames[:b.deck.firstPCFrames]...),
|
||||
firstPCSymbolizeResult: b.deck.firstPCSymbolizeResult,
|
||||
}
|
||||
|
||||
start := b.pb.startMessage()
|
||||
b.pb.uint64Opt(tagLocation_ID, id)
|
||||
b.pb.uint64Opt(tagLocation_Address, uint64(firstFrame.PC))
|
||||
for _, frame := range b.deck.frames {
|
||||
funcName := runtimeFrameSymbolName(&frame)
|
||||
funcID := uint64(b.funcs[funcName])
|
||||
if funcID == 0 {
|
||||
funcID = uint64(len(b.funcs)) + 1
|
||||
b.funcs[funcName] = int(funcID)
|
||||
newFuncs = append(newFuncs, newFunc{
|
||||
id: funcID,
|
||||
name: funcName,
|
||||
file: frame.File,
|
||||
startLine: int64(runtimeFrameStartLine(&frame)),
|
||||
})
|
||||
}
|
||||
b.pbLine(tagLocation_Line, funcID, int64(frame.Line))
|
||||
}
|
||||
for i := range b.mem {
|
||||
if (b.mem[i].start <= addr && addr < b.mem[i].end) || b.mem[i].fake {
|
||||
b.pb.uint64Opt(tagLocation_MappingID, uint64(i+1))
|
||||
mapping := b.mem[i]
|
||||
mapping.funcs |= b.deck.symbolizeResult
|
||||
b.mem[i] = mapping
|
||||
break
|
||||
}
|
||||
}
|
||||
b.pb.endMessage(tagProfile_Location, start)
|
||||
|
||||
for _, fn := range newFuncs {
|
||||
start := b.pb.startMessage()
|
||||
b.pb.uint64Opt(tagFunction_ID, fn.id)
|
||||
b.pb.int64Opt(tagFunction_Name, b.stringIndex(fn.name))
|
||||
b.pb.int64Opt(tagFunction_SystemName, b.stringIndex(fn.name))
|
||||
b.pb.int64Opt(tagFunction_Filename, b.stringIndex(fn.file))
|
||||
b.pb.int64Opt(tagFunction_StartLine, fn.startLine)
|
||||
b.pb.endMessage(tagProfile_Function, start)
|
||||
}
|
||||
|
||||
b.flush()
|
||||
return id
|
||||
}
|
||||
|
||||
func (b *profileBuilder) addMapping(lo uint64, hi uint64, offset uint64, file string, buildID string) {
|
||||
b.addMappingEntry(lo, hi, offset, file, buildID, false)
|
||||
}
|
||||
|
||||
func (b *profileBuilder) addMappingEntry(lo uint64, hi uint64, offset uint64, file string, buildID string, fake bool) {
|
||||
b.mem = append(b.mem, memMap{
|
||||
start: uintptr(lo),
|
||||
end: uintptr(hi),
|
||||
offset: offset,
|
||||
file: file,
|
||||
buildID: buildID,
|
||||
fake: fake,
|
||||
})
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
//go:build darwin && amd64
|
||||
|
||||
package oomprofile
|
||||
|
||||
type machVMRegionBasicInfoData struct {
|
||||
Protection int32
|
||||
MaxProtection int32
|
||||
Inheritance uint32
|
||||
Shared uint32
|
||||
Reserved uint32
|
||||
Offset [8]byte
|
||||
Behavior int32
|
||||
UserWiredCount uint16
|
||||
PadCgo1 [2]byte
|
||||
}
|
||||
|
||||
const (
|
||||
_VM_PROT_READ = 0x1
|
||||
_VM_PROT_EXECUTE = 0x4
|
||||
|
||||
_MACH_SEND_INVALID_DEST = 0x10000003
|
||||
|
||||
_MAXPATHLEN = 0x400
|
||||
)
|
||||
@@ -1,24 +0,0 @@
|
||||
//go:build darwin && arm64
|
||||
|
||||
package oomprofile
|
||||
|
||||
type machVMRegionBasicInfoData struct {
|
||||
Protection int32
|
||||
MaxProtection int32
|
||||
Inheritance uint32
|
||||
Shared int32
|
||||
Reserved int32
|
||||
Offset [8]byte
|
||||
Behavior int32
|
||||
UserWiredCount uint16
|
||||
PadCgo1 [2]byte
|
||||
}
|
||||
|
||||
const (
|
||||
_VM_PROT_READ = 0x1
|
||||
_VM_PROT_EXECUTE = 0x4
|
||||
|
||||
_MACH_SEND_INVALID_DEST = 0x10000003
|
||||
|
||||
_MAXPATHLEN = 0x400
|
||||
)
|
||||
@@ -1,46 +0,0 @@
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package oomprofile
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
_ "runtime/pprof"
|
||||
"unsafe"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
//go:linkname runtimeMemProfileInternal runtime.pprof_memProfileInternal
|
||||
func runtimeMemProfileInternal(p []memProfileRecord, inuseZero bool) (n int, ok bool)
|
||||
|
||||
//go:linkname runtimeBlockProfileInternal runtime.pprof_blockProfileInternal
|
||||
func runtimeBlockProfileInternal(p []blockProfileRecord) (n int, ok bool)
|
||||
|
||||
//go:linkname runtimeMutexProfileInternal runtime.pprof_mutexProfileInternal
|
||||
func runtimeMutexProfileInternal(p []blockProfileRecord) (n int, ok bool)
|
||||
|
||||
//go:linkname runtimeThreadCreateInternal runtime.pprof_threadCreateInternal
|
||||
func runtimeThreadCreateInternal(p []stackRecord) (n int, ok bool)
|
||||
|
||||
//go:linkname runtimeGoroutineProfileWithLabels runtime.pprof_goroutineProfileWithLabels
|
||||
func runtimeGoroutineProfileWithLabels(p []stackRecord, labels []unsafe.Pointer) (n int, ok bool)
|
||||
|
||||
//go:linkname runtimeCyclesPerSecond runtime/pprof.runtime_cyclesPerSecond
|
||||
func runtimeCyclesPerSecond() int64
|
||||
|
||||
//go:linkname runtimeMakeProfStack runtime.pprof_makeProfStack
|
||||
func runtimeMakeProfStack() []uintptr
|
||||
|
||||
//go:linkname runtimeFrameStartLine runtime/pprof.runtime_FrameStartLine
|
||||
func runtimeFrameStartLine(f *runtime.Frame) int
|
||||
|
||||
//go:linkname runtimeFrameSymbolName runtime/pprof.runtime_FrameSymbolName
|
||||
func runtimeFrameSymbolName(f *runtime.Frame) string
|
||||
|
||||
//go:linkname runtimeExpandFinalInlineFrame runtime/pprof.runtime_expandFinalInlineFrame
|
||||
func runtimeExpandFinalInlineFrame(stk []uintptr) []uintptr
|
||||
|
||||
//go:linkname stdParseProcSelfMaps runtime/pprof.parseProcSelfMaps
|
||||
func stdParseProcSelfMaps(data []byte, addMapping func(lo uint64, hi uint64, offset uint64, file string, buildID string))
|
||||
|
||||
//go:linkname stdELFBuildID runtime/pprof.elfBuildID
|
||||
func stdELFBuildID(file string) (string, error)
|
||||
@@ -1,56 +0,0 @@
|
||||
//go:build darwin
|
||||
|
||||
package oomprofile
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"os"
|
||||
"unsafe"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
func isExecutable(protection int32) bool {
|
||||
return (protection&_VM_PROT_EXECUTE) != 0 && (protection&_VM_PROT_READ) != 0
|
||||
}
|
||||
|
||||
func (b *profileBuilder) readMapping() {
|
||||
if !machVMInfo(b.addMapping) {
|
||||
b.addMappingEntry(0, 0, 0, "", "", true)
|
||||
}
|
||||
}
|
||||
|
||||
func machVMInfo(addMapping func(lo uint64, hi uint64, off uint64, file string, buildID string)) bool {
|
||||
added := false
|
||||
addr := uint64(0x1)
|
||||
for {
|
||||
var regionSize uint64
|
||||
var info machVMRegionBasicInfoData
|
||||
kr := machVMRegion(&addr, ®ionSize, unsafe.Pointer(&info))
|
||||
if kr != 0 {
|
||||
if kr == _MACH_SEND_INVALID_DEST {
|
||||
return true
|
||||
}
|
||||
return added
|
||||
}
|
||||
if isExecutable(info.Protection) {
|
||||
addMapping(addr, addr+regionSize, binary.LittleEndian.Uint64(info.Offset[:]), regionFilename(addr), "")
|
||||
added = true
|
||||
}
|
||||
addr += regionSize
|
||||
}
|
||||
}
|
||||
|
||||
func regionFilename(address uint64) string {
|
||||
buf := make([]byte, _MAXPATHLEN)
|
||||
n := procRegionFilename(os.Getpid(), address, unsafe.SliceData(buf), int64(cap(buf)))
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
return string(buf[:n])
|
||||
}
|
||||
|
||||
//go:linkname machVMRegion runtime/pprof.mach_vm_region
|
||||
func machVMRegion(address *uint64, regionSize *uint64, info unsafe.Pointer) int32
|
||||
|
||||
//go:linkname procRegionFilename runtime/pprof.proc_regionfilename
|
||||
func procRegionFilename(pid int, address uint64, buf *byte, buflen int64) int32
|
||||
@@ -1,13 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
package oomprofile
|
||||
|
||||
import "os"
|
||||
|
||||
func (b *profileBuilder) readMapping() {
|
||||
data, _ := os.ReadFile("/proc/self/maps")
|
||||
stdParseProcSelfMaps(data, b.addMapping)
|
||||
if len(b.mem) == 0 {
|
||||
b.addMappingEntry(0, 0, 0, "", "", true)
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
//go:build windows
|
||||
|
||||
package oomprofile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func (b *profileBuilder) readMapping() {
|
||||
snapshot, err := createModuleSnapshot()
|
||||
if err != nil {
|
||||
b.addMappingEntry(0, 0, 0, "", "", true)
|
||||
return
|
||||
}
|
||||
defer windows.CloseHandle(snapshot)
|
||||
|
||||
var module windows.ModuleEntry32
|
||||
module.Size = uint32(windows.SizeofModuleEntry32)
|
||||
err = windows.Module32First(snapshot, &module)
|
||||
if err != nil {
|
||||
b.addMappingEntry(0, 0, 0, "", "", true)
|
||||
return
|
||||
}
|
||||
for err == nil {
|
||||
exe := windows.UTF16ToString(module.ExePath[:])
|
||||
b.addMappingEntry(
|
||||
uint64(module.ModBaseAddr),
|
||||
uint64(module.ModBaseAddr)+uint64(module.ModBaseSize),
|
||||
0,
|
||||
exe,
|
||||
peBuildID(exe),
|
||||
false,
|
||||
)
|
||||
err = windows.Module32Next(snapshot, &module)
|
||||
}
|
||||
}
|
||||
|
||||
func createModuleSnapshot() (windows.Handle, error) {
|
||||
for {
|
||||
snapshot, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE|windows.TH32CS_SNAPMODULE32, uint32(windows.GetCurrentProcessId()))
|
||||
var errno windows.Errno
|
||||
if err != nil && errors.As(err, &errno) && errno == windows.ERROR_BAD_LENGTH {
|
||||
continue
|
||||
}
|
||||
return snapshot, err
|
||||
}
|
||||
}
|
||||
|
||||
func peBuildID(file string) string {
|
||||
info, err := os.Stat(file)
|
||||
if err != nil {
|
||||
return file
|
||||
}
|
||||
return file + info.ModTime().String()
|
||||
}
|
||||
@@ -1,380 +0,0 @@
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package oomprofile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type stackRecord struct {
|
||||
Stack []uintptr
|
||||
}
|
||||
|
||||
type memProfileRecord struct {
|
||||
AllocBytes, FreeBytes int64
|
||||
AllocObjects, FreeObjects int64
|
||||
Stack []uintptr
|
||||
}
|
||||
|
||||
func (r *memProfileRecord) InUseBytes() int64 {
|
||||
return r.AllocBytes - r.FreeBytes
|
||||
}
|
||||
|
||||
func (r *memProfileRecord) InUseObjects() int64 {
|
||||
return r.AllocObjects - r.FreeObjects
|
||||
}
|
||||
|
||||
type blockProfileRecord struct {
|
||||
Count int64
|
||||
Cycles int64
|
||||
Stack []uintptr
|
||||
}
|
||||
|
||||
type label struct {
|
||||
key string
|
||||
value string
|
||||
}
|
||||
|
||||
type labelSet struct {
|
||||
list []label
|
||||
}
|
||||
|
||||
type labelMap struct {
|
||||
labelSet
|
||||
}
|
||||
|
||||
func WriteFile(destPath string, name string) (string, error) {
|
||||
writer, ok := profileWriters[name]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unsupported profile %q", name)
|
||||
}
|
||||
|
||||
filePath := filepath.Join(destPath, name+".pb")
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := writer(file); err != nil {
|
||||
_ = os.Remove(filePath)
|
||||
return "", err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
_ = os.Remove(filePath)
|
||||
return "", err
|
||||
}
|
||||
return filePath, nil
|
||||
}
|
||||
|
||||
var profileWriters = map[string]func(io.Writer) error{
|
||||
"allocs": writeAlloc,
|
||||
"block": writeBlock,
|
||||
"goroutine": writeGoroutine,
|
||||
"heap": writeHeap,
|
||||
"mutex": writeMutex,
|
||||
"threadcreate": writeThreadCreate,
|
||||
}
|
||||
|
||||
func writeHeap(w io.Writer) error {
|
||||
return writeHeapInternal(w, "")
|
||||
}
|
||||
|
||||
func writeAlloc(w io.Writer) error {
|
||||
return writeHeapInternal(w, "alloc_space")
|
||||
}
|
||||
|
||||
func writeHeapInternal(w io.Writer, defaultSampleType string) error {
|
||||
var profile []memProfileRecord
|
||||
n, ok := runtimeMemProfileInternal(nil, true)
|
||||
for {
|
||||
profile = make([]memProfileRecord, n+50)
|
||||
n, ok = runtimeMemProfileInternal(profile, true)
|
||||
if ok {
|
||||
profile = profile[:n]
|
||||
break
|
||||
}
|
||||
}
|
||||
return writeHeapProto(w, profile, int64(runtime.MemProfileRate), defaultSampleType)
|
||||
}
|
||||
|
||||
func writeGoroutine(w io.Writer) error {
|
||||
return writeRuntimeProfile(w, "goroutine", runtimeGoroutineProfileWithLabels)
|
||||
}
|
||||
|
||||
func writeThreadCreate(w io.Writer) error {
|
||||
return writeRuntimeProfile(w, "threadcreate", func(p []stackRecord, _ []unsafe.Pointer) (int, bool) {
|
||||
return runtimeThreadCreateInternal(p)
|
||||
})
|
||||
}
|
||||
|
||||
func writeRuntimeProfile(w io.Writer, name string, fetch func([]stackRecord, []unsafe.Pointer) (int, bool)) error {
|
||||
var profile []stackRecord
|
||||
var labels []unsafe.Pointer
|
||||
|
||||
n, ok := fetch(nil, nil)
|
||||
for {
|
||||
profile = make([]stackRecord, n+10)
|
||||
labels = make([]unsafe.Pointer, n+10)
|
||||
n, ok = fetch(profile, labels)
|
||||
if ok {
|
||||
profile = profile[:n]
|
||||
labels = labels[:n]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return writeCountProfile(w, name, &runtimeProfile{profile, labels})
|
||||
}
|
||||
|
||||
func writeBlock(w io.Writer) error {
|
||||
return writeCycleProfile(w, "contentions", "delay", runtimeBlockProfileInternal)
|
||||
}
|
||||
|
||||
func writeMutex(w io.Writer) error {
|
||||
return writeCycleProfile(w, "contentions", "delay", runtimeMutexProfileInternal)
|
||||
}
|
||||
|
||||
func writeCycleProfile(w io.Writer, countName string, cycleName string, fetch func([]blockProfileRecord) (int, bool)) error {
|
||||
var profile []blockProfileRecord
|
||||
n, ok := fetch(nil)
|
||||
for {
|
||||
profile = make([]blockProfileRecord, n+50)
|
||||
n, ok = fetch(profile)
|
||||
if ok {
|
||||
profile = profile[:n]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(profile, func(i, j int) bool {
|
||||
return profile[i].Cycles > profile[j].Cycles
|
||||
})
|
||||
|
||||
builder := newProfileBuilder(w)
|
||||
builder.pbValueType(tagProfile_PeriodType, countName, "count")
|
||||
builder.pb.int64Opt(tagProfile_Period, 1)
|
||||
builder.pbValueType(tagProfile_SampleType, countName, "count")
|
||||
builder.pbValueType(tagProfile_SampleType, cycleName, "nanoseconds")
|
||||
|
||||
cpuGHz := float64(runtimeCyclesPerSecond()) / 1e9
|
||||
values := []int64{0, 0}
|
||||
var locs []uint64
|
||||
expandedStack := runtimeMakeProfStack()
|
||||
for _, record := range profile {
|
||||
values[0] = record.Count
|
||||
if cpuGHz > 0 {
|
||||
values[1] = int64(float64(record.Cycles) / cpuGHz)
|
||||
} else {
|
||||
values[1] = 0
|
||||
}
|
||||
n := expandInlinedFrames(expandedStack, record.Stack)
|
||||
locs = builder.appendLocsForStack(locs[:0], expandedStack[:n])
|
||||
builder.pbSample(values, locs, nil)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
type countProfile interface {
|
||||
Len() int
|
||||
Stack(i int) []uintptr
|
||||
Label(i int) *labelMap
|
||||
}
|
||||
|
||||
type runtimeProfile struct {
|
||||
stk []stackRecord
|
||||
labels []unsafe.Pointer
|
||||
}
|
||||
|
||||
func (p *runtimeProfile) Len() int {
|
||||
return len(p.stk)
|
||||
}
|
||||
|
||||
func (p *runtimeProfile) Stack(i int) []uintptr {
|
||||
return p.stk[i].Stack
|
||||
}
|
||||
|
||||
func (p *runtimeProfile) Label(i int) *labelMap {
|
||||
return (*labelMap)(p.labels[i])
|
||||
}
|
||||
|
||||
func writeCountProfile(w io.Writer, name string, profile countProfile) error {
|
||||
var buf strings.Builder
|
||||
key := func(stk []uintptr, labels *labelMap) string {
|
||||
buf.Reset()
|
||||
buf.WriteByte('@')
|
||||
for _, pc := range stk {
|
||||
fmt.Fprintf(&buf, " %#x", pc)
|
||||
}
|
||||
if labels != nil {
|
||||
buf.WriteString("\n# labels:")
|
||||
for _, label := range labels.list {
|
||||
fmt.Fprintf(&buf, " %q:%q", label.key, label.value)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
counts := make(map[string]int)
|
||||
index := make(map[string]int)
|
||||
var keys []string
|
||||
for i := 0; i < profile.Len(); i++ {
|
||||
k := key(profile.Stack(i), profile.Label(i))
|
||||
if counts[k] == 0 {
|
||||
index[k] = i
|
||||
keys = append(keys, k)
|
||||
}
|
||||
counts[k]++
|
||||
}
|
||||
|
||||
sort.Sort(&keysByCount{keys: keys, count: counts})
|
||||
|
||||
builder := newProfileBuilder(w)
|
||||
builder.pbValueType(tagProfile_PeriodType, name, "count")
|
||||
builder.pb.int64Opt(tagProfile_Period, 1)
|
||||
builder.pbValueType(tagProfile_SampleType, name, "count")
|
||||
|
||||
values := []int64{0}
|
||||
var locs []uint64
|
||||
for _, k := range keys {
|
||||
values[0] = int64(counts[k])
|
||||
idx := index[k]
|
||||
locs = builder.appendLocsForStack(locs[:0], profile.Stack(idx))
|
||||
|
||||
var labels func()
|
||||
if profile.Label(idx) != nil {
|
||||
labels = func() {
|
||||
for _, label := range profile.Label(idx).list {
|
||||
builder.pbLabel(tagSample_Label, label.key, label.value, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.pbSample(values, locs, labels)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
type keysByCount struct {
|
||||
keys []string
|
||||
count map[string]int
|
||||
}
|
||||
|
||||
func (x *keysByCount) Len() int {
|
||||
return len(x.keys)
|
||||
}
|
||||
|
||||
func (x *keysByCount) Swap(i int, j int) {
|
||||
x.keys[i], x.keys[j] = x.keys[j], x.keys[i]
|
||||
}
|
||||
|
||||
func (x *keysByCount) Less(i int, j int) bool {
|
||||
ki, kj := x.keys[i], x.keys[j]
|
||||
ci, cj := x.count[ki], x.count[kj]
|
||||
if ci != cj {
|
||||
return ci > cj
|
||||
}
|
||||
return ki < kj
|
||||
}
|
||||
|
||||
func expandInlinedFrames(dst []uintptr, pcs []uintptr) int {
|
||||
frames := runtime.CallersFrames(pcs)
|
||||
var n int
|
||||
for n < len(dst) {
|
||||
frame, more := frames.Next()
|
||||
dst[n] = frame.PC + 1
|
||||
n++
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func writeHeapProto(w io.Writer, profile []memProfileRecord, rate int64, defaultSampleType string) error {
|
||||
builder := newProfileBuilder(w)
|
||||
builder.pbValueType(tagProfile_PeriodType, "space", "bytes")
|
||||
builder.pb.int64Opt(tagProfile_Period, rate)
|
||||
builder.pbValueType(tagProfile_SampleType, "alloc_objects", "count")
|
||||
builder.pbValueType(tagProfile_SampleType, "alloc_space", "bytes")
|
||||
builder.pbValueType(tagProfile_SampleType, "inuse_objects", "count")
|
||||
builder.pbValueType(tagProfile_SampleType, "inuse_space", "bytes")
|
||||
if defaultSampleType != "" {
|
||||
builder.pb.int64Opt(tagProfile_DefaultSampleType, builder.stringIndex(defaultSampleType))
|
||||
}
|
||||
|
||||
values := []int64{0, 0, 0, 0}
|
||||
var locs []uint64
|
||||
for _, record := range profile {
|
||||
hideRuntime := true
|
||||
for tries := 0; tries < 2; tries++ {
|
||||
stk := record.Stack
|
||||
if hideRuntime {
|
||||
for i, addr := range stk {
|
||||
if f := runtime.FuncForPC(addr); f != nil && (strings.HasPrefix(f.Name(), "runtime.") || strings.HasPrefix(f.Name(), "internal/runtime/")) {
|
||||
continue
|
||||
}
|
||||
stk = stk[i:]
|
||||
break
|
||||
}
|
||||
}
|
||||
locs = builder.appendLocsForStack(locs[:0], stk)
|
||||
if len(locs) > 0 {
|
||||
break
|
||||
}
|
||||
hideRuntime = false
|
||||
}
|
||||
|
||||
values[0], values[1] = scaleHeapSample(record.AllocObjects, record.AllocBytes, rate)
|
||||
values[2], values[3] = scaleHeapSample(record.InUseObjects(), record.InUseBytes(), rate)
|
||||
|
||||
var blockSize int64
|
||||
if record.AllocObjects > 0 {
|
||||
blockSize = record.AllocBytes / record.AllocObjects
|
||||
}
|
||||
builder.pbSample(values, locs, func() {
|
||||
if blockSize != 0 {
|
||||
builder.pbLabel(tagSample_Label, "bytes", "", blockSize)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
func scaleHeapSample(count int64, size int64, rate int64) (int64, int64) {
|
||||
if count == 0 || size == 0 {
|
||||
return 0, 0
|
||||
}
|
||||
if rate <= 1 {
|
||||
return count, size
|
||||
}
|
||||
|
||||
avgSize := float64(size) / float64(count)
|
||||
scale := 1 / (1 - math.Exp(-avgSize/float64(rate)))
|
||||
return int64(float64(count) * scale), int64(float64(size) * scale)
|
||||
}
|
||||
|
||||
type profileBuilder struct {
|
||||
start time.Time
|
||||
w io.Writer
|
||||
err error
|
||||
|
||||
pb protobuf
|
||||
strings []string
|
||||
stringMap map[string]int
|
||||
locs map[uintptr]locInfo
|
||||
funcs map[string]int
|
||||
mem []memMap
|
||||
deck pcDeck
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package oomprofile
|
||||
|
||||
type protobuf struct {
|
||||
data []byte
|
||||
tmp [16]byte
|
||||
nest int
|
||||
}
|
||||
|
||||
func (b *protobuf) varint(x uint64) {
|
||||
for x >= 128 {
|
||||
b.data = append(b.data, byte(x)|0x80)
|
||||
x >>= 7
|
||||
}
|
||||
b.data = append(b.data, byte(x))
|
||||
}
|
||||
|
||||
func (b *protobuf) length(tag int, length int) {
|
||||
b.varint(uint64(tag)<<3 | 2)
|
||||
b.varint(uint64(length))
|
||||
}
|
||||
|
||||
func (b *protobuf) uint64(tag int, x uint64) {
|
||||
b.varint(uint64(tag)<<3 | 0)
|
||||
b.varint(x)
|
||||
}
|
||||
|
||||
func (b *protobuf) uint64s(tag int, x []uint64) {
|
||||
if len(x) > 2 {
|
||||
n1 := len(b.data)
|
||||
for _, u := range x {
|
||||
b.varint(u)
|
||||
}
|
||||
n2 := len(b.data)
|
||||
b.length(tag, n2-n1)
|
||||
n3 := len(b.data)
|
||||
copy(b.tmp[:], b.data[n2:n3])
|
||||
copy(b.data[n1+(n3-n2):], b.data[n1:n2])
|
||||
copy(b.data[n1:], b.tmp[:n3-n2])
|
||||
return
|
||||
}
|
||||
for _, u := range x {
|
||||
b.uint64(tag, u)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *protobuf) uint64Opt(tag int, x uint64) {
|
||||
if x == 0 {
|
||||
return
|
||||
}
|
||||
b.uint64(tag, x)
|
||||
}
|
||||
|
||||
func (b *protobuf) int64(tag int, x int64) {
|
||||
b.uint64(tag, uint64(x))
|
||||
}
|
||||
|
||||
func (b *protobuf) int64Opt(tag int, x int64) {
|
||||
if x == 0 {
|
||||
return
|
||||
}
|
||||
b.int64(tag, x)
|
||||
}
|
||||
|
||||
func (b *protobuf) int64s(tag int, x []int64) {
|
||||
if len(x) > 2 {
|
||||
n1 := len(b.data)
|
||||
for _, u := range x {
|
||||
b.varint(uint64(u))
|
||||
}
|
||||
n2 := len(b.data)
|
||||
b.length(tag, n2-n1)
|
||||
n3 := len(b.data)
|
||||
copy(b.tmp[:], b.data[n2:n3])
|
||||
copy(b.data[n1+(n3-n2):], b.data[n1:n2])
|
||||
copy(b.data[n1:], b.tmp[:n3-n2])
|
||||
return
|
||||
}
|
||||
for _, u := range x {
|
||||
b.int64(tag, u)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *protobuf) bool(tag int, x bool) {
|
||||
if x {
|
||||
b.uint64(tag, 1)
|
||||
} else {
|
||||
b.uint64(tag, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *protobuf) string(tag int, x string) {
|
||||
b.length(tag, len(x))
|
||||
b.data = append(b.data, x...)
|
||||
}
|
||||
|
||||
func (b *protobuf) strings(tag int, x []string) {
|
||||
for _, s := range x {
|
||||
b.string(tag, s)
|
||||
}
|
||||
}
|
||||
|
||||
type msgOffset int
|
||||
|
||||
func (b *protobuf) startMessage() msgOffset {
|
||||
b.nest++
|
||||
return msgOffset(len(b.data))
|
||||
}
|
||||
|
||||
func (b *protobuf) endMessage(tag int, start msgOffset) {
|
||||
n1 := int(start)
|
||||
n2 := len(b.data)
|
||||
b.length(tag, n2-n1)
|
||||
n3 := len(b.data)
|
||||
copy(b.tmp[:], b.data[n2:n3])
|
||||
copy(b.data[n1+(n3-n2):], b.data[n1:n2])
|
||||
copy(b.data[n1:], b.tmp[:n3-n2])
|
||||
b.nest--
|
||||
}
|
||||
@@ -1,76 +1,24 @@
|
||||
//go:build darwin || linux || windows
|
||||
//go:build darwin || linux
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
type crashReportMetadata struct {
|
||||
reportMetadata
|
||||
CrashedAt string `json:"crashedAt,omitempty"`
|
||||
SignalName string `json:"signalName,omitempty"`
|
||||
SignalCode string `json:"signalCode,omitempty"`
|
||||
ExceptionName string `json:"exceptionName,omitempty"`
|
||||
ExceptionReason string `json:"exceptionReason,omitempty"`
|
||||
}
|
||||
var crashOutputFile *os.File
|
||||
|
||||
func archiveCrashReport(path string, crashReportsDir string) {
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil || len(content) == 0 {
|
||||
return
|
||||
func RedirectStderr(path string) error {
|
||||
if stats, err := os.Stat(path); err == nil && stats.Size() > 0 {
|
||||
_ = os.Rename(path, path+".old")
|
||||
}
|
||||
|
||||
info, _ := os.Stat(path)
|
||||
crashTime := time.Now().UTC()
|
||||
if info != nil {
|
||||
crashTime = info.ModTime().UTC()
|
||||
}
|
||||
|
||||
initReportDir(crashReportsDir)
|
||||
destPath, err := nextAvailableReportPath(crashReportsDir, crashTime)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
initReportDir(destPath)
|
||||
|
||||
writeReportFile(destPath, "go.log", content)
|
||||
metadata := crashReportMetadata{
|
||||
reportMetadata: baseReportMetadata(),
|
||||
CrashedAt: crashTime.Format(time.RFC3339),
|
||||
}
|
||||
writeReportMetadata(destPath, metadata)
|
||||
os.Remove(path)
|
||||
copyConfigSnapshot(destPath)
|
||||
}
|
||||
|
||||
func configSnapshotPath() string {
|
||||
return filepath.Join(sBasePath, "configuration.json")
|
||||
}
|
||||
|
||||
func saveConfigSnapshot(configContent string) {
|
||||
snapshotPath := configSnapshotPath()
|
||||
os.WriteFile(snapshotPath, []byte(configContent), 0o666)
|
||||
chownReport(snapshotPath)
|
||||
}
|
||||
|
||||
func redirectStderr(path string) error {
|
||||
crashReportsDir := filepath.Join(sWorkingPath, "crash_reports")
|
||||
archiveCrashReport(path, crashReportsDir)
|
||||
archiveCrashReport(path+".old", crashReportsDir)
|
||||
|
||||
outputFile, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if runtime.GOOS != "android" && runtime.GOOS != "windows" {
|
||||
if runtime.GOOS != "android" {
|
||||
err = outputFile.Chown(sUserID, sGroupID)
|
||||
if err != nil {
|
||||
outputFile.Close()
|
||||
@@ -78,88 +26,12 @@ func redirectStderr(path string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = debug.SetCrashOutput(outputFile, debug.CrashOptions{})
|
||||
if err != nil {
|
||||
outputFile.Close()
|
||||
os.Remove(outputFile.Name())
|
||||
return err
|
||||
}
|
||||
_ = outputFile.Close()
|
||||
crashOutputFile = outputFile
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateZipArchive(sourcePath string, destinationPath string) error {
|
||||
sourceInfo, err := os.Stat(sourcePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !sourceInfo.IsDir() {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
destinationFile, err := os.Create(destinationPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = destinationFile.Close()
|
||||
}()
|
||||
|
||||
zipWriter := zip.NewWriter(destinationFile)
|
||||
|
||||
rootName := filepath.Base(sourcePath)
|
||||
err = filepath.WalkDir(sourcePath, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relativePath, err := filepath.Rel(sourcePath, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if relativePath == "." {
|
||||
return nil
|
||||
}
|
||||
|
||||
archivePath := filepath.ToSlash(filepath.Join(rootName, relativePath))
|
||||
if d.IsDir() {
|
||||
_, err = zipWriter.Create(archivePath + "/")
|
||||
return err
|
||||
}
|
||||
|
||||
fileInfo, err := d.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header, err := zip.FileInfoHeader(fileInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header.Name = archivePath
|
||||
header.Method = zip.Deflate
|
||||
|
||||
writer, err := zipWriter.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sourceFile, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(writer, sourceFile)
|
||||
closeErr := sourceFile.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return closeErr
|
||||
})
|
||||
if err != nil {
|
||||
_ = zipWriter.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return zipWriter.Close()
|
||||
}
|
||||
|
||||
26
experimental/libbox/memory.go
Normal file
26
experimental/libbox/memory.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"math"
|
||||
runtimeDebug "runtime/debug"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
)
|
||||
|
||||
var memoryLimitEnabled bool
|
||||
|
||||
func SetMemoryLimit(enabled bool) {
|
||||
memoryLimitEnabled = enabled
|
||||
const memoryLimitGo = 45 * 1024 * 1024
|
||||
if enabled {
|
||||
runtimeDebug.SetGCPercent(10)
|
||||
if C.IsIos {
|
||||
runtimeDebug.SetMemoryLimit(memoryLimitGo)
|
||||
}
|
||||
} else {
|
||||
runtimeDebug.SetGCPercent(100)
|
||||
if C.IsIos {
|
||||
runtimeDebug.SetMemoryLimit(math.MaxInt64)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/networkquality"
|
||||
)
|
||||
|
||||
type NetworkQualityTest struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func NewNetworkQualityTest() *NetworkQualityTest {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &NetworkQualityTest{ctx: ctx, cancel: cancel}
|
||||
}
|
||||
|
||||
func (t *NetworkQualityTest) Start(configURL string, handler NetworkQualityTestHandler) {
|
||||
t.StartWithSerialAndRuntime(configURL, false, NetworkQualityDefaultMaxRuntimeSeconds, handler)
|
||||
}
|
||||
|
||||
func (t *NetworkQualityTest) StartWithSerial(configURL string, serial bool, handler NetworkQualityTestHandler) {
|
||||
t.StartWithSerialAndRuntime(configURL, serial, NetworkQualityDefaultMaxRuntimeSeconds, handler)
|
||||
}
|
||||
|
||||
func (t *NetworkQualityTest) StartWithSerialAndRuntime(configURL string, serial bool, maxRuntimeSeconds int32, handler NetworkQualityTestHandler) {
|
||||
go func() {
|
||||
httpClient := networkquality.NewHTTPClient(nil)
|
||||
defer httpClient.CloseIdleConnections()
|
||||
|
||||
result, err := networkquality.Run(networkquality.Options{
|
||||
ConfigURL: configURL,
|
||||
HTTPClient: httpClient,
|
||||
Serial: serial,
|
||||
MaxRuntime: time.Duration(maxRuntimeSeconds) * time.Second,
|
||||
Context: t.ctx,
|
||||
OnProgress: func(p networkquality.Progress) {
|
||||
handler.OnProgress(&NetworkQualityProgress{
|
||||
Phase: int32(p.Phase),
|
||||
DownloadCapacity: p.DownloadCapacity,
|
||||
UploadCapacity: p.UploadCapacity,
|
||||
DownloadRPM: p.DownloadRPM,
|
||||
UploadRPM: p.UploadRPM,
|
||||
IdleLatencyMs: p.IdleLatencyMs,
|
||||
ElapsedMs: p.ElapsedMs,
|
||||
DownloadCapacityAccuracy: int32(p.DownloadCapacityAccuracy),
|
||||
UploadCapacityAccuracy: int32(p.UploadCapacityAccuracy),
|
||||
DownloadRPMAccuracy: int32(p.DownloadRPMAccuracy),
|
||||
UploadRPMAccuracy: int32(p.UploadRPMAccuracy),
|
||||
})
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
handler.OnError(err.Error())
|
||||
return
|
||||
}
|
||||
handler.OnResult(&NetworkQualityResult{
|
||||
DownloadCapacity: result.DownloadCapacity,
|
||||
UploadCapacity: result.UploadCapacity,
|
||||
DownloadRPM: result.DownloadRPM,
|
||||
UploadRPM: result.UploadRPM,
|
||||
IdleLatencyMs: result.IdleLatencyMs,
|
||||
DownloadCapacityAccuracy: int32(result.DownloadCapacityAccuracy),
|
||||
UploadCapacityAccuracy: int32(result.UploadCapacityAccuracy),
|
||||
DownloadRPMAccuracy: int32(result.DownloadRPMAccuracy),
|
||||
UploadRPMAccuracy: int32(result.UploadRPMAccuracy),
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
func (t *NetworkQualityTest) Cancel() {
|
||||
t.cancel()
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/experimental/libbox/internal/oomprofile"
|
||||
"github.com/sagernet/sing-box/service/oomkiller"
|
||||
"github.com/sagernet/sing/common/byteformats"
|
||||
"github.com/sagernet/sing/common/memory"
|
||||
)
|
||||
|
||||
func init() {
|
||||
sOOMReporter = &oomReporter{}
|
||||
}
|
||||
|
||||
var oomReportProfiles = []string{
|
||||
"allocs",
|
||||
"block",
|
||||
"goroutine",
|
||||
"heap",
|
||||
"mutex",
|
||||
"threadcreate",
|
||||
}
|
||||
|
||||
type oomReportMetadata struct {
|
||||
reportMetadata
|
||||
RecordedAt string `json:"recordedAt"`
|
||||
MemoryUsage string `json:"memoryUsage"`
|
||||
AvailableMemory string `json:"availableMemory,omitempty"`
|
||||
// Heap
|
||||
HeapAlloc string `json:"heapAlloc,omitempty"`
|
||||
HeapObjects uint64 `json:"heapObjects,omitempty,string"`
|
||||
HeapInuse string `json:"heapInuse,omitempty"`
|
||||
HeapIdle string `json:"heapIdle,omitempty"`
|
||||
HeapReleased string `json:"heapReleased,omitempty"`
|
||||
HeapSys string `json:"heapSys,omitempty"`
|
||||
// Stack
|
||||
StackInuse string `json:"stackInuse,omitempty"`
|
||||
StackSys string `json:"stackSys,omitempty"`
|
||||
// Runtime metadata
|
||||
MSpanInuse string `json:"mSpanInuse,omitempty"`
|
||||
MSpanSys string `json:"mSpanSys,omitempty"`
|
||||
MCacheSys string `json:"mCacheSys,omitempty"`
|
||||
BuckHashSys string `json:"buckHashSys,omitempty"`
|
||||
GCSys string `json:"gcSys,omitempty"`
|
||||
OtherSys string `json:"otherSys,omitempty"`
|
||||
Sys string `json:"sys,omitempty"`
|
||||
// GC & runtime
|
||||
TotalAlloc string `json:"totalAlloc,omitempty"`
|
||||
NumGC uint32 `json:"numGC,omitempty,string"`
|
||||
NumGoroutine int `json:"numGoroutine,omitempty,string"`
|
||||
NextGC string `json:"nextGC,omitempty"`
|
||||
LastGC string `json:"lastGC,omitempty"`
|
||||
}
|
||||
|
||||
type oomReporter struct{}
|
||||
|
||||
var _ oomkiller.OOMReporter = (*oomReporter)(nil)
|
||||
|
||||
func (r *oomReporter) WriteReport(memoryUsage uint64) error {
|
||||
now := time.Now().UTC()
|
||||
reportsDir := filepath.Join(sWorkingPath, "oom_reports")
|
||||
err := os.MkdirAll(reportsDir, 0o777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chownReport(reportsDir)
|
||||
|
||||
destPath, err := nextAvailableReportPath(reportsDir, now)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(destPath, 0o777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chownReport(destPath)
|
||||
|
||||
for _, name := range oomReportProfiles {
|
||||
writeOOMProfile(destPath, name)
|
||||
}
|
||||
|
||||
writeReportFile(destPath, "cmdline", []byte(strings.Join(os.Args, "\000")))
|
||||
|
||||
var memStats runtime.MemStats
|
||||
runtime.ReadMemStats(&memStats)
|
||||
|
||||
metadata := oomReportMetadata{
|
||||
reportMetadata: baseReportMetadata(),
|
||||
RecordedAt: now.Format(time.RFC3339),
|
||||
MemoryUsage: byteformats.FormatMemoryBytes(memoryUsage),
|
||||
// Heap
|
||||
HeapAlloc: byteformats.FormatMemoryBytes(memStats.HeapAlloc),
|
||||
HeapObjects: memStats.HeapObjects,
|
||||
HeapInuse: byteformats.FormatMemoryBytes(memStats.HeapInuse),
|
||||
HeapIdle: byteformats.FormatMemoryBytes(memStats.HeapIdle),
|
||||
HeapReleased: byteformats.FormatMemoryBytes(memStats.HeapReleased),
|
||||
HeapSys: byteformats.FormatMemoryBytes(memStats.HeapSys),
|
||||
// Stack
|
||||
StackInuse: byteformats.FormatMemoryBytes(memStats.StackInuse),
|
||||
StackSys: byteformats.FormatMemoryBytes(memStats.StackSys),
|
||||
// Runtime metadata
|
||||
MSpanInuse: byteformats.FormatMemoryBytes(memStats.MSpanInuse),
|
||||
MSpanSys: byteformats.FormatMemoryBytes(memStats.MSpanSys),
|
||||
MCacheSys: byteformats.FormatMemoryBytes(memStats.MCacheSys),
|
||||
BuckHashSys: byteformats.FormatMemoryBytes(memStats.BuckHashSys),
|
||||
GCSys: byteformats.FormatMemoryBytes(memStats.GCSys),
|
||||
OtherSys: byteformats.FormatMemoryBytes(memStats.OtherSys),
|
||||
Sys: byteformats.FormatMemoryBytes(memStats.Sys),
|
||||
// GC & runtime
|
||||
TotalAlloc: byteformats.FormatMemoryBytes(memStats.TotalAlloc),
|
||||
NumGC: memStats.NumGC,
|
||||
NumGoroutine: runtime.NumGoroutine(),
|
||||
NextGC: byteformats.FormatMemoryBytes(memStats.NextGC),
|
||||
}
|
||||
if memStats.LastGC > 0 {
|
||||
metadata.LastGC = time.Unix(0, int64(memStats.LastGC)).UTC().Format(time.RFC3339)
|
||||
}
|
||||
availableMemory := memory.Available()
|
||||
if availableMemory > 0 {
|
||||
metadata.AvailableMemory = byteformats.FormatMemoryBytes(availableMemory)
|
||||
}
|
||||
writeReportMetadata(destPath, metadata)
|
||||
copyConfigSnapshot(destPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeOOMProfile(destPath string, name string) {
|
||||
filePath, err := oomprofile.WriteFile(destPath, name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
chownReport(filePath)
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
//go:build darwin || linux || windows
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
type reportMetadata struct {
|
||||
Source string `json:"source,omitempty"`
|
||||
BundleIdentifier string `json:"bundleIdentifier,omitempty"`
|
||||
ProcessName string `json:"processName,omitempty"`
|
||||
ProcessPath string `json:"processPath,omitempty"`
|
||||
StartedAt string `json:"startedAt,omitempty"`
|
||||
AppVersion string `json:"appVersion,omitempty"`
|
||||
AppMarketingVersion string `json:"appMarketingVersion,omitempty"`
|
||||
CoreVersion string `json:"coreVersion,omitempty"`
|
||||
GoVersion string `json:"goVersion,omitempty"`
|
||||
}
|
||||
|
||||
func baseReportMetadata() reportMetadata {
|
||||
processPath, _ := os.Executable()
|
||||
processName := filepath.Base(processPath)
|
||||
if processName == "." {
|
||||
processName = ""
|
||||
}
|
||||
return reportMetadata{
|
||||
Source: sCrashReportSource,
|
||||
ProcessName: processName,
|
||||
ProcessPath: processPath,
|
||||
CoreVersion: C.Version,
|
||||
GoVersion: GoVersion(),
|
||||
}
|
||||
}
|
||||
|
||||
func writeReportFile(destPath string, name string, content []byte) {
|
||||
filePath := filepath.Join(destPath, name)
|
||||
os.WriteFile(filePath, content, 0o666)
|
||||
chownReport(filePath)
|
||||
}
|
||||
|
||||
func writeReportMetadata(destPath string, metadata any) {
|
||||
data, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
writeReportFile(destPath, "metadata.json", data)
|
||||
}
|
||||
|
||||
func copyConfigSnapshot(destPath string) {
|
||||
snapshotPath := configSnapshotPath()
|
||||
content, err := os.ReadFile(snapshotPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(bytes.TrimSpace(content)) == 0 {
|
||||
return
|
||||
}
|
||||
writeReportFile(destPath, "configuration.json", content)
|
||||
}
|
||||
|
||||
func initReportDir(path string) {
|
||||
os.MkdirAll(path, 0o777)
|
||||
chownReport(path)
|
||||
}
|
||||
|
||||
func chownReport(path string) {
|
||||
if runtime.GOOS != "android" && runtime.GOOS != "windows" {
|
||||
os.Chown(path, sUserID, sGroupID)
|
||||
}
|
||||
}
|
||||
|
||||
func nextAvailableReportPath(reportsDir string, timestamp time.Time) (string, error) {
|
||||
destName := timestamp.Format("2006-01-02T15-04-05")
|
||||
destPath := filepath.Join(reportsDir, destName)
|
||||
_, err := os.Stat(destPath)
|
||||
if os.IsNotExist(err) {
|
||||
return destPath, nil
|
||||
}
|
||||
for i := 1; i <= 1000; i++ {
|
||||
suffixedPath := filepath.Join(reportsDir, destName+"-"+strconv.Itoa(i))
|
||||
_, err = os.Stat(suffixedPath)
|
||||
if os.IsNotExist(err) {
|
||||
return suffixedPath, nil
|
||||
}
|
||||
}
|
||||
return "", E.New("no available report path for ", destName)
|
||||
}
|
||||
@@ -1,22 +1,14 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/networkquality"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/locale"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/service/oomkiller"
|
||||
"github.com/sagernet/sing/common/byteformats"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -30,10 +22,6 @@ var (
|
||||
sCommandServerSecret string
|
||||
sLogMaxLines int
|
||||
sDebug bool
|
||||
sCrashReportSource string
|
||||
sOOMKillerEnabled bool
|
||||
sOOMKillerDisabled bool
|
||||
sOOMMemoryLimit int64
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -50,13 +38,9 @@ type SetupOptions struct {
|
||||
CommandServerSecret string
|
||||
LogMaxLines int
|
||||
Debug bool
|
||||
CrashReportSource string
|
||||
OomKillerEnabled bool
|
||||
OomKillerDisabled bool
|
||||
OomMemoryLimit int64
|
||||
}
|
||||
|
||||
func applySetupOptions(options *SetupOptions) {
|
||||
func Setup(options *SetupOptions) error {
|
||||
sBasePath = options.BasePath
|
||||
sWorkingPath = options.WorkingPath
|
||||
sTempPath = options.TempPath
|
||||
@@ -72,53 +56,20 @@ func applySetupOptions(options *SetupOptions) {
|
||||
sCommandServerSecret = options.CommandServerSecret
|
||||
sLogMaxLines = options.LogMaxLines
|
||||
sDebug = options.Debug
|
||||
sCrashReportSource = options.CrashReportSource
|
||||
ReloadSetupOptions(options)
|
||||
}
|
||||
|
||||
func ReloadSetupOptions(options *SetupOptions) {
|
||||
sOOMKillerEnabled = options.OomKillerEnabled
|
||||
sOOMKillerDisabled = options.OomKillerDisabled
|
||||
sOOMMemoryLimit = options.OomMemoryLimit
|
||||
if sOOMKillerEnabled {
|
||||
if sOOMMemoryLimit == 0 && C.IsIos {
|
||||
sOOMMemoryLimit = oomkiller.DefaultAppleNetworkExtensionMemoryLimit
|
||||
}
|
||||
if sOOMMemoryLimit > 0 {
|
||||
debug.SetMemoryLimit(sOOMMemoryLimit * 3 / 4)
|
||||
} else {
|
||||
debug.SetMemoryLimit(math.MaxInt64)
|
||||
}
|
||||
} else {
|
||||
debug.SetMemoryLimit(math.MaxInt64)
|
||||
}
|
||||
}
|
||||
|
||||
func Setup(options *SetupOptions) error {
|
||||
applySetupOptions(options)
|
||||
os.MkdirAll(sWorkingPath, 0o777)
|
||||
os.MkdirAll(sTempPath, 0o777)
|
||||
return redirectStderr(filepath.Join(sWorkingPath, "CrashReport-"+sCrashReportSource+".log"))
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetLocale(localeId string) error {
|
||||
if strings.Contains(localeId, "@") {
|
||||
localeId = strings.Split(localeId, "@")[0]
|
||||
}
|
||||
if !locale.Set(localeId) {
|
||||
return E.New("unsupported locale: ", localeId)
|
||||
}
|
||||
return nil
|
||||
func SetLocale(localeId string) {
|
||||
locale.Set(localeId)
|
||||
}
|
||||
|
||||
func Version() string {
|
||||
return C.Version
|
||||
}
|
||||
|
||||
func GoVersion() string {
|
||||
return runtime.Version() + ", " + runtime.GOOS + "/" + runtime.GOARCH
|
||||
}
|
||||
|
||||
func FormatBytes(length int64) string {
|
||||
return byteformats.FormatKBytes(uint64(length))
|
||||
}
|
||||
@@ -131,29 +82,6 @@ func FormatDuration(duration int64) string {
|
||||
return log.FormatDuration(time.Duration(duration) * time.Millisecond)
|
||||
}
|
||||
|
||||
func FormatBitrate(bps int64) string {
|
||||
switch {
|
||||
case bps >= 1_000_000_000:
|
||||
return fmt.Sprintf("%.1f Gbps", float64(bps)/1_000_000_000)
|
||||
case bps >= 1_000_000:
|
||||
return fmt.Sprintf("%.1f Mbps", float64(bps)/1_000_000)
|
||||
case bps >= 1_000:
|
||||
return fmt.Sprintf("%.1f Kbps", float64(bps)/1_000)
|
||||
default:
|
||||
return fmt.Sprintf("%d bps", bps)
|
||||
}
|
||||
}
|
||||
|
||||
const NetworkQualityDefaultConfigURL = networkquality.DefaultConfigURL
|
||||
|
||||
const NetworkQualityDefaultMaxRuntimeSeconds = int32(networkquality.DefaultMaxRuntime / time.Second)
|
||||
|
||||
const (
|
||||
NetworkQualityAccuracyLow = int32(networkquality.AccuracyLow)
|
||||
NetworkQualityAccuracyMedium = int32(networkquality.AccuracyMedium)
|
||||
NetworkQualityAccuracyHigh = int32(networkquality.AccuracyHigh)
|
||||
)
|
||||
|
||||
func ProxyDisplayType(proxyType string) string {
|
||||
return C.ProxyDisplayName(proxyType)
|
||||
}
|
||||
|
||||
10
go.mod
10
go.mod
@@ -6,7 +6,6 @@ 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
|
||||
@@ -20,7 +19,6 @@ 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
|
||||
@@ -37,13 +35,13 @@ 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.5-0.20260404181712-947827ec3849
|
||||
github.com/sagernet/sing v0.8.3-0.20260315153529-ed51f65fbfde
|
||||
github.com/sagernet/sing-mux v0.3.4
|
||||
github.com/sagernet/sing-quic v0.6.2-0.20260330152607-bf674c163212
|
||||
github.com/sagernet/sing-quic v0.6.0
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
||||
github.com/sagernet/sing-tun v0.8.7-0.20260407152316-3ded9b354c8a
|
||||
github.com/sagernet/sing-tun v0.8.7-0.20260323120017-8eb4e8acfc2d
|
||||
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
|
||||
github.com/sagernet/smux v1.5.50-sing-box-mod.1
|
||||
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7
|
||||
@@ -71,6 +69,7 @@ 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
|
||||
@@ -97,6 +96,7 @@ 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
|
||||
|
||||
12
go.sum
12
go.sum
@@ -236,20 +236,20 @@ 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.5-0.20260404181712-947827ec3849 h1:P8jaGN561IbHBxjlU8IGrFK65n1vDOrHo8FOMgHfn14=
|
||||
github.com/sagernet/sing v0.8.5-0.20260404181712-947827ec3849/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
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-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.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-quic v0.6.0 h1:dhrFnP45wgVKEOT1EvtsToxdzRnHIDIAgj6WHV9pLyM=
|
||||
github.com/sagernet/sing-quic v0.6.0/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.20260407152316-3ded9b354c8a h1:L3757AYMq32oOb9iW2j7D/tat7eE7nvnthi7V1rJvwM=
|
||||
github.com/sagernet/sing-tun v0.8.7-0.20260407152316-3ded9b354c8a/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs=
|
||||
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-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=
|
||||
|
||||
276
option/dns.go
276
option/dns.go
@@ -3,14 +3,19 @@ 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 {
|
||||
@@ -21,29 +26,80 @@ type RawDNSOptions struct {
|
||||
DNSClientOptions
|
||||
}
|
||||
|
||||
type DNSOptions struct {
|
||||
RawDNSOptions
|
||||
type LegacyDNSOptions struct {
|
||||
FakeIP *LegacyDNSFakeIPOptions `json:"fakeip,omitempty"`
|
||||
}
|
||||
|
||||
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"
|
||||
)
|
||||
type DNSOptions struct {
|
||||
RawDNSOptions
|
||||
LegacyDNSOptions
|
||||
}
|
||||
|
||||
type removedLegacyDNSOptions struct {
|
||||
FakeIP json.RawMessage `json:"fakeip,omitempty"`
|
||||
type contextKeyDontUpgrade struct{}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error {
|
||||
var legacyOptions removedLegacyDNSOptions
|
||||
err := json.UnmarshalContext(ctx, content, &legacyOptions)
|
||||
err := json.UnmarshalContext(ctx, content, &o.LegacyDNSOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(legacyOptions.FakeIP) != 0 {
|
||||
return E.New(legacyDNSFakeIPRemovedMessage)
|
||||
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{}
|
||||
}
|
||||
return badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions)
|
||||
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))
|
||||
}
|
||||
|
||||
type DNSClientOptions struct {
|
||||
@@ -55,6 +111,12 @@ 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)
|
||||
}
|
||||
@@ -67,6 +129,10 @@ 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)
|
||||
}
|
||||
|
||||
@@ -82,7 +148,9 @@ func (o *DNSServerOptions) UnmarshalJSONContext(ctx context.Context, content []b
|
||||
var options any
|
||||
switch o.Type {
|
||||
case "", C.DNSTypeLegacy:
|
||||
return E.New(legacyDNSServerRemovedMessage)
|
||||
o.Type = C.DNSTypeLegacy
|
||||
options = new(LegacyDNSServerOptions)
|
||||
deprecated.Report(ctx, deprecated.OptionLegacyDNSTransport)
|
||||
default:
|
||||
var loaded bool
|
||||
options, loaded = registry.CreateOptions(o.Type)
|
||||
@@ -95,6 +163,169 @@ 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
|
||||
}
|
||||
|
||||
@@ -119,6 +350,16 @@ 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"`
|
||||
@@ -126,6 +367,10 @@ type HostsDNSServerOptions struct {
|
||||
|
||||
type RawLocalDNSServerOptions struct {
|
||||
DialerOptions
|
||||
Legacy bool `json:"-"`
|
||||
LegacyStrategy DomainStrategy `json:"-"`
|
||||
LegacyDefaultDialer bool `json:"-"`
|
||||
LegacyClientSubnet netip.Prefix `json:"-"`
|
||||
}
|
||||
|
||||
type LocalDNSServerOptions struct {
|
||||
@@ -136,6 +381,9 @@ type LocalDNSServerOptions struct {
|
||||
type RemoteDNSServerOptions struct {
|
||||
RawLocalDNSServerOptions
|
||||
DNSServerAddressOptions
|
||||
LegacyAddressResolver string `json:"-"`
|
||||
LegacyAddressStrategy DomainStrategy `json:"-"`
|
||||
LegacyAddressFallbackDelay badoption.Duration `json:"-"`
|
||||
}
|
||||
|
||||
type RemoteTLSDNSServerOptions struct {
|
||||
|
||||
@@ -2,7 +2,6 @@ package option
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -12,8 +11,6 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const defaultDNSRecordTTL uint32 = 3600
|
||||
|
||||
type DNSRCode int
|
||||
|
||||
func (r DNSRCode) MarshalJSON() ([]byte, error) {
|
||||
@@ -79,13 +76,10 @@ func (o *DNSRecordOptions) UnmarshalJSON(data []byte) error {
|
||||
if err == nil {
|
||||
return o.unmarshalBase64(binary)
|
||||
}
|
||||
record, err := parseDNSRecord(stringValue)
|
||||
record, err := dns.NewRR(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()
|
||||
}
|
||||
@@ -93,16 +87,6 @@ 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 {
|
||||
@@ -116,10 +100,3 @@ 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)
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
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 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))
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@ 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"`
|
||||
}
|
||||
|
||||
@@ -113,15 +112,13 @@ type Hysteria2MasqueradeString struct {
|
||||
type Hysteria2OutboundOptions struct {
|
||||
DialerOptions
|
||||
ServerOptions
|
||||
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"`
|
||||
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"`
|
||||
OutboundTLSOptionsContainer
|
||||
BBRProfile string `json:"bbr_profile,omitempty"`
|
||||
BrutalDebug bool `json:"brutal_debug,omitempty"`
|
||||
BrutalDebug bool `json:"brutal_debug,omitempty"`
|
||||
}
|
||||
|
||||
@@ -6,10 +6,9 @@ import (
|
||||
)
|
||||
|
||||
type OOMKillerServiceOptions struct {
|
||||
MemoryLimit *byteformats.MemoryBytes `json:"memory_limit,omitempty"`
|
||||
SafetyMargin *byteformats.MemoryBytes `json:"safety_margin,omitempty"`
|
||||
MinInterval badoption.Duration `json:"min_interval,omitempty"`
|
||||
MaxInterval badoption.Duration `json:"max_interval,omitempty"`
|
||||
KillerDisabled bool `json:"-"`
|
||||
MemoryLimitOverride uint64 `json:"-"`
|
||||
MemoryLimit *byteformats.MemoryBytes `json:"memory_limit,omitempty"`
|
||||
SafetyMargin *byteformats.MemoryBytes `json:"safety_margin,omitempty"`
|
||||
MinInterval badoption.Duration `json:"min_interval,omitempty"`
|
||||
MaxInterval badoption.Duration `json:"max_interval,omitempty"`
|
||||
ChecksBeforeLimit int `json:"checks_before_limit,omitempty"`
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
@@ -34,24 +33,26 @@ func (r Rule) MarshalJSON() ([]byte, error) {
|
||||
return badjson.MarshallObjects((_Rule)(r), v)
|
||||
}
|
||||
|
||||
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)
|
||||
func (r *Rule) UnmarshalJSON(bytes []byte) error {
|
||||
err := json.Unmarshal(bytes, (*_Rule)(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var v any
|
||||
switch r.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
r.Type = C.RuleTypeDefault
|
||||
return unmarshalDefaultRuleContext(ctx, payload, &r.DefaultOptions)
|
||||
v = &r.DefaultOptions
|
||||
case C.RuleTypeLogical:
|
||||
return unmarshalLogicalRuleContext(ctx, payload, &r.LogicalOptions)
|
||||
v = &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 {
|
||||
@@ -159,64 +160,6 @@ 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,10 +115,6 @@ func (r DNSRuleAction) MarshalJSON() ([]byte, error) {
|
||||
case C.RuleActionTypeRoute:
|
||||
r.Action = ""
|
||||
v = r.RouteOptions
|
||||
case C.RuleActionTypeEvaluate:
|
||||
v = r.RouteOptions
|
||||
case C.RuleActionTypeRespond:
|
||||
v = nil
|
||||
case C.RuleActionTypeRouteOptions:
|
||||
v = r.RouteOptionsOptions
|
||||
case C.RuleActionTypeReject:
|
||||
@@ -128,9 +124,6 @@ func (r DNSRuleAction) MarshalJSON() ([]byte, error) {
|
||||
default:
|
||||
return nil, E.New("unknown DNS rule action: " + r.Action)
|
||||
}
|
||||
if v == nil {
|
||||
return badjson.MarshallObjects((_DNSRuleAction)(r))
|
||||
}
|
||||
return badjson.MarshallObjects((_DNSRuleAction)(r), v)
|
||||
}
|
||||
|
||||
@@ -144,10 +137,6 @@ 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.RuleActionTypeRespond:
|
||||
v = nil
|
||||
case C.RuleActionTypeRouteOptions:
|
||||
v = &r.RouteOptionsOptions
|
||||
case C.RuleActionTypeReject:
|
||||
@@ -157,9 +146,6 @@ func (r *DNSRuleAction) UnmarshalJSONContext(ctx context.Context, data []byte) e
|
||||
default:
|
||||
return E.New("unknown DNS rule action: " + r.Action)
|
||||
}
|
||||
if v == nil {
|
||||
return json.UnmarshalDisallowUnknownFields(data, &_DNSRuleAction{})
|
||||
}
|
||||
return badjson.UnmarshallExcludedContext(ctx, data, (*_DNSRuleAction)(r), v)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDNSRuleActionRespondUnmarshalJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var action DNSRuleAction
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{"action":"respond"}`), &action)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.RuleActionTypeRespond, action.Action)
|
||||
require.Equal(t, DNSRouteActionOptions{}, action.RouteOptions)
|
||||
}
|
||||
|
||||
func TestDNSRuleActionRespondRejectsUnknownFields(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var action DNSRuleAction
|
||||
err := json.UnmarshalContext(context.Background(), []byte(`{"action":"respond","disable_cache":true}`), &action)
|
||||
require.ErrorContains(t, err, "unknown field")
|
||||
}
|
||||
@@ -35,7 +35,7 @@ func (r DNSRule) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (r *DNSRule) UnmarshalJSONContext(ctx context.Context, bytes []byte) error {
|
||||
err := json.UnmarshalContext(ctx, bytes, (*_DNSRule)(r))
|
||||
err := json.Unmarshal(bytes, (*_DNSRule)(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -78,6 +78,12 @@ 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"`
|
||||
@@ -104,23 +110,9 @@ 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"`
|
||||
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"`
|
||||
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,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"`
|
||||
}
|
||||
@@ -135,27 +127,11 @@ func (r DefaultDNSRule) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (r *DefaultDNSRule) UnmarshalJSONContext(ctx context.Context, data []byte) error {
|
||||
rawAction, routeOptions, err := inspectDNSRuleAction(ctx, data)
|
||||
err := json.UnmarshalContext(ctx, data, &r.RawDefaultDNSRule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
return badjson.UnmarshallExcludedContext(ctx, data, &r.RawDefaultDNSRule, &r.DNSRuleAction)
|
||||
}
|
||||
|
||||
func (r DefaultDNSRule) IsValid() bool {
|
||||
@@ -180,27 +156,11 @@ func (r LogicalDNSRule) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) UnmarshalJSONContext(ctx context.Context, data []byte) error {
|
||||
rawAction, routeOptions, err := inspectDNSRuleAction(ctx, data)
|
||||
err := json.Unmarshal(data, &r.RawLogicalDNSRule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
return badjson.UnmarshallExcludedContext(ctx, data, &r.RawLogicalDNSRule, &r.DNSRuleAction)
|
||||
}
|
||||
|
||||
func (r *LogicalDNSRule) IsValid() bool {
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
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{}{}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"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 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 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,7 +125,6 @@ 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,14 +73,12 @@ 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,10 +29,7 @@ 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)
|
||||
WrapError func(error) 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)
|
||||
|
||||
func RegisterInbound(registry *inbound.Registry) {
|
||||
inbound.Register[option.NaiveInboundOptions](registry, C.TypeNaive, NewInbound)
|
||||
|
||||
@@ -95,7 +95,7 @@ func (p *paddingConn) writeWithPadding(writer io.Writer, data []byte) (n int, er
|
||||
binary.BigEndian.PutUint16(header, uint16(len(data)))
|
||||
header[2] = byte(paddingSize)
|
||||
common.Must1(buffer.Write(data))
|
||||
common.Must(buffer.WriteZeroN(paddingSize))
|
||||
buffer.Extend(paddingSize)
|
||||
_, err = writer.Write(buffer.Bytes())
|
||||
if err == nil {
|
||||
n = len(data)
|
||||
@@ -117,7 +117,7 @@ func (p *paddingConn) writeBufferWithPadding(writer io.Writer, buffer *buf.Buffe
|
||||
header := buffer.ExtendHeader(3)
|
||||
binary.BigEndian.PutUint16(header, uint16(bufferLen))
|
||||
header[2] = byte(paddingSize)
|
||||
common.Must(buffer.WriteZeroN(paddingSize))
|
||||
buffer.Extend(paddingSize)
|
||||
p.writePadding++
|
||||
}
|
||||
return common.Error(writer.Write(buffer.Bytes()))
|
||||
@@ -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, wrapError(err)
|
||||
return n, baderror.WrapH2(err)
|
||||
}
|
||||
|
||||
func (c *naiveConn) Write(p []byte) (n int, err error) {
|
||||
n, err = c.writeChunked(c.Conn, p)
|
||||
return n, wrapError(err)
|
||||
return n, baderror.WrapH2(err)
|
||||
}
|
||||
|
||||
func (c *naiveConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
defer buffer.Release()
|
||||
err := c.writeBufferWithPadding(c.Conn, buffer)
|
||||
return wrapError(err)
|
||||
return baderror.WrapH2(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, wrapError(err)
|
||||
return n, baderror.WrapH2(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, wrapError(err)
|
||||
return n, baderror.WrapH2(err)
|
||||
}
|
||||
|
||||
func (c *naiveH2Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
@@ -227,15 +227,7 @@ func (c *naiveH2Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
if err == nil {
|
||||
c.flusher.Flush()
|
||||
}
|
||||
return wrapError(err)
|
||||
}
|
||||
|
||||
func wrapError(err error) error {
|
||||
err = baderror.WrapH2(err)
|
||||
if WrapError != nil {
|
||||
err = WrapError(err)
|
||||
}
|
||||
return err
|
||||
return baderror.WrapH2(err)
|
||||
}
|
||||
|
||||
func (c *naiveH2Conn) Close() error {
|
||||
|
||||
@@ -124,5 +124,4 @@ func init() {
|
||||
|
||||
return quicListener, nil
|
||||
}
|
||||
naive.WrapError = qtls.WrapError
|
||||
}
|
||||
|
||||
@@ -70,10 +70,6 @@ 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, "]")
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
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
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user