mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
Compare commits
11 Commits
dev-next
...
v1.10.0-al
Author | SHA1 | Date | |
---|---|---|---|
![]() |
228b40932b | ||
![]() |
bab495301d | ||
![]() |
98e8fce9cd | ||
![]() |
468d78da49 | ||
![]() |
61705e8a93 | ||
![]() |
f62add5954 | ||
![]() |
72ee3b35ae | ||
![]() |
aa2975474f | ||
![]() |
c82f460d95 | ||
![]() |
8ec7c239cd | ||
![]() |
8b2792df40 |
20
.github/workflows/debug.yml
vendored
20
.github/workflows/debug.yml
vendored
@ -33,26 +33,6 @@ jobs:
|
|||||||
- name: Run Test
|
- name: Run Test
|
||||||
run: |
|
run: |
|
||||||
go test -v ./...
|
go test -v ./...
|
||||||
build_go118:
|
|
||||||
name: Debug build (Go 1.18)
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: ~1.18
|
|
||||||
- name: Cache go module
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: go118-${{ hashFiles('**/go.sum') }}
|
|
||||||
- name: Run Test
|
|
||||||
run: make ci_build_go118
|
|
||||||
build_go120:
|
build_go120:
|
||||||
name: Debug build (Go 1.20)
|
name: Debug build (Go 1.20)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -14,3 +14,5 @@
|
|||||||
/*.xcframework/
|
/*.xcframework/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/config.d/
|
/config.d/
|
||||||
|
/venv/
|
||||||
|
|
||||||
|
17
Makefile
17
Makefile
@ -1,7 +1,6 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api
|
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
|
||||||
TAGS_GO120 = with_quic,with_utls
|
|
||||||
TAGS_GO121 = with_ech
|
TAGS_GO121 = with_ech
|
||||||
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
|
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
||||||
@ -20,13 +19,9 @@ PREFIX ?= $(shell go env GOPATH)
|
|||||||
build:
|
build:
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
ci_build_go118:
|
|
||||||
go build $(PARAMS) $(MAIN)
|
|
||||||
go build $(PARAMS) -tags "$(TAGS_GO118)" $(MAIN)
|
|
||||||
|
|
||||||
ci_build_go120:
|
ci_build_go120:
|
||||||
go build $(PARAMS) $(MAIN)
|
go build $(PARAMS) $(MAIN)
|
||||||
go build $(PARAMS) -tags "$(TAGS_GO118),$(TAGS_GO120)" $(MAIN)
|
go build $(PARAMS) -tags "$(TAGS_GO120)" $(MAIN)
|
||||||
|
|
||||||
ci_build:
|
ci_build:
|
||||||
go build $(PARAMS) $(MAIN)
|
go build $(PARAMS) $(MAIN)
|
||||||
@ -197,13 +192,15 @@ lib_install:
|
|||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.3
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.3
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
|
|
||||||
publish_docs:
|
publish_docs:
|
||||||
mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
|
venv/bin/mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
|
||||||
|
|
||||||
docs_install:
|
docs_install:
|
||||||
pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
|
python -m venv venv
|
||||||
|
source ./venv/bin/active && pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin dist sing-box
|
rm -rf bin dist sing-box
|
||||||
rm -f $(shell go env GOPATH)/sing-box
|
rm -f $(shell go env GOPATH)/sing-box
|
||||||
|
@ -10,15 +10,18 @@ import (
|
|||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
mdns "github.com/miekg/dns"
|
mdns "github.com/miekg/dns"
|
||||||
|
"go4.org/netipx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Router interface {
|
type Router interface {
|
||||||
Service
|
Service
|
||||||
PreStarter
|
PreStarter
|
||||||
PostStarter
|
PostStarter
|
||||||
|
Cleanup() error
|
||||||
|
|
||||||
Outbounds() []Outbound
|
Outbounds() []Outbound
|
||||||
Outbound(tag string) (Outbound, bool)
|
Outbound(tag string) (Outbound, bool)
|
||||||
@ -45,7 +48,9 @@ type Router interface {
|
|||||||
DefaultInterface() string
|
DefaultInterface() string
|
||||||
AutoDetectInterface() bool
|
AutoDetectInterface() bool
|
||||||
AutoDetectInterfaceFunc() control.Func
|
AutoDetectInterfaceFunc() control.Func
|
||||||
DefaultMark() int
|
DefaultMark() uint32
|
||||||
|
RegisterAutoRedirectOutputMark(mark uint32) error
|
||||||
|
AutoRedirectOutputMark() uint32
|
||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
@ -92,12 +97,21 @@ type DNSRule interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RuleSet interface {
|
type RuleSet interface {
|
||||||
|
Name() string
|
||||||
StartContext(ctx context.Context, startContext RuleSetStartContext) error
|
StartContext(ctx context.Context, startContext RuleSetStartContext) error
|
||||||
Metadata() RuleSetMetadata
|
Metadata() RuleSetMetadata
|
||||||
|
ExtractIPSet() []*netipx.IPSet
|
||||||
|
IncRef()
|
||||||
|
DecRef()
|
||||||
|
Cleanup()
|
||||||
|
RegisterCallback(callback RuleSetUpdateCallback) *list.Element[RuleSetUpdateCallback]
|
||||||
|
UnregisterCallback(element *list.Element[RuleSetUpdateCallback])
|
||||||
Close() error
|
Close() error
|
||||||
HeadlessRule
|
HeadlessRule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RuleSetUpdateCallback func(it RuleSet)
|
||||||
|
|
||||||
type RuleSetMetadata struct {
|
type RuleSetMetadata struct {
|
||||||
ContainsProcessRule bool
|
ContainsProcessRule bool
|
||||||
ContainsWIFIRule bool
|
ContainsWIFIRule bool
|
||||||
|
29
box.go
29
box.go
@ -111,6 +111,7 @@ func New(options Options) (*Box, error) {
|
|||||||
ctx,
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
inboundOptions,
|
inboundOptions,
|
||||||
options.PlatformInterface,
|
options.PlatformInterface,
|
||||||
)
|
)
|
||||||
@ -302,7 +303,11 @@ func (s *Box) start() error {
|
|||||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s.postStart()
|
err = s.postStart()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.router.Cleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) postStart() error {
|
func (s *Box) postStart() error {
|
||||||
@ -312,16 +317,28 @@ func (s *Box) postStart() error {
|
|||||||
return E.Cause(err, "start ", serviceName)
|
return E.Cause(err, "start ", serviceName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, outbound := range s.outbounds {
|
// TODO: reorganize ALL start order
|
||||||
if lateOutbound, isLateOutbound := outbound.(adapter.PostStarter); isLateOutbound {
|
for _, out := range s.outbounds {
|
||||||
|
if lateOutbound, isLateOutbound := out.(adapter.PostStarter); isLateOutbound {
|
||||||
err := lateOutbound.PostStart()
|
err := lateOutbound.PostStart()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "post-start outbound/", outbound.Tag())
|
return E.Cause(err, "post-start outbound/", out.Tag())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
err := s.router.PostStart()
|
||||||
return s.router.PostStart()
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, in := range s.inbounds {
|
||||||
|
if lateInbound, isLateInbound := in.(adapter.PostStarter); isLateInbound {
|
||||||
|
err = lateInbound.PostStart()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "post-start inbound/", in.Tag())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) Close() error {
|
func (s *Box) Close() error {
|
||||||
|
@ -9,8 +9,10 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -32,7 +34,10 @@ func init() {
|
|||||||
commandTools.AddCommand(commandFetch)
|
commandTools.AddCommand(commandFetch)
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpClient *http.Client
|
var (
|
||||||
|
httpClient *http.Client
|
||||||
|
http3Client *http.Client
|
||||||
|
)
|
||||||
|
|
||||||
func fetch(args []string) error {
|
func fetch(args []string) error {
|
||||||
instance, err := createPreStartedClient()
|
instance, err := createPreStartedClient()
|
||||||
@ -53,8 +58,16 @@ func fetch(args []string) error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
defer httpClient.CloseIdleConnections()
|
defer httpClient.CloseIdleConnections()
|
||||||
|
if C.WithQUIC {
|
||||||
|
err = initializeHTTP3Client(instance)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer http3Client.CloseIdleConnections()
|
||||||
|
}
|
||||||
for _, urlString := range args {
|
for _, urlString := range args {
|
||||||
parsedURL, err := url.Parse(urlString)
|
var parsedURL *url.URL
|
||||||
|
parsedURL, err = url.Parse(urlString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -63,16 +76,27 @@ func fetch(args []string) error {
|
|||||||
parsedURL.Scheme = "http"
|
parsedURL.Scheme = "http"
|
||||||
fallthrough
|
fallthrough
|
||||||
case "http", "https":
|
case "http", "https":
|
||||||
err = fetchHTTP(parsedURL)
|
err = fetchHTTP(httpClient, parsedURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case "http3":
|
||||||
|
if !C.WithQUIC {
|
||||||
|
return C.ErrQUICNotIncluded
|
||||||
|
}
|
||||||
|
parsedURL.Scheme = "https"
|
||||||
|
err = fetchHTTP(http3Client, parsedURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return E.New("unsupported scheme: ", parsedURL.Scheme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchHTTP(parsedURL *url.URL) error {
|
func fetchHTTP(httpClient *http.Client, parsedURL *url.URL) error {
|
||||||
request, err := http.NewRequest("GET", parsedURL.String(), nil)
|
request, err := http.NewRequest("GET", parsedURL.String(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
36
cmd/sing-box/cmd_tools_fetch_http3.go
Normal file
36
cmd/sing-box/cmd_tools_fetch_http3.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
//go:build with_quic
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/sagernet/quic-go"
|
||||||
|
"github.com/sagernet/quic-go/http3"
|
||||||
|
box "github.com/sagernet/sing-box"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initializeHTTP3Client(instance *box.Box) error {
|
||||||
|
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
http3Client = &http.Client{
|
||||||
|
Transport: &http3.RoundTripper{
|
||||||
|
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
|
destination := M.ParseSocksaddr(addr)
|
||||||
|
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
|
||||||
|
if dErr != nil {
|
||||||
|
return nil, dErr
|
||||||
|
}
|
||||||
|
return quic.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsCfg, cfg)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
18
cmd/sing-box/cmd_tools_fetch_http3_stub.go
Normal file
18
cmd/sing-box/cmd_tools_fetch_http3_stub.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
//go:build !with_quic
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
box "github.com/sagernet/sing-box"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initializeHTTP3Client(instance *box.Box) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchHTTP3(parsedURL *url.URL) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
@ -50,12 +50,26 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
|||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
}
|
}
|
||||||
if options.RoutingMark != 0 {
|
var autoRedirectOutputMark uint32
|
||||||
|
if router != nil {
|
||||||
|
autoRedirectOutputMark = router.AutoRedirectOutputMark()
|
||||||
|
}
|
||||||
|
if autoRedirectOutputMark > 0 {
|
||||||
|
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||||
|
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||||
|
}
|
||||||
|
if options.RoutingMark > 0 {
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
|
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
|
||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
|
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
|
||||||
} else if router != nil && router.DefaultMark() != 0 {
|
if autoRedirectOutputMark > 0 {
|
||||||
|
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `routing_mark`")
|
||||||
|
}
|
||||||
|
} else if router != nil && router.DefaultMark() > 0 {
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(router.DefaultMark()))
|
dialer.Control = control.Append(dialer.Control, control.RoutingMark(router.DefaultMark()))
|
||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
|
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
|
||||||
|
if autoRedirectOutputMark > 0 {
|
||||||
|
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `default_mark`")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if options.ReuseAddr {
|
if options.ReuseAddr {
|
||||||
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
||||||
|
113
common/sniff/bittorrent.go
Normal file
113
common/sniff/bittorrent.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package sniff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
trackerConnectFlag = iota
|
||||||
|
trackerAnnounceFlag
|
||||||
|
trackerScrapeFlag
|
||||||
|
|
||||||
|
trackerProtocolID = 0x41727101980
|
||||||
|
|
||||||
|
trackerConnectMinSize = 16
|
||||||
|
trackerAnnounceMinSize = 20
|
||||||
|
trackerScrapeMinSize = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
// BitTorrent detects if the stream is a BitTorrent connection.
|
||||||
|
// For the BitTorrent protocol specification, see https://www.bittorrent.org/beps/bep_0003.html
|
||||||
|
func BitTorrent(_ context.Context, reader io.Reader) (*adapter.InboundContext, error) {
|
||||||
|
var first byte
|
||||||
|
err := binary.Read(reader, binary.BigEndian, &first)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if first != 19 {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
var protocol [19]byte
|
||||||
|
_, err = reader.Read(protocol[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if string(protocol[:]) != "BitTorrent protocol" {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
return &adapter.InboundContext{
|
||||||
|
Protocol: C.ProtocolBitTorrent,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UTP detects if the packet is a uTP connection packet.
|
||||||
|
// For the uTP protocol specification, see
|
||||||
|
// 1. https://www.bittorrent.org/beps/bep_0029.html
|
||||||
|
// 2. https://github.com/bittorrent/libutp/blob/2b364cbb0650bdab64a5de2abb4518f9f228ec44/utp_internal.cpp#L112
|
||||||
|
func UTP(_ context.Context, packet []byte) (*adapter.InboundContext, error) {
|
||||||
|
// A valid uTP packet must be at least 20 bytes long.
|
||||||
|
if len(packet) < 20 {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
version := packet[0] & 0x0F
|
||||||
|
ty := packet[0] >> 4
|
||||||
|
if version != 1 || ty > 4 {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the extensions
|
||||||
|
extension := packet[1]
|
||||||
|
reader := bytes.NewReader(packet[20:])
|
||||||
|
for extension != 0 {
|
||||||
|
err := binary.Read(reader, binary.BigEndian, &extension)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var length byte
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = reader.Seek(int64(length), io.SeekCurrent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &adapter.InboundContext{
|
||||||
|
Protocol: C.ProtocolBitTorrent,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UDPTracker detects if the packet is a UDP Tracker Protocol packet.
|
||||||
|
// For the UDP Tracker Protocol specification, see https://www.bittorrent.org/beps/bep_0015.html
|
||||||
|
func UDPTracker(_ context.Context, packet []byte) (*adapter.InboundContext, error) {
|
||||||
|
switch {
|
||||||
|
case len(packet) >= trackerConnectMinSize &&
|
||||||
|
binary.BigEndian.Uint64(packet[:8]) == trackerProtocolID &&
|
||||||
|
binary.BigEndian.Uint32(packet[8:12]) == trackerConnectFlag:
|
||||||
|
fallthrough
|
||||||
|
case len(packet) >= trackerAnnounceMinSize &&
|
||||||
|
binary.BigEndian.Uint32(packet[8:12]) == trackerAnnounceFlag:
|
||||||
|
fallthrough
|
||||||
|
case len(packet) >= trackerScrapeMinSize &&
|
||||||
|
binary.BigEndian.Uint32(packet[8:12]) == trackerScrapeFlag:
|
||||||
|
return &adapter.InboundContext{
|
||||||
|
Protocol: C.ProtocolBitTorrent,
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
}
|
81
common/sniff/bittorrent_test.go
Normal file
81
common/sniff/bittorrent_test.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package sniff_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/sniff"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSniffBittorrent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
packets := []string{
|
||||||
|
"13426974546f7272656e742070726f746f636f6c0000000000100000e21ea9569b69bab33c97851d0298bdfa89bc90922d5554313631302dea812fcd6a3563e3be40c1d1",
|
||||||
|
"13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452333030302d653369733079647675763638",
|
||||||
|
"13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452343035302d6f7a316c6e79377931716130",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pkt := range packets {
|
||||||
|
pkt, err := hex.DecodeString(pkt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
metadata, err := sniff.BitTorrent(context.TODO(), bytes.NewReader(pkt))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSniffUTP(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
packets := []string{
|
||||||
|
"010041a282d7ee7b583afb160004000006d8318da776968f92d666f7963f32dae23ba0d2c810d8b8209cc4939f54fde9eeaa521c2c20c9ba7f43f4fb0375f28de06643b5e3ca4685ab7ac76adca99783be72ef05ed59ef4234f5712b75b4c7c0d7bee8fe2ca20ad626ba5bb0ffcc16bf06790896f888048cf72716419a07db1a3dca4550fbcea75b53e97235168a221cf3e553dfbb723961bd719fab038d86e0ecb74747f5a2cd669de1c4b9ad375f3a492d09d98cdfad745435625401315bbba98d35d32086299801377b93495a63a9efddb8d05f5b37a5c5b1c0a25e917f12007bb5e05013ada8aff544fab8cadf61d80ddb0b60f12741e44515a109d144fd53ef845acb4b5ccf0d6fc302d7003d76df3fc3423bb0237301c9e88f900c2d392a8e0fdb36d143cf7527a93fd0a2638b746e72f6699fffcd4fd15348fce780d4caa04382fd9faf1ca0ae377ca805da7536662b84f5ee18dd3ae38fcb095a7543e55f9069ae92c8cf54ae44e97b558d35e2545c66601ed2149cbc32bd6df199a2be7cf0da8b2ff137e0d23e776bc87248425013876d3a3cc31a83b424b752bd0346437f24b532978005d8f5b1b0be1a37a2489c32a18a9ad3118e3f9d30eb299bffae18e1f0677c2a5c185e62519093fe6bc2b7339299ea50a587989f726ca6443a75dd5bb936f6367c6355d80fae53ff529d740b2e5576e3eefdf1fdbfc69c3c8d8ac750512635de63e054bee1d3b689bc1b2bc3d2601e42a00b5c89066d173d4ae7ffedfd2274e5cf6d868fbe640aedb69b8246142f00b32d459974287537ddd5373460dcbc92f5cfdd7a3ed6020822ae922d947893752ca1983d0d32977374c384ac8f5ab566859019b7351526b9f13e932037a55bb052d9deb3b3c23317e0784fdc51a64f2159bfea3b069cf5caf02ee2c3c1a6b6b427bb16165713e8802d95b5c8ed77953690e994bd38c9ae113fedaf6ee7fc2b96c032ceafc2a530ad0422e84546b9c6ad8ef6ea02fa508abddd1805c38a7b42e9b7c971b1b636865ebec06ed754bb404cd6b4e6cc8cb77bd4a0c43410d5cd5ef8fe853a66d49b3b9e06cb141236cdbfdd5761601dc54d1250b86c660e0f898fe62526fdd9acf0eab60a3bbbb2151970461f28f10b31689594bea646c4b03ee197d63bdef4e5a7c22716b3bb9494a83b78ecd81b338b80ac6c09c43485b1b09ba41c74343832c78f0520c1d659ac9eb1502094141e82fb9e5e620970ebc0655514c43c294a7714cbf9a499d277daf089f556398a01589a77494bec8bfb60a108f3813b55368672b88c1af40f6b3c8b513f7c70c3e0efce85228b8b9ec67ba0393f9f7305024d8e2da6a26cf85613d14f249170ce1000089df4c9c260df7f8292aa2ecb5d5bac97656d59aa248caedea2d198e51ce87baece338716d114b458de02d65c9ff808ca5b5b73723b4d1e962d9ac2d98176544dc9984cf8554d07820ef3dd0861cfe57b478328046380de589adad94ee44743ffac73bb7361feca5d56f07cf8ce75080e261282ae30350d7882679b15cab9e7e53ddf93310b33f7390ae5d318bb53f387e6af5d0ef4f947fc9cb8e7e38b52c7f8d772ece6156b38d88796ea19df02c53723b44df7c76315a0de9462f27287e682d2b4cda1a68fe00d7e48c51ee981be44e1ca940fb5190c12655edb4a83c3a4f33e48a015692df4f0b3d61656e362aca657b5ae8c12db5a0db3db1e45135ee918b66918f40e53c4f83e9da0cddfe63f736ae751ab3837a30ae3220d8e8e311487093a7b90c7e7e40dd54ca750e19452f9193aa892aa6a6229ab493dadae988b1724f7898ee69c36d3eb7364c4adbeca811cfe2065873e78c2b6dfdf1595f7a7831c07e03cda82e4f86f76438dfb2b07c13638ce7b509cfa71b88b5102b39a203b423202088e1c2103319cb32c13c1e546ff8612fa194c95a7808ab767c265a1bd5fa0efed5c8ec1701876a00ec8",
|
||||||
|
"01001ecb68176f215d04326300100000dbcf30292d14b54e9ee2d115ee5b8ebc7fad3e882d4fcdd0c14c6b917c11cb4c6a9f410b52a33ae97c2ac77c7a2b122b8955e09af3c5c595f1b2e79ca57cfe44c44e069610773b9bc9ba223d7f6b383e3adddd03fb88a8476028e30979c2ef321ffc97c5c132bcf9ac5b410bbb5ec6cefca3c7209202a14c5ae922b6b157b0a80249d13ffe5b996af0bc8e54ba576d148372494303e7ead0602b05b9c8fc97d48508a028a04d63a1fd28b0edfcd5c51715f63188b53eefede98a76912dca98518551a8856567307a56a702cbfcc115ea0c755b418bc2c7b57721239b82f09fb24328a4b0ce0f109bcb2a64e04b8aadb1f8487585425acdf8fc4ec8ea93cfcec5ac098bb29d42ddef6e46b03f34a5de28316726699b7cb5195c33e5c48abe87d591d63f9991c84c30819d186d6e0e95fd83c8dff07aa669c4430989bcaccfeacb9bcadbdb4d8f1964dbeb9687745656edd30b21c66cc0a1d742a78717d134a19a7f02d285a4973b1a198c00cfdff4676608dc4f3e817e3463c3b4e2c80d3e8d4fbac541a58a2fb7ad6939f607f8144eff6c8b0adc28ee5609ea158987519892fb",
|
||||||
|
"21001ecb6817f2805d044fd700100000dbd03029",
|
||||||
|
"410277ef0b1fb1f60000000000040000c233000000080000000000000000",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pkt := range packets {
|
||||||
|
pkt, err := hex.DecodeString(pkt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
metadata, err := sniff.UTP(context.TODO(), pkt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSniffUDPTracker(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
connectPackets := []string{
|
||||||
|
// connect packets
|
||||||
|
"00000417271019800000000078e90560",
|
||||||
|
"00000417271019800000000022c5d64d",
|
||||||
|
"000004172710198000000000b3863541",
|
||||||
|
|
||||||
|
// announce packets
|
||||||
|
"3d7592ead4b8c9e300000001b871a3820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
|
||||||
|
"3d7592ead4b8c9e30000000188deed1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
|
||||||
|
"3d7592ead4b8c9e300000001ceb948ad0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a3362cdb7020ff920e5aa642c3d4066950dd1f01f4d00000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
|
||||||
|
|
||||||
|
// scrape packets
|
||||||
|
"3d7592ead4b8c9e300000002d2f4bba5a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
|
||||||
|
"3d7592ead4b8c9e300000002441243292aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
|
||||||
|
"3d7592ead4b8c9e300000002b2aa461b1ad1fa9661cf3fe45fb2504ad52ec6c67758e294",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pkt := range connectPackets {
|
||||||
|
pkt, err := hex.DecodeString(pkt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
metadata, err := sniff.UDPTracker(context.TODO(), pkt)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
||||||
|
}
|
||||||
|
}
|
@ -6,4 +6,5 @@ const (
|
|||||||
ProtocolQUIC = "quic"
|
ProtocolQUIC = "quic"
|
||||||
ProtocolDNS = "dns"
|
ProtocolDNS = "dns"
|
||||||
ProtocolSTUN = "stun"
|
ProtocolSTUN = "stun"
|
||||||
|
ProtocolBitTorrent = "bittorrent"
|
||||||
)
|
)
|
||||||
|
@ -32,6 +32,12 @@ const (
|
|||||||
|
|
||||||
func ProxyDisplayName(proxyType string) string {
|
func ProxyDisplayName(proxyType string) string {
|
||||||
switch proxyType {
|
switch proxyType {
|
||||||
|
case TypeTun:
|
||||||
|
return "TUN"
|
||||||
|
case TypeRedirect:
|
||||||
|
return "Redirect"
|
||||||
|
case TypeTProxy:
|
||||||
|
return "TProxy"
|
||||||
case TypeDirect:
|
case TypeDirect:
|
||||||
return "Direct"
|
return "Direct"
|
||||||
case TypeBlock:
|
case TypeBlock:
|
||||||
@ -42,6 +48,8 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "SOCKS"
|
return "SOCKS"
|
||||||
case TypeHTTP:
|
case TypeHTTP:
|
||||||
return "HTTP"
|
return "HTTP"
|
||||||
|
case TypeMixed:
|
||||||
|
return "Mixed"
|
||||||
case TypeShadowsocks:
|
case TypeShadowsocks:
|
||||||
return "Shadowsocks"
|
return "Shadowsocks"
|
||||||
case TypeVMess:
|
case TypeVMess:
|
||||||
|
@ -2,19 +2,117 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
#### 1.10.0-alpha.13
|
||||||
|
|
||||||
|
* TUN address fields are merged **1**
|
||||||
|
* Add route address set support for auto-redirect **2**
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
See [Migration](/migration/#tun-address-fields-are-merged).
|
||||||
|
|
||||||
|
**2**:
|
||||||
|
|
||||||
|
The new feature will allow you to configure the destination IP CIDR rules
|
||||||
|
in the specified rule-sets to the firewall automatically.
|
||||||
|
|
||||||
|
Specified or unspecified destinations will bypass the sing-box routes to get better performance
|
||||||
|
(for example, keep hardware offloading of direct traffics on the router).
|
||||||
|
|
||||||
|
See [route_address_set](/configuration/inbound/tun/#route_address_set)
|
||||||
|
and [route_exclude_address_set](/configuration/inbound/tun/#route_exclude_address_set).
|
||||||
|
|
||||||
|
#### 1.10.0-alpha.12
|
||||||
|
|
||||||
|
* Fix auto-redirect not configuring nftables forward chain correctly
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
### 1.9.3
|
### 1.9.3
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.10.0-alpha.10
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
### 1.9.2
|
### 1.9.2
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.10.0-alpha.8
|
||||||
|
|
||||||
|
* Drop support for go1.18 and go1.19 **1**
|
||||||
|
* Update quic-go to v0.45.0
|
||||||
|
* Update Hysteria2 BBR congestion control
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.
|
||||||
|
|
||||||
### 1.9.1
|
### 1.9.1
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.10.0-alpha.7
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.10.0-alpha.5
|
||||||
|
|
||||||
|
* Improve auto-redirect **1**
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
nftables support and DNS hijacking has been added.
|
||||||
|
|
||||||
|
Tun inbounds with `auto_route` and `auto_redirect` now works as expected on routers **without intervention**.
|
||||||
|
|
||||||
|
#### 1.10.0-alpha.4
|
||||||
|
|
||||||
|
* Fix auto-redirect **1**
|
||||||
|
* Improve auto-route on linux **2**
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Tun inbounds with `auto_route` and `auto_redirect` now works as expected on routers.
|
||||||
|
|
||||||
|
**2**:
|
||||||
|
|
||||||
|
Tun inbounds with `auto_route` and `strict_route` now works as expected on routers and servers,
|
||||||
|
but the usages of [exclude_interface](/configuration/inbound/tun/#exclude_interface) need to be updated.
|
||||||
|
|
||||||
|
#### 1.10.0-alpha.2
|
||||||
|
|
||||||
|
* Move auto-redirect to Tun **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
Linux support are added.
|
||||||
|
|
||||||
|
See [Tun](/configuration/inbound/tun/#auto_redirect).
|
||||||
|
|
||||||
|
#### 1.10.0-alpha.1
|
||||||
|
|
||||||
|
* Add tailing comma support in JSON configuration
|
||||||
|
* Add simple auto-redirect for Android **1**
|
||||||
|
* Add BitTorrent sniffer **2**
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
It allows you to use redirect inbound in the sing-box Android client
|
||||||
|
and automatically configures IPv4 TCP redirection via su.
|
||||||
|
|
||||||
|
This may alleviate the symptoms of some OCD patients who think that
|
||||||
|
redirect can effectively save power compared to the system HTTP Proxy.
|
||||||
|
|
||||||
|
See [Redirect](/configuration/inbound/redirect/).
|
||||||
|
|
||||||
|
**2**:
|
||||||
|
|
||||||
|
See [Protocol Sniff](/configuration/route/sniff/).
|
||||||
|
|
||||||
### 1.9.0
|
### 1.9.0
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
@ -2,6 +2,21 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.10.0"
|
||||||
|
|
||||||
|
:material-plus: [address](#address)
|
||||||
|
:material-delete-clock: [inet4_address](#inet4_address)
|
||||||
|
:material-delete-clock: [inet6_address](#inet6_address)
|
||||||
|
:material-plus: [route_address](#route_address)
|
||||||
|
:material-delete-clock: [inet4_route_address](#inet4_route_address)
|
||||||
|
:material-delete-clock: [inet6_route_address](#inet6_route_address)
|
||||||
|
:material-plus: [route_exclude_address](#route_address)
|
||||||
|
:material-delete-clock: [inet4_route_exclude_address](#inet4_route_exclude_address)
|
||||||
|
:material-delete-clock: [inet6_route_exclude_address](#inet6_route_exclude_address)
|
||||||
|
:material-plus: [auto_redirect](#auto_redirect)
|
||||||
|
:material-plus: [route_address_set](#route_address_set)
|
||||||
|
:material-plus: [route_exclude_address_set](#route_address_set)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.9.0"
|
!!! quote "Changes in sing-box 1.9.0"
|
||||||
|
|
||||||
:material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain)
|
:material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain)
|
||||||
@ -23,26 +38,57 @@ icon: material/new-box
|
|||||||
"type": "tun",
|
"type": "tun",
|
||||||
"tag": "tun-in",
|
"tag": "tun-in",
|
||||||
"interface_name": "tun0",
|
"interface_name": "tun0",
|
||||||
"inet4_address": "172.19.0.1/30",
|
"address": [
|
||||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
"172.18.0.1/30",
|
||||||
|
"fdfe:dcba:9876::1/126"
|
||||||
|
],
|
||||||
|
// deprecated
|
||||||
|
"inet4_address": [
|
||||||
|
"172.19.0.1/30"
|
||||||
|
],
|
||||||
|
// deprecated
|
||||||
|
"inet6_address": [
|
||||||
|
"fdfe:dcba:9876::1/126"
|
||||||
|
],
|
||||||
"mtu": 9000,
|
"mtu": 9000,
|
||||||
"gso": false,
|
"gso": false,
|
||||||
"auto_route": true,
|
"auto_route": true,
|
||||||
"strict_route": true,
|
"strict_route": true,
|
||||||
|
"auto_redirect": false,
|
||||||
|
"route_address": [
|
||||||
|
"0.0.0.0/1",
|
||||||
|
"128.0.0.0/1",
|
||||||
|
"::/1",
|
||||||
|
"8000::/1"
|
||||||
|
],
|
||||||
|
// deprecated
|
||||||
"inet4_route_address": [
|
"inet4_route_address": [
|
||||||
"0.0.0.0/1",
|
"0.0.0.0/1",
|
||||||
"128.0.0.0/1"
|
"128.0.0.0/1"
|
||||||
],
|
],
|
||||||
|
// deprecated
|
||||||
"inet6_route_address": [
|
"inet6_route_address": [
|
||||||
"::/1",
|
"::/1",
|
||||||
"8000::/1"
|
"8000::/1"
|
||||||
],
|
],
|
||||||
|
"route_exclude_address": [
|
||||||
|
"192.168.0.0/16",
|
||||||
|
"fc00::/7"
|
||||||
|
],
|
||||||
|
// deprecated
|
||||||
"inet4_route_exclude_address": [
|
"inet4_route_exclude_address": [
|
||||||
"192.168.0.0/16"
|
"192.168.0.0/16"
|
||||||
],
|
],
|
||||||
|
// deprecated
|
||||||
"inet6_route_exclude_address": [
|
"inet6_route_exclude_address": [
|
||||||
"fc00::/7"
|
"fc00::/7"
|
||||||
],
|
],
|
||||||
|
"route_address_set": [
|
||||||
|
"geoip-cloudflare"
|
||||||
|
],
|
||||||
|
"route_exclude_address_set": [
|
||||||
|
"geoip-cn"
|
||||||
|
],
|
||||||
"endpoint_independent_nat": false,
|
"endpoint_independent_nat": false,
|
||||||
"udp_timeout": "5m",
|
"udp_timeout": "5m",
|
||||||
"stack": "system",
|
"stack": "system",
|
||||||
@ -102,14 +148,26 @@ icon: material/new-box
|
|||||||
|
|
||||||
Virtual device name, automatically selected if empty.
|
Virtual device name, automatically selected if empty.
|
||||||
|
|
||||||
|
#### address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
IPv4 and IPv6 prefix for the tun interface.
|
||||||
|
|
||||||
#### inet4_address
|
#### inet4_address
|
||||||
|
|
||||||
==Required==
|
!!! failure "Deprecated in sing-box 1.10.0"
|
||||||
|
|
||||||
|
`inet4_address` is merged to `address` and will be removed in sing-box 1.11.0.
|
||||||
|
|
||||||
IPv4 prefix for the tun interface.
|
IPv4 prefix for the tun interface.
|
||||||
|
|
||||||
#### inet6_address
|
#### inet6_address
|
||||||
|
|
||||||
|
!!! failure "Deprecated in sing-box 1.10.0"
|
||||||
|
|
||||||
|
`inet6_address` is merged to `address` and will be removed in sing-box 1.11.0.
|
||||||
|
|
||||||
IPv6 prefix for the tun interface.
|
IPv6 prefix for the tun interface.
|
||||||
|
|
||||||
#### mtu
|
#### mtu
|
||||||
@ -145,9 +203,10 @@ Enforce strict routing rules when `auto_route` is enabled:
|
|||||||
*In Linux*:
|
*In Linux*:
|
||||||
|
|
||||||
* Let unsupported network unreachable
|
* Let unsupported network unreachable
|
||||||
|
* Make ICMP traffic route to tun instead of upstream interfaces
|
||||||
* Route all connections to tun
|
* Route all connections to tun
|
||||||
|
|
||||||
It prevents address leaks and makes DNS hijacking work on Android.
|
It prevents IP address leaks and makes DNS hijacking work on Android.
|
||||||
|
|
||||||
*In Windows*:
|
*In Windows*:
|
||||||
|
|
||||||
@ -156,22 +215,95 @@ It prevents address leaks and makes DNS hijacking work on Android.
|
|||||||
|
|
||||||
It may prevent some applications (such as VirtualBox) from working properly in certain situations.
|
It may prevent some applications (such as VirtualBox) from working properly in certain situations.
|
||||||
|
|
||||||
|
#### auto_redirect
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux with `auto_route` enabled.
|
||||||
|
|
||||||
|
Automatically configure iptables/nftables to redirect connections.
|
||||||
|
|
||||||
|
*In Android*:
|
||||||
|
|
||||||
|
Only local connections are forwarded. To share your VPN connection over hotspot or repeater,
|
||||||
|
use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
||||||
|
|
||||||
|
*In Linux*:
|
||||||
|
|
||||||
|
`auto_route` with `auto_redirect` now works as expected on routers **without intervention**.
|
||||||
|
|
||||||
|
#### route_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
Use custom routes instead of default when `auto_route` is enabled.
|
||||||
|
|
||||||
#### inet4_route_address
|
#### inet4_route_address
|
||||||
|
|
||||||
|
!!! failure "Deprecated in sing-box 1.10.0"
|
||||||
|
|
||||||
|
`inet4_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address) instead.
|
||||||
|
|
||||||
Use custom routes instead of default when `auto_route` is enabled.
|
Use custom routes instead of default when `auto_route` is enabled.
|
||||||
|
|
||||||
#### inet6_route_address
|
#### inet6_route_address
|
||||||
|
|
||||||
|
!!! failure "Deprecated in sing-box 1.10.0"
|
||||||
|
|
||||||
|
`inet6_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address) instead.
|
||||||
|
|
||||||
Use custom routes instead of default when `auto_route` is enabled.
|
Use custom routes instead of default when `auto_route` is enabled.
|
||||||
|
|
||||||
|
#### route_exclude_address
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
Exclude custom routes when `auto_route` is enabled.
|
||||||
|
|
||||||
#### inet4_route_exclude_address
|
#### inet4_route_exclude_address
|
||||||
|
|
||||||
|
!!! failure "Deprecated in sing-box 1.10.0"
|
||||||
|
|
||||||
|
`inet4_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_exclude_address](#route_exclude_address) instead.
|
||||||
|
|
||||||
Exclude custom routes when `auto_route` is enabled.
|
Exclude custom routes when `auto_route` is enabled.
|
||||||
|
|
||||||
#### inet6_route_exclude_address
|
#### inet6_route_exclude_address
|
||||||
|
|
||||||
|
!!! failure "Deprecated in sing-box 1.10.0"
|
||||||
|
|
||||||
|
`inet6_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_exclude_address](#route_exclude_address) instead.
|
||||||
|
|
||||||
Exclude custom routes when `auto_route` is enabled.
|
Exclude custom routes when `auto_route` is enabled.
|
||||||
|
|
||||||
|
#### route_address_set
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
|
||||||
|
|
||||||
|
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
||||||
|
Unmatched traffic will bypass the sing-box routes.
|
||||||
|
|
||||||
|
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
||||||
|
|
||||||
|
#### route_exclude_address_set
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
|
||||||
|
|
||||||
|
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
||||||
|
Matched traffic will bypass the sing-box routes.
|
||||||
|
|
||||||
|
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
||||||
|
|
||||||
#### endpoint_independent_nat
|
#### endpoint_independent_nat
|
||||||
|
|
||||||
!!! info ""
|
!!! info ""
|
||||||
@ -214,6 +346,10 @@ Conflict with `exclude_interface`.
|
|||||||
|
|
||||||
#### exclude_interface
|
#### exclude_interface
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
When `strict_route` enabled, return traffic to excluded interfaces will not be automatically excluded, so add them as well (example: `br-lan` and `pppoe-wan`).
|
||||||
|
|
||||||
Exclude interfaces in route.
|
Exclude interfaces in route.
|
||||||
|
|
||||||
Conflict with `include_interface`.
|
Conflict with `include_interface`.
|
||||||
|
@ -2,6 +2,21 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.10.0"
|
||||||
|
|
||||||
|
:material-plus: [address](#address)
|
||||||
|
:material-delete-clock: [inet4_address](#inet4_address)
|
||||||
|
:material-delete-clock: [inet6_address](#inet6_address)
|
||||||
|
:material-plus: [route_address](#route_address)
|
||||||
|
:material-delete-clock: [inet4_route_address](#inet4_route_address)
|
||||||
|
:material-delete-clock: [inet6_route_address](#inet6_route_address)
|
||||||
|
:material-plus: [route_exclude_address](#route_address)
|
||||||
|
:material-delete-clock: [inet4_route_exclude_address](#inet4_route_exclude_address)
|
||||||
|
:material-delete-clock: [inet6_route_exclude_address](#inet6_route_exclude_address)
|
||||||
|
:material-plus: [auto_redirect](#auto_redirect)
|
||||||
|
:material-plus: [route_address_set](#route_address_set)
|
||||||
|
:material-plus: [route_exclude_address_set](#route_address_set)
|
||||||
|
|
||||||
!!! quote "sing-box 1.9.0 中的更改"
|
!!! quote "sing-box 1.9.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain)
|
:material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain)
|
||||||
@ -23,26 +38,57 @@ icon: material/new-box
|
|||||||
"type": "tun",
|
"type": "tun",
|
||||||
"tag": "tun-in",
|
"tag": "tun-in",
|
||||||
"interface_name": "tun0",
|
"interface_name": "tun0",
|
||||||
"inet4_address": "172.19.0.1/30",
|
"address": [
|
||||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
"172.18.0.1/30",
|
||||||
|
"fdfe:dcba:9876::1/126"
|
||||||
|
],
|
||||||
|
// 已弃用
|
||||||
|
"inet4_address": [
|
||||||
|
"172.19.0.1/30"
|
||||||
|
],
|
||||||
|
// 已弃用
|
||||||
|
"inet6_address": [
|
||||||
|
"fdfe:dcba:9876::1/126"
|
||||||
|
],
|
||||||
"mtu": 9000,
|
"mtu": 9000,
|
||||||
"gso": false,
|
"gso": false,
|
||||||
"auto_route": true,
|
"auto_route": true,
|
||||||
"strict_route": true,
|
"strict_route": true,
|
||||||
|
"auto_redirect": false,
|
||||||
|
"route_address": [
|
||||||
|
"0.0.0.0/1",
|
||||||
|
"128.0.0.0/1",
|
||||||
|
"::/1",
|
||||||
|
"8000::/1"
|
||||||
|
],
|
||||||
|
// 已弃用
|
||||||
"inet4_route_address": [
|
"inet4_route_address": [
|
||||||
"0.0.0.0/1",
|
"0.0.0.0/1",
|
||||||
"128.0.0.0/1"
|
"128.0.0.0/1"
|
||||||
],
|
],
|
||||||
|
// 已弃用
|
||||||
"inet6_route_address": [
|
"inet6_route_address": [
|
||||||
"::/1",
|
"::/1",
|
||||||
"8000::/1"
|
"8000::/1"
|
||||||
],
|
],
|
||||||
|
"route_exclude_address": [
|
||||||
|
"192.168.0.0/16",
|
||||||
|
"fc00::/7"
|
||||||
|
],
|
||||||
|
// 已弃用
|
||||||
"inet4_route_exclude_address": [
|
"inet4_route_exclude_address": [
|
||||||
"192.168.0.0/16"
|
"192.168.0.0/16"
|
||||||
],
|
],
|
||||||
|
// 已弃用
|
||||||
"inet6_route_exclude_address": [
|
"inet6_route_exclude_address": [
|
||||||
"fc00::/7"
|
"fc00::/7"
|
||||||
],
|
],
|
||||||
|
"route_address_set": [
|
||||||
|
"geoip-cloudflare"
|
||||||
|
],
|
||||||
|
"route_exclude_address_set": [
|
||||||
|
"geoip-cn"
|
||||||
|
],
|
||||||
"endpoint_independent_nat": false,
|
"endpoint_independent_nat": false,
|
||||||
"udp_timeout": "5m",
|
"udp_timeout": "5m",
|
||||||
"stack": "system",
|
"stack": "system",
|
||||||
@ -102,14 +148,30 @@ icon: material/new-box
|
|||||||
|
|
||||||
虚拟设备名称,默认自动选择。
|
虚拟设备名称,默认自动选择。
|
||||||
|
|
||||||
|
#### address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
tun 接口的 IPv4 和 IPv6 前缀。
|
||||||
|
|
||||||
#### inet4_address
|
#### inet4_address
|
||||||
|
|
||||||
|
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||||
|
|
||||||
|
`inet4_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除.
|
||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
tun 接口的 IPv4 前缀。
|
tun 接口的 IPv4 前缀。
|
||||||
|
|
||||||
#### inet6_address
|
#### inet6_address
|
||||||
|
|
||||||
|
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||||
|
|
||||||
|
`inet6_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除.
|
||||||
|
|
||||||
tun 接口的 IPv6 前缀。
|
tun 接口的 IPv6 前缀。
|
||||||
|
|
||||||
#### mtu
|
#### mtu
|
||||||
@ -145,9 +207,10 @@ tun 接口的 IPv6 前缀。
|
|||||||
*在 Linux 中*:
|
*在 Linux 中*:
|
||||||
|
|
||||||
* 让不支持的网络无法到达
|
* 让不支持的网络无法到达
|
||||||
|
* 使 ICMP 流量路由到 tun 而不是上游接口
|
||||||
* 将所有连接路由到 tun
|
* 将所有连接路由到 tun
|
||||||
|
|
||||||
它可以防止地址泄漏,并使 DNS 劫持在 Android 上工作。
|
它可以防止 IP 地址泄漏,并使 DNS 劫持在 Android 上工作。
|
||||||
|
|
||||||
*在 Windows 中*:
|
*在 Windows 中*:
|
||||||
|
|
||||||
@ -157,22 +220,94 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
它可能会使某些应用程序(如 VirtualBox)在某些情况下无法正常工作。
|
它可能会使某些应用程序(如 VirtualBox)在某些情况下无法正常工作。
|
||||||
|
|
||||||
|
#### auto_redirect
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux。
|
||||||
|
|
||||||
|
自动配置 iptables 以重定向 TCP 连接。
|
||||||
|
|
||||||
|
*在 Android 中*:
|
||||||
|
|
||||||
|
仅转发本地 IPv4 连接。 要通过热点或中继共享您的 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
|
||||||
|
|
||||||
|
*在 Linux 中*:
|
||||||
|
|
||||||
|
带有 `auto_redirect `的 `auto_route` 现在可以在路由器上按预期工作,**无需干预**。
|
||||||
|
|
||||||
|
#### route_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
设置到 Tun 的自定义路由。
|
||||||
|
|
||||||
#### inet4_route_address
|
#### inet4_route_address
|
||||||
|
|
||||||
|
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||||
|
|
||||||
|
`inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除.
|
||||||
|
|
||||||
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
||||||
|
|
||||||
#### inet6_route_address
|
#### inet6_route_address
|
||||||
|
|
||||||
|
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||||
|
|
||||||
|
`inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除.
|
||||||
|
|
||||||
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
||||||
|
|
||||||
|
#### route_exclude_address
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
设置到 Tun 的排除自定义路由。
|
||||||
|
|
||||||
#### inet4_route_exclude_address
|
#### inet4_route_exclude_address
|
||||||
|
|
||||||
|
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||||
|
|
||||||
|
`inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除.
|
||||||
|
|
||||||
启用 `auto_route` 时排除自定义路由。
|
启用 `auto_route` 时排除自定义路由。
|
||||||
|
|
||||||
#### inet6_route_exclude_address
|
#### inet6_route_exclude_address
|
||||||
|
|
||||||
|
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||||
|
|
||||||
|
`inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除.
|
||||||
|
|
||||||
启用 `auto_route` 时排除自定义路由。
|
启用 `auto_route` 时排除自定义路由。
|
||||||
|
|
||||||
|
#### route_address_set
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。
|
||||||
|
|
||||||
|
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
||||||
|
不匹配的流量将绕过 sing-box 路由。
|
||||||
|
|
||||||
|
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
||||||
|
|
||||||
|
#### route_exclude_address_set
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
|
!!! quote ""
|
||||||
|
|
||||||
|
仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。
|
||||||
|
|
||||||
|
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
||||||
|
匹配的流量将绕过 sing-box 路由。
|
||||||
|
|
||||||
|
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
||||||
|
|
||||||
#### endpoint_independent_nat
|
#### endpoint_independent_nat
|
||||||
|
|
||||||
启用独立于端点的 NAT。
|
启用独立于端点的 NAT。
|
||||||
@ -211,6 +346,10 @@ TCP/IP 栈。
|
|||||||
|
|
||||||
#### exclude_interface
|
#### exclude_interface
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
当 `strict_route` 启用,到被排除接口的回程流量将不会被自动排除,因此也要添加它们(例:`br-lan` 与 `pppoe-wan`)。
|
||||||
|
|
||||||
排除路由的接口。
|
排除路由的接口。
|
||||||
|
|
||||||
与 `include_interface` 冲突。
|
与 `include_interface` 冲突。
|
||||||
|
@ -3,9 +3,10 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c
|
|||||||
#### Supported Protocols
|
#### Supported Protocols
|
||||||
|
|
||||||
| Network | Protocol | Domain Name |
|
| Network | Protocol | Domain Name |
|
||||||
|:-------:|:--------:|:-----------:|
|
|:-------:|:-----------:|:-----------:|
|
||||||
| TCP | HTTP | Host |
|
| TCP | HTTP | Host |
|
||||||
| TCP | TLS | Server Name |
|
| TCP | TLS | Server Name |
|
||||||
| UDP | QUIC | Server Name |
|
| UDP | QUIC | Server Name |
|
||||||
| UDP | STUN | / |
|
| UDP | STUN | / |
|
||||||
| TCP/UDP | DNS | / |
|
| TCP/UDP | DNS | / |
|
||||||
|
| TCP/UDP | BitTorrent | / |
|
@ -3,9 +3,10 @@
|
|||||||
#### 支持的协议
|
#### 支持的协议
|
||||||
|
|
||||||
| 网络 | 协议 | 域名 |
|
| 网络 | 协议 | 域名 |
|
||||||
|:-------:|:----:|:-----------:|
|
|:-------:|:-----------:|:-----------:|
|
||||||
| TCP | HTTP | Host |
|
| TCP | HTTP | Host |
|
||||||
| TCP | TLS | Server Name |
|
| TCP | TLS | Server Name |
|
||||||
| UDP | QUIC | Server Name |
|
| UDP | QUIC | Server Name |
|
||||||
| UDP | STUN | / |
|
| UDP | STUN | / |
|
||||||
| TCP/UDP | DNS | / |
|
| TCP/UDP | DNS | / |
|
||||||
|
| TCP/UDP | BitTorrent | / |
|
@ -4,6 +4,20 @@ icon: material/delete-alert
|
|||||||
|
|
||||||
# Deprecated Feature List
|
# Deprecated Feature List
|
||||||
|
|
||||||
|
## 1.10.0
|
||||||
|
|
||||||
|
#### TUN address fields are merged
|
||||||
|
|
||||||
|
`inet4_address` and `inet6_address` are merged into `address`,
|
||||||
|
`inet4_route_address` and `inet6_route_address` are merged into `route_address`,
|
||||||
|
`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.
|
||||||
|
|
||||||
|
Old fields are deprecated and will be removed in sing-box 1.11.0.
|
||||||
|
|
||||||
|
#### Drop support for go1.18 and go1.19
|
||||||
|
|
||||||
|
Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.
|
||||||
|
|
||||||
## 1.8.0
|
## 1.8.0
|
||||||
|
|
||||||
#### Cache file and related features in Clash API
|
#### Cache file and related features in Clash API
|
||||||
|
@ -4,6 +4,20 @@ icon: material/delete-alert
|
|||||||
|
|
||||||
# 废弃功能列表
|
# 废弃功能列表
|
||||||
|
|
||||||
|
## 1.10.0
|
||||||
|
|
||||||
|
#### TUN 地址字段已合并
|
||||||
|
|
||||||
|
`inet4_address` 和 `inet6_address` 已合并为 `address`,
|
||||||
|
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
||||||
|
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
||||||
|
|
||||||
|
旧字段已废弃,且将在 sing-box 1.11.0 中移除。
|
||||||
|
|
||||||
|
#### 移除对 go1.18 和 go1.19 的支持
|
||||||
|
|
||||||
|
由于维护困难,sing-box 1.10.0 要求至少 Go 1.20 才能编译。
|
||||||
|
|
||||||
## 1.8.0
|
## 1.8.0
|
||||||
|
|
||||||
#### Clash API 中的 Cache file 及相关功能
|
#### Clash API 中的 Cache file 及相关功能
|
||||||
|
@ -2,12 +2,76 @@
|
|||||||
icon: material/arrange-bring-forward
|
icon: material/arrange-bring-forward
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 1.10.0
|
||||||
|
|
||||||
|
### TUN address fields are merged
|
||||||
|
|
||||||
|
`inet4_address` and `inet6_address` are merged into `address`,
|
||||||
|
`inet4_route_address` and `inet6_route_address` are merged into `route_address`,
|
||||||
|
`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.
|
||||||
|
|
||||||
|
Old fields are deprecated and will be removed in sing-box 1.11.0.
|
||||||
|
|
||||||
|
!!! info "References"
|
||||||
|
|
||||||
|
[TUN](/configuration/inbound/tun/)
|
||||||
|
|
||||||
|
=== ":material-card-remove: Deprecated"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "tun",
|
||||||
|
"inet4_address": "172.19.0.1/30",
|
||||||
|
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||||
|
"inet4_route_address": [
|
||||||
|
"0.0.0.0/1",
|
||||||
|
"128.0.0.0/1"
|
||||||
|
],
|
||||||
|
"inet6_route_address": [
|
||||||
|
"::/1",
|
||||||
|
"8000::/1"
|
||||||
|
],
|
||||||
|
"inet4_route_exclude_address": [
|
||||||
|
"192.168.0.0/16"
|
||||||
|
],
|
||||||
|
"inet6_route_exclude_address": [
|
||||||
|
"fc00::/7"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== ":material-card-multiple: New"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "tun",
|
||||||
|
"address": [
|
||||||
|
"172.19.0.1/30",
|
||||||
|
"fdfe:dcba:9876::1/126"
|
||||||
|
],
|
||||||
|
"route_address": [
|
||||||
|
"0.0.0.0/1",
|
||||||
|
"128.0.0.0/1",
|
||||||
|
"::/1",
|
||||||
|
"8000::/1"
|
||||||
|
],
|
||||||
|
"route_exclude_address": [
|
||||||
|
"192.168.0.0/16",
|
||||||
|
"fc00::/7"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 1.9.0
|
## 1.9.0
|
||||||
|
|
||||||
!!! warning "Unstable"
|
|
||||||
|
|
||||||
This version is still under development, and the following migration guide may be changed in the future.
|
|
||||||
|
|
||||||
### `domain_suffix` behavior update
|
### `domain_suffix` behavior update
|
||||||
|
|
||||||
For historical reasons, sing-box's `domain_suffix` rule matches literal prefixes instead of the same as other projects.
|
For historical reasons, sing-box's `domain_suffix` rule matches literal prefixes instead of the same as other projects.
|
||||||
|
@ -2,12 +2,76 @@
|
|||||||
icon: material/arrange-bring-forward
|
icon: material/arrange-bring-forward
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 1.10.0
|
||||||
|
|
||||||
|
### TUN 地址字段已合并
|
||||||
|
|
||||||
|
`inet4_address` 和 `inet6_address` 已合并为 `address`,
|
||||||
|
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
||||||
|
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
||||||
|
|
||||||
|
旧字段已废弃,且将在 sing-box 1.11.0 中移除。
|
||||||
|
|
||||||
|
!!! info "参考"
|
||||||
|
|
||||||
|
[TUN](/zh/configuration/inbound/tun/)
|
||||||
|
|
||||||
|
=== ":material-card-remove: 弃用的"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "tun",
|
||||||
|
"inet4_address": "172.19.0.1/30",
|
||||||
|
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||||
|
"inet4_route_address": [
|
||||||
|
"0.0.0.0/1",
|
||||||
|
"128.0.0.0/1"
|
||||||
|
],
|
||||||
|
"inet6_route_address": [
|
||||||
|
"::/1",
|
||||||
|
"8000::/1"
|
||||||
|
],
|
||||||
|
"inet4_route_exclude_address": [
|
||||||
|
"192.168.0.0/16"
|
||||||
|
],
|
||||||
|
"inet6_route_exclude_address": [
|
||||||
|
"fc00::/7"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== ":material-card-multiple: 新的"
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "tun",
|
||||||
|
"address": [
|
||||||
|
"172.19.0.1/30",
|
||||||
|
"fdfe:dcba:9876::1/126"
|
||||||
|
],
|
||||||
|
"route_address": [
|
||||||
|
"0.0.0.0/1",
|
||||||
|
"128.0.0.0/1",
|
||||||
|
"::/1",
|
||||||
|
"8000::/1"
|
||||||
|
],
|
||||||
|
"route_exclude_address": [
|
||||||
|
"192.168.0.0/16",
|
||||||
|
"fc00::/7"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 1.9.0
|
## 1.9.0
|
||||||
|
|
||||||
!!! warning "不稳定的"
|
|
||||||
|
|
||||||
该版本仍在开发中,迁移指南可能将在未来更改。
|
|
||||||
|
|
||||||
### `domain_suffix` 行为更新
|
### `domain_suffix` 行为更新
|
||||||
|
|
||||||
由于历史原因,sing-box 的 `domain_suffix` 规则匹配字面前缀,而不与其他项目相同。
|
由于历史原因,sing-box 的 `domain_suffix` 规则匹配字面前缀,而不与其他项目相同。
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
func connectionRouter(router adapter.Router, trafficManager *trafficontrol.Manager) http.Handler {
|
func connectionRouter(router adapter.Router, trafficManager *trafficontrol.Manager) http.Handler {
|
||||||
@ -76,10 +77,10 @@ func getConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseW
|
|||||||
|
|
||||||
func closeConnection(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
|
func closeConnection(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
id := chi.URLParam(r, "id")
|
id := uuid.FromStringOrNil(chi.URLParam(r, "id"))
|
||||||
snapshot := trafficManager.Snapshot()
|
snapshot := trafficManager.Snapshot()
|
||||||
for _, c := range snapshot.Connections {
|
for _, c := range snapshot.Connections {
|
||||||
if id == c.ID() {
|
if id == c.Metadata().ID {
|
||||||
c.Close()
|
c.Close()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
@ -218,58 +217,15 @@ func (s *Server) TrafficManager() *trafficontrol.Manager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) {
|
func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) {
|
||||||
tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule)
|
tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.router, matchedRule)
|
||||||
return tracker, tracker
|
return tracker, tracker
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule) (N.PacketConn, adapter.Tracker) {
|
func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule) (N.PacketConn, adapter.Tracker) {
|
||||||
tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule)
|
tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.router, matchedRule)
|
||||||
return tracker, tracker
|
return tracker, tracker
|
||||||
}
|
}
|
||||||
|
|
||||||
func castMetadata(metadata adapter.InboundContext) trafficontrol.Metadata {
|
|
||||||
var inbound string
|
|
||||||
if metadata.Inbound != "" {
|
|
||||||
inbound = metadata.InboundType + "/" + metadata.Inbound
|
|
||||||
} else {
|
|
||||||
inbound = metadata.InboundType
|
|
||||||
}
|
|
||||||
var domain string
|
|
||||||
if metadata.Domain != "" {
|
|
||||||
domain = metadata.Domain
|
|
||||||
} else {
|
|
||||||
domain = metadata.Destination.Fqdn
|
|
||||||
}
|
|
||||||
var processPath string
|
|
||||||
if metadata.ProcessInfo != nil {
|
|
||||||
if metadata.ProcessInfo.ProcessPath != "" {
|
|
||||||
processPath = metadata.ProcessInfo.ProcessPath
|
|
||||||
} else if metadata.ProcessInfo.PackageName != "" {
|
|
||||||
processPath = metadata.ProcessInfo.PackageName
|
|
||||||
}
|
|
||||||
if processPath == "" {
|
|
||||||
if metadata.ProcessInfo.UserId != -1 {
|
|
||||||
processPath = F.ToString(metadata.ProcessInfo.UserId)
|
|
||||||
}
|
|
||||||
} else if metadata.ProcessInfo.User != "" {
|
|
||||||
processPath = F.ToString(processPath, " (", metadata.ProcessInfo.User, ")")
|
|
||||||
} else if metadata.ProcessInfo.UserId != -1 {
|
|
||||||
processPath = F.ToString(processPath, " (", metadata.ProcessInfo.UserId, ")")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return trafficontrol.Metadata{
|
|
||||||
NetWork: metadata.Network,
|
|
||||||
Type: inbound,
|
|
||||||
SrcIP: metadata.Source.Addr,
|
|
||||||
DstIP: metadata.Destination.Addr,
|
|
||||||
SrcPort: F.ToString(metadata.Source.Port),
|
|
||||||
DstPort: F.ToString(metadata.Destination.Port),
|
|
||||||
Host: domain,
|
|
||||||
DNSMode: "normal",
|
|
||||||
ProcessPath: processPath,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func authentication(serverSecret string) func(next http.Handler) http.Handler {
|
func authentication(serverSecret string) func(next http.Handler) http.Handler {
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -2,10 +2,17 @@ package trafficontrol
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi/compatible"
|
"github.com/sagernet/sing-box/experimental/clashapi/compatible"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
"github.com/sagernet/sing/common/atomic"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
@ -16,7 +23,9 @@ type Manager struct {
|
|||||||
uploadTotal atomic.Int64
|
uploadTotal atomic.Int64
|
||||||
downloadTotal atomic.Int64
|
downloadTotal atomic.Int64
|
||||||
|
|
||||||
connections compatible.Map[string, tracker]
|
connections compatible.Map[uuid.UUID, Tracker]
|
||||||
|
closedConnectionsAccess sync.Mutex
|
||||||
|
closedConnections list.List[TrackerMetadata]
|
||||||
ticker *time.Ticker
|
ticker *time.Ticker
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
// process *process.Process
|
// process *process.Process
|
||||||
@ -33,12 +42,22 @@ func NewManager() *Manager {
|
|||||||
return manager
|
return manager
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Join(c tracker) {
|
func (m *Manager) Join(c Tracker) {
|
||||||
m.connections.Store(c.ID(), c)
|
m.connections.Store(c.Metadata().ID, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Leave(c tracker) {
|
func (m *Manager) Leave(c Tracker) {
|
||||||
m.connections.Delete(c.ID())
|
metadata := c.Metadata()
|
||||||
|
_, loaded := m.connections.LoadAndDelete(metadata.ID)
|
||||||
|
if loaded {
|
||||||
|
metadata.ClosedAt = time.Now()
|
||||||
|
m.closedConnectionsAccess.Lock()
|
||||||
|
defer m.closedConnectionsAccess.Unlock()
|
||||||
|
if m.closedConnections.Len() >= 1000 {
|
||||||
|
m.closedConnections.PopFront()
|
||||||
|
}
|
||||||
|
m.closedConnections.PushBack(metadata)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) PushUploaded(size int64) {
|
func (m *Manager) PushUploaded(size int64) {
|
||||||
@ -59,14 +78,39 @@ func (m *Manager) Total() (up int64, down int64) {
|
|||||||
return m.uploadTotal.Load(), m.downloadTotal.Load()
|
return m.uploadTotal.Load(), m.downloadTotal.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Connections() int {
|
func (m *Manager) ConnectionsLen() int {
|
||||||
return m.connections.Len()
|
return m.connections.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Connections() []TrackerMetadata {
|
||||||
|
var connections []TrackerMetadata
|
||||||
|
m.connections.Range(func(_ uuid.UUID, value Tracker) bool {
|
||||||
|
connections = append(connections, value.Metadata())
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return connections
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) ClosedConnections() []TrackerMetadata {
|
||||||
|
m.closedConnectionsAccess.Lock()
|
||||||
|
defer m.closedConnectionsAccess.Unlock()
|
||||||
|
return m.closedConnections.Array()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Connection(id uuid.UUID) Tracker {
|
||||||
|
connection, loaded := m.connections.Load(id)
|
||||||
|
if !loaded {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return connection
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) Snapshot() *Snapshot {
|
func (m *Manager) Snapshot() *Snapshot {
|
||||||
var connections []tracker
|
var connections []Tracker
|
||||||
m.connections.Range(func(_ string, value tracker) bool {
|
m.connections.Range(func(_ uuid.UUID, value Tracker) bool {
|
||||||
|
if value.Metadata().OutboundType != C.TypeDNS {
|
||||||
connections = append(connections, value)
|
connections = append(connections, value)
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -75,8 +119,8 @@ func (m *Manager) Snapshot() *Snapshot {
|
|||||||
m.memory = memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased
|
m.memory = memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased
|
||||||
|
|
||||||
return &Snapshot{
|
return &Snapshot{
|
||||||
UploadTotal: m.uploadTotal.Load(),
|
Upload: m.uploadTotal.Load(),
|
||||||
DownloadTotal: m.downloadTotal.Load(),
|
Download: m.downloadTotal.Load(),
|
||||||
Connections: connections,
|
Connections: connections,
|
||||||
Memory: m.memory,
|
Memory: m.memory,
|
||||||
}
|
}
|
||||||
@ -114,8 +158,17 @@ func (m *Manager) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Snapshot struct {
|
type Snapshot struct {
|
||||||
DownloadTotal int64 `json:"downloadTotal"`
|
Download int64
|
||||||
UploadTotal int64 `json:"uploadTotal"`
|
Upload int64
|
||||||
Connections []tracker `json:"connections"`
|
Connections []Tracker
|
||||||
Memory uint64 `json:"memory"`
|
Memory uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Snapshot) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(map[string]any{
|
||||||
|
"downloadTotal": s.Download,
|
||||||
|
"uploadTotal": s.Upload,
|
||||||
|
"connections": common.Map(s.Connections, func(t Tracker) TrackerMetadata { return t.Metadata() }),
|
||||||
|
"memory": s.Memory,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -2,97 +2,135 @@ package trafficontrol
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
"github.com/sagernet/sing/common/atomic"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Metadata struct {
|
type TrackerMetadata struct {
|
||||||
NetWork string `json:"network"`
|
ID uuid.UUID
|
||||||
Type string `json:"type"`
|
Metadata adapter.InboundContext
|
||||||
SrcIP netip.Addr `json:"sourceIP"`
|
CreatedAt time.Time
|
||||||
DstIP netip.Addr `json:"destinationIP"`
|
ClosedAt time.Time
|
||||||
SrcPort string `json:"sourcePort"`
|
Upload *atomic.Int64
|
||||||
DstPort string `json:"destinationPort"`
|
Download *atomic.Int64
|
||||||
Host string `json:"host"`
|
Chain []string
|
||||||
DNSMode string `json:"dnsMode"`
|
Rule adapter.Rule
|
||||||
ProcessPath string `json:"processPath"`
|
Outbound string
|
||||||
|
OutboundType string
|
||||||
}
|
}
|
||||||
|
|
||||||
type tracker interface {
|
func (t TrackerMetadata) MarshalJSON() ([]byte, error) {
|
||||||
ID() string
|
var inbound string
|
||||||
Close() error
|
if t.Metadata.Inbound != "" {
|
||||||
Leave()
|
inbound = t.Metadata.InboundType + "/" + t.Metadata.Inbound
|
||||||
}
|
} else {
|
||||||
|
inbound = t.Metadata.InboundType
|
||||||
type trackerInfo struct {
|
}
|
||||||
UUID uuid.UUID `json:"id"`
|
var domain string
|
||||||
Metadata Metadata `json:"metadata"`
|
if t.Metadata.Domain != "" {
|
||||||
UploadTotal *atomic.Int64 `json:"upload"`
|
domain = t.Metadata.Domain
|
||||||
DownloadTotal *atomic.Int64 `json:"download"`
|
} else {
|
||||||
Start time.Time `json:"start"`
|
domain = t.Metadata.Destination.Fqdn
|
||||||
Chain []string `json:"chains"`
|
}
|
||||||
Rule string `json:"rule"`
|
var processPath string
|
||||||
RulePayload string `json:"rulePayload"`
|
if t.Metadata.ProcessInfo != nil {
|
||||||
}
|
if t.Metadata.ProcessInfo.ProcessPath != "" {
|
||||||
|
processPath = t.Metadata.ProcessInfo.ProcessPath
|
||||||
func (t trackerInfo) MarshalJSON() ([]byte, error) {
|
} else if t.Metadata.ProcessInfo.PackageName != "" {
|
||||||
|
processPath = t.Metadata.ProcessInfo.PackageName
|
||||||
|
}
|
||||||
|
if processPath == "" {
|
||||||
|
if t.Metadata.ProcessInfo.UserId != -1 {
|
||||||
|
processPath = F.ToString(t.Metadata.ProcessInfo.UserId)
|
||||||
|
}
|
||||||
|
} else if t.Metadata.ProcessInfo.User != "" {
|
||||||
|
processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.User, ")")
|
||||||
|
} else if t.Metadata.ProcessInfo.UserId != -1 {
|
||||||
|
processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.UserId, ")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var rule string
|
||||||
|
if t.Rule != nil {
|
||||||
|
rule = F.ToString(t.Rule, " => ", t.Rule.Outbound())
|
||||||
|
} else {
|
||||||
|
rule = "final"
|
||||||
|
}
|
||||||
return json.Marshal(map[string]any{
|
return json.Marshal(map[string]any{
|
||||||
"id": t.UUID.String(),
|
"id": t.ID,
|
||||||
"metadata": t.Metadata,
|
"metadata": map[string]any{
|
||||||
"upload": t.UploadTotal.Load(),
|
"network": t.Metadata.Network,
|
||||||
"download": t.DownloadTotal.Load(),
|
"type": inbound,
|
||||||
"start": t.Start,
|
"sourceIP": t.Metadata.Source.Addr,
|
||||||
|
"destinationIP": t.Metadata.Destination.Addr,
|
||||||
|
"sourcePort": t.Metadata.Source.Port,
|
||||||
|
"destinationPort": t.Metadata.Destination.Port,
|
||||||
|
"host": domain,
|
||||||
|
"dnsMode": "normal",
|
||||||
|
"processPath": processPath,
|
||||||
|
},
|
||||||
|
"upload": t.Upload.Load(),
|
||||||
|
"download": t.Download.Load(),
|
||||||
|
"start": t.CreatedAt,
|
||||||
"chains": t.Chain,
|
"chains": t.Chain,
|
||||||
"rule": t.Rule,
|
"rule": rule,
|
||||||
"rulePayload": t.RulePayload,
|
"rulePayload": "",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type tcpTracker struct {
|
type Tracker interface {
|
||||||
N.ExtendedConn `json:"-"`
|
adapter.Tracker
|
||||||
*trackerInfo
|
Metadata() TrackerMetadata
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type TCPConn struct {
|
||||||
|
N.ExtendedConn
|
||||||
|
metadata TrackerMetadata
|
||||||
manager *Manager
|
manager *Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tt *tcpTracker) ID() string {
|
func (tt *TCPConn) Metadata() TrackerMetadata {
|
||||||
return tt.UUID.String()
|
return tt.metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tt *tcpTracker) Close() error {
|
func (tt *TCPConn) Close() error {
|
||||||
tt.manager.Leave(tt)
|
tt.manager.Leave(tt)
|
||||||
return tt.ExtendedConn.Close()
|
return tt.ExtendedConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tt *tcpTracker) Leave() {
|
func (tt *TCPConn) Leave() {
|
||||||
tt.manager.Leave(tt)
|
tt.manager.Leave(tt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tt *tcpTracker) Upstream() any {
|
func (tt *TCPConn) Upstream() any {
|
||||||
return tt.ExtendedConn
|
return tt.ExtendedConn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tt *tcpTracker) ReaderReplaceable() bool {
|
func (tt *TCPConn) ReaderReplaceable() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tt *tcpTracker) WriterReplaceable() bool {
|
func (tt *TCPConn) WriterReplaceable() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *tcpTracker {
|
func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *TCPConn {
|
||||||
uuid, _ := uuid.NewV4()
|
id, _ := uuid.NewV4()
|
||||||
|
var (
|
||||||
var chain []string
|
chain []string
|
||||||
var next string
|
next string
|
||||||
|
outbound string
|
||||||
|
outboundType string
|
||||||
|
)
|
||||||
if rule == nil {
|
if rule == nil {
|
||||||
if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
|
if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
|
||||||
next = defaultOutbound.Tag()
|
next = defaultOutbound.Tag()
|
||||||
@ -106,17 +144,17 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad
|
|||||||
if !loaded {
|
if !loaded {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
outbound = detour.Tag()
|
||||||
|
outboundType = detour.Type()
|
||||||
group, isGroup := detour.(adapter.OutboundGroup)
|
group, isGroup := detour.(adapter.OutboundGroup)
|
||||||
if !isGroup {
|
if !isGroup {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
next = group.Now()
|
next = group.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
upload := new(atomic.Int64)
|
upload := new(atomic.Int64)
|
||||||
download := new(atomic.Int64)
|
download := new(atomic.Int64)
|
||||||
|
tracker := &TCPConn{
|
||||||
t := &tcpTracker{
|
|
||||||
ExtendedConn: bufio.NewCounterConn(conn, []N.CountFunc{func(n int64) {
|
ExtendedConn: bufio.NewCounterConn(conn, []N.CountFunc{func(n int64) {
|
||||||
upload.Add(n)
|
upload.Add(n)
|
||||||
manager.PushUploaded(n)
|
manager.PushUploaded(n)
|
||||||
@ -124,64 +162,62 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad
|
|||||||
download.Add(n)
|
download.Add(n)
|
||||||
manager.PushDownloaded(n)
|
manager.PushDownloaded(n)
|
||||||
}}),
|
}}),
|
||||||
manager: manager,
|
metadata: TrackerMetadata{
|
||||||
trackerInfo: &trackerInfo{
|
ID: id,
|
||||||
UUID: uuid,
|
|
||||||
Start: time.Now(),
|
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
Upload: upload,
|
||||||
|
Download: download,
|
||||||
Chain: common.Reverse(chain),
|
Chain: common.Reverse(chain),
|
||||||
Rule: "",
|
Rule: rule,
|
||||||
UploadTotal: upload,
|
Outbound: outbound,
|
||||||
DownloadTotal: download,
|
OutboundType: outboundType,
|
||||||
},
|
},
|
||||||
|
manager: manager,
|
||||||
}
|
}
|
||||||
|
manager.Join(tracker)
|
||||||
if rule != nil {
|
return tracker
|
||||||
t.trackerInfo.Rule = rule.String() + " => " + rule.Outbound()
|
|
||||||
} else {
|
|
||||||
t.trackerInfo.Rule = "final"
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.Join(t)
|
|
||||||
return t
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type udpTracker struct {
|
type UDPConn struct {
|
||||||
N.PacketConn `json:"-"`
|
N.PacketConn `json:"-"`
|
||||||
*trackerInfo
|
metadata TrackerMetadata
|
||||||
manager *Manager
|
manager *Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ut *udpTracker) ID() string {
|
func (ut *UDPConn) Metadata() TrackerMetadata {
|
||||||
return ut.UUID.String()
|
return ut.metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ut *udpTracker) Close() error {
|
func (ut *UDPConn) Close() error {
|
||||||
ut.manager.Leave(ut)
|
ut.manager.Leave(ut)
|
||||||
return ut.PacketConn.Close()
|
return ut.PacketConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ut *udpTracker) Leave() {
|
func (ut *UDPConn) Leave() {
|
||||||
ut.manager.Leave(ut)
|
ut.manager.Leave(ut)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ut *udpTracker) Upstream() any {
|
func (ut *UDPConn) Upstream() any {
|
||||||
return ut.PacketConn
|
return ut.PacketConn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ut *udpTracker) ReaderReplaceable() bool {
|
func (ut *UDPConn) ReaderReplaceable() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ut *udpTracker) WriterReplaceable() bool {
|
func (ut *UDPConn) WriterReplaceable() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *udpTracker {
|
func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *UDPConn {
|
||||||
uuid, _ := uuid.NewV4()
|
id, _ := uuid.NewV4()
|
||||||
|
var (
|
||||||
var chain []string
|
chain []string
|
||||||
var next string
|
next string
|
||||||
|
outbound string
|
||||||
|
outboundType string
|
||||||
|
)
|
||||||
if rule == nil {
|
if rule == nil {
|
||||||
if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil {
|
if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil {
|
||||||
next = defaultOutbound.Tag()
|
next = defaultOutbound.Tag()
|
||||||
@ -195,17 +231,17 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, route
|
|||||||
if !loaded {
|
if !loaded {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
outbound = detour.Tag()
|
||||||
|
outboundType = detour.Type()
|
||||||
group, isGroup := detour.(adapter.OutboundGroup)
|
group, isGroup := detour.(adapter.OutboundGroup)
|
||||||
if !isGroup {
|
if !isGroup {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
next = group.Now()
|
next = group.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
upload := new(atomic.Int64)
|
upload := new(atomic.Int64)
|
||||||
download := new(atomic.Int64)
|
download := new(atomic.Int64)
|
||||||
|
trackerConn := &UDPConn{
|
||||||
ut := &udpTracker{
|
|
||||||
PacketConn: bufio.NewCounterPacketConn(conn, []N.CountFunc{func(n int64) {
|
PacketConn: bufio.NewCounterPacketConn(conn, []N.CountFunc{func(n int64) {
|
||||||
upload.Add(n)
|
upload.Add(n)
|
||||||
manager.PushUploaded(n)
|
manager.PushUploaded(n)
|
||||||
@ -213,24 +249,19 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, route
|
|||||||
download.Add(n)
|
download.Add(n)
|
||||||
manager.PushDownloaded(n)
|
manager.PushDownloaded(n)
|
||||||
}}),
|
}}),
|
||||||
manager: manager,
|
metadata: TrackerMetadata{
|
||||||
trackerInfo: &trackerInfo{
|
ID: id,
|
||||||
UUID: uuid,
|
|
||||||
Start: time.Now(),
|
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
Upload: upload,
|
||||||
|
Download: download,
|
||||||
Chain: common.Reverse(chain),
|
Chain: common.Reverse(chain),
|
||||||
Rule: "",
|
Rule: rule,
|
||||||
UploadTotal: upload,
|
Outbound: outbound,
|
||||||
DownloadTotal: download,
|
OutboundType: outboundType,
|
||||||
},
|
},
|
||||||
|
manager: manager,
|
||||||
}
|
}
|
||||||
|
manager.Join(trackerConn)
|
||||||
if rule != nil {
|
return trackerConn
|
||||||
ut.trackerInfo.Rule = rule.String() + " => " + rule.Outbound()
|
|
||||||
} else {
|
|
||||||
ut.trackerInfo.Rule = "final"
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.Join(ut)
|
|
||||||
return ut
|
|
||||||
}
|
}
|
||||||
|
@ -14,4 +14,6 @@ const (
|
|||||||
CommandSetClashMode
|
CommandSetClashMode
|
||||||
CommandGetSystemProxyStatus
|
CommandGetSystemProxyStatus
|
||||||
CommandSetSystemProxyEnabled
|
CommandSetSystemProxyEnabled
|
||||||
|
CommandConnections
|
||||||
|
CommandCloseConnection
|
||||||
)
|
)
|
||||||
|
@ -31,6 +31,7 @@ type CommandClientHandler interface {
|
|||||||
WriteGroups(message OutboundGroupIterator)
|
WriteGroups(message OutboundGroupIterator)
|
||||||
InitializeClashMode(modeList StringIterator, currentMode string)
|
InitializeClashMode(modeList StringIterator, currentMode string)
|
||||||
UpdateClashMode(newMode string)
|
UpdateClashMode(newMode string)
|
||||||
|
WriteConnections(message *Connections)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStandaloneCommandClient() *CommandClient {
|
func NewStandaloneCommandClient() *CommandClient {
|
||||||
@ -116,6 +117,13 @@ func (c *CommandClient) Connect() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
go c.handleModeConn(conn)
|
go c.handleModeConn(conn)
|
||||||
|
case CommandConnections:
|
||||||
|
err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "write interval")
|
||||||
|
}
|
||||||
|
c.handler.Connected()
|
||||||
|
go c.handleConnectionsConn(conn)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
53
experimental/libbox/command_close_connection.go
Normal file
53
experimental/libbox/command_close_connection.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||||
|
"github.com/sagernet/sing/common/binary"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *CommandClient) CloseConnection(connId string) error {
|
||||||
|
conn, err := c.directConnect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
writer := bufio.NewWriter(conn)
|
||||||
|
err = binary.WriteData(writer, binary.BigEndian, connId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = writer.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return readError(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommandServer) handleCloseConnection(conn net.Conn) error {
|
||||||
|
reader := bufio.NewReader(conn)
|
||||||
|
var connId string
|
||||||
|
err := binary.ReadData(reader, binary.BigEndian, &connId)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "read connection id")
|
||||||
|
}
|
||||||
|
service := s.service
|
||||||
|
if service == nil {
|
||||||
|
return writeError(conn, E.New("service not ready"))
|
||||||
|
}
|
||||||
|
clashServer := service.instance.Router().ClashServer()
|
||||||
|
if clashServer == nil {
|
||||||
|
return writeError(conn, E.New("Clash API disabled"))
|
||||||
|
}
|
||||||
|
targetConn := clashServer.(*clashapi.Server).TrafficManager().Connection(uuid.FromStringOrNil(connId))
|
||||||
|
if targetConn == nil {
|
||||||
|
return writeError(conn, E.New("connection already closed"))
|
||||||
|
}
|
||||||
|
targetConn.Close()
|
||||||
|
return writeError(conn, nil)
|
||||||
|
}
|
268
experimental/libbox/command_connections.go
Normal file
268
experimental/libbox/command_connections.go
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
package libbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||||
|
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||||
|
"github.com/sagernet/sing/common/binary"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *CommandClient) handleConnectionsConn(conn net.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
reader := bufio.NewReader(conn)
|
||||||
|
var connections Connections
|
||||||
|
for {
|
||||||
|
err := binary.ReadData(reader, binary.BigEndian, &connections.connections)
|
||||||
|
if err != nil {
|
||||||
|
c.handler.Disconnected(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.handler.WriteConnections(&connections)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CommandServer) handleConnectionsConn(conn net.Conn) error {
|
||||||
|
var interval int64
|
||||||
|
err := binary.Read(conn, binary.BigEndian, &interval)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "read interval")
|
||||||
|
}
|
||||||
|
ticker := time.NewTicker(time.Duration(interval))
|
||||||
|
defer ticker.Stop()
|
||||||
|
ctx := connKeepAlive(conn)
|
||||||
|
var trafficManager *trafficontrol.Manager
|
||||||
|
for {
|
||||||
|
service := s.service
|
||||||
|
if service != nil {
|
||||||
|
clashServer := service.instance.Router().ClashServer()
|
||||||
|
if clashServer == nil {
|
||||||
|
return E.New("Clash API disabled")
|
||||||
|
}
|
||||||
|
trafficManager = clashServer.(*clashapi.Server).TrafficManager()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-ticker.C:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
connections = make(map[uuid.UUID]*Connection)
|
||||||
|
outConnections []Connection
|
||||||
|
)
|
||||||
|
writer := bufio.NewWriter(conn)
|
||||||
|
for {
|
||||||
|
outConnections = outConnections[:0]
|
||||||
|
for _, connection := range trafficManager.Connections() {
|
||||||
|
outConnections = append(outConnections, newConnection(connections, connection, false))
|
||||||
|
}
|
||||||
|
for _, connection := range trafficManager.ClosedConnections() {
|
||||||
|
outConnections = append(outConnections, newConnection(connections, connection, true))
|
||||||
|
}
|
||||||
|
err = binary.WriteData(writer, binary.BigEndian, outConnections)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = writer.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-ticker.C:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ConnectionStateAll = iota
|
||||||
|
ConnectionStateActive
|
||||||
|
ConnectionStateClosed
|
||||||
|
)
|
||||||
|
|
||||||
|
type Connections struct {
|
||||||
|
connections []Connection
|
||||||
|
filteredConnections []Connection
|
||||||
|
outConnections *[]Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connections) FilterState(state int32) {
|
||||||
|
c.filteredConnections = c.filteredConnections[:0]
|
||||||
|
switch state {
|
||||||
|
case ConnectionStateAll:
|
||||||
|
c.filteredConnections = append(c.filteredConnections, c.connections...)
|
||||||
|
case ConnectionStateActive:
|
||||||
|
for _, connection := range c.connections {
|
||||||
|
if connection.ClosedAt == 0 {
|
||||||
|
c.filteredConnections = append(c.filteredConnections, connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ConnectionStateClosed:
|
||||||
|
for _, connection := range c.connections {
|
||||||
|
if connection.ClosedAt != 0 {
|
||||||
|
c.filteredConnections = append(c.filteredConnections, connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connections) SortByDate() {
|
||||||
|
slices.SortStableFunc(c.filteredConnections, func(x, y Connection) int {
|
||||||
|
if x.CreatedAt < y.CreatedAt {
|
||||||
|
return 1
|
||||||
|
} else if x.CreatedAt > y.CreatedAt {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return strings.Compare(y.ID, x.ID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connections) SortByTraffic() {
|
||||||
|
slices.SortStableFunc(c.filteredConnections, func(x, y Connection) int {
|
||||||
|
xTraffic := x.Uplink + x.Downlink
|
||||||
|
yTraffic := y.Uplink + y.Downlink
|
||||||
|
if xTraffic < yTraffic {
|
||||||
|
return 1
|
||||||
|
} else if xTraffic > yTraffic {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return strings.Compare(y.ID, x.ID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connections) SortByTrafficTotal() {
|
||||||
|
slices.SortStableFunc(c.filteredConnections, func(x, y Connection) int {
|
||||||
|
xTraffic := x.UplinkTotal + x.DownlinkTotal
|
||||||
|
yTraffic := y.UplinkTotal + y.DownlinkTotal
|
||||||
|
if xTraffic < yTraffic {
|
||||||
|
return 1
|
||||||
|
} else if xTraffic > yTraffic {
|
||||||
|
return -1
|
||||||
|
} else {
|
||||||
|
return strings.Compare(y.ID, x.ID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connections) Iterator() ConnectionIterator {
|
||||||
|
return newPtrIterator(c.filteredConnections)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Connection struct {
|
||||||
|
ID string
|
||||||
|
Inbound string
|
||||||
|
InboundType string
|
||||||
|
IPVersion int32
|
||||||
|
Network string
|
||||||
|
Source string
|
||||||
|
Destination string
|
||||||
|
Domain string
|
||||||
|
Protocol string
|
||||||
|
User string
|
||||||
|
FromOutbound string
|
||||||
|
CreatedAt int64
|
||||||
|
ClosedAt int64
|
||||||
|
Uplink int64
|
||||||
|
Downlink int64
|
||||||
|
UplinkTotal int64
|
||||||
|
DownlinkTotal int64
|
||||||
|
Rule string
|
||||||
|
Outbound string
|
||||||
|
OutboundType string
|
||||||
|
ChainList []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) Chain() StringIterator {
|
||||||
|
return newIterator(c.ChainList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) DisplayDestination() string {
|
||||||
|
destination := M.ParseSocksaddr(c.Destination)
|
||||||
|
if destination.IsIP() && c.Domain != "" {
|
||||||
|
destination = M.Socksaddr{
|
||||||
|
Fqdn: c.Domain,
|
||||||
|
Port: destination.Port,
|
||||||
|
}
|
||||||
|
return destination.String()
|
||||||
|
}
|
||||||
|
return c.Destination
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionIterator interface {
|
||||||
|
Next() *Connection
|
||||||
|
HasNext() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConnection(connections map[uuid.UUID]*Connection, metadata trafficontrol.TrackerMetadata, isClosed bool) Connection {
|
||||||
|
if oldConnection, loaded := connections[metadata.ID]; loaded {
|
||||||
|
if isClosed {
|
||||||
|
if oldConnection.ClosedAt == 0 {
|
||||||
|
oldConnection.Uplink = 0
|
||||||
|
oldConnection.Downlink = 0
|
||||||
|
oldConnection.ClosedAt = metadata.ClosedAt.UnixMilli()
|
||||||
|
}
|
||||||
|
return *oldConnection
|
||||||
|
}
|
||||||
|
lastUplink := oldConnection.UplinkTotal
|
||||||
|
lastDownlink := oldConnection.DownlinkTotal
|
||||||
|
uplinkTotal := metadata.Upload.Load()
|
||||||
|
downlinkTotal := metadata.Download.Load()
|
||||||
|
oldConnection.Uplink = uplinkTotal - lastUplink
|
||||||
|
oldConnection.Downlink = downlinkTotal - lastDownlink
|
||||||
|
oldConnection.UplinkTotal = uplinkTotal
|
||||||
|
oldConnection.DownlinkTotal = downlinkTotal
|
||||||
|
return *oldConnection
|
||||||
|
}
|
||||||
|
var rule string
|
||||||
|
if metadata.Rule != nil {
|
||||||
|
rule = metadata.Rule.String()
|
||||||
|
}
|
||||||
|
uplinkTotal := metadata.Upload.Load()
|
||||||
|
downlinkTotal := metadata.Download.Load()
|
||||||
|
uplink := uplinkTotal
|
||||||
|
downlink := downlinkTotal
|
||||||
|
var closedAt int64
|
||||||
|
if !metadata.ClosedAt.IsZero() {
|
||||||
|
closedAt = metadata.ClosedAt.UnixMilli()
|
||||||
|
uplink = 0
|
||||||
|
downlink = 0
|
||||||
|
}
|
||||||
|
connection := Connection{
|
||||||
|
ID: metadata.ID.String(),
|
||||||
|
Inbound: metadata.Metadata.Inbound,
|
||||||
|
InboundType: metadata.Metadata.InboundType,
|
||||||
|
IPVersion: int32(metadata.Metadata.IPVersion),
|
||||||
|
Network: metadata.Metadata.Network,
|
||||||
|
Source: metadata.Metadata.Source.String(),
|
||||||
|
Destination: metadata.Metadata.Destination.String(),
|
||||||
|
Domain: metadata.Metadata.Domain,
|
||||||
|
Protocol: metadata.Metadata.Protocol,
|
||||||
|
User: metadata.Metadata.User,
|
||||||
|
FromOutbound: metadata.Metadata.Outbound,
|
||||||
|
CreatedAt: metadata.CreatedAt.UnixMilli(),
|
||||||
|
ClosedAt: closedAt,
|
||||||
|
Uplink: uplink,
|
||||||
|
Downlink: downlink,
|
||||||
|
UplinkTotal: uplinkTotal,
|
||||||
|
DownlinkTotal: downlinkTotal,
|
||||||
|
Rule: rule,
|
||||||
|
Outbound: metadata.Outbound,
|
||||||
|
OutboundType: metadata.OutboundType,
|
||||||
|
ChainList: metadata.Chain,
|
||||||
|
}
|
||||||
|
connections[metadata.ID] = &connection
|
||||||
|
return connection
|
||||||
|
}
|
@ -14,36 +14,6 @@ import (
|
|||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OutboundGroup struct {
|
|
||||||
Tag string
|
|
||||||
Type string
|
|
||||||
Selectable bool
|
|
||||||
Selected string
|
|
||||||
IsExpand bool
|
|
||||||
items []*OutboundGroupItem
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *OutboundGroup) GetItems() OutboundGroupItemIterator {
|
|
||||||
return newIterator(g.items)
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutboundGroupIterator interface {
|
|
||||||
Next() *OutboundGroup
|
|
||||||
HasNext() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutboundGroupItem struct {
|
|
||||||
Tag string
|
|
||||||
Type string
|
|
||||||
URLTestTime int64
|
|
||||||
URLTestDelay int32
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutboundGroupItemIterator interface {
|
|
||||||
Next() *OutboundGroupItem
|
|
||||||
HasNext() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CommandClient) handleGroupConn(conn net.Conn) {
|
func (c *CommandClient) handleGroupConn(conn net.Conn) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
@ -92,6 +62,36 @@ func (s *CommandServer) handleGroupConn(conn net.Conn) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OutboundGroup struct {
|
||||||
|
Tag string
|
||||||
|
Type string
|
||||||
|
Selectable bool
|
||||||
|
Selected string
|
||||||
|
IsExpand bool
|
||||||
|
items []*OutboundGroupItem
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *OutboundGroup) GetItems() OutboundGroupItemIterator {
|
||||||
|
return newIterator(g.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutboundGroupIterator interface {
|
||||||
|
Next() *OutboundGroup
|
||||||
|
HasNext() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutboundGroupItem struct {
|
||||||
|
Tag string
|
||||||
|
Type string
|
||||||
|
URLTestTime int64
|
||||||
|
URLTestDelay int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutboundGroupItemIterator interface {
|
||||||
|
Next() *OutboundGroupItem
|
||||||
|
HasNext() bool
|
||||||
|
}
|
||||||
|
|
||||||
func readGroups(reader io.Reader) (OutboundGroupIterator, error) {
|
func readGroups(reader io.Reader) (OutboundGroupIterator, error) {
|
||||||
var groupLength uint16
|
var groupLength uint16
|
||||||
err := binary.Read(reader, binary.BigEndian, &groupLength)
|
err := binary.Read(reader, binary.BigEndian, &groupLength)
|
||||||
|
@ -33,6 +33,8 @@ type CommandServer struct {
|
|||||||
urlTestUpdate chan struct{}
|
urlTestUpdate chan struct{}
|
||||||
modeUpdate chan struct{}
|
modeUpdate chan struct{}
|
||||||
logReset chan struct{}
|
logReset chan struct{}
|
||||||
|
|
||||||
|
closedConnections []Connection
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommandServerHandler interface {
|
type CommandServerHandler interface {
|
||||||
@ -176,6 +178,10 @@ func (s *CommandServer) handleConnection(conn net.Conn) error {
|
|||||||
return s.handleGetSystemProxyStatus(conn)
|
return s.handleGetSystemProxyStatus(conn)
|
||||||
case CommandSetSystemProxyEnabled:
|
case CommandSetSystemProxyEnabled:
|
||||||
return s.handleSetSystemProxyEnabled(conn)
|
return s.handleSetSystemProxyEnabled(conn)
|
||||||
|
case CommandConnections:
|
||||||
|
return s.handleConnectionsConn(conn)
|
||||||
|
case CommandCloseConnection:
|
||||||
|
return s.handleCloseConnection(conn)
|
||||||
default:
|
default:
|
||||||
return E.New("unknown command: ", command)
|
return E.New("unknown command: ", command)
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ func (s *CommandServer) readStatus() StatusMessage {
|
|||||||
trafficManager := clashServer.(*clashapi.Server).TrafficManager()
|
trafficManager := clashServer.(*clashapi.Server).TrafficManager()
|
||||||
message.Uplink, message.Downlink = trafficManager.Now()
|
message.Uplink, message.Downlink = trafficManager.Now()
|
||||||
message.UplinkTotal, message.DownlinkTotal = trafficManager.Total()
|
message.UplinkTotal, message.DownlinkTotal = trafficManager.Total()
|
||||||
message.ConnectionsIn = int32(trafficManager.Connections())
|
message.ConnectionsIn = int32(trafficManager.ConnectionsLen())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,10 @@ func newIterator[T any](values []T) *iterator[T] {
|
|||||||
return &iterator[T]{values}
|
return &iterator[T]{values}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newPtrIterator[T any](values []T) *iterator[*T] {
|
||||||
|
return &iterator[*T]{common.Map(values, func(value T) *T { return &value })}
|
||||||
|
}
|
||||||
|
|
||||||
func (i *iterator[T]) Next() T {
|
func (i *iterator[T]) Next() T {
|
||||||
if len(i.values) == 0 {
|
if len(i.values) == 0 {
|
||||||
return common.DefaultValue[T]()
|
return common.DefaultValue[T]()
|
||||||
|
@ -149,33 +149,6 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions
|
|||||||
return tun.New(*options)
|
return tun.New(*options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) {
|
|
||||||
var uid int32
|
|
||||||
if w.useProcFS {
|
|
||||||
uid = procfs.ResolveSocketByProcSearch(network, source, destination)
|
|
||||||
if uid == -1 {
|
|
||||||
return nil, E.New("procfs: not found")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var ipProtocol int32
|
|
||||||
switch N.NetworkName(network) {
|
|
||||||
case N.NetworkTCP:
|
|
||||||
ipProtocol = syscall.IPPROTO_TCP
|
|
||||||
case N.NetworkUDP:
|
|
||||||
ipProtocol = syscall.IPPROTO_UDP
|
|
||||||
default:
|
|
||||||
return nil, E.New("unknown network: ", network)
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
uid, err = w.iif.FindConnectionOwner(ipProtocol, source.Addr().String(), int32(source.Port()), destination.Addr().String(), int32(destination.Port()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
packageName, _ := w.iif.PackageNameByUid(uid)
|
|
||||||
return &process.Info{UserId: uid, PackageName: packageName}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool {
|
func (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool {
|
||||||
return w.iif.UsePlatformDefaultInterfaceMonitor()
|
return w.iif.UsePlatformDefaultInterfaceMonitor()
|
||||||
}
|
}
|
||||||
@ -229,6 +202,33 @@ func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState {
|
|||||||
return (adapter.WIFIState)(*wifiState)
|
return (adapter.WIFIState)(*wifiState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) {
|
||||||
|
var uid int32
|
||||||
|
if w.useProcFS {
|
||||||
|
uid = procfs.ResolveSocketByProcSearch(network, source, destination)
|
||||||
|
if uid == -1 {
|
||||||
|
return nil, E.New("procfs: not found")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var ipProtocol int32
|
||||||
|
switch N.NetworkName(network) {
|
||||||
|
case N.NetworkTCP:
|
||||||
|
ipProtocol = syscall.IPPROTO_TCP
|
||||||
|
case N.NetworkUDP:
|
||||||
|
ipProtocol = syscall.IPPROTO_UDP
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown network: ", network)
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
uid, err = w.iif.FindConnectionOwner(ipProtocol, source.Addr().String(), int32(source.Port()), destination.Addr().String(), int32(destination.Port()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
packageName, _ := w.iif.PackageNameByUid(uid)
|
||||||
|
return &process.Info{UserId: uid, PackageName: packageName}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) DisableColors() bool {
|
func (w *platformInterfaceWrapper) DisableColors() bool {
|
||||||
return runtime.GOOS != "android"
|
return runtime.GOOS != "android"
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/humanize"
|
"github.com/sagernet/sing-box/common/humanize"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
_ "github.com/sagernet/sing-box/include"
|
_ "github.com/sagernet/sing-box/include"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -59,6 +61,10 @@ func FormatMemoryBytes(length int64) string {
|
|||||||
return humanize.MemoryBytes(uint64(length))
|
return humanize.MemoryBytes(uint64(length))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FormatDuration(duration int64) string {
|
||||||
|
return log.FormatDuration(time.Duration(duration) * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
func ProxyDisplayType(proxyType string) string {
|
func ProxyDisplayType(proxyType string) string {
|
||||||
return C.ProxyDisplayName(proxyType)
|
return C.ProxyDisplayName(proxyType)
|
||||||
}
|
}
|
||||||
|
30
go.mod
30
go.mod
@ -24,16 +24,16 @@ require (
|
|||||||
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1
|
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1
|
||||||
github.com/sagernet/gomobile v0.1.3
|
github.com/sagernet/gomobile v0.1.3
|
||||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f
|
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f
|
||||||
github.com/sagernet/quic-go v0.43.1-beta.2
|
github.com/sagernet/quic-go v0.45.0-beta.2
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||||
github.com/sagernet/sing v0.4.1
|
github.com/sagernet/sing v0.5.0-alpha.10.0.20240615025518-3de31664ce39
|
||||||
github.com/sagernet/sing-dns v0.2.0
|
github.com/sagernet/sing-dns v0.3.0-beta.5
|
||||||
github.com/sagernet/sing-mux v0.2.0
|
github.com/sagernet/sing-mux v0.2.0
|
||||||
github.com/sagernet/sing-quic v0.2.0-beta.5
|
github.com/sagernet/sing-quic v0.2.0-beta.9
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.6
|
github.com/sagernet/sing-shadowsocks v0.2.6
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.0
|
github.com/sagernet/sing-shadowsocks2 v0.2.0
|
||||||
github.com/sagernet/sing-shadowtls v0.1.4
|
github.com/sagernet/sing-shadowtls v0.1.4
|
||||||
github.com/sagernet/sing-tun v0.3.2
|
github.com/sagernet/sing-tun v0.4.0-beta.9.0.20240616073727-85f5f2dd587d
|
||||||
github.com/sagernet/sing-vmess v0.1.8
|
github.com/sagernet/sing-vmess v0.1.8
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
|
||||||
github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6
|
github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6
|
||||||
@ -44,8 +44,8 @@ require (
|
|||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.23.0
|
golang.org/x/crypto v0.24.0
|
||||||
golang.org/x/net v0.25.0
|
golang.org/x/net v0.26.0
|
||||||
golang.org/x/sys v0.21.0
|
golang.org/x/sys v0.21.0
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
google.golang.org/grpc v1.63.2
|
google.golang.org/grpc v1.63.2
|
||||||
@ -65,6 +65,7 @@ require (
|
|||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
||||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
@ -72,24 +73,27 @@ require (
|
|||||||
github.com/klauspost/compress v1.17.4 // indirect
|
github.com/klauspost/compress v1.17.4 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||||
github.com/libdns/libdns v0.2.2 // indirect
|
github.com/libdns/libdns v0.2.2 // indirect
|
||||||
|
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||||
|
github.com/mdlayher/socket v0.4.1 // indirect
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.9.7 // indirect
|
github.com/onsi/ginkgo/v2 v2.9.7 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||||
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba // indirect
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||||
|
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
||||||
golang.org/x/mod v0.17.0 // indirect
|
golang.org/x/mod v0.18.0 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
golang.org/x/text v0.15.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
golang.org/x/tools v0.21.0 // indirect
|
golang.org/x/tools v0.22.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
62
go.sum
62
go.sum
@ -40,6 +40,7 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
|
|||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
||||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||||
@ -69,6 +70,10 @@ github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s=
|
|||||||
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||||
|
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||||
|
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||||
|
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||||
|
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||||
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
||||||
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
||||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||||
@ -99,29 +104,31 @@ github.com/sagernet/gomobile v0.1.3 h1:ohjIb1Ou2+1558PnZour3od69suSuvkdSVOlO1tC4
|
|||||||
github.com/sagernet/gomobile v0.1.3/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E=
|
github.com/sagernet/gomobile v0.1.3/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E=
|
||||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I=
|
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I=
|
||||||
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0=
|
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0=
|
||||||
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk=
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
||||||
github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
github.com/sagernet/quic-go v0.43.1-beta.2 h1:6YRCE9t1Q3UbNX1/dJGqpwFQbh6DXC6XBrQr2xp6hXY=
|
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
||||||
github.com/sagernet/quic-go v0.43.1-beta.2/go.mod h1:BkrQYeop7Jx3hN3TW8/76CXcdhYiNPyYEBL/BVJ1ifc=
|
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||||
|
github.com/sagernet/quic-go v0.45.0-beta.2 h1:nWq9KJTR+cGU8UU4E20XNjdM6QgbLkBgpq+NCExg5RY=
|
||||||
|
github.com/sagernet/quic-go v0.45.0-beta.2/go.mod h1:rs3XCo3SQ2sB96NtaKnEyq+ZkyaKWL51BvIW3veaiWw=
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||||
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
||||||
github.com/sagernet/sing v0.4.1 h1:zVlpE+7k7AFoC2pv6ReqLf0PIHjihL/jsBl5k05PQFk=
|
github.com/sagernet/sing v0.5.0-alpha.10.0.20240615025518-3de31664ce39 h1:Q1Hlje8FURQLHyDq8V2myk/YG+0RPdcnTRZ7Of9/iv8=
|
||||||
github.com/sagernet/sing v0.4.1/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
|
github.com/sagernet/sing v0.5.0-alpha.10.0.20240615025518-3de31664ce39/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-dns v0.2.0 h1:dka3weRX6+CrYO3v+hrTy2z68rCOCZXNBiNXpLZ6JNs=
|
github.com/sagernet/sing-dns v0.3.0-beta.5 h1:lX+wfnBVaOlSd7+GBgb431Tt/gmYwJXSHvS1HutfnD4=
|
||||||
github.com/sagernet/sing-dns v0.2.0/go.mod h1:BJpJv6XLnrUbSyIntOT6DG9FW0f4fETmPAHvNjOprLg=
|
github.com/sagernet/sing-dns v0.3.0-beta.5/go.mod h1:qeO/lOUK/c3Zczp5a1VO13fbmolaM8xGKCUXtaX0/NQ=
|
||||||
github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo=
|
github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo=
|
||||||
github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
|
github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
|
||||||
github.com/sagernet/sing-quic v0.2.0-beta.5 h1:ceKFLd1iS5AtM+pScKmcDp5k7R6WgYIe8vl6nB0aVsE=
|
github.com/sagernet/sing-quic v0.2.0-beta.9 h1:gfqUwVgKA6APwFOPhwge9VrPZ0XQtmuXF8hbbIVZML8=
|
||||||
github.com/sagernet/sing-quic v0.2.0-beta.5/go.mod h1:lfad61lScAZhAxZ0DHZWvEIcAaT38O6zPTR4vLsHeP0=
|
github.com/sagernet/sing-quic v0.2.0-beta.9/go.mod h1:hb6RwYy9Js0gZi80zlTBWABMOIvJDv46K5yak/pbZ4w=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
|
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
|
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
|
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
|
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
|
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
|
||||||
github.com/sagernet/sing-tun v0.3.2 h1:z0bLUT/YXH9RrJS9DsIpB0Bb9afl2hVJOmHd0zA3HJY=
|
github.com/sagernet/sing-tun v0.4.0-beta.9.0.20240616073727-85f5f2dd587d h1:ppBKR8H4x64nbtselcGdGOrIMJUIXQL135s5tNUcQMQ=
|
||||||
github.com/sagernet/sing-tun v0.3.2/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ=
|
github.com/sagernet/sing-tun v0.4.0-beta.9.0.20240616073727-85f5f2dd587d/go.mod h1:YgaSM4cm+YIn6erBZN/eF+sW7I27BfBt91EWGo53MME=
|
||||||
github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc=
|
github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc=
|
||||||
github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
|
github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
||||||
@ -146,8 +153,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
|
|||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
||||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||||
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
||||||
@ -163,20 +170,19 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
|
|||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@ -187,16 +193,16 @@ golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|||||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
|
||||||
|
@ -11,43 +11,43 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.Inbound, platformInterface platform.Interface) (adapter.Inbound, error) {
|
func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Inbound, platformInterface platform.Interface) (adapter.Inbound, error) {
|
||||||
if options.Type == "" {
|
if options.Type == "" {
|
||||||
return nil, E.New("missing inbound type")
|
return nil, E.New("missing inbound type")
|
||||||
}
|
}
|
||||||
switch options.Type {
|
switch options.Type {
|
||||||
case C.TypeTun:
|
case C.TypeTun:
|
||||||
return NewTun(ctx, router, logger, options.Tag, options.TunOptions, platformInterface)
|
return NewTun(ctx, router, logger, tag, options.TunOptions, platformInterface)
|
||||||
case C.TypeRedirect:
|
case C.TypeRedirect:
|
||||||
return NewRedirect(ctx, router, logger, options.Tag, options.RedirectOptions), nil
|
return NewRedirect(ctx, router, logger, tag, options.RedirectOptions), nil
|
||||||
case C.TypeTProxy:
|
case C.TypeTProxy:
|
||||||
return NewTProxy(ctx, router, logger, options.Tag, options.TProxyOptions), nil
|
return NewTProxy(ctx, router, logger, tag, options.TProxyOptions), nil
|
||||||
case C.TypeDirect:
|
case C.TypeDirect:
|
||||||
return NewDirect(ctx, router, logger, options.Tag, options.DirectOptions), nil
|
return NewDirect(ctx, router, logger, tag, options.DirectOptions), nil
|
||||||
case C.TypeSOCKS:
|
case C.TypeSOCKS:
|
||||||
return NewSocks(ctx, router, logger, options.Tag, options.SocksOptions), nil
|
return NewSocks(ctx, router, logger, tag, options.SocksOptions), nil
|
||||||
case C.TypeHTTP:
|
case C.TypeHTTP:
|
||||||
return NewHTTP(ctx, router, logger, options.Tag, options.HTTPOptions)
|
return NewHTTP(ctx, router, logger, tag, options.HTTPOptions)
|
||||||
case C.TypeMixed:
|
case C.TypeMixed:
|
||||||
return NewMixed(ctx, router, logger, options.Tag, options.MixedOptions), nil
|
return NewMixed(ctx, router, logger, tag, options.MixedOptions), nil
|
||||||
case C.TypeShadowsocks:
|
case C.TypeShadowsocks:
|
||||||
return NewShadowsocks(ctx, router, logger, options.Tag, options.ShadowsocksOptions)
|
return NewShadowsocks(ctx, router, logger, tag, options.ShadowsocksOptions)
|
||||||
case C.TypeVMess:
|
case C.TypeVMess:
|
||||||
return NewVMess(ctx, router, logger, options.Tag, options.VMessOptions)
|
return NewVMess(ctx, router, logger, tag, options.VMessOptions)
|
||||||
case C.TypeTrojan:
|
case C.TypeTrojan:
|
||||||
return NewTrojan(ctx, router, logger, options.Tag, options.TrojanOptions)
|
return NewTrojan(ctx, router, logger, tag, options.TrojanOptions)
|
||||||
case C.TypeNaive:
|
case C.TypeNaive:
|
||||||
return NewNaive(ctx, router, logger, options.Tag, options.NaiveOptions)
|
return NewNaive(ctx, router, logger, tag, options.NaiveOptions)
|
||||||
case C.TypeHysteria:
|
case C.TypeHysteria:
|
||||||
return NewHysteria(ctx, router, logger, options.Tag, options.HysteriaOptions)
|
return NewHysteria(ctx, router, logger, tag, options.HysteriaOptions)
|
||||||
case C.TypeShadowTLS:
|
case C.TypeShadowTLS:
|
||||||
return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions)
|
return NewShadowTLS(ctx, router, logger, tag, options.ShadowTLSOptions)
|
||||||
case C.TypeVLESS:
|
case C.TypeVLESS:
|
||||||
return NewVLESS(ctx, router, logger, options.Tag, options.VLESSOptions)
|
return NewVLESS(ctx, router, logger, tag, options.VLESSOptions)
|
||||||
case C.TypeTUIC:
|
case C.TypeTUIC:
|
||||||
return NewTUIC(ctx, router, logger, options.Tag, options.TUICOptions)
|
return NewTUIC(ctx, router, logger, tag, options.TUICOptions)
|
||||||
case C.TypeHysteria2:
|
case C.TypeHysteria2:
|
||||||
return NewHysteria2(ctx, router, logger, options.Tag, options.Hysteria2Options)
|
return NewHysteria2(ctx, router, logger, tag, options.Hysteria2Options)
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown inbound type: ", options.Type)
|
return nil, E.New("unknown inbound type: ", options.Type)
|
||||||
}
|
}
|
||||||
|
198
inbound/tun.go
198
inbound/tun.go
@ -3,6 +3,8 @@ package inbound
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -19,6 +21,9 @@ import (
|
|||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/ranges"
|
"github.com/sagernet/sing/common/ranges"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
|
||||||
|
"go4.org/netipx"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.Inbound = (*Tun)(nil)
|
var _ adapter.Inbound = (*Tun)(nil)
|
||||||
@ -37,9 +42,70 @@ type Tun struct {
|
|||||||
tunStack tun.Stack
|
tunStack tun.Stack
|
||||||
platformInterface platform.Interface
|
platformInterface platform.Interface
|
||||||
platformOptions option.TunPlatformOptions
|
platformOptions option.TunPlatformOptions
|
||||||
|
autoRedirect tun.AutoRedirect
|
||||||
|
routeRuleSet []adapter.RuleSet
|
||||||
|
routeRuleSetCallback []*list.Element[adapter.RuleSetUpdateCallback]
|
||||||
|
routeExcludeRuleSet []adapter.RuleSet
|
||||||
|
routeExcludeRuleSetCallback []*list.Element[adapter.RuleSetUpdateCallback]
|
||||||
|
routeAddressSet []*netipx.IPSet
|
||||||
|
routeExcludeAddressSet []*netipx.IPSet
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
|
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
|
||||||
|
address := options.Address
|
||||||
|
//nolint:staticcheck
|
||||||
|
//goland:noinspection GoDeprecation
|
||||||
|
if len(options.Inet4Address) > 0 {
|
||||||
|
address = append(address, options.Inet4Address...)
|
||||||
|
}
|
||||||
|
//nolint:staticcheck
|
||||||
|
//goland:noinspection GoDeprecation
|
||||||
|
if len(options.Inet6Address) > 0 {
|
||||||
|
address = append(address, options.Inet6Address...)
|
||||||
|
}
|
||||||
|
inet4Address := common.Filter(address, func(it netip.Prefix) bool {
|
||||||
|
return it.Addr().Is4()
|
||||||
|
})
|
||||||
|
inet6Address := common.Filter(address, func(it netip.Prefix) bool {
|
||||||
|
return it.Addr().Is6()
|
||||||
|
})
|
||||||
|
|
||||||
|
routeAddress := options.RouteAddress
|
||||||
|
//nolint:staticcheck
|
||||||
|
//goland:noinspection GoDeprecation
|
||||||
|
if len(options.Inet4RouteAddress) > 0 {
|
||||||
|
routeAddress = append(routeAddress, options.Inet4RouteAddress...)
|
||||||
|
}
|
||||||
|
//nolint:staticcheck
|
||||||
|
//goland:noinspection GoDeprecation
|
||||||
|
if len(options.Inet6RouteAddress) > 0 {
|
||||||
|
routeAddress = append(routeAddress, options.Inet6RouteAddress...)
|
||||||
|
}
|
||||||
|
inet4RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool {
|
||||||
|
return it.Addr().Is4()
|
||||||
|
})
|
||||||
|
inet6RouteAddress := common.Filter(routeAddress, func(it netip.Prefix) bool {
|
||||||
|
return it.Addr().Is6()
|
||||||
|
})
|
||||||
|
|
||||||
|
routeExcludeAddress := options.RouteExcludeAddress
|
||||||
|
//nolint:staticcheck
|
||||||
|
//goland:noinspection GoDeprecation
|
||||||
|
if len(options.Inet4RouteExcludeAddress) > 0 {
|
||||||
|
routeExcludeAddress = append(routeExcludeAddress, options.Inet4RouteExcludeAddress...)
|
||||||
|
}
|
||||||
|
//nolint:staticcheck
|
||||||
|
//goland:noinspection GoDeprecation
|
||||||
|
if len(options.Inet6RouteExcludeAddress) > 0 {
|
||||||
|
routeExcludeAddress = append(routeExcludeAddress, options.Inet6RouteExcludeAddress...)
|
||||||
|
}
|
||||||
|
inet4RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool {
|
||||||
|
return it.Addr().Is4()
|
||||||
|
})
|
||||||
|
inet6RouteExcludeAddress := common.Filter(routeExcludeAddress, func(it netip.Prefix) bool {
|
||||||
|
return it.Addr().Is6()
|
||||||
|
})
|
||||||
|
|
||||||
tunMTU := options.MTU
|
tunMTU := options.MTU
|
||||||
if tunMTU == 0 {
|
if tunMTU == 0 {
|
||||||
tunMTU = 9000
|
tunMTU = 9000
|
||||||
@ -50,9 +116,9 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
|||||||
} else {
|
} else {
|
||||||
udpTimeout = C.UDPTimeout
|
udpTimeout = C.UDPTimeout
|
||||||
}
|
}
|
||||||
|
var err error
|
||||||
includeUID := uidToRange(options.IncludeUID)
|
includeUID := uidToRange(options.IncludeUID)
|
||||||
if len(options.IncludeUIDRange) > 0 {
|
if len(options.IncludeUIDRange) > 0 {
|
||||||
var err error
|
|
||||||
includeUID, err = parseRange(includeUID, options.IncludeUIDRange)
|
includeUID, err = parseRange(includeUID, options.IncludeUIDRange)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse include_uid_range")
|
return nil, E.Cause(err, "parse include_uid_range")
|
||||||
@ -60,13 +126,30 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
|||||||
}
|
}
|
||||||
excludeUID := uidToRange(options.ExcludeUID)
|
excludeUID := uidToRange(options.ExcludeUID)
|
||||||
if len(options.ExcludeUIDRange) > 0 {
|
if len(options.ExcludeUIDRange) > 0 {
|
||||||
var err error
|
|
||||||
excludeUID, err = parseRange(excludeUID, options.ExcludeUIDRange)
|
excludeUID, err = parseRange(excludeUID, options.ExcludeUIDRange)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse exclude_uid_range")
|
return nil, E.Cause(err, "parse exclude_uid_range")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &Tun{
|
|
||||||
|
tableIndex := options.IPRoute2TableIndex
|
||||||
|
if tableIndex == 0 {
|
||||||
|
tableIndex = tun.DefaultIPRoute2TableIndex
|
||||||
|
}
|
||||||
|
ruleIndex := options.IPRoute2RuleIndex
|
||||||
|
if ruleIndex == 0 {
|
||||||
|
ruleIndex = tun.DefaultIPRoute2RuleIndex
|
||||||
|
}
|
||||||
|
inputMark := options.AutoRedirectInputMark
|
||||||
|
if inputMark == 0 {
|
||||||
|
inputMark = tun.DefaultAutoRedirectInputMark
|
||||||
|
}
|
||||||
|
outputMark := options.AutoRedirectOutputMark
|
||||||
|
if outputMark == 0 {
|
||||||
|
outputMark = tun.DefaultAutoRedirectOutputMark
|
||||||
|
}
|
||||||
|
|
||||||
|
inbound := &Tun{
|
||||||
tag: tag,
|
tag: tag,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
router: router,
|
router: router,
|
||||||
@ -76,30 +159,81 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
|||||||
Name: options.InterfaceName,
|
Name: options.InterfaceName,
|
||||||
MTU: tunMTU,
|
MTU: tunMTU,
|
||||||
GSO: options.GSO,
|
GSO: options.GSO,
|
||||||
Inet4Address: options.Inet4Address,
|
Inet4Address: inet4Address,
|
||||||
Inet6Address: options.Inet6Address,
|
Inet6Address: inet6Address,
|
||||||
AutoRoute: options.AutoRoute,
|
AutoRoute: options.AutoRoute,
|
||||||
|
IPRoute2TableIndex: tableIndex,
|
||||||
|
IPRoute2RuleIndex: ruleIndex,
|
||||||
|
AutoRedirectInputMark: inputMark,
|
||||||
|
AutoRedirectOutputMark: outputMark,
|
||||||
StrictRoute: options.StrictRoute,
|
StrictRoute: options.StrictRoute,
|
||||||
IncludeInterface: options.IncludeInterface,
|
IncludeInterface: options.IncludeInterface,
|
||||||
ExcludeInterface: options.ExcludeInterface,
|
ExcludeInterface: options.ExcludeInterface,
|
||||||
Inet4RouteAddress: options.Inet4RouteAddress,
|
Inet4RouteAddress: inet4RouteAddress,
|
||||||
Inet6RouteAddress: options.Inet6RouteAddress,
|
Inet6RouteAddress: inet6RouteAddress,
|
||||||
Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress,
|
Inet4RouteExcludeAddress: inet4RouteExcludeAddress,
|
||||||
Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress,
|
Inet6RouteExcludeAddress: inet6RouteExcludeAddress,
|
||||||
IncludeUID: includeUID,
|
IncludeUID: includeUID,
|
||||||
ExcludeUID: excludeUID,
|
ExcludeUID: excludeUID,
|
||||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||||
IncludePackage: options.IncludePackage,
|
IncludePackage: options.IncludePackage,
|
||||||
ExcludePackage: options.ExcludePackage,
|
ExcludePackage: options.ExcludePackage,
|
||||||
InterfaceMonitor: router.InterfaceMonitor(),
|
InterfaceMonitor: router.InterfaceMonitor(),
|
||||||
TableIndex: 2022,
|
|
||||||
},
|
},
|
||||||
endpointIndependentNat: options.EndpointIndependentNat,
|
endpointIndependentNat: options.EndpointIndependentNat,
|
||||||
udpTimeout: int64(udpTimeout.Seconds()),
|
udpTimeout: int64(udpTimeout.Seconds()),
|
||||||
stack: options.Stack,
|
stack: options.Stack,
|
||||||
platformInterface: platformInterface,
|
platformInterface: platformInterface,
|
||||||
platformOptions: common.PtrValueOrDefault(options.Platform),
|
platformOptions: common.PtrValueOrDefault(options.Platform),
|
||||||
}, nil
|
}
|
||||||
|
if options.AutoRedirect {
|
||||||
|
if !options.AutoRoute {
|
||||||
|
return nil, E.New("`auto_route` is required by `auto_redirect`")
|
||||||
|
}
|
||||||
|
disableNFTables, dErr := strconv.ParseBool(os.Getenv("DISABLE_NFTABLES"))
|
||||||
|
inbound.autoRedirect, err = tun.NewAutoRedirect(tun.AutoRedirectOptions{
|
||||||
|
TunOptions: &inbound.tunOptions,
|
||||||
|
Context: ctx,
|
||||||
|
Handler: inbound,
|
||||||
|
Logger: logger,
|
||||||
|
NetworkMonitor: router.NetworkMonitor(),
|
||||||
|
InterfaceFinder: router.InterfaceFinder(),
|
||||||
|
TableName: "sing-box",
|
||||||
|
DisableNFTables: dErr == nil && disableNFTables,
|
||||||
|
RouteAddressSet: &inbound.routeAddressSet,
|
||||||
|
RouteExcludeAddressSet: &inbound.routeExcludeAddressSet,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize auto-redirect")
|
||||||
|
}
|
||||||
|
var markMode bool
|
||||||
|
for _, routeAddressSet := range options.RouteAddressSet {
|
||||||
|
ruleSet, loaded := router.RuleSet(routeAddressSet)
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("parse route_address_set: rule-set not found: ", routeAddressSet)
|
||||||
|
}
|
||||||
|
ruleSet.IncRef()
|
||||||
|
inbound.routeRuleSet = append(inbound.routeRuleSet, ruleSet)
|
||||||
|
markMode = true
|
||||||
|
}
|
||||||
|
for _, routeExcludeAddressSet := range options.RouteExcludeAddressSet {
|
||||||
|
ruleSet, loaded := router.RuleSet(routeExcludeAddressSet)
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("parse route_exclude_address_set: rule-set not found: ", routeExcludeAddressSet)
|
||||||
|
}
|
||||||
|
ruleSet.IncRef()
|
||||||
|
inbound.routeExcludeRuleSet = append(inbound.routeExcludeRuleSet, ruleSet)
|
||||||
|
markMode = true
|
||||||
|
}
|
||||||
|
if markMode {
|
||||||
|
inbound.tunOptions.AutoRedirectMarkMode = true
|
||||||
|
err = router.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inbound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func uidToRange(uidList option.Listable[uint32]) []ranges.Range[uint32] {
|
func uidToRange(uidList option.Listable[uint32]) []ranges.Range[uint32] {
|
||||||
@ -121,11 +255,11 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.
|
|||||||
}
|
}
|
||||||
var start, end uint64
|
var start, end uint64
|
||||||
var err error
|
var err error
|
||||||
start, err = strconv.ParseUint(uidRange[:subIndex], 10, 32)
|
start, err = strconv.ParseUint(uidRange[:subIndex], 0, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse range start")
|
return nil, E.Cause(err, "parse range start")
|
||||||
}
|
}
|
||||||
end, err = strconv.ParseUint(uidRange[subIndex+1:], 10, 32)
|
end, err = strconv.ParseUint(uidRange[subIndex+1:], 0, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse range end")
|
return nil, E.Cause(err, "parse range end")
|
||||||
}
|
}
|
||||||
@ -199,10 +333,44 @@ func (t *Tun) Start() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tun) PostStart() error {
|
||||||
|
monitor := taskmonitor.New(t.logger, C.StartTimeout)
|
||||||
|
if t.autoRedirect != nil {
|
||||||
|
t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
|
||||||
|
t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
|
||||||
|
monitor.Start("initiating auto-redirect")
|
||||||
|
err := t.autoRedirect.Start()
|
||||||
|
monitor.Finish()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "auto-redirect")
|
||||||
|
}
|
||||||
|
for _, routeRuleSet := range t.routeRuleSet {
|
||||||
|
t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet))
|
||||||
|
routeRuleSet.DecRef()
|
||||||
|
}
|
||||||
|
for _, routeExcludeRuleSet := range t.routeExcludeRuleSet {
|
||||||
|
t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet))
|
||||||
|
routeExcludeRuleSet.DecRef()
|
||||||
|
}
|
||||||
|
t.routeAddressSet = nil
|
||||||
|
t.routeExcludeAddressSet = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tun) updateRouteAddressSet(it adapter.RuleSet) {
|
||||||
|
t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
|
||||||
|
t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
|
||||||
|
t.autoRedirect.UpdateRouteAddressSet()
|
||||||
|
t.routeAddressSet = nil
|
||||||
|
t.routeExcludeAddressSet = nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Tun) Close() error {
|
func (t *Tun) Close() error {
|
||||||
return common.Close(
|
return common.Close(
|
||||||
t.tunStack,
|
t.tunStack,
|
||||||
t.tunIf,
|
t.tunIf,
|
||||||
|
t.autoRedirect,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,7 +382,11 @@ func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata
|
|||||||
metadata.Source = upstreamMetadata.Source
|
metadata.Source = upstreamMetadata.Source
|
||||||
metadata.Destination = upstreamMetadata.Destination
|
metadata.Destination = upstreamMetadata.Destination
|
||||||
metadata.InboundOptions = t.inboundOptions
|
metadata.InboundOptions = t.inboundOptions
|
||||||
|
if upstreamMetadata.Protocol != "" {
|
||||||
|
t.logger.InfoContext(ctx, "inbound ", upstreamMetadata.Protocol, " connection from ", metadata.Source)
|
||||||
|
} else {
|
||||||
t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||||
|
}
|
||||||
t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
|
||||||
err := t.router.RouteConnection(ctx, conn, metadata)
|
err := t.router.RouteConnection(ctx, conn, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -43,7 +43,7 @@ func (f Formatter) Format(ctx context.Context, level Level, tag string, message
|
|||||||
id, hasId = IDFromContext(ctx)
|
id, hasId = IDFromContext(ctx)
|
||||||
}
|
}
|
||||||
if hasId {
|
if hasId {
|
||||||
activeDuration := formatDuration(time.Since(id.CreatedAt))
|
activeDuration := FormatDuration(time.Since(id.CreatedAt))
|
||||||
if !f.DisableColors {
|
if !f.DisableColors {
|
||||||
var color aurora.Color
|
var color aurora.Color
|
||||||
color = aurora.Color(uint8(id.ID))
|
color = aurora.Color(uint8(id.ID))
|
||||||
@ -113,7 +113,7 @@ func (f Formatter) FormatWithSimple(ctx context.Context, level Level, tag string
|
|||||||
id, hasId = IDFromContext(ctx)
|
id, hasId = IDFromContext(ctx)
|
||||||
}
|
}
|
||||||
if hasId {
|
if hasId {
|
||||||
activeDuration := formatDuration(time.Since(id.CreatedAt))
|
activeDuration := FormatDuration(time.Since(id.CreatedAt))
|
||||||
if !f.DisableColors {
|
if !f.DisableColors {
|
||||||
var color aurora.Color
|
var color aurora.Color
|
||||||
color = aurora.Color(uint8(id.ID))
|
color = aurora.Color(uint8(id.ID))
|
||||||
@ -163,7 +163,7 @@ func xd(value int, x int) string {
|
|||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatDuration(duration time.Duration) string {
|
func FormatDuration(duration time.Duration) string {
|
||||||
if duration < time.Second {
|
if duration < time.Second {
|
||||||
return F.ToString(duration.Milliseconds(), "ms")
|
return F.ToString(duration.Milliseconds(), "ms")
|
||||||
} else if duration < time.Minute {
|
} else if duration < time.Minute {
|
||||||
|
@ -113,7 +113,7 @@ type DialerOptions struct {
|
|||||||
Inet4BindAddress *ListenAddress `json:"inet4_bind_address,omitempty"`
|
Inet4BindAddress *ListenAddress `json:"inet4_bind_address,omitempty"`
|
||||||
Inet6BindAddress *ListenAddress `json:"inet6_bind_address,omitempty"`
|
Inet6BindAddress *ListenAddress `json:"inet6_bind_address,omitempty"`
|
||||||
ProtectPath string `json:"protect_path,omitempty"`
|
ProtectPath string `json:"protect_path,omitempty"`
|
||||||
RoutingMark int `json:"routing_mark,omitempty"`
|
RoutingMark uint32 `json:"routing_mark,omitempty"`
|
||||||
ReuseAddr bool `json:"reuse_addr,omitempty"`
|
ReuseAddr bool `json:"reuse_addr,omitempty"`
|
||||||
ConnectTimeout Duration `json:"connect_timeout,omitempty"`
|
ConnectTimeout Duration `json:"connect_timeout,omitempty"`
|
||||||
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
||||||
|
@ -10,7 +10,7 @@ type RouteOptions struct {
|
|||||||
AutoDetectInterface bool `json:"auto_detect_interface,omitempty"`
|
AutoDetectInterface bool `json:"auto_detect_interface,omitempty"`
|
||||||
OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"`
|
OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"`
|
||||||
DefaultInterface string `json:"default_interface,omitempty"`
|
DefaultInterface string `json:"default_interface,omitempty"`
|
||||||
DefaultMark int `json:"default_mark,omitempty"`
|
DefaultMark uint32 `json:"default_mark,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GeoIPOptions struct {
|
type GeoIPOptions struct {
|
||||||
|
@ -6,14 +6,18 @@ type TunInboundOptions struct {
|
|||||||
InterfaceName string `json:"interface_name,omitempty"`
|
InterfaceName string `json:"interface_name,omitempty"`
|
||||||
MTU uint32 `json:"mtu,omitempty"`
|
MTU uint32 `json:"mtu,omitempty"`
|
||||||
GSO bool `json:"gso,omitempty"`
|
GSO bool `json:"gso,omitempty"`
|
||||||
Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"`
|
Address Listable[netip.Prefix] `json:"address,omitempty"`
|
||||||
Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"`
|
|
||||||
AutoRoute bool `json:"auto_route,omitempty"`
|
AutoRoute bool `json:"auto_route,omitempty"`
|
||||||
|
IPRoute2TableIndex int `json:"iproute2_table_index,omitempty"`
|
||||||
|
IPRoute2RuleIndex int `json:"iproute2_rule_index,omitempty"`
|
||||||
|
AutoRedirect bool `json:"auto_redirect,omitempty"`
|
||||||
|
AutoRedirectInputMark uint32 `json:"auto_redirect_input_mark,omitempty"`
|
||||||
|
AutoRedirectOutputMark uint32 `json:"auto_redirect_output_mark,omitempty"`
|
||||||
StrictRoute bool `json:"strict_route,omitempty"`
|
StrictRoute bool `json:"strict_route,omitempty"`
|
||||||
Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"`
|
RouteAddress Listable[netip.Prefix] `json:"route_address,omitempty"`
|
||||||
Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"`
|
RouteAddressSet Listable[string] `json:"route_address_set,omitempty"`
|
||||||
Inet4RouteExcludeAddress Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"`
|
RouteExcludeAddress Listable[netip.Prefix] `json:"route_exclude_address,omitempty"`
|
||||||
Inet6RouteExcludeAddress Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"`
|
RouteExcludeAddressSet Listable[string] `json:"route_exclude_address_set,omitempty"`
|
||||||
IncludeInterface Listable[string] `json:"include_interface,omitempty"`
|
IncludeInterface Listable[string] `json:"include_interface,omitempty"`
|
||||||
ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"`
|
ExcludeInterface Listable[string] `json:"exclude_interface,omitempty"`
|
||||||
IncludeUID Listable[uint32] `json:"include_uid,omitempty"`
|
IncludeUID Listable[uint32] `json:"include_uid,omitempty"`
|
||||||
@ -28,4 +32,17 @@ type TunInboundOptions struct {
|
|||||||
Stack string `json:"stack,omitempty"`
|
Stack string `json:"stack,omitempty"`
|
||||||
Platform *TunPlatformOptions `json:"platform,omitempty"`
|
Platform *TunPlatformOptions `json:"platform,omitempty"`
|
||||||
InboundOptions
|
InboundOptions
|
||||||
|
|
||||||
|
// Deprecated: merged to Address
|
||||||
|
Inet4Address Listable[netip.Prefix] `json:"inet4_address,omitempty"`
|
||||||
|
// Deprecated: merged to Address
|
||||||
|
Inet6Address Listable[netip.Prefix] `json:"inet6_address,omitempty"`
|
||||||
|
// Deprecated: merged to RouteAddress
|
||||||
|
Inet4RouteAddress Listable[netip.Prefix] `json:"inet4_route_address,omitempty"`
|
||||||
|
// Deprecated: merged to RouteAddress
|
||||||
|
Inet6RouteAddress Listable[netip.Prefix] `json:"inet6_route_address,omitempty"`
|
||||||
|
// Deprecated: merged to RouteExcludeAddress
|
||||||
|
Inet4RouteExcludeAddress Listable[netip.Prefix] `json:"inet4_route_exclude_address,omitempty"`
|
||||||
|
// Deprecated: merged to RouteExcludeAddress
|
||||||
|
Inet6RouteExcludeAddress Listable[netip.Prefix] `json:"inet6_route_exclude_address,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,8 @@ type Router struct {
|
|||||||
interfaceFinder *control.DefaultInterfaceFinder
|
interfaceFinder *control.DefaultInterfaceFinder
|
||||||
autoDetectInterface bool
|
autoDetectInterface bool
|
||||||
defaultInterface string
|
defaultInterface string
|
||||||
defaultMark int
|
defaultMark uint32
|
||||||
|
autoRedirectOutputMark uint32
|
||||||
networkMonitor tun.NetworkUpdateMonitor
|
networkMonitor tun.NetworkUpdateMonitor
|
||||||
interfaceMonitor tun.DefaultInterfaceMonitor
|
interfaceMonitor tun.DefaultInterfaceMonitor
|
||||||
packageManager tun.PackageManager
|
packageManager tun.PackageManager
|
||||||
@ -728,6 +729,14 @@ func (r *Router) PostStart() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) Cleanup() error {
|
||||||
|
for _, ruleSet := range r.ruleSetMap {
|
||||||
|
ruleSet.Cleanup()
|
||||||
|
}
|
||||||
|
runtime.GC()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
|
func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
|
||||||
outbound, loaded := r.outboundByTag[tag]
|
outbound, loaded := r.outboundByTag[tag]
|
||||||
return outbound, loaded
|
return outbound, loaded
|
||||||
@ -822,7 +831,16 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
|
|
||||||
if metadata.InboundOptions.SniffEnabled {
|
if metadata.InboundOptions.SniffEnabled {
|
||||||
buffer := buf.NewPacket()
|
buffer := buf.NewPacket()
|
||||||
sniffMetadata, err := sniff.PeekStream(ctx, conn, buffer, time.Duration(metadata.InboundOptions.SniffTimeout), sniff.StreamDomainNameQuery, sniff.TLSClientHello, sniff.HTTPHost)
|
sniffMetadata, err := sniff.PeekStream(
|
||||||
|
ctx,
|
||||||
|
conn,
|
||||||
|
buffer,
|
||||||
|
time.Duration(metadata.InboundOptions.SniffTimeout),
|
||||||
|
sniff.StreamDomainNameQuery,
|
||||||
|
sniff.TLSClientHello,
|
||||||
|
sniff.HTTPHost,
|
||||||
|
sniff.BitTorrent,
|
||||||
|
)
|
||||||
if sniffMetadata != nil {
|
if sniffMetadata != nil {
|
||||||
metadata.Protocol = sniffMetadata.Protocol
|
metadata.Protocol = sniffMetadata.Protocol
|
||||||
metadata.Domain = sniffMetadata.Domain
|
metadata.Domain = sniffMetadata.Domain
|
||||||
@ -949,7 +967,15 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
metadata.Destination = destination
|
metadata.Destination = destination
|
||||||
}
|
}
|
||||||
if metadata.InboundOptions.SniffEnabled {
|
if metadata.InboundOptions.SniffEnabled {
|
||||||
sniffMetadata, _ := sniff.PeekPacket(ctx, buffer.Bytes(), sniff.DomainNameQuery, sniff.QUICClientHello, sniff.STUNMessage)
|
sniffMetadata, _ := sniff.PeekPacket(
|
||||||
|
ctx,
|
||||||
|
buffer.Bytes(),
|
||||||
|
sniff.DomainNameQuery,
|
||||||
|
sniff.QUICClientHello,
|
||||||
|
sniff.STUNMessage,
|
||||||
|
sniff.UTP,
|
||||||
|
sniff.UDPTracker,
|
||||||
|
)
|
||||||
if sniffMetadata != nil {
|
if sniffMetadata != nil {
|
||||||
metadata.Protocol = sniffMetadata.Protocol
|
metadata.Protocol = sniffMetadata.Protocol
|
||||||
metadata.Domain = sniffMetadata.Domain
|
metadata.Domain = sniffMetadata.Domain
|
||||||
@ -1114,11 +1140,23 @@ func (r *Router) AutoDetectInterfaceFunc() control.Func {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) RegisterAutoRedirectOutputMark(mark uint32) error {
|
||||||
|
if r.autoRedirectOutputMark > 0 {
|
||||||
|
return E.New("only one auto-redirect can be configured")
|
||||||
|
}
|
||||||
|
r.autoRedirectOutputMark = mark
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) AutoRedirectOutputMark() uint32 {
|
||||||
|
return r.autoRedirectOutputMark
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Router) DefaultInterface() string {
|
func (r *Router) DefaultInterface() string {
|
||||||
return r.defaultInterface
|
return r.defaultInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) DefaultMark() int {
|
func (r *Router) DefaultMark() uint32 {
|
||||||
return r.defaultMark
|
return r.defaultMark
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing/common/cache"
|
"github.com/sagernet/sing/common/cache"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@ -122,12 +121,10 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
|
|||||||
for {
|
for {
|
||||||
var (
|
var (
|
||||||
dnsCtx context.Context
|
dnsCtx context.Context
|
||||||
cancel context.CancelFunc
|
|
||||||
addressLimit bool
|
addressLimit bool
|
||||||
)
|
)
|
||||||
|
|
||||||
dnsCtx, transport, strategy, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex)
|
dnsCtx, transport, strategy, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex)
|
||||||
dnsCtx, cancel = context.WithTimeout(dnsCtx, C.DNSTimeout)
|
|
||||||
if rule != nil && rule.WithAddressLimit() && isAddressQuery(message) {
|
if rule != nil && rule.WithAddressLimit() && isAddressQuery(message) {
|
||||||
addressLimit = true
|
addressLimit = true
|
||||||
response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, strategy, func(response *mDNS.Msg) bool {
|
response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, strategy, func(response *mDNS.Msg) bool {
|
||||||
@ -138,7 +135,6 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
|
|||||||
addressLimit = false
|
addressLimit = false
|
||||||
response, err = r.dnsClient.Exchange(dnsCtx, transport, message, strategy)
|
response, err = r.dnsClient.Exchange(dnsCtx, transport, message, strategy)
|
||||||
}
|
}
|
||||||
cancel()
|
|
||||||
var rejected bool
|
var rejected bool
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, dns.ErrResponseRejectedCached) {
|
if errors.Is(err, dns.ErrResponseRejectedCached) {
|
||||||
@ -200,7 +196,6 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS
|
|||||||
for {
|
for {
|
||||||
var (
|
var (
|
||||||
dnsCtx context.Context
|
dnsCtx context.Context
|
||||||
cancel context.CancelFunc
|
|
||||||
addressLimit bool
|
addressLimit bool
|
||||||
)
|
)
|
||||||
metadata.ResetRuleCache()
|
metadata.ResetRuleCache()
|
||||||
@ -209,7 +204,6 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS
|
|||||||
if strategy == dns.DomainStrategyAsIS {
|
if strategy == dns.DomainStrategyAsIS {
|
||||||
strategy = transportStrategy
|
strategy = transportStrategy
|
||||||
}
|
}
|
||||||
dnsCtx, cancel = context.WithTimeout(dnsCtx, C.DNSTimeout)
|
|
||||||
if rule != nil && rule.WithAddressLimit() {
|
if rule != nil && rule.WithAddressLimit() {
|
||||||
addressLimit = true
|
addressLimit = true
|
||||||
responseAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, strategy, func(responseAddrs []netip.Addr) bool {
|
responseAddrs, err = r.dnsClient.LookupWithResponseCheck(dnsCtx, transport, domain, strategy, func(responseAddrs []netip.Addr) bool {
|
||||||
@ -220,7 +214,6 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS
|
|||||||
addressLimit = false
|
addressLimit = false
|
||||||
responseAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, strategy)
|
responseAddrs, err = r.dnsClient.Lookup(dnsCtx, transport, domain, strategy)
|
||||||
}
|
}
|
||||||
cancel()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, dns.ErrResponseRejectedCached) {
|
if errors.Is(err, dns.ErrResponseRejectedCached) {
|
||||||
r.dnsLogger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
|
r.dnsLogger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
|
||||||
|
@ -32,6 +32,7 @@ func (r *RuleSetItem) Start() error {
|
|||||||
if !loaded {
|
if !loaded {
|
||||||
return E.New("rule-set not found: ", tag)
|
return E.New("rule-set not found: ", tag)
|
||||||
}
|
}
|
||||||
|
ruleSet.IncRef()
|
||||||
r.setList = append(r.setList, ruleSet)
|
r.setList = append(r.setList, ruleSet)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -9,10 +9,13 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"go4.org/netipx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) (adapter.RuleSet, error) {
|
func NewRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) (adapter.RuleSet, error) {
|
||||||
@ -26,6 +29,24 @@ func NewRuleSet(ctx context.Context, router adapter.Router, logger logger.Contex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractIPSetFromRule(rawRule adapter.HeadlessRule) []*netipx.IPSet {
|
||||||
|
switch rule := rawRule.(type) {
|
||||||
|
case *DefaultHeadlessRule:
|
||||||
|
return common.FlatMap(rule.destinationIPCIDRItems, func(rawItem RuleItem) []*netipx.IPSet {
|
||||||
|
switch item := rawItem.(type) {
|
||||||
|
case *IPCIDRItem:
|
||||||
|
return []*netipx.IPSet{item.ipSet}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
case *LogicalHeadlessRule:
|
||||||
|
return common.FlatMap(rule.rules, extractIPSetFromRule)
|
||||||
|
default:
|
||||||
|
panic("unexpected rule type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var _ adapter.RuleSetStartContext = (*RuleSetStartContext)(nil)
|
var _ adapter.RuleSetStartContext = (*RuleSetStartContext)(nil)
|
||||||
|
|
||||||
type RuleSetStartContext struct {
|
type RuleSetStartContext struct {
|
||||||
|
@ -9,16 +9,23 @@ import (
|
|||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/atomic"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
|
||||||
|
"go4.org/netipx"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.RuleSet = (*LocalRuleSet)(nil)
|
var _ adapter.RuleSet = (*LocalRuleSet)(nil)
|
||||||
|
|
||||||
type LocalRuleSet struct {
|
type LocalRuleSet struct {
|
||||||
|
tag string
|
||||||
rules []adapter.HeadlessRule
|
rules []adapter.HeadlessRule
|
||||||
metadata adapter.RuleSetMetadata
|
metadata adapter.RuleSetMetadata
|
||||||
|
refs atomic.Int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLocalRuleSet(router adapter.Router, options option.RuleSet) (*LocalRuleSet, error) {
|
func NewLocalRuleSet(router adapter.Router, options option.RuleSet) (*LocalRuleSet, error) {
|
||||||
@ -58,16 +65,11 @@ func NewLocalRuleSet(router adapter.Router, options option.RuleSet) (*LocalRuleS
|
|||||||
metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
|
||||||
metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
||||||
metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
||||||
return &LocalRuleSet{rules, metadata}, nil
|
return &LocalRuleSet{tag: options.Tag, rules: rules, metadata: metadata}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool {
|
func (s *LocalRuleSet) Name() string {
|
||||||
for _, rule := range s.rules {
|
return s.tag
|
||||||
if rule.Match(metadata) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRuleSet) String() string {
|
func (s *LocalRuleSet) String() string {
|
||||||
@ -82,6 +84,43 @@ func (s *LocalRuleSet) Metadata() adapter.RuleSetMetadata {
|
|||||||
return s.metadata
|
return s.metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRuleSet) Close() error {
|
func (s *LocalRuleSet) ExtractIPSet() []*netipx.IPSet {
|
||||||
|
return common.FlatMap(s.rules, extractIPSetFromRule)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LocalRuleSet) IncRef() {
|
||||||
|
s.refs.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LocalRuleSet) DecRef() {
|
||||||
|
if s.refs.Add(-1) < 0 {
|
||||||
|
panic("rule-set: negative refs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LocalRuleSet) Cleanup() {
|
||||||
|
if s.refs.Load() == 0 {
|
||||||
|
s.rules = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LocalRuleSet) Close() error {
|
||||||
|
s.rules = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LocalRuleSet) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
for _, rule := range s.rules {
|
||||||
|
if rule.Match(metadata) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -8,20 +8,26 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/atomic"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
|
|
||||||
|
"go4.org/netipx"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.RuleSet = (*RemoteRuleSet)(nil)
|
var _ adapter.RuleSet = (*RemoteRuleSet)(nil)
|
||||||
@ -40,6 +46,9 @@ type RemoteRuleSet struct {
|
|||||||
lastEtag string
|
lastEtag string
|
||||||
updateTicker *time.Ticker
|
updateTicker *time.Ticker
|
||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
|
callbackAccess sync.Mutex
|
||||||
|
callbacks list.List[adapter.RuleSetUpdateCallback]
|
||||||
|
refs atomic.Int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRemoteRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet {
|
func NewRemoteRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet {
|
||||||
@ -61,13 +70,8 @@ func NewRemoteRuleSet(ctx context.Context, router adapter.Router, logger logger.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool {
|
func (s *RemoteRuleSet) Name() string {
|
||||||
for _, rule := range s.rules {
|
return s.options.Tag
|
||||||
if rule.Match(metadata) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRuleSet) String() string {
|
func (s *RemoteRuleSet) String() string {
|
||||||
@ -116,6 +120,38 @@ func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata {
|
|||||||
return s.metadata
|
return s.metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *RemoteRuleSet) ExtractIPSet() []*netipx.IPSet {
|
||||||
|
return common.FlatMap(s.rules, extractIPSetFromRule)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteRuleSet) IncRef() {
|
||||||
|
s.refs.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteRuleSet) DecRef() {
|
||||||
|
if s.refs.Add(-1) < 0 {
|
||||||
|
panic("rule-set: negative refs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteRuleSet) Cleanup() {
|
||||||
|
if s.refs.Load() == 0 {
|
||||||
|
s.rules = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
|
||||||
|
s.callbackAccess.Lock()
|
||||||
|
defer s.callbackAccess.Unlock()
|
||||||
|
return s.callbacks.PushBack(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {
|
||||||
|
s.callbackAccess.Lock()
|
||||||
|
defer s.callbackAccess.Unlock()
|
||||||
|
s.callbacks.Remove(element)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *RemoteRuleSet) loadBytes(content []byte) error {
|
func (s *RemoteRuleSet) loadBytes(content []byte) error {
|
||||||
var (
|
var (
|
||||||
plainRuleSet option.PlainRuleSet
|
plainRuleSet option.PlainRuleSet
|
||||||
@ -148,6 +184,16 @@ func (s *RemoteRuleSet) loadBytes(content []byte) error {
|
|||||||
s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
|
||||||
s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
s.metadata.ContainsIPCIDRRule = hasHeadlessRule(plainRuleSet.Rules, isIPCIDRHeadlessRule)
|
||||||
s.rules = rules
|
s.rules = rules
|
||||||
|
s.callbackAccess.Lock()
|
||||||
|
callbacks := s.callbacks.Array()
|
||||||
|
s.callbackAccess.Unlock()
|
||||||
|
for _, callback := range callbacks {
|
||||||
|
callback(s)
|
||||||
|
}
|
||||||
|
if s.refs.Load() == 0 {
|
||||||
|
s.rules = nil
|
||||||
|
runtime.GC()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,7 +299,17 @@ func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext adapter.Rule
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *RemoteRuleSet) Close() error {
|
func (s *RemoteRuleSet) Close() error {
|
||||||
|
s.rules = nil
|
||||||
s.updateTicker.Stop()
|
s.updateTicker.Stop()
|
||||||
s.cancel()
|
s.cancel()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
for _, rule := range s.rules {
|
||||||
|
if rule.Match(metadata) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -113,7 +113,7 @@ func testSuitLargeUDP(t *testing.T, clientPort uint16, testPort uint16) {
|
|||||||
require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))
|
require.NoError(t, testPingPongWithPacketConn(t, testPort, dialUDP))
|
||||||
require.NoError(t, testLargeDataWithConn(t, testPort, dialTCP))
|
require.NoError(t, testLargeDataWithConn(t, testPort, dialTCP))
|
||||||
require.NoError(t, testLargeDataWithPacketConn(t, testPort, dialUDP))
|
require.NoError(t, testLargeDataWithPacketConn(t, testPort, dialUDP))
|
||||||
require.NoError(t, testLargeDataWithPacketConnSize(t, testPort, 5000, dialUDP))
|
require.NoError(t, testLargeDataWithPacketConnSize(t, testPort, 4096, dialUDP))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTCP(t *testing.T, clientPort uint16, testPort uint16) {
|
func testTCP(t *testing.T, clientPort uint16, testPort uint16) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user