mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
Compare commits
41 Commits
dev-next
...
v1.3-beta1
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e556dfad68 | ||
![]() |
b0a978d4b6 | ||
![]() |
c6e6621ecf | ||
![]() |
d3b41dfc01 | ||
![]() |
08f4384579 | ||
![]() |
d08b82b71c | ||
![]() |
d2d3c82ccf | ||
![]() |
841ef1acaf | ||
![]() |
115507fb2a | ||
![]() |
c5067af884 | ||
![]() |
974b2a3165 | ||
![]() |
955028d4dc | ||
![]() |
7050011802 | ||
![]() |
2a76b8fbeb | ||
![]() |
e3286d62ce | ||
![]() |
37657851ae | ||
![]() |
26bfcbd33c | ||
![]() |
50827bcff1 | ||
![]() |
6b64ebd3c0 | ||
![]() |
bec606ee88 | ||
![]() |
8545e41b2f | ||
![]() |
d8810b6e8f | ||
![]() |
f568bb9fe3 | ||
![]() |
ccb872a41e | ||
![]() |
9c287094e2 | ||
![]() |
f61c5600e0 | ||
![]() |
2e98777f82 | ||
![]() |
73e72e9aec | ||
![]() |
922acced94 | ||
![]() |
988d7331c6 | ||
![]() |
c0d6dde95b | ||
![]() |
4dbf95875b | ||
![]() |
81c4312be8 | ||
![]() |
cc94dfaa4b | ||
![]() |
c6fc411164 | ||
![]() |
be00e19162 | ||
![]() |
eb57cbc4ad | ||
![]() |
f98cfdf5e4 | ||
![]() |
cbf0099681 | ||
![]() |
a86afa0e5b | ||
![]() |
b5d2062359 |
2
Makefile
2
Makefile
@ -22,7 +22,7 @@ install:
|
|||||||
fmt:
|
fmt:
|
||||||
@gofumpt -l -w .
|
@gofumpt -l -w .
|
||||||
@gofmt -s -w .
|
@gofmt -s -w .
|
||||||
@gci write --custom-order -s "standard,prefix(github.com/sagernet/),default" .
|
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
|
||||||
|
|
||||||
fmt_install:
|
fmt_install:
|
||||||
go install -v mvdan.cc/gofumpt@latest
|
go install -v mvdan.cc/gofumpt@latest
|
||||||
|
@ -13,6 +13,7 @@ type ClashServer interface {
|
|||||||
PreStarter
|
PreStarter
|
||||||
Mode() string
|
Mode() string
|
||||||
StoreSelected() bool
|
StoreSelected() bool
|
||||||
|
StoreFakeIP() bool
|
||||||
CacheFile() ClashCacheFile
|
CacheFile() ClashCacheFile
|
||||||
HistoryStorage() *urltest.HistoryStorage
|
HistoryStorage() *urltest.HistoryStorage
|
||||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
||||||
@ -22,6 +23,7 @@ type ClashServer interface {
|
|||||||
type ClashCacheFile interface {
|
type ClashCacheFile interface {
|
||||||
LoadSelected(group string) string
|
LoadSelected(group string) string
|
||||||
StoreSelected(group string, selected string) error
|
StoreSelected(group string, selected string) error
|
||||||
|
FakeIPStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tracker interface {
|
type Tracker interface {
|
||||||
@ -33,6 +35,11 @@ type OutboundGroup interface {
|
|||||||
All() []string
|
All() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type URLTestGroup interface {
|
||||||
|
OutboundGroup
|
||||||
|
URLTest(ctx context.Context, url string) (map[string]uint16, error)
|
||||||
|
}
|
||||||
|
|
||||||
func OutboundTag(detour Outbound) string {
|
func OutboundTag(detour Outbound) string {
|
||||||
if group, isGroup := detour.(OutboundGroup); isGroup {
|
if group, isGroup := detour.(OutboundGroup); isGroup {
|
||||||
return group.Now()
|
return group.Now()
|
||||||
|
23
adapter/fakeip.go
Normal file
23
adapter/fakeip.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FakeIPStore interface {
|
||||||
|
Service
|
||||||
|
Contains(address netip.Addr) bool
|
||||||
|
Create(domain string, strategy dns.DomainStrategy) (netip.Addr, error)
|
||||||
|
Lookup(address netip.Addr) (string, bool)
|
||||||
|
Reset() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type FakeIPStorage interface {
|
||||||
|
FakeIPMetadata() *FakeIPMetadata
|
||||||
|
FakeIPSaveMetadata(metadata *FakeIPMetadata) error
|
||||||
|
FakeIPStore(address netip.Addr, domain string) error
|
||||||
|
FakeIPLoad(address netip.Addr) (string, bool)
|
||||||
|
FakeIPReset() error
|
||||||
|
}
|
50
adapter/fakeip_metadata.go
Normal file
50
adapter/fakeip_metadata.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FakeIPMetadata struct {
|
||||||
|
Inet4Range netip.Prefix
|
||||||
|
Inet6Range netip.Prefix
|
||||||
|
Inet4Current netip.Addr
|
||||||
|
Inet6Current netip.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FakeIPMetadata) MarshalBinary() (data []byte, err error) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
for _, marshaler := range []encoding.BinaryMarshaler{m.Inet4Range, m.Inet6Range, m.Inet4Current, m.Inet6Current} {
|
||||||
|
data, err = marshaler.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
common.Must(binary.Write(&buffer, binary.BigEndian, uint16(len(data))))
|
||||||
|
buffer.Write(data)
|
||||||
|
}
|
||||||
|
data = buffer.Bytes()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *FakeIPMetadata) UnmarshalBinary(data []byte) error {
|
||||||
|
reader := bytes.NewReader(data)
|
||||||
|
for _, unmarshaler := range []encoding.BinaryUnmarshaler{&m.Inet4Range, &m.Inet6Range, &m.Inet4Current, &m.Inet6Current} {
|
||||||
|
var length uint16
|
||||||
|
common.Must(binary.Read(reader, binary.BigEndian, &length))
|
||||||
|
element := make([]byte, length)
|
||||||
|
_, err := io.ReadFull(reader, element)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = unmarshaler.UnmarshalBinary(element)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -27,7 +27,7 @@ type InjectableInbound interface {
|
|||||||
type InboundContext struct {
|
type InboundContext struct {
|
||||||
Inbound string
|
Inbound string
|
||||||
InboundType string
|
InboundType string
|
||||||
IPVersion int
|
IPVersion uint8
|
||||||
Network string
|
Network string
|
||||||
Source M.Socksaddr
|
Source M.Socksaddr
|
||||||
Destination M.Socksaddr
|
Destination M.Socksaddr
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,3 +18,8 @@ type Outbound interface {
|
|||||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IPOutbound interface {
|
||||||
|
Outbound
|
||||||
|
NewIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) (tun.DirectDestination, error)
|
||||||
|
}
|
||||||
|
@ -21,8 +21,13 @@ type Router interface {
|
|||||||
Outbound(tag string) (Outbound, bool)
|
Outbound(tag string) (Outbound, bool)
|
||||||
DefaultOutbound(network string) Outbound
|
DefaultOutbound(network string) Outbound
|
||||||
|
|
||||||
|
FakeIPStore() FakeIPStore
|
||||||
|
|
||||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
|
RouteIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) tun.RouteAction
|
||||||
|
|
||||||
|
NatRequired(outbound string) bool
|
||||||
|
|
||||||
GeoIPReader() *geoip.Reader
|
GeoIPReader() *geoip.Reader
|
||||||
LoadGeosite(code string) (Rule, error)
|
LoadGeosite(code string) (Rule, error)
|
||||||
@ -40,7 +45,9 @@ type Router interface {
|
|||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
|
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
|
IPRules() []IPRule
|
||||||
|
|
||||||
TimeService
|
TimeService
|
||||||
|
|
||||||
@ -49,6 +56,8 @@ type Router interface {
|
|||||||
|
|
||||||
V2RayServer() V2RayServer
|
V2RayServer() V2RayServer
|
||||||
SetV2RayServer(server V2RayServer)
|
SetV2RayServer(server V2RayServer)
|
||||||
|
|
||||||
|
ResetNetwork() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type routerContextKey struct{}
|
type routerContextKey struct{}
|
||||||
@ -77,6 +86,12 @@ type Rule interface {
|
|||||||
type DNSRule interface {
|
type DNSRule interface {
|
||||||
Rule
|
Rule
|
||||||
DisableCache() bool
|
DisableCache() bool
|
||||||
|
RewriteTTL() *uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPRule interface {
|
||||||
|
Rule
|
||||||
|
Action() tun.ActionType
|
||||||
}
|
}
|
||||||
|
|
||||||
type InterfaceUpdateListener interface {
|
type InterfaceUpdateListener interface {
|
||||||
|
3
box.go
3
box.go
@ -62,6 +62,7 @@ func New(options Options) (*Box, error) {
|
|||||||
defaultLogWriter = io.Discard
|
defaultLogWriter = io.Discard
|
||||||
}
|
}
|
||||||
logFactory, err := log.New(log.Options{
|
logFactory, err := log.New(log.Options{
|
||||||
|
Context: ctx,
|
||||||
Options: common.PtrValueOrDefault(options.Log),
|
Options: common.PtrValueOrDefault(options.Log),
|
||||||
Observable: needClashAPI,
|
Observable: needClashAPI,
|
||||||
DefaultWriter: defaultLogWriter,
|
DefaultWriter: defaultLogWriter,
|
||||||
@ -142,7 +143,7 @@ func New(options Options) (*Box, error) {
|
|||||||
preServices := make(map[string]adapter.Service)
|
preServices := make(map[string]adapter.Service)
|
||||||
postServices := make(map[string]adapter.Service)
|
postServices := make(map[string]adapter.Service)
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashServer, err := experimental.NewClashServer(router, logFactory.(log.ObservableFactory), common.PtrValueOrDefault(options.Experimental.ClashAPI))
|
clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), common.PtrValueOrDefault(options.Experimental.ClashAPI))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create clash api server")
|
return nil, E.Cause(err, "create clash api server")
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
_ "github.com/sagernet/gomobile/event/key"
|
_ "github.com/sagernet/gomobile/event/key"
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||||
@ -38,18 +39,23 @@ func main() {
|
|||||||
var (
|
var (
|
||||||
sharedFlags []string
|
sharedFlags []string
|
||||||
debugFlags []string
|
debugFlags []string
|
||||||
|
sharedTags []string
|
||||||
|
debugTags []string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
sharedFlags = append(sharedFlags, "-trimpath")
|
sharedFlags = append(sharedFlags, "-trimpath")
|
||||||
sharedFlags = append(sharedFlags, "-ldflags")
|
sharedFlags = append(sharedFlags, "-ldflags")
|
||||||
|
|
||||||
currentTag, err := build_shared.ReadTag()
|
currentTag, err := build_shared.ReadTag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
currentTag = "unknown"
|
currentTag = "unknown"
|
||||||
}
|
}
|
||||||
sharedFlags = append(sharedFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
sharedFlags = append(sharedFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
||||||
debugFlags = append(debugFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
debugFlags = append(debugFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||||
|
|
||||||
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api")
|
||||||
|
sharedTags = append(sharedTags, "test_sing_shadowsocks2")
|
||||||
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAndroid() {
|
func buildAndroid() {
|
||||||
@ -70,9 +76,9 @@ func buildAndroid() {
|
|||||||
|
|
||||||
args = append(args, "-tags")
|
args = append(args, "-tags")
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
args = append(args, "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api")
|
args = append(args, strings.Join(sharedTags, ","))
|
||||||
} else {
|
} else {
|
||||||
args = append(args, "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api,debug")
|
args = append(args, strings.Join(append(sharedTags, debugTags...), ","))
|
||||||
}
|
}
|
||||||
args = append(args, "./experimental/libbox")
|
args = append(args, "./experimental/libbox")
|
||||||
|
|
||||||
@ -109,11 +115,12 @@ func buildiOS() {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tags := append(sharedTags, "with_low_memory", "with_conntrack")
|
||||||
args = append(args, "-tags")
|
args = append(args, "-tags")
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
args = append(args, "with_gvisor,with_quic,with_utls,with_clash_api,with_low_memory,with_conntrack")
|
args = append(args, strings.Join(tags, ","))
|
||||||
} else {
|
} else {
|
||||||
args = append(args, "with_gvisor,with_quic,with_utls,with_clash_api,with_low_memory,with_conntrack,debug")
|
args = append(args, strings.Join(append(tags, debugTags...), ","))
|
||||||
}
|
}
|
||||||
args = append(args, "./experimental/libbox")
|
args = append(args, "./experimental/libbox")
|
||||||
|
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
//go:build debug
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
_ "net/http/pprof"
|
|
||||||
"runtime"
|
|
||||||
"runtime/debug"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/badjson"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
http.HandleFunc("/debug/gc", func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
writer.WriteHeader(http.StatusNoContent)
|
|
||||||
go debug.FreeOSMemory()
|
|
||||||
})
|
|
||||||
http.HandleFunc("/debug/memory", func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
var memStats runtime.MemStats
|
|
||||||
runtime.ReadMemStats(&memStats)
|
|
||||||
|
|
||||||
var memObject badjson.JSONObject
|
|
||||||
memObject.Put("heap", humanize.IBytes(memStats.HeapInuse))
|
|
||||||
memObject.Put("stack", humanize.IBytes(memStats.StackInuse))
|
|
||||||
memObject.Put("idle", humanize.IBytes(memStats.HeapIdle-memStats.HeapReleased))
|
|
||||||
memObject.Put("goroutines", runtime.NumGoroutine())
|
|
||||||
memObject.Put("rss", rusageMaxRSS())
|
|
||||||
|
|
||||||
encoder := json.NewEncoder(writer)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
encoder.Encode(memObject)
|
|
||||||
})
|
|
||||||
go func() {
|
|
||||||
err := http.ListenAndServe("0.0.0.0:8964", nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Debug(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
@ -21,7 +21,7 @@ func TestMergeJSON(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Type: C.RuleTypeDefault,
|
Type: C.RuleTypeDefault,
|
||||||
DefaultOptions: option.DefaultRule{
|
DefaultOptions: option.DefaultRule{
|
||||||
Network: N.NetworkTCP,
|
Network: []string{N.NetworkTCP},
|
||||||
Outbound: "direct",
|
Outbound: "direct",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -42,7 +42,7 @@ func TestMergeJSON(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Type: C.RuleTypeDefault,
|
Type: C.RuleTypeDefault,
|
||||||
DefaultOptions: option.DefaultRule{
|
DefaultOptions: option.DefaultRule{
|
||||||
Network: N.NetworkUDP,
|
Network: []string{N.NetworkUDP},
|
||||||
Outbound: "direct",
|
Outbound: "direct",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//go:build go1.19 && !go1.20
|
//go:build go1.20 && !go1.21
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
@ -14,39 +14,60 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
*tls.Conn
|
*tls.Conn
|
||||||
writer N.ExtendedWriter
|
writer N.ExtendedWriter
|
||||||
activeCall *int32
|
isHandshakeComplete *atomic.Bool
|
||||||
closeNotifySent *bool
|
activeCall *atomic.Int32
|
||||||
version *uint16
|
closeNotifySent *bool
|
||||||
rand io.Reader
|
version *uint16
|
||||||
halfAccess *sync.Mutex
|
rand io.Reader
|
||||||
halfError *error
|
halfAccess *sync.Mutex
|
||||||
cipher cipher.AEAD
|
halfError *error
|
||||||
explicitNonceLen int
|
cipher cipher.AEAD
|
||||||
halfPtr uintptr
|
explicitNonceLen int
|
||||||
halfSeq []byte
|
halfPtr uintptr
|
||||||
halfScratchBuf []byte
|
halfSeq []byte
|
||||||
|
halfScratchBuf []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func Create(conn *tls.Conn) (TLSConn, error) {
|
func TryCreate(conn aTLS.Conn) aTLS.Conn {
|
||||||
if !handshakeComplete(conn) {
|
tlsConn, ok := conn.(*tls.Conn)
|
||||||
|
if !ok {
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
badConn, err := Create(tlsConn)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("initialize badtls: ", err)
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
return badConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func Create(conn *tls.Conn) (aTLS.Conn, error) {
|
||||||
|
rawConn := reflect.Indirect(reflect.ValueOf(conn))
|
||||||
|
rawIsHandshakeComplete := rawConn.FieldByName("isHandshakeComplete")
|
||||||
|
if !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct {
|
||||||
|
return nil, E.New("badtls: invalid isHandshakeComplete")
|
||||||
|
}
|
||||||
|
isHandshakeComplete := (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr()))
|
||||||
|
if !isHandshakeComplete.Load() {
|
||||||
return nil, E.New("handshake not finished")
|
return nil, E.New("handshake not finished")
|
||||||
}
|
}
|
||||||
rawConn := reflect.Indirect(reflect.ValueOf(conn))
|
|
||||||
rawActiveCall := rawConn.FieldByName("activeCall")
|
rawActiveCall := rawConn.FieldByName("activeCall")
|
||||||
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Int32 {
|
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct {
|
||||||
return nil, E.New("badtls: invalid active call")
|
return nil, E.New("badtls: invalid active call")
|
||||||
}
|
}
|
||||||
activeCall := (*int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
|
activeCall := (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
|
||||||
rawHalfConn := rawConn.FieldByName("out")
|
rawHalfConn := rawConn.FieldByName("out")
|
||||||
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
|
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
|
||||||
return nil, E.New("badtls: invalid half conn")
|
return nil, E.New("badtls: invalid half conn")
|
||||||
@ -108,19 +129,20 @@ func Create(conn *tls.Conn) (TLSConn, error) {
|
|||||||
}
|
}
|
||||||
halfScratchBuf := rawHalfScratchBuf.Bytes()
|
halfScratchBuf := rawHalfScratchBuf.Bytes()
|
||||||
return &Conn{
|
return &Conn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
writer: bufio.NewExtendedWriter(conn.NetConn()),
|
writer: bufio.NewExtendedWriter(conn.NetConn()),
|
||||||
activeCall: activeCall,
|
isHandshakeComplete: isHandshakeComplete,
|
||||||
closeNotifySent: closeNotifySent,
|
activeCall: activeCall,
|
||||||
version: version,
|
closeNotifySent: closeNotifySent,
|
||||||
halfAccess: halfAccess,
|
version: version,
|
||||||
halfError: halfError,
|
halfAccess: halfAccess,
|
||||||
cipher: aeadCipher,
|
halfError: halfError,
|
||||||
explicitNonceLen: explicitNonceLen,
|
cipher: aeadCipher,
|
||||||
rand: randReader,
|
explicitNonceLen: explicitNonceLen,
|
||||||
halfPtr: rawHalfConn.UnsafeAddr(),
|
rand: randReader,
|
||||||
halfSeq: halfSeq,
|
halfPtr: rawHalfConn.UnsafeAddr(),
|
||||||
halfScratchBuf: halfScratchBuf,
|
halfSeq: halfSeq,
|
||||||
|
halfScratchBuf: halfScratchBuf,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,15 +152,15 @@ func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
|||||||
return common.Error(c.Write(buffer.Bytes()))
|
return common.Error(c.Write(buffer.Bytes()))
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
x := atomic.LoadInt32(c.activeCall)
|
x := c.activeCall.Load()
|
||||||
if x&1 != 0 {
|
if x&1 != 0 {
|
||||||
return net.ErrClosed
|
return net.ErrClosed
|
||||||
}
|
}
|
||||||
if atomic.CompareAndSwapInt32(c.activeCall, x, x+2) {
|
if c.activeCall.CompareAndSwap(x, x+2) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer atomic.AddInt32(c.activeCall, -2)
|
defer c.activeCall.Add(-2)
|
||||||
c.halfAccess.Lock()
|
c.halfAccess.Lock()
|
||||||
defer c.halfAccess.Unlock()
|
defer c.halfAccess.Unlock()
|
||||||
if err := *c.halfError; err != nil {
|
if err := *c.halfError; err != nil {
|
||||||
@ -186,6 +208,7 @@ func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
|||||||
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+c.explicitNonceLen+c.cipher.Overhead()))
|
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+c.explicitNonceLen+c.cipher.Overhead()))
|
||||||
}
|
}
|
||||||
incSeq(c.halfPtr)
|
incSeq(c.halfPtr)
|
||||||
|
log.Trace("badtls write ", buffer.Len())
|
||||||
return c.writer.WriteBuffer(buffer)
|
return c.writer.WriteBuffer(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//go:build !go1.19 || go1.20
|
//go:build !go1.19 || go1.21
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
package badtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TLSConn interface {
|
|
||||||
net.Conn
|
|
||||||
HandshakeContext(ctx context.Context) error
|
|
||||||
ConnectionState() tls.ConnectionState
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
//go:build go1.19 && !go.1.20
|
//go:build go1.20 && !go.1.21
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
)
|
)
|
||||||
@ -16,9 +15,6 @@ const (
|
|||||||
//go:linkname errShutdown crypto/tls.errShutdown
|
//go:linkname errShutdown crypto/tls.errShutdown
|
||||||
var errShutdown error
|
var errShutdown error
|
||||||
|
|
||||||
//go:linkname handshakeComplete crypto/tls.(*Conn).handshakeComplete
|
|
||||||
func handshakeComplete(conn *tls.Conn) bool
|
|
||||||
|
|
||||||
//go:linkname incSeq crypto/tls.(*halfConn).incSeq
|
//go:linkname incSeq crypto/tls.(*halfConn).incSeq
|
||||||
func incSeq(conn uintptr)
|
func incSeq(conn uintptr)
|
||||||
|
|
||||||
|
114
common/badversion/version.go
Normal file
114
common/badversion/version.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package badversion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Version struct {
|
||||||
|
Major int
|
||||||
|
Minor int
|
||||||
|
Patch int
|
||||||
|
PreReleaseIdentifier string
|
||||||
|
PreReleaseVersion int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) After(anotherVersion Version) bool {
|
||||||
|
if v.Major > anotherVersion.Major {
|
||||||
|
return true
|
||||||
|
} else if v.Major < anotherVersion.Major {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v.Minor > anotherVersion.Minor {
|
||||||
|
return true
|
||||||
|
} else if v.Minor < anotherVersion.Minor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v.Patch > anotherVersion.Patch {
|
||||||
|
return true
|
||||||
|
} else if v.Patch < anotherVersion.Patch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v.PreReleaseIdentifier == "" && anotherVersion.PreReleaseIdentifier != "" {
|
||||||
|
return true
|
||||||
|
} else if v.PreReleaseIdentifier != "" && anotherVersion.PreReleaseIdentifier == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v.PreReleaseIdentifier != "" && anotherVersion.PreReleaseIdentifier != "" {
|
||||||
|
if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" {
|
||||||
|
return true
|
||||||
|
} else if v.PreReleaseIdentifier == "alpha" && anotherVersion.PreReleaseIdentifier == "beta" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v.PreReleaseVersion > anotherVersion.PreReleaseVersion {
|
||||||
|
return true
|
||||||
|
} else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) String() string {
|
||||||
|
version := F.ToString(v.Major, ".", v.Minor, ".", v.Patch)
|
||||||
|
if v.PreReleaseIdentifier != "" {
|
||||||
|
version = F.ToString(version, "-", v.PreReleaseIdentifier, ".", v.PreReleaseVersion)
|
||||||
|
}
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) BadString() string {
|
||||||
|
version := F.ToString(v.Major, ".", v.Minor)
|
||||||
|
if v.Patch > 0 {
|
||||||
|
version = F.ToString(version, ".", v.Patch)
|
||||||
|
}
|
||||||
|
if v.PreReleaseIdentifier != "" {
|
||||||
|
version = F.ToString(version, "-", v.PreReleaseIdentifier)
|
||||||
|
if v.PreReleaseVersion > 0 {
|
||||||
|
version = F.ToString(version, v.PreReleaseVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(versionName string) (version Version) {
|
||||||
|
if strings.HasPrefix(versionName, "v") {
|
||||||
|
versionName = versionName[1:]
|
||||||
|
}
|
||||||
|
if strings.Contains(versionName, "-") {
|
||||||
|
parts := strings.Split(versionName, "-")
|
||||||
|
versionName = parts[0]
|
||||||
|
identifier := parts[1]
|
||||||
|
if strings.Contains(identifier, ".") {
|
||||||
|
identifierParts := strings.Split(identifier, ".")
|
||||||
|
version.PreReleaseIdentifier = identifierParts[0]
|
||||||
|
if len(identifierParts) >= 2 {
|
||||||
|
version.PreReleaseVersion, _ = strconv.Atoi(identifierParts[1])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if strings.HasPrefix(identifier, "alpha") {
|
||||||
|
version.PreReleaseIdentifier = "alpha"
|
||||||
|
version.PreReleaseVersion, _ = strconv.Atoi(identifier[5:])
|
||||||
|
} else if strings.HasPrefix(identifier, "beta") {
|
||||||
|
version.PreReleaseIdentifier = "beta"
|
||||||
|
version.PreReleaseVersion, _ = strconv.Atoi(identifier[4:])
|
||||||
|
} else {
|
||||||
|
version.PreReleaseIdentifier = identifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
versionElements := strings.Split(versionName, ".")
|
||||||
|
versionLen := len(versionElements)
|
||||||
|
if versionLen >= 1 {
|
||||||
|
version.Major, _ = strconv.Atoi(versionElements[0])
|
||||||
|
}
|
||||||
|
if versionLen >= 2 {
|
||||||
|
version.Minor, _ = strconv.Atoi(versionElements[1])
|
||||||
|
}
|
||||||
|
if versionLen >= 3 {
|
||||||
|
version.Patch, _ = strconv.Atoi(versionElements[2])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
17
common/badversion/version_json.go
Normal file
17
common/badversion/version_json.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package badversion
|
||||||
|
|
||||||
|
import "github.com/sagernet/sing-box/common/json"
|
||||||
|
|
||||||
|
func (v Version) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(v.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Version) UnmarshalJSON(data []byte) error {
|
||||||
|
var version string
|
||||||
|
err := json.Unmarshal(data, &version)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*v = Parse(version)
|
||||||
|
return nil
|
||||||
|
}
|
18
common/badversion/version_test.go
Normal file
18
common/badversion/version_test.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package badversion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCompareVersion(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String())
|
||||||
|
require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString())
|
||||||
|
require.True(t, Parse("1.3.0").After(Parse("1.3-beta1")))
|
||||||
|
require.True(t, Parse("1.3.0").After(Parse("1.3.0-beta1")))
|
||||||
|
require.True(t, Parse("1.3.0-beta1").After(Parse("1.3.0-alpha1")))
|
||||||
|
require.True(t, Parse("1.3.1").After(Parse("1.3.0")))
|
||||||
|
require.True(t, Parse("1.4").After(Parse("1.3")))
|
||||||
|
}
|
@ -4,6 +4,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ func (c *PacketConn) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *PacketConn) Upstream() any {
|
func (c *PacketConn) Upstream() any {
|
||||||
return c.PacketConn
|
return bufio.NewPacketConn(c.PacketConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PacketConn) ReaderReplaceable() bool {
|
func (c *PacketConn) ReaderReplaceable() bool {
|
||||||
|
@ -27,7 +27,12 @@ type slowOpenConn struct {
|
|||||||
|
|
||||||
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
|
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
|
||||||
return dialer.DialContext(ctx, network, destination.String(), nil)
|
switch N.NetworkName(network) {
|
||||||
|
case N.NetworkTCP, N.NetworkUDP:
|
||||||
|
return dialer.Dialer.DialContext(ctx, network, destination.String())
|
||||||
|
default:
|
||||||
|
return dialer.Dialer.DialContext(ctx, network, destination.AddrString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &slowOpenConn{
|
return &slowOpenConn{
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
|
@ -1,535 +1,21 @@
|
|||||||
package mux
|
package mux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing-mux"
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ N.Dialer = (*Client)(nil)
|
func NewClientWithOptions(dialer N.Dialer, options option.MultiplexOptions) (*Client, error) {
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
access sync.Mutex
|
|
||||||
connections list.List[abstractSession]
|
|
||||||
ctx context.Context
|
|
||||||
dialer N.Dialer
|
|
||||||
protocol Protocol
|
|
||||||
maxConnections int
|
|
||||||
minStreams int
|
|
||||||
maxStreams int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClient(ctx context.Context, dialer N.Dialer, protocol Protocol, maxConnections int, minStreams int, maxStreams int) *Client {
|
|
||||||
return &Client{
|
|
||||||
ctx: ctx,
|
|
||||||
dialer: dialer,
|
|
||||||
protocol: protocol,
|
|
||||||
maxConnections: maxConnections,
|
|
||||||
minStreams: minStreams,
|
|
||||||
maxStreams: maxStreams,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClientWithOptions(ctx context.Context, dialer N.Dialer, options option.MultiplexOptions) (N.Dialer, error) {
|
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if options.MaxConnections == 0 && options.MaxStreams == 0 {
|
return mux.NewClient(mux.Options{
|
||||||
options.MinStreams = 8
|
Dialer: dialer,
|
||||||
}
|
Protocol: options.Protocol,
|
||||||
protocol, err := ParseProtocol(options.Protocol)
|
MaxConnections: options.MaxConnections,
|
||||||
if err != nil {
|
MinStreams: options.MinStreams,
|
||||||
return nil, err
|
MaxStreams: options.MaxStreams,
|
||||||
}
|
Padding: options.Padding,
|
||||||
return NewClient(ctx, dialer, protocol, options.MaxConnections, options.MinStreams, options.MaxStreams), nil
|
})
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
|
||||||
switch N.NetworkName(network) {
|
|
||||||
case N.NetworkTCP:
|
|
||||||
stream, err := c.openStream()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &ClientConn{Conn: stream, destination: destination}, nil
|
|
||||||
case N.NetworkUDP:
|
|
||||||
stream, err := c.openStream()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return bufio.NewUnbindPacketConn(&ClientPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}), nil
|
|
||||||
default:
|
|
||||||
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
|
||||||
stream, err := c.openStream()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &ClientPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) openStream() (net.Conn, error) {
|
|
||||||
var (
|
|
||||||
session abstractSession
|
|
||||||
stream net.Conn
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
for attempts := 0; attempts < 2; attempts++ {
|
|
||||||
session, err = c.offer()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
stream, err = session.Open()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &wrapStream{stream}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) offer() (abstractSession, error) {
|
|
||||||
c.access.Lock()
|
|
||||||
defer c.access.Unlock()
|
|
||||||
|
|
||||||
sessions := make([]abstractSession, 0, c.maxConnections)
|
|
||||||
for element := c.connections.Front(); element != nil; {
|
|
||||||
if element.Value.IsClosed() {
|
|
||||||
nextElement := element.Next()
|
|
||||||
c.connections.Remove(element)
|
|
||||||
element = nextElement
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
sessions = append(sessions, element.Value)
|
|
||||||
element = element.Next()
|
|
||||||
}
|
|
||||||
sLen := len(sessions)
|
|
||||||
if sLen == 0 {
|
|
||||||
return c.offerNew()
|
|
||||||
}
|
|
||||||
session := common.MinBy(sessions, abstractSession.NumStreams)
|
|
||||||
numStreams := session.NumStreams()
|
|
||||||
if numStreams == 0 {
|
|
||||||
return session, nil
|
|
||||||
}
|
|
||||||
if c.maxConnections > 0 {
|
|
||||||
if sLen >= c.maxConnections || numStreams < c.minStreams {
|
|
||||||
return session, nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if c.maxStreams > 0 && numStreams < c.maxStreams {
|
|
||||||
return session, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.offerNew()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) offerNew() (abstractSession, error) {
|
|
||||||
conn, err := c.dialer.DialContext(c.ctx, N.NetworkTCP, Destination)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if vectorisedWriter, isVectorised := bufio.CreateVectorisedWriter(conn); isVectorised {
|
|
||||||
conn = &vectorisedProtocolConn{protocolConn{Conn: conn, protocol: c.protocol}, vectorisedWriter}
|
|
||||||
} else {
|
|
||||||
conn = &protocolConn{Conn: conn, protocol: c.protocol}
|
|
||||||
}
|
|
||||||
session, err := c.protocol.newClient(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c.connections.PushBack(session)
|
|
||||||
return session, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Close() error {
|
|
||||||
c.access.Lock()
|
|
||||||
defer c.access.Unlock()
|
|
||||||
for _, session := range c.connections.Array() {
|
|
||||||
session.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClientConn struct {
|
|
||||||
net.Conn
|
|
||||||
destination M.Socksaddr
|
|
||||||
requestWrite bool
|
|
||||||
responseRead bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientConn) readResponse() error {
|
|
||||||
response, err := ReadStreamResponse(c.Conn)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if response.Status == statusError {
|
|
||||||
return E.New("remote error: ", response.Message)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientConn) Read(b []byte) (n int, err error) {
|
|
||||||
if !c.responseRead {
|
|
||||||
err = c.readResponse()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.responseRead = true
|
|
||||||
}
|
|
||||||
return c.Conn.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientConn) Write(b []byte) (n int, err error) {
|
|
||||||
if c.requestWrite {
|
|
||||||
return c.Conn.Write(b)
|
|
||||||
}
|
|
||||||
request := StreamRequest{
|
|
||||||
Network: N.NetworkTCP,
|
|
||||||
Destination: c.destination,
|
|
||||||
}
|
|
||||||
_buffer := buf.StackNewSize(requestLen(request) + len(b))
|
|
||||||
defer common.KeepAlive(_buffer)
|
|
||||||
buffer := common.Dup(_buffer)
|
|
||||||
defer buffer.Release()
|
|
||||||
EncodeStreamRequest(request, buffer)
|
|
||||||
buffer.Write(b)
|
|
||||||
_, err = c.Conn.Write(buffer.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.requestWrite = true
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientConn) ReadFrom(r io.Reader) (n int64, err error) {
|
|
||||||
if !c.requestWrite {
|
|
||||||
return bufio.ReadFrom0(c, r)
|
|
||||||
}
|
|
||||||
return bufio.Copy(c.Conn, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientConn) WriteTo(w io.Writer) (n int64, err error) {
|
|
||||||
if !c.responseRead {
|
|
||||||
return bufio.WriteTo0(c, w)
|
|
||||||
}
|
|
||||||
return bufio.Copy(w, c.Conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientConn) LocalAddr() net.Addr {
|
|
||||||
return c.Conn.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientConn) RemoteAddr() net.Addr {
|
|
||||||
return c.destination.TCPAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientConn) ReaderReplaceable() bool {
|
|
||||||
return c.responseRead
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientConn) WriterReplaceable() bool {
|
|
||||||
return c.requestWrite
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientConn) NeedAdditionalReadDeadline() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientConn) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClientPacketConn struct {
|
|
||||||
N.ExtendedConn
|
|
||||||
destination M.Socksaddr
|
|
||||||
requestWrite bool
|
|
||||||
responseRead bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketConn) readResponse() error {
|
|
||||||
response, err := ReadStreamResponse(c.ExtendedConn)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if response.Status == statusError {
|
|
||||||
return E.New("remote error: ", response.Message)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketConn) Read(b []byte) (n int, err error) {
|
|
||||||
if !c.responseRead {
|
|
||||||
err = c.readResponse()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.responseRead = true
|
|
||||||
}
|
|
||||||
var length uint16
|
|
||||||
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if cap(b) < int(length) {
|
|
||||||
return 0, io.ErrShortBuffer
|
|
||||||
}
|
|
||||||
return io.ReadFull(c.ExtendedConn, b[:length])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketConn) writeRequest(payload []byte) (n int, err error) {
|
|
||||||
request := StreamRequest{
|
|
||||||
Network: N.NetworkUDP,
|
|
||||||
Destination: c.destination,
|
|
||||||
}
|
|
||||||
rLen := requestLen(request)
|
|
||||||
if len(payload) > 0 {
|
|
||||||
rLen += 2 + len(payload)
|
|
||||||
}
|
|
||||||
_buffer := buf.StackNewSize(rLen)
|
|
||||||
defer common.KeepAlive(_buffer)
|
|
||||||
buffer := common.Dup(_buffer)
|
|
||||||
defer buffer.Release()
|
|
||||||
EncodeStreamRequest(request, buffer)
|
|
||||||
if len(payload) > 0 {
|
|
||||||
common.Must(
|
|
||||||
binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
|
|
||||||
common.Error(buffer.Write(payload)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_, err = c.ExtendedConn.Write(buffer.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.requestWrite = true
|
|
||||||
return len(payload), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketConn) Write(b []byte) (n int, err error) {
|
|
||||||
if !c.requestWrite {
|
|
||||||
return c.writeRequest(b)
|
|
||||||
}
|
|
||||||
err = binary.Write(c.ExtendedConn, binary.BigEndian, uint16(len(b)))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.ExtendedConn.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketConn) ReadBuffer(buffer *buf.Buffer) (err error) {
|
|
||||||
if !c.responseRead {
|
|
||||||
err = c.readResponse()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.responseRead = true
|
|
||||||
}
|
|
||||||
var length uint16
|
|
||||||
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketConn) WriteBuffer(buffer *buf.Buffer) error {
|
|
||||||
if !c.requestWrite {
|
|
||||||
defer buffer.Release()
|
|
||||||
return common.Error(c.writeRequest(buffer.Bytes()))
|
|
||||||
}
|
|
||||||
bLen := buffer.Len()
|
|
||||||
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(bLen))
|
|
||||||
return c.ExtendedConn.WriteBuffer(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketConn) FrontHeadroom() int {
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
|
||||||
err = c.ReadBuffer(buffer)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
|
||||||
return c.WriteBuffer(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketConn) LocalAddr() net.Addr {
|
|
||||||
return c.ExtendedConn.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketConn) RemoteAddr() net.Addr {
|
|
||||||
return c.destination.UDPAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketConn) NeedAdditionalReadDeadline() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketConn) Upstream() any {
|
|
||||||
return c.ExtendedConn
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ N.NetPacketConn = (*ClientPacketAddrConn)(nil)
|
|
||||||
|
|
||||||
type ClientPacketAddrConn struct {
|
|
||||||
N.ExtendedConn
|
|
||||||
destination M.Socksaddr
|
|
||||||
requestWrite bool
|
|
||||||
responseRead bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketAddrConn) readResponse() error {
|
|
||||||
response, err := ReadStreamResponse(c.ExtendedConn)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if response.Status == statusError {
|
|
||||||
return E.New("remote error: ", response.Message)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketAddrConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
|
||||||
if !c.responseRead {
|
|
||||||
err = c.readResponse()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.responseRead = true
|
|
||||||
}
|
|
||||||
destination, err := M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if destination.IsFqdn() {
|
|
||||||
addr = destination
|
|
||||||
} else {
|
|
||||||
addr = destination.UDPAddr()
|
|
||||||
}
|
|
||||||
var length uint16
|
|
||||||
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if cap(p) < int(length) {
|
|
||||||
return 0, nil, io.ErrShortBuffer
|
|
||||||
}
|
|
||||||
n, err = io.ReadFull(c.ExtendedConn, p[:length])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketAddrConn) writeRequest(payload []byte, destination M.Socksaddr) (n int, err error) {
|
|
||||||
request := StreamRequest{
|
|
||||||
Network: N.NetworkUDP,
|
|
||||||
Destination: c.destination,
|
|
||||||
PacketAddr: true,
|
|
||||||
}
|
|
||||||
rLen := requestLen(request)
|
|
||||||
if len(payload) > 0 {
|
|
||||||
rLen += M.SocksaddrSerializer.AddrPortLen(destination) + 2 + len(payload)
|
|
||||||
}
|
|
||||||
_buffer := buf.StackNewSize(rLen)
|
|
||||||
defer common.KeepAlive(_buffer)
|
|
||||||
buffer := common.Dup(_buffer)
|
|
||||||
defer buffer.Release()
|
|
||||||
EncodeStreamRequest(request, buffer)
|
|
||||||
if len(payload) > 0 {
|
|
||||||
common.Must(
|
|
||||||
M.SocksaddrSerializer.WriteAddrPort(buffer, destination),
|
|
||||||
binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
|
|
||||||
common.Error(buffer.Write(payload)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
_, err = c.ExtendedConn.Write(buffer.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.requestWrite = true
|
|
||||||
return len(payload), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketAddrConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
|
||||||
if !c.requestWrite {
|
|
||||||
return c.writeRequest(p, M.SocksaddrFromNet(addr))
|
|
||||||
}
|
|
||||||
err = M.SocksaddrSerializer.WriteAddrPort(c.ExtendedConn, M.SocksaddrFromNet(addr))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = binary.Write(c.ExtendedConn, binary.BigEndian, uint16(len(p)))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.ExtendedConn.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
|
||||||
if !c.responseRead {
|
|
||||||
err = c.readResponse()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.responseRead = true
|
|
||||||
}
|
|
||||||
destination, err = M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var length uint16
|
|
||||||
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
|
||||||
if !c.requestWrite {
|
|
||||||
defer buffer.Release()
|
|
||||||
return common.Error(c.writeRequest(buffer.Bytes(), destination))
|
|
||||||
}
|
|
||||||
bLen := buffer.Len()
|
|
||||||
header := buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination) + 2))
|
|
||||||
common.Must(
|
|
||||||
M.SocksaddrSerializer.WriteAddrPort(header, destination),
|
|
||||||
binary.Write(header, binary.BigEndian, uint16(bLen)),
|
|
||||||
)
|
|
||||||
return c.ExtendedConn.WriteBuffer(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketAddrConn) LocalAddr() net.Addr {
|
|
||||||
return c.ExtendedConn.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketAddrConn) FrontHeadroom() int {
|
|
||||||
return 2 + M.MaxSocksaddrLength
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketAddrConn) NeedAdditionalReadDeadline() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketAddrConn) Upstream() any {
|
|
||||||
return c.ExtendedConn
|
|
||||||
}
|
}
|
||||||
|
@ -1,240 +1,14 @@
|
|||||||
package mux
|
package mux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"github.com/sagernet/sing-mux"
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
|
||||||
"github.com/sagernet/smux"
|
|
||||||
|
|
||||||
"github.com/hashicorp/yamux"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var Destination = M.Socksaddr{
|
type (
|
||||||
Fqdn: "sp.mux.sing-box.arpa",
|
Client = mux.Client
|
||||||
Port: 444,
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
ProtocolSMux Protocol = iota
|
|
||||||
ProtocolYAMux
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Protocol byte
|
var (
|
||||||
|
Destination = mux.Destination
|
||||||
func ParseProtocol(name string) (Protocol, error) {
|
HandleConnection = mux.HandleConnection
|
||||||
switch name {
|
|
||||||
case "", "smux":
|
|
||||||
return ProtocolSMux, nil
|
|
||||||
case "yamux":
|
|
||||||
return ProtocolYAMux, nil
|
|
||||||
default:
|
|
||||||
return ProtocolYAMux, E.New("unknown multiplex protocol: ", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Protocol) newServer(conn net.Conn) (abstractSession, error) {
|
|
||||||
switch p {
|
|
||||||
case ProtocolSMux:
|
|
||||||
session, err := smux.Server(conn, smuxConfig())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &smuxSession{session}, nil
|
|
||||||
case ProtocolYAMux:
|
|
||||||
return yamux.Server(conn, yaMuxConfig())
|
|
||||||
default:
|
|
||||||
panic("unknown protocol")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Protocol) newClient(conn net.Conn) (abstractSession, error) {
|
|
||||||
switch p {
|
|
||||||
case ProtocolSMux:
|
|
||||||
session, err := smux.Client(conn, smuxConfig())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &smuxSession{session}, nil
|
|
||||||
case ProtocolYAMux:
|
|
||||||
return yamux.Client(conn, yaMuxConfig())
|
|
||||||
default:
|
|
||||||
panic("unknown protocol")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func smuxConfig() *smux.Config {
|
|
||||||
config := smux.DefaultConfig()
|
|
||||||
config.KeepAliveDisabled = true
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func yaMuxConfig() *yamux.Config {
|
|
||||||
config := yamux.DefaultConfig()
|
|
||||||
config.LogOutput = io.Discard
|
|
||||||
config.StreamCloseTimeout = C.TCPTimeout
|
|
||||||
config.StreamOpenTimeout = C.TCPTimeout
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Protocol) String() string {
|
|
||||||
switch p {
|
|
||||||
case ProtocolSMux:
|
|
||||||
return "smux"
|
|
||||||
case ProtocolYAMux:
|
|
||||||
return "yamux"
|
|
||||||
default:
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
version0 = 0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Request struct {
|
|
||||||
Protocol Protocol
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadRequest(reader io.Reader) (*Request, error) {
|
|
||||||
version, err := rw.ReadByte(reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if version != version0 {
|
|
||||||
return nil, E.New("unsupported version: ", version)
|
|
||||||
}
|
|
||||||
protocol, err := rw.ReadByte(reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if protocol > byte(ProtocolYAMux) {
|
|
||||||
return nil, E.New("unsupported protocol: ", protocol)
|
|
||||||
}
|
|
||||||
return &Request{Protocol: Protocol(protocol)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func EncodeRequest(buffer *buf.Buffer, request Request) {
|
|
||||||
buffer.WriteByte(version0)
|
|
||||||
buffer.WriteByte(byte(request.Protocol))
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
flagUDP = 1
|
|
||||||
flagAddr = 2
|
|
||||||
statusSuccess = 0
|
|
||||||
statusError = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
type StreamRequest struct {
|
|
||||||
Network string
|
|
||||||
Destination M.Socksaddr
|
|
||||||
PacketAddr bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadStreamRequest(reader io.Reader) (*StreamRequest, error) {
|
|
||||||
var flags uint16
|
|
||||||
err := binary.Read(reader, binary.BigEndian, &flags)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
destination, err := M.SocksaddrSerializer.ReadAddrPort(reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var network string
|
|
||||||
var udpAddr bool
|
|
||||||
if flags&flagUDP == 0 {
|
|
||||||
network = N.NetworkTCP
|
|
||||||
} else {
|
|
||||||
network = N.NetworkUDP
|
|
||||||
udpAddr = flags&flagAddr != 0
|
|
||||||
}
|
|
||||||
return &StreamRequest{network, destination, udpAddr}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestLen(request StreamRequest) int {
|
|
||||||
var rLen int
|
|
||||||
rLen += 1 // version
|
|
||||||
rLen += 2 // flags
|
|
||||||
rLen += M.SocksaddrSerializer.AddrPortLen(request.Destination)
|
|
||||||
return rLen
|
|
||||||
}
|
|
||||||
|
|
||||||
func EncodeStreamRequest(request StreamRequest, buffer *buf.Buffer) {
|
|
||||||
destination := request.Destination
|
|
||||||
var flags uint16
|
|
||||||
if request.Network == N.NetworkUDP {
|
|
||||||
flags |= flagUDP
|
|
||||||
}
|
|
||||||
if request.PacketAddr {
|
|
||||||
flags |= flagAddr
|
|
||||||
if !destination.IsValid() {
|
|
||||||
destination = Destination
|
|
||||||
}
|
|
||||||
}
|
|
||||||
common.Must(
|
|
||||||
binary.Write(buffer, binary.BigEndian, flags),
|
|
||||||
M.SocksaddrSerializer.WriteAddrPort(buffer, destination),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
type StreamResponse struct {
|
|
||||||
Status uint8
|
|
||||||
Message string
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadStreamResponse(reader io.Reader) (*StreamResponse, error) {
|
|
||||||
var response StreamResponse
|
|
||||||
status, err := rw.ReadByte(reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
response.Status = status
|
|
||||||
if status == statusError {
|
|
||||||
response.Message, err = rw.ReadVString(reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type wrapStream struct {
|
|
||||||
net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *wrapStream) Read(p []byte) (n int, err error) {
|
|
||||||
n, err = w.Conn.Read(p)
|
|
||||||
err = wrapError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *wrapStream) Write(p []byte) (n int, err error) {
|
|
||||||
n, err = w.Conn.Write(p)
|
|
||||||
err = wrapError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *wrapStream) WriteIsThreadUnsafe() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *wrapStream) Upstream() any {
|
|
||||||
return w.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapError(err error) error {
|
|
||||||
switch err {
|
|
||||||
case yamux.ErrStreamClosed:
|
|
||||||
return io.EOF
|
|
||||||
default:
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,269 +0,0 @@
|
|||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
|
||||||
"github.com/sagernet/sing/common/task"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, conn net.Conn, metadata adapter.InboundContext) error {
|
|
||||||
request, err := ReadRequest(conn)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
session, err := request.Protocol.newServer(conn)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var group task.Group
|
|
||||||
group.Append0(func(ctx context.Context) error {
|
|
||||||
var stream net.Conn
|
|
||||||
for {
|
|
||||||
stream, err = session.Accept()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
go newConnection(ctx, router, errorHandler, logger, stream, metadata)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
group.Cleanup(func() {
|
|
||||||
session.Close()
|
|
||||||
})
|
|
||||||
return group.Run(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, stream net.Conn, metadata adapter.InboundContext) {
|
|
||||||
stream = &wrapStream{stream}
|
|
||||||
request, err := ReadStreamRequest(stream)
|
|
||||||
if err != nil {
|
|
||||||
logger.ErrorContext(ctx, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
metadata.Destination = request.Destination
|
|
||||||
if request.Network == N.NetworkTCP {
|
|
||||||
logger.InfoContext(ctx, "inbound multiplex connection to ", metadata.Destination)
|
|
||||||
hErr := router.RouteConnection(ctx, &ServerConn{ExtendedConn: bufio.NewExtendedConn(stream)}, metadata)
|
|
||||||
stream.Close()
|
|
||||||
if hErr != nil {
|
|
||||||
errorHandler.NewError(ctx, hErr)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var packetConn N.PacketConn
|
|
||||||
if !request.PacketAddr {
|
|
||||||
logger.InfoContext(ctx, "inbound multiplex packet connection to ", metadata.Destination)
|
|
||||||
packetConn = &ServerPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: request.Destination}
|
|
||||||
} else {
|
|
||||||
logger.InfoContext(ctx, "inbound multiplex packet connection")
|
|
||||||
packetConn = &ServerPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream)}
|
|
||||||
}
|
|
||||||
hErr := router.RoutePacketConnection(ctx, packetConn, metadata)
|
|
||||||
stream.Close()
|
|
||||||
if hErr != nil {
|
|
||||||
errorHandler.NewError(ctx, hErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ N.HandshakeConn = (*ServerConn)(nil)
|
|
||||||
|
|
||||||
type ServerConn struct {
|
|
||||||
N.ExtendedConn
|
|
||||||
responseWrite bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerConn) HandshakeFailure(err error) error {
|
|
||||||
errMessage := err.Error()
|
|
||||||
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
|
|
||||||
defer common.KeepAlive(_buffer)
|
|
||||||
buffer := common.Dup(_buffer)
|
|
||||||
defer buffer.Release()
|
|
||||||
common.Must(
|
|
||||||
buffer.WriteByte(statusError),
|
|
||||||
rw.WriteVString(_buffer, errMessage),
|
|
||||||
)
|
|
||||||
return c.ExtendedConn.WriteBuffer(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerConn) Write(b []byte) (n int, err error) {
|
|
||||||
if c.responseWrite {
|
|
||||||
return c.ExtendedConn.Write(b)
|
|
||||||
}
|
|
||||||
_buffer := buf.StackNewSize(1 + len(b))
|
|
||||||
defer common.KeepAlive(_buffer)
|
|
||||||
buffer := common.Dup(_buffer)
|
|
||||||
defer buffer.Release()
|
|
||||||
common.Must(
|
|
||||||
buffer.WriteByte(statusSuccess),
|
|
||||||
common.Error(buffer.Write(b)),
|
|
||||||
)
|
|
||||||
_, err = c.ExtendedConn.Write(buffer.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.responseWrite = true
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerConn) WriteBuffer(buffer *buf.Buffer) error {
|
|
||||||
if c.responseWrite {
|
|
||||||
return c.ExtendedConn.WriteBuffer(buffer)
|
|
||||||
}
|
|
||||||
buffer.ExtendHeader(1)[0] = statusSuccess
|
|
||||||
c.responseWrite = true
|
|
||||||
return c.ExtendedConn.WriteBuffer(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerConn) FrontHeadroom() int {
|
|
||||||
if !c.responseWrite {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerConn) NeedAdditionalReadDeadline() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerConn) Upstream() any {
|
|
||||||
return c.ExtendedConn
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ N.HandshakeConn = (*ServerPacketConn)(nil)
|
|
||||||
_ N.PacketConn = (*ServerPacketConn)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type ServerPacketConn struct {
|
|
||||||
N.ExtendedConn
|
|
||||||
destination M.Socksaddr
|
|
||||||
responseWrite bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerPacketConn) HandshakeFailure(err error) error {
|
|
||||||
errMessage := err.Error()
|
|
||||||
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
|
|
||||||
defer common.KeepAlive(_buffer)
|
|
||||||
buffer := common.Dup(_buffer)
|
|
||||||
defer buffer.Release()
|
|
||||||
common.Must(
|
|
||||||
buffer.WriteByte(statusError),
|
|
||||||
rw.WriteVString(_buffer, errMessage),
|
|
||||||
)
|
|
||||||
return c.ExtendedConn.WriteBuffer(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
|
||||||
var length uint16
|
|
||||||
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
destination = c.destination
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
|
||||||
pLen := buffer.Len()
|
|
||||||
common.Must(binary.Write(buf.With(buffer.ExtendHeader(2)), binary.BigEndian, uint16(pLen)))
|
|
||||||
if !c.responseWrite {
|
|
||||||
buffer.ExtendHeader(1)[0] = statusSuccess
|
|
||||||
c.responseWrite = true
|
|
||||||
}
|
|
||||||
return c.ExtendedConn.WriteBuffer(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerPacketConn) NeedAdditionalReadDeadline() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerPacketConn) Upstream() any {
|
|
||||||
return c.ExtendedConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerPacketConn) FrontHeadroom() int {
|
|
||||||
if !c.responseWrite {
|
|
||||||
return 3
|
|
||||||
}
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ N.HandshakeConn = (*ServerPacketAddrConn)(nil)
|
|
||||||
_ N.PacketConn = (*ServerPacketAddrConn)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type ServerPacketAddrConn struct {
|
|
||||||
N.ExtendedConn
|
|
||||||
responseWrite bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerPacketAddrConn) HandshakeFailure(err error) error {
|
|
||||||
errMessage := err.Error()
|
|
||||||
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
|
|
||||||
defer common.KeepAlive(_buffer)
|
|
||||||
buffer := common.Dup(_buffer)
|
|
||||||
defer buffer.Release()
|
|
||||||
common.Must(
|
|
||||||
buffer.WriteByte(statusError),
|
|
||||||
rw.WriteVString(_buffer, errMessage),
|
|
||||||
)
|
|
||||||
return c.ExtendedConn.WriteBuffer(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
|
||||||
destination, err = M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var length uint16
|
|
||||||
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
|
||||||
pLen := buffer.Len()
|
|
||||||
common.Must(binary.Write(buf.With(buffer.ExtendHeader(2)), binary.BigEndian, uint16(pLen)))
|
|
||||||
common.Must(M.SocksaddrSerializer.WriteAddrPort(buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination))), destination))
|
|
||||||
if !c.responseWrite {
|
|
||||||
buffer.ExtendHeader(1)[0] = statusSuccess
|
|
||||||
c.responseWrite = true
|
|
||||||
}
|
|
||||||
return c.ExtendedConn.WriteBuffer(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerPacketAddrConn) NeedAdditionalReadDeadline() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerPacketAddrConn) Upstream() any {
|
|
||||||
return c.ExtendedConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerPacketAddrConn) FrontHeadroom() int {
|
|
||||||
if !c.responseWrite {
|
|
||||||
return 3 + M.MaxSocksaddrLength
|
|
||||||
}
|
|
||||||
return 2 + M.MaxSocksaddrLength
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/smux"
|
|
||||||
)
|
|
||||||
|
|
||||||
type abstractSession interface {
|
|
||||||
Open() (net.Conn, error)
|
|
||||||
Accept() (net.Conn, error)
|
|
||||||
NumStreams() int
|
|
||||||
Close() error
|
|
||||||
IsClosed() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ abstractSession = (*smuxSession)(nil)
|
|
||||||
|
|
||||||
type smuxSession struct {
|
|
||||||
*smux.Session
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *smuxSession) Open() (net.Conn, error) {
|
|
||||||
return s.OpenStream()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *smuxSession) Accept() (net.Conn, error) {
|
|
||||||
return s.AcceptStream()
|
|
||||||
}
|
|
||||||
|
|
||||||
type protocolConn struct {
|
|
||||||
net.Conn
|
|
||||||
protocol Protocol
|
|
||||||
protocolWritten bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *protocolConn) Write(p []byte) (n int, err error) {
|
|
||||||
if c.protocolWritten {
|
|
||||||
return c.Conn.Write(p)
|
|
||||||
}
|
|
||||||
_buffer := buf.StackNewSize(2 + len(p))
|
|
||||||
defer common.KeepAlive(_buffer)
|
|
||||||
buffer := common.Dup(_buffer)
|
|
||||||
defer buffer.Release()
|
|
||||||
EncodeRequest(buffer, Request{
|
|
||||||
Protocol: c.protocol,
|
|
||||||
})
|
|
||||||
common.Must(common.Error(buffer.Write(p)))
|
|
||||||
n, err = c.Conn.Write(buffer.Bytes())
|
|
||||||
if err == nil {
|
|
||||||
n--
|
|
||||||
}
|
|
||||||
c.protocolWritten = true
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *protocolConn) ReadFrom(r io.Reader) (n int64, err error) {
|
|
||||||
if !c.protocolWritten {
|
|
||||||
return bufio.ReadFrom0(c, r)
|
|
||||||
}
|
|
||||||
return bufio.Copy(c.Conn, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *protocolConn) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
type vectorisedProtocolConn struct {
|
|
||||||
protocolConn
|
|
||||||
N.VectorisedWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *vectorisedProtocolConn) WriteVectorised(buffers []*buf.Buffer) error {
|
|
||||||
if c.protocolWritten {
|
|
||||||
return c.VectorisedWriter.WriteVectorised(buffers)
|
|
||||||
}
|
|
||||||
c.protocolWritten = true
|
|
||||||
_buffer := buf.StackNewSize(2)
|
|
||||||
defer common.KeepAlive(_buffer)
|
|
||||||
buffer := common.Dup(_buffer)
|
|
||||||
defer buffer.Release()
|
|
||||||
EncodeRequest(buffer, Request{
|
|
||||||
Protocol: c.protocol,
|
|
||||||
})
|
|
||||||
return c.VectorisedWriter.WriteVectorised(append([]*buf.Buffer{buffer}, buffers...))
|
|
||||||
}
|
|
@ -125,7 +125,7 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
|||||||
|
|
||||||
hello.SessionId[0] = 1
|
hello.SessionId[0] = 1
|
||||||
hello.SessionId[1] = 8
|
hello.SessionId[1] = 8
|
||||||
hello.SessionId[2] = 0
|
hello.SessionId[2] = 1
|
||||||
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))
|
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))
|
||||||
copy(hello.SessionId[8:], e.shortID[:])
|
copy(hello.SessionId[8:], e.shortID[:])
|
||||||
|
|
||||||
|
@ -50,6 +50,9 @@ func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
|
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
|
||||||
|
if link == "" {
|
||||||
|
link = "https://www.gstatic.com/generate_204"
|
||||||
|
}
|
||||||
linkURL, err := url.Parse(link)
|
linkURL, err := url.Parse(link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -3,28 +3,13 @@ package constant
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dirName = "sing-box"
|
const dirName = "sing-box"
|
||||||
|
|
||||||
var (
|
var resourcePaths []string
|
||||||
basePath string
|
|
||||||
resourcePaths []string
|
|
||||||
)
|
|
||||||
|
|
||||||
func BasePath(name string) string {
|
|
||||||
if basePath == "" || strings.HasPrefix(name, "/") {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
return filepath.Join(basePath, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetBasePath(path string) {
|
|
||||||
basePath = path
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindPath(name string) (string, bool) {
|
func FindPath(name string) (string, bool) {
|
||||||
name = os.ExpandEnv(name)
|
name = os.ExpandEnv(name)
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func applyDebugOptions(options option.DebugOptions) {
|
func applyDebugOptions(options option.DebugOptions) {
|
||||||
|
applyDebugListenOption(options)
|
||||||
if options.GCPercent != nil {
|
if options.GCPercent != nil {
|
||||||
debug.SetGCPercent(*options.GCPercent)
|
debug.SetGCPercent(*options.GCPercent)
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func applyDebugOptions(options option.DebugOptions) {
|
func applyDebugOptions(options option.DebugOptions) {
|
||||||
|
applyDebugListenOption(options)
|
||||||
if options.GCPercent != nil {
|
if options.GCPercent != nil {
|
||||||
debug.SetGCPercent(*options.GCPercent)
|
debug.SetGCPercent(*options.GCPercent)
|
||||||
}
|
}
|
67
debug_http.go
Normal file
67
debug_http.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package box
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/pprof"
|
||||||
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/badjson"
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var debugHTTPServer *http.Server
|
||||||
|
|
||||||
|
func applyDebugListenOption(options option.DebugOptions) {
|
||||||
|
if debugHTTPServer != nil {
|
||||||
|
debugHTTPServer.Close()
|
||||||
|
debugHTTPServer = nil
|
||||||
|
}
|
||||||
|
if options.Listen == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r := chi.NewMux()
|
||||||
|
r.Route("/debug", func(r chi.Router) {
|
||||||
|
r.Get("/gc", func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
writer.WriteHeader(http.StatusNoContent)
|
||||||
|
go debug.FreeOSMemory()
|
||||||
|
})
|
||||||
|
r.Get("/memory", func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
var memStats runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&memStats)
|
||||||
|
|
||||||
|
var memObject badjson.JSONObject
|
||||||
|
memObject.Put("heap", humanize.IBytes(memStats.HeapInuse))
|
||||||
|
memObject.Put("stack", humanize.IBytes(memStats.StackInuse))
|
||||||
|
memObject.Put("idle", humanize.IBytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||||
|
memObject.Put("goroutines", runtime.NumGoroutine())
|
||||||
|
memObject.Put("rss", rusageMaxRSS())
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(writer)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
encoder.Encode(memObject)
|
||||||
|
})
|
||||||
|
r.HandleFunc("/pprof", pprof.Index)
|
||||||
|
r.HandleFunc("/pprof/*", pprof.Index)
|
||||||
|
r.HandleFunc("/pprof/cmdline", pprof.Cmdline)
|
||||||
|
r.HandleFunc("/pprof/profile", pprof.Profile)
|
||||||
|
r.HandleFunc("/pprof/symbol", pprof.Symbol)
|
||||||
|
r.HandleFunc("/pprof/trace", pprof.Trace)
|
||||||
|
})
|
||||||
|
debugHTTPServer = &http.Server{
|
||||||
|
Addr: options.Listen,
|
||||||
|
Handler: r,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
err := debugHTTPServer.ListenAndServe()
|
||||||
|
if err != nil && !E.IsClosed(err) {
|
||||||
|
log.Error(E.Cause(err, "serve debug HTTP server"))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
@ -1,6 +1,4 @@
|
|||||||
//go:build debug
|
package box
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
"runtime"
|
@ -1,6 +1,6 @@
|
|||||||
//go:build debug && !linux
|
//go:build !linux
|
||||||
|
|
||||||
package main
|
package box
|
||||||
|
|
||||||
func rusageMaxRSS() float64 {
|
func rusageMaxRSS() float64 {
|
||||||
return -1
|
return -1
|
@ -1,7 +1,97 @@
|
|||||||
|
#### 1.3-beta10
|
||||||
|
|
||||||
|
* Improve direct copy **1**
|
||||||
|
* Improve DNS caching
|
||||||
|
* Add `independent_cache` [option](/configuration/dns#independent_cache) for DNS
|
||||||
|
* Reimplemented shadowsocks client **2**
|
||||||
|
* Add multiplex support for VLESS outbound
|
||||||
|
* Set TCP keepalive for WireGuard gVisor TCP connections
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
**1**:
|
||||||
|
|
||||||
|
* Make splice work with traffic statistics systems like Clash API
|
||||||
|
* Significantly reduces memory usage of idle connections
|
||||||
|
|
||||||
|
**2**:
|
||||||
|
|
||||||
|
Improved performance and reduced memory usage.
|
||||||
|
|
||||||
|
#### 1.3-beta9
|
||||||
|
|
||||||
|
* Improve multiplex **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
*1*:
|
||||||
|
|
||||||
|
Added new `h2mux` multiplex protocol and `padding` multiplex option, see [Multiplex](/configuration/shared/multiplex).
|
||||||
|
|
||||||
#### 1.2.6
|
#### 1.2.6
|
||||||
|
|
||||||
* Fix bugs and update dependencies
|
* Fix bugs and update dependencies
|
||||||
|
|
||||||
|
#### 1.3-beta8
|
||||||
|
|
||||||
|
* Fix `system` tun stack for ios
|
||||||
|
* Fix network monitor for android/ios
|
||||||
|
* Update VLESS and XUDP protocol **1**
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
*1:
|
||||||
|
|
||||||
|
This is an incompatible update for XUDP in VLESS if vision flow is enabled.
|
||||||
|
|
||||||
|
#### 1.3-beta7
|
||||||
|
|
||||||
|
* Add `path` and `headers` options for HTTP outbound
|
||||||
|
* Add multi-user support for Shadowsocks legacy AEAD inbound
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.2.4
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.3-beta6
|
||||||
|
|
||||||
|
* Fix WireGuard reconnect
|
||||||
|
* Perform URLTest recheck after network changes
|
||||||
|
* Fix bugs and update dependencies
|
||||||
|
|
||||||
|
#### 1.3-beta5
|
||||||
|
|
||||||
|
* Add Clash.Meta API compatibility for Clash API
|
||||||
|
* Download Yacd-meta by default if the specified Clash `external_ui` directory is empty
|
||||||
|
* Add path and headers option for HTTP outbound
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
#### 1.3-beta4
|
||||||
|
|
||||||
|
* Fix bugs
|
||||||
|
|
||||||
|
#### 1.3-beta2
|
||||||
|
|
||||||
|
* Download clash-dashboard if the specified Clash `external_ui` directory is empty
|
||||||
|
* Fix bugs and update dependencies
|
||||||
|
|
||||||
|
#### 1.3-beta1
|
||||||
|
|
||||||
|
* Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support
|
||||||
|
* Add [L3 routing](/configuration/route/ip-rule) support **1**
|
||||||
|
* Add `rewrite_ttl` DNS rule action
|
||||||
|
* Add [FakeIP](/configuration/dns/fakeip) support **2**
|
||||||
|
* Add `store_fakeip` Clash API option
|
||||||
|
* Add multi-peer support for [WireGuard](/configuration/outbound/wireguard#peers) outbound
|
||||||
|
* Add loopback detect
|
||||||
|
|
||||||
|
*1*:
|
||||||
|
|
||||||
|
It can currently be used to [route connections directly to WireGuard](/examples/wireguard-direct) or block connections
|
||||||
|
at the IP layer.
|
||||||
|
|
||||||
|
*2*:
|
||||||
|
|
||||||
|
See [FAQ](/faq/fakeip) for more information.
|
||||||
|
|
||||||
#### 1.2.3
|
#### 1.2.3
|
||||||
|
|
||||||
* Introducing our [new Android client application](/installation/clients/sfa)
|
* Introducing our [new Android client application](/installation/clients/sfa)
|
||||||
|
25
docs/configuration/dns/fakeip.md
Normal file
25
docs/configuration/dns/fakeip.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# FakeIP
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"inet4_range": "198.18.0.0/15",
|
||||||
|
"inet6_range": "fc00::/18"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### enabled
|
||||||
|
|
||||||
|
Enable FakeIP service.
|
||||||
|
|
||||||
|
#### inet4_range
|
||||||
|
|
||||||
|
IPv4 address range for FakeIP.
|
||||||
|
|
||||||
|
#### inet6_address
|
||||||
|
|
||||||
|
IPv6 address range for FakeIP.
|
25
docs/configuration/dns/fakeip.zh.md
Normal file
25
docs/configuration/dns/fakeip.zh.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# FakeIP
|
||||||
|
|
||||||
|
### 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"inet4_range": "198.18.0.0/15",
|
||||||
|
"inet6_range": "fc00::/18"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
|
#### enabled
|
||||||
|
|
||||||
|
启用 FakeIP 服务。
|
||||||
|
|
||||||
|
#### inet4_range
|
||||||
|
|
||||||
|
用于 FakeIP 的 IPv4 地址范围。
|
||||||
|
|
||||||
|
#### inet6_range
|
||||||
|
|
||||||
|
用于 FakeIP 的 IPv6 地址范围。
|
@ -10,7 +10,10 @@
|
|||||||
"final": "",
|
"final": "",
|
||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"disable_expire": false
|
"disable_expire": false,
|
||||||
|
"independent_cache": false,
|
||||||
|
"reverse_mapping": false,
|
||||||
|
"fakeip": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,6 +25,7 @@
|
|||||||
|----------|--------------------------------|
|
|----------|--------------------------------|
|
||||||
| `server` | List of [DNS Server](./server) |
|
| `server` | List of [DNS Server](./server) |
|
||||||
| `rules` | List of [DNS Rule](./rule) |
|
| `rules` | List of [DNS Rule](./rule) |
|
||||||
|
| `fakeip` | [FakeIP](./fakeip) |
|
||||||
|
|
||||||
#### final
|
#### final
|
||||||
|
|
||||||
@ -43,4 +47,19 @@ Disable dns cache.
|
|||||||
|
|
||||||
#### disable_expire
|
#### disable_expire
|
||||||
|
|
||||||
Disable dns cache expire.
|
Disable dns cache expire.
|
||||||
|
|
||||||
|
#### independent_cache
|
||||||
|
|
||||||
|
Make each DNS server's cache independent for special purposes. If enabled, will slightly degrade performance.
|
||||||
|
|
||||||
|
#### reverse_mapping
|
||||||
|
|
||||||
|
Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.
|
||||||
|
|
||||||
|
Since this process relies on the act of resolving domain names by an application before making a request, it can be
|
||||||
|
problematic in environments such as macOS, where DNS is proxied and cached by the system.
|
||||||
|
|
||||||
|
#### fakeip
|
||||||
|
|
||||||
|
[FakeIP](./fakeip) settings.
|
||||||
|
@ -10,7 +10,10 @@
|
|||||||
"final": "",
|
"final": "",
|
||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"disable_expire": false
|
"disable_expire": false,
|
||||||
|
"independent_cache": false,
|
||||||
|
"reverse_mapping": false,
|
||||||
|
"fakeip": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,4 +46,18 @@
|
|||||||
|
|
||||||
#### disable_expire
|
#### disable_expire
|
||||||
|
|
||||||
禁用 DNS 缓存过期。
|
禁用 DNS 缓存过期。
|
||||||
|
|
||||||
|
#### independent_cache
|
||||||
|
|
||||||
|
使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。
|
||||||
|
|
||||||
|
#### reverse_mapping
|
||||||
|
|
||||||
|
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
|
||||||
|
|
||||||
|
由于此过程依赖于应用程序在发出请求之前解析域名的行为,因此在 macOS 等 DNS 由系统代理和缓存的环境中可能会出现问题。
|
||||||
|
|
||||||
|
#### fakeip
|
||||||
|
|
||||||
|
[FakeIP](./fakeip) 设置。
|
||||||
|
@ -84,14 +84,16 @@
|
|||||||
"direct"
|
"direct"
|
||||||
],
|
],
|
||||||
"server": "local",
|
"server": "local",
|
||||||
"disable_cache": false
|
"disable_cache": false,
|
||||||
|
"rewrite_ttl": 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"type": "logical",
|
||||||
"mode": "and",
|
"mode": "and",
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"server": "local",
|
"server": "local",
|
||||||
"disable_cache": false
|
"disable_cache": false,
|
||||||
|
"rewrite_ttl": 100
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -244,6 +246,10 @@ Tag of the target dns server.
|
|||||||
|
|
||||||
Disable cache and save cache in this query.
|
Disable cache and save cache in this query.
|
||||||
|
|
||||||
|
#### rewrite_ttl
|
||||||
|
|
||||||
|
Rewrite TTL in DNS responses.
|
||||||
|
|
||||||
### Logical Fields
|
### Logical Fields
|
||||||
|
|
||||||
#### type
|
#### type
|
||||||
|
@ -243,6 +243,10 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
在此查询中禁用缓存。
|
在此查询中禁用缓存。
|
||||||
|
|
||||||
|
#### rewrite_ttl
|
||||||
|
|
||||||
|
重写 DNS 回应中的 TTL。
|
||||||
|
|
||||||
### 逻辑字段
|
### 逻辑字段
|
||||||
|
|
||||||
#### type
|
#### type
|
||||||
|
@ -30,17 +30,18 @@ The tag of the dns server.
|
|||||||
|
|
||||||
The address of the dns server.
|
The address of the dns server.
|
||||||
|
|
||||||
| Protocol | Format |
|
| Protocol | Format |
|
||||||
|----------|-------------------------------|
|
|-------------------------------------|-------------------------------|
|
||||||
| `System` | `local` |
|
| `System` | `local` |
|
||||||
| `TCP` | `tcp://1.0.0.1` |
|
| `TCP` | `tcp://1.0.0.1` |
|
||||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||||
| `TLS` | `tls://dns.google` |
|
| `TLS` | `tls://dns.google` |
|
||||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||||
| `QUIC` | `quic://dns.adguard.com` |
|
| `QUIC` | `quic://dns.adguard.com` |
|
||||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||||
| `RCode` | `rcode://refused` |
|
| `RCode` | `rcode://refused` |
|
||||||
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
|
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
|
||||||
|
| [FakeIP](/configuration/dns/fakeip) | `fakeip` |
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
@ -93,4 +94,4 @@ Take no effect if override by other settings.
|
|||||||
|
|
||||||
Tag of an outbound for connecting to the dns server.
|
Tag of an outbound for connecting to the dns server.
|
||||||
|
|
||||||
Default outbound will be used if empty.
|
Default outbound will be used if empty.
|
||||||
|
@ -30,17 +30,18 @@ DNS 服务器的标签。
|
|||||||
|
|
||||||
DNS 服务器的地址。
|
DNS 服务器的地址。
|
||||||
|
|
||||||
| 协议 | 格式 |
|
| 协议 | 格式 |
|
||||||
|----------|------------------------------|
|
|-------------------------------------|------------------------------|
|
||||||
| `System` | `local` |
|
| `System` | `local` |
|
||||||
| `TCP` | `tcp://1.0.0.1` |
|
| `TCP` | `tcp://1.0.0.1` |
|
||||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||||
| `TLS` | `tls://dns.google` |
|
| `TLS` | `tls://dns.google` |
|
||||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||||
| `QUIC` | `quic://dns.adguard.com` |
|
| `QUIC` | `quic://dns.adguard.com` |
|
||||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||||
| `RCode` | `rcode://refused` |
|
| `RCode` | `rcode://refused` |
|
||||||
| `DHCP` | `dhcp://auto` 或 `dhcp://en0` |
|
| `DHCP` | `dhcp://auto` 或 `dhcp://en0` |
|
||||||
|
| [FakeIP](/configuration/dns/fakeip) | `fakeip` |
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
"clash_api": {
|
"clash_api": {
|
||||||
"external_controller": "127.0.0.1:9090",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui": "folder",
|
"external_ui": "folder",
|
||||||
|
"external_ui_download_url": "",
|
||||||
|
"external_ui_download_detour": "",
|
||||||
"secret": "",
|
"secret": "",
|
||||||
"default_mode": "rule",
|
"default_mode": "rule",
|
||||||
"store_selected": false,
|
"store_selected": false,
|
||||||
@ -53,6 +55,18 @@ A relative path to the configuration directory or an absolute path to a
|
|||||||
directory in which you put some static web resource. sing-box will then
|
directory in which you put some static web resource. sing-box will then
|
||||||
serve it at `http://{{external-controller}}/ui`.
|
serve it at `http://{{external-controller}}/ui`.
|
||||||
|
|
||||||
|
#### external_ui_download_url
|
||||||
|
|
||||||
|
ZIP download URL for the external UI, will be used if the specified `external_ui` directory is empty.
|
||||||
|
|
||||||
|
`https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip` will be used if empty.
|
||||||
|
|
||||||
|
#### external_ui_download_detour
|
||||||
|
|
||||||
|
The tag of the outbound to download the external UI.
|
||||||
|
|
||||||
|
Default outbound will be used if empty.
|
||||||
|
|
||||||
#### secret
|
#### secret
|
||||||
|
|
||||||
Secret for the RESTful API (optional)
|
Secret for the RESTful API (optional)
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
"clash_api": {
|
"clash_api": {
|
||||||
"external_controller": "127.0.0.1:9090",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui": "folder",
|
"external_ui": "folder",
|
||||||
|
"external_ui_download_url": "",
|
||||||
|
"external_ui_download_detour": "",
|
||||||
"secret": "",
|
"secret": "",
|
||||||
"default_mode": "rule",
|
"default_mode": "rule",
|
||||||
"store_selected": false,
|
"store_selected": false,
|
||||||
@ -51,6 +53,18 @@ RESTful web API 监听地址。如果为空,则禁用 Clash API。
|
|||||||
|
|
||||||
到静态网页资源目录的相对路径或绝对路径。sing-box 会在 `http://{{external-controller}}/ui` 下提供它。
|
到静态网页资源目录的相对路径或绝对路径。sing-box 会在 `http://{{external-controller}}/ui` 下提供它。
|
||||||
|
|
||||||
|
#### external_ui_download_url
|
||||||
|
|
||||||
|
静态网页资源的 ZIP 下载 URL,如果指定的 `external_ui` 目录为空,将使用。
|
||||||
|
|
||||||
|
默认使用 `https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip`。
|
||||||
|
|
||||||
|
#### external_ui_download_detour
|
||||||
|
|
||||||
|
用于下载静态网页资源的出站的标签。
|
||||||
|
|
||||||
|
如果为空,将使用默认出站。
|
||||||
|
|
||||||
#### secret
|
#### secret
|
||||||
|
|
||||||
RESTful API 的密钥(可选)
|
RESTful API 的密钥(可选)
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
"username": "sekai",
|
"username": "sekai",
|
||||||
"password": "admin",
|
"password": "admin",
|
||||||
|
"path": "",
|
||||||
|
"headers": {},
|
||||||
"tls": {},
|
"tls": {},
|
||||||
|
|
||||||
... // Dial Fields
|
... // Dial Fields
|
||||||
@ -39,6 +41,14 @@ Basic authorization username.
|
|||||||
|
|
||||||
Basic authorization password.
|
Basic authorization password.
|
||||||
|
|
||||||
|
#### path
|
||||||
|
|
||||||
|
Path of HTTP request.
|
||||||
|
|
||||||
|
#### headers
|
||||||
|
|
||||||
|
Extra headers of HTTP request.
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|
||||||
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
"username": "sekai",
|
"username": "sekai",
|
||||||
"password": "admin",
|
"password": "admin",
|
||||||
|
"path": "",
|
||||||
|
"headers": {},
|
||||||
"tls": {},
|
"tls": {},
|
||||||
|
|
||||||
... // 拨号字段
|
... // 拨号字段
|
||||||
@ -39,6 +41,14 @@ Basic 认证用户名。
|
|||||||
|
|
||||||
Basic 认证密码。
|
Basic 认证密码。
|
||||||
|
|
||||||
|
#### path
|
||||||
|
|
||||||
|
HTTP 请求路径。
|
||||||
|
|
||||||
|
#### headers
|
||||||
|
|
||||||
|
HTTP 请求的额外标头。
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|
||||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
||||||
|
@ -37,4 +37,10 @@
|
|||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
The tag of the outbound.
|
The tag of the outbound.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
#### Outbounds that support IP connection
|
||||||
|
|
||||||
|
* `WireGuard`
|
||||||
|
@ -36,4 +36,10 @@
|
|||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
出站的标签。
|
出站的标签。
|
||||||
|
|
||||||
|
### 特性
|
||||||
|
|
||||||
|
#### 支持 IP 连接的出站
|
||||||
|
|
||||||
|
* `WireGuard`
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"proxy-b",
|
"proxy-b",
|
||||||
"proxy-c"
|
"proxy-c"
|
||||||
],
|
],
|
||||||
"url": "http://www.gstatic.com/generate_204",
|
"url": "https://www.gstatic.com/generate_204",
|
||||||
"interval": "1m",
|
"interval": "1m",
|
||||||
"tolerance": 50
|
"tolerance": 50
|
||||||
}
|
}
|
||||||
@ -26,7 +26,7 @@ List of outbound tags to test.
|
|||||||
|
|
||||||
#### url
|
#### url
|
||||||
|
|
||||||
The URL to test. `http://www.gstatic.com/generate_204` will be used if empty.
|
The URL to test. `https://www.gstatic.com/generate_204` will be used if empty.
|
||||||
|
|
||||||
#### interval
|
#### interval
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
"proxy-b",
|
"proxy-b",
|
||||||
"proxy-c"
|
"proxy-c"
|
||||||
],
|
],
|
||||||
"url": "http://www.gstatic.com/generate_204",
|
"url": "https://www.gstatic.com/generate_204",
|
||||||
"interval": "1m",
|
"interval": "1m",
|
||||||
"tolerance": 50
|
"tolerance": 50
|
||||||
}
|
}
|
||||||
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
#### url
|
#### url
|
||||||
|
|
||||||
用于测试的链接。默认使用 `http://www.gstatic.com/generate_204`。
|
用于测试的链接。默认使用 `https://www.gstatic.com/generate_204`。
|
||||||
|
|
||||||
#### interval
|
#### interval
|
||||||
|
|
||||||
|
@ -13,6 +13,18 @@
|
|||||||
"10.0.0.2/32"
|
"10.0.0.2/32"
|
||||||
],
|
],
|
||||||
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
|
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
|
||||||
|
"peers": [
|
||||||
|
{
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 1080,
|
||||||
|
"public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
|
||||||
|
"pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=",
|
||||||
|
"allowed_ips": [
|
||||||
|
"0.0.0.0/0"
|
||||||
|
],
|
||||||
|
"reserved": [0, 0, 0]
|
||||||
|
}
|
||||||
|
],
|
||||||
"peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
|
"peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
|
||||||
"pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=",
|
"pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=",
|
||||||
"reserved": [0, 0, 0],
|
"reserved": [0, 0, 0],
|
||||||
@ -36,13 +48,13 @@
|
|||||||
|
|
||||||
#### server
|
#### server
|
||||||
|
|
||||||
==Required==
|
==Required if multi-peer disabled==
|
||||||
|
|
||||||
The server address.
|
The server address.
|
||||||
|
|
||||||
#### server_port
|
#### server_port
|
||||||
|
|
||||||
==Required==
|
==Required if multi-peer disabled==
|
||||||
|
|
||||||
The server port.
|
The server port.
|
||||||
|
|
||||||
@ -75,9 +87,25 @@ wg genkey
|
|||||||
echo "private key" || wg pubkey
|
echo "private key" || wg pubkey
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### peers
|
||||||
|
|
||||||
|
Multi-peer support.
|
||||||
|
|
||||||
|
If enabled, `server, server_port, peer_public_key, pre_shared_key` will be ignored.
|
||||||
|
|
||||||
|
#### peers.allowed_ips
|
||||||
|
|
||||||
|
WireGuard allowed IPs.
|
||||||
|
|
||||||
|
#### peers.reserved
|
||||||
|
|
||||||
|
WireGuard reserved field bytes.
|
||||||
|
|
||||||
|
`$outbound.reserved` will be used if empty.
|
||||||
|
|
||||||
#### peer_public_key
|
#### peer_public_key
|
||||||
|
|
||||||
==Required==
|
==Required if multi-peer disabled==
|
||||||
|
|
||||||
WireGuard peer public key.
|
WireGuard peer public key.
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
"route": {
|
"route": {
|
||||||
"geoip": {},
|
"geoip": {},
|
||||||
"geosite": {},
|
"geosite": {},
|
||||||
|
"ip_rules": [],
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"final": "",
|
"final": "",
|
||||||
"auto_detect_interface": false,
|
"auto_detect_interface": false,
|
||||||
@ -19,11 +20,12 @@
|
|||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
| Key | Format |
|
| Key | Format |
|
||||||
|-----------|------------------------------|
|
|------------|------------------------------------|
|
||||||
| `geoip` | [GeoIP](./geoip) |
|
| `geoip` | [GeoIP](./geoip) |
|
||||||
| `geosite` | [Geosite](./geosite) |
|
| `geosite` | [Geosite](./geosite) |
|
||||||
| `rules` | List of [Route Rule](./rule) |
|
| `ip_rules` | List of [IP Route Rule](./ip-rule) |
|
||||||
|
| `rules` | List of [Route Rule](./rule) |
|
||||||
|
|
||||||
#### final
|
#### final
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
"route": {
|
"route": {
|
||||||
"geoip": {},
|
"geoip": {},
|
||||||
"geosite": {},
|
"geosite": {},
|
||||||
|
"ip_rules": [],
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"final": "",
|
"final": "",
|
||||||
"auto_detect_interface": false,
|
"auto_detect_interface": false,
|
||||||
@ -19,11 +20,12 @@
|
|||||||
|
|
||||||
### 字段
|
### 字段
|
||||||
|
|
||||||
| 键 | 格式 |
|
| 键 | 格式 |
|
||||||
|-----------|----------------------|
|
|------------|-------------------------|
|
||||||
| `geoip` | [GeoIP](./geoip) |
|
| `geoip` | [GeoIP](./geoip) |
|
||||||
| `geosite` | [GeoSite](./geosite) |
|
| `geosite` | [GeoSite](./geosite) |
|
||||||
| `rules` | 一组 [路由规则](./rule) |
|
| `ip_rules` | 一组 [IP 路由规则](./ip-rule) |
|
||||||
|
| `rules` | 一组 [路由规则](./rule) |
|
||||||
|
|
||||||
#### final
|
#### final
|
||||||
|
|
||||||
@ -65,4 +67,4 @@
|
|||||||
|
|
||||||
默认为出站连接设置路由标记。
|
默认为出站连接设置路由标记。
|
||||||
|
|
||||||
如果设置了 `outbound.routing_mark` 设置,则不生效。
|
如果设置了 `outbound.routing_mark` 设置,则不生效。
|
205
docs/configuration/route/ip-rule.md
Normal file
205
docs/configuration/route/ip-rule.md
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"route": {
|
||||||
|
"ip_rules": [
|
||||||
|
{
|
||||||
|
"inbound": [
|
||||||
|
"mixed-in"
|
||||||
|
],
|
||||||
|
"ip_version": 6,
|
||||||
|
"network": [
|
||||||
|
"tcp"
|
||||||
|
],
|
||||||
|
"domain": [
|
||||||
|
"test.com"
|
||||||
|
],
|
||||||
|
"domain_suffix": [
|
||||||
|
".cn"
|
||||||
|
],
|
||||||
|
"domain_keyword": [
|
||||||
|
"test"
|
||||||
|
],
|
||||||
|
"domain_regex": [
|
||||||
|
"^stun\\..+"
|
||||||
|
],
|
||||||
|
"geosite": [
|
||||||
|
"cn"
|
||||||
|
],
|
||||||
|
"source_geoip": [
|
||||||
|
"private"
|
||||||
|
],
|
||||||
|
"geoip": [
|
||||||
|
"cn"
|
||||||
|
],
|
||||||
|
"source_ip_cidr": [
|
||||||
|
"10.0.0.0/24",
|
||||||
|
"192.168.0.1"
|
||||||
|
],
|
||||||
|
"ip_cidr": [
|
||||||
|
"10.0.0.0/24",
|
||||||
|
"192.168.0.1"
|
||||||
|
],
|
||||||
|
"source_port": [
|
||||||
|
12345
|
||||||
|
],
|
||||||
|
"source_port_range": [
|
||||||
|
"1000:2000",
|
||||||
|
":3000",
|
||||||
|
"4000:"
|
||||||
|
],
|
||||||
|
"port": [
|
||||||
|
80,
|
||||||
|
443
|
||||||
|
],
|
||||||
|
"port_range": [
|
||||||
|
"1000:2000",
|
||||||
|
":3000",
|
||||||
|
"4000:"
|
||||||
|
],
|
||||||
|
"invert": false,
|
||||||
|
"action": "direct",
|
||||||
|
"outbound": "wireguard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "logical",
|
||||||
|
"mode": "and",
|
||||||
|
"rules": [],
|
||||||
|
"invert": false,
|
||||||
|
"action": "direct",
|
||||||
|
"outbound": "wireguard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note ""
|
||||||
|
|
||||||
|
You can ignore the JSON Array [] tag when the content is only one item
|
||||||
|
|
||||||
|
### Default Fields
|
||||||
|
|
||||||
|
!!! note ""
|
||||||
|
|
||||||
|
The default rule uses the following matching logic:
|
||||||
|
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) &&
|
||||||
|
(`port` || `port_range`) &&
|
||||||
|
(`source_geoip` || `source_ip_cidr`) &&
|
||||||
|
(`source_port` || `source_port_range`) &&
|
||||||
|
`other fields`
|
||||||
|
|
||||||
|
#### inbound
|
||||||
|
|
||||||
|
Tags of [Inbound](/configuration/inbound).
|
||||||
|
|
||||||
|
#### ip_version
|
||||||
|
|
||||||
|
4 or 6.
|
||||||
|
|
||||||
|
Not limited if empty.
|
||||||
|
|
||||||
|
#### network
|
||||||
|
|
||||||
|
Match network protocol.
|
||||||
|
|
||||||
|
Available values:
|
||||||
|
|
||||||
|
* `tcp`
|
||||||
|
* `udp`
|
||||||
|
* `icmpv4`
|
||||||
|
* `icmpv6`
|
||||||
|
|
||||||
|
#### domain
|
||||||
|
|
||||||
|
Match full domain.
|
||||||
|
|
||||||
|
#### domain_suffix
|
||||||
|
|
||||||
|
Match domain suffix.
|
||||||
|
|
||||||
|
#### domain_keyword
|
||||||
|
|
||||||
|
Match domain using keyword.
|
||||||
|
|
||||||
|
#### domain_regex
|
||||||
|
|
||||||
|
Match domain using regular expression.
|
||||||
|
|
||||||
|
#### geosite
|
||||||
|
|
||||||
|
Match geosite.
|
||||||
|
|
||||||
|
#### source_geoip
|
||||||
|
|
||||||
|
Match source geoip.
|
||||||
|
|
||||||
|
#### geoip
|
||||||
|
|
||||||
|
Match geoip.
|
||||||
|
|
||||||
|
#### source_ip_cidr
|
||||||
|
|
||||||
|
Match source ip cidr.
|
||||||
|
|
||||||
|
#### ip_cidr
|
||||||
|
|
||||||
|
Match ip cidr.
|
||||||
|
|
||||||
|
#### source_port
|
||||||
|
|
||||||
|
Match source port.
|
||||||
|
|
||||||
|
#### source_port_range
|
||||||
|
|
||||||
|
Match source port range.
|
||||||
|
|
||||||
|
#### port
|
||||||
|
|
||||||
|
Match port.
|
||||||
|
|
||||||
|
#### port_range
|
||||||
|
|
||||||
|
Match port range.
|
||||||
|
|
||||||
|
#### invert
|
||||||
|
|
||||||
|
Invert match result.
|
||||||
|
|
||||||
|
#### action
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
| Action | Description |
|
||||||
|
|--------|--------------------------------------------------------------------|
|
||||||
|
| return | Stop IP routing and assemble the connection to the transport layer |
|
||||||
|
| block | Block the connection |
|
||||||
|
| direct | Directly forward the connection |
|
||||||
|
|
||||||
|
#### outbound
|
||||||
|
|
||||||
|
==Required if action is direct==
|
||||||
|
|
||||||
|
Tag of the target outbound.
|
||||||
|
|
||||||
|
Only outbound which supports IP connection can be used, see [Outbounds that support IP connection](/configuration/outbound/#outbounds-that-support-ip-connection).
|
||||||
|
|
||||||
|
### Logical Fields
|
||||||
|
|
||||||
|
#### type
|
||||||
|
|
||||||
|
`logical`
|
||||||
|
|
||||||
|
#### mode
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
`and` or `or`
|
||||||
|
|
||||||
|
#### rules
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
Included default rules.
|
204
docs/configuration/route/ip-rule.zh.md
Normal file
204
docs/configuration/route/ip-rule.zh.md
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
### 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"route": {
|
||||||
|
"ip_rules": [
|
||||||
|
{
|
||||||
|
"inbound": [
|
||||||
|
"mixed-in"
|
||||||
|
],
|
||||||
|
"ip_version": 6,
|
||||||
|
"network": [
|
||||||
|
"tcp"
|
||||||
|
],
|
||||||
|
"domain": [
|
||||||
|
"test.com"
|
||||||
|
],
|
||||||
|
"domain_suffix": [
|
||||||
|
".cn"
|
||||||
|
],
|
||||||
|
"domain_keyword": [
|
||||||
|
"test"
|
||||||
|
],
|
||||||
|
"domain_regex": [
|
||||||
|
"^stun\\..+"
|
||||||
|
],
|
||||||
|
"geosite": [
|
||||||
|
"cn"
|
||||||
|
],
|
||||||
|
"source_geoip": [
|
||||||
|
"private"
|
||||||
|
],
|
||||||
|
"geoip": [
|
||||||
|
"cn"
|
||||||
|
],
|
||||||
|
"source_ip_cidr": [
|
||||||
|
"10.0.0.0/24",
|
||||||
|
"192.168.0.1"
|
||||||
|
],
|
||||||
|
"ip_cidr": [
|
||||||
|
"10.0.0.0/24",
|
||||||
|
"192.168.0.1"
|
||||||
|
],
|
||||||
|
"source_port": [
|
||||||
|
12345
|
||||||
|
],
|
||||||
|
"source_port_range": [
|
||||||
|
"1000:2000",
|
||||||
|
":3000",
|
||||||
|
"4000:"
|
||||||
|
],
|
||||||
|
"port": [
|
||||||
|
80,
|
||||||
|
443
|
||||||
|
],
|
||||||
|
"port_range": [
|
||||||
|
"1000:2000",
|
||||||
|
":3000",
|
||||||
|
"4000:"
|
||||||
|
],
|
||||||
|
"invert": false,
|
||||||
|
"action": "direct",
|
||||||
|
"outbound": "wireguard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "logical",
|
||||||
|
"mode": "and",
|
||||||
|
"rules": [],
|
||||||
|
"invert": false,
|
||||||
|
"action": "direct",
|
||||||
|
"outbound": "wireguard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note ""
|
||||||
|
|
||||||
|
当内容只有一项时,可以忽略 JSON 数组 [] 标签。
|
||||||
|
|
||||||
|
### Default Fields
|
||||||
|
|
||||||
|
!!! note ""
|
||||||
|
|
||||||
|
默认规则使用以下匹配逻辑:
|
||||||
|
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) &&
|
||||||
|
(`port` || `port_range`) &&
|
||||||
|
(`source_geoip` || `source_ip_cidr`) &&
|
||||||
|
(`source_port` || `source_port_range`) &&
|
||||||
|
`other fields`
|
||||||
|
|
||||||
|
#### inbound
|
||||||
|
|
||||||
|
[入站](/zh/configuration/inbound) 标签。
|
||||||
|
|
||||||
|
#### ip_version
|
||||||
|
|
||||||
|
4 或 6。
|
||||||
|
|
||||||
|
默认不限制。
|
||||||
|
|
||||||
|
#### network
|
||||||
|
|
||||||
|
匹配网络协议。
|
||||||
|
|
||||||
|
可用值:
|
||||||
|
|
||||||
|
* `tcp`
|
||||||
|
* `udp`
|
||||||
|
* `icmpv4`
|
||||||
|
* `icmpv6`
|
||||||
|
|
||||||
|
#### domain
|
||||||
|
|
||||||
|
匹配完整域名。
|
||||||
|
|
||||||
|
#### domain_suffix
|
||||||
|
|
||||||
|
匹配域名后缀。
|
||||||
|
|
||||||
|
#### domain_keyword
|
||||||
|
|
||||||
|
匹配域名关键字。
|
||||||
|
|
||||||
|
#### domain_regex
|
||||||
|
|
||||||
|
匹配域名正则表达式。
|
||||||
|
|
||||||
|
#### geosite
|
||||||
|
|
||||||
|
匹配 GeoSite。
|
||||||
|
|
||||||
|
#### source_geoip
|
||||||
|
|
||||||
|
匹配源 GeoIP。
|
||||||
|
|
||||||
|
#### geoip
|
||||||
|
|
||||||
|
匹配 GeoIP。
|
||||||
|
|
||||||
|
#### source_ip_cidr
|
||||||
|
|
||||||
|
匹配源 IP CIDR。
|
||||||
|
|
||||||
|
#### ip_cidr
|
||||||
|
|
||||||
|
匹配 IP CIDR。
|
||||||
|
|
||||||
|
#### source_port
|
||||||
|
|
||||||
|
匹配源端口。
|
||||||
|
|
||||||
|
#### source_port_range
|
||||||
|
|
||||||
|
匹配源端口范围。
|
||||||
|
|
||||||
|
#### port
|
||||||
|
|
||||||
|
匹配端口。
|
||||||
|
|
||||||
|
#### port_range
|
||||||
|
|
||||||
|
匹配端口范围。
|
||||||
|
|
||||||
|
#### invert
|
||||||
|
|
||||||
|
反选匹配结果。
|
||||||
|
|
||||||
|
#### action
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
| Action | 描述 |
|
||||||
|
|--------|---------------------|
|
||||||
|
| return | 停止 IP 路由并将该连接组装到传输层 |
|
||||||
|
| block | 屏蔽该连接 |
|
||||||
|
| direct | 直接转发该连接 |
|
||||||
|
|
||||||
|
|
||||||
|
#### outbound
|
||||||
|
|
||||||
|
==action 为 direct 则必填==
|
||||||
|
|
||||||
|
目标出站的标签。
|
||||||
|
|
||||||
|
### 逻辑字段
|
||||||
|
|
||||||
|
#### type
|
||||||
|
|
||||||
|
`logical`
|
||||||
|
|
||||||
|
#### mode
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
`and` 或 `or`
|
||||||
|
|
||||||
|
#### rules
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
包括的默认规则。
|
@ -9,7 +9,9 @@
|
|||||||
"mixed-in"
|
"mixed-in"
|
||||||
],
|
],
|
||||||
"ip_version": 6,
|
"ip_version": 6,
|
||||||
"network": "tcp",
|
"network": [
|
||||||
|
"tcp"
|
||||||
|
],
|
||||||
"auth_user": [
|
"auth_user": [
|
||||||
"usera",
|
"usera",
|
||||||
"userb"
|
"userb"
|
||||||
@ -244,18 +246,12 @@ Tag of the target outbound.
|
|||||||
|
|
||||||
#### mode
|
#### mode
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
`and` or `or`
|
`and` or `or`
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
Included default rules.
|
|
||||||
|
|
||||||
#### invert
|
|
||||||
|
|
||||||
Invert match result.
|
|
||||||
|
|
||||||
#### outbound
|
|
||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
Tag of the target outbound.
|
Included default rules.
|
||||||
|
@ -9,7 +9,9 @@
|
|||||||
"mixed-in"
|
"mixed-in"
|
||||||
],
|
],
|
||||||
"ip_version": 6,
|
"ip_version": 6,
|
||||||
"network": "tcp",
|
"network": [
|
||||||
|
"tcp"
|
||||||
|
],
|
||||||
"auth_user": [
|
"auth_user": [
|
||||||
"usera",
|
"usera",
|
||||||
"userb"
|
"userb"
|
||||||
@ -242,18 +244,12 @@
|
|||||||
|
|
||||||
#### mode
|
#### mode
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
`and` 或 `or`
|
`and` 或 `or`
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
包括的默认规则。
|
|
||||||
|
|
||||||
#### invert
|
|
||||||
|
|
||||||
反选匹配结果。
|
|
||||||
|
|
||||||
#### outbound
|
|
||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
目标出站的标签。
|
包括的默认规则。
|
@ -10,7 +10,8 @@
|
|||||||
"protocol": "smux",
|
"protocol": "smux",
|
||||||
"max_connections": 4,
|
"max_connections": 4,
|
||||||
"min_streams": 4,
|
"min_streams": 4,
|
||||||
"max_streams": 0
|
"max_streams": 0,
|
||||||
|
"padding": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -28,8 +29,9 @@ Multiplex protocol.
|
|||||||
|----------|------------------------------------|
|
|----------|------------------------------------|
|
||||||
| smux | https://github.com/xtaci/smux |
|
| smux | https://github.com/xtaci/smux |
|
||||||
| yamux | https://github.com/hashicorp/yamux |
|
| yamux | https://github.com/hashicorp/yamux |
|
||||||
|
| h2mux | https://golang.org/x/net/http2 |
|
||||||
|
|
||||||
SMux is used by default.
|
h2mux is used by default.
|
||||||
|
|
||||||
#### max_connections
|
#### max_connections
|
||||||
|
|
||||||
@ -48,3 +50,12 @@ Conflict with `max_streams`.
|
|||||||
Maximum multiplexed streams in a connection before opening a new connection.
|
Maximum multiplexed streams in a connection before opening a new connection.
|
||||||
|
|
||||||
Conflict with `max_connections` and `min_streams`.
|
Conflict with `max_connections` and `min_streams`.
|
||||||
|
|
||||||
|
#### padding
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
|
||||||
|
Requires sing-box server version 1.3-beta9 or later.
|
||||||
|
|
||||||
|
Enable padding.
|
||||||
|
|
||||||
|
@ -28,8 +28,9 @@
|
|||||||
|-------|------------------------------------|
|
|-------|------------------------------------|
|
||||||
| smux | https://github.com/xtaci/smux |
|
| smux | https://github.com/xtaci/smux |
|
||||||
| yamux | https://github.com/hashicorp/yamux |
|
| yamux | https://github.com/hashicorp/yamux |
|
||||||
|
| h2mux | https://golang.org/x/net/http2 |
|
||||||
|
|
||||||
默认使用 SMux。
|
默认使用 h2mux。
|
||||||
|
|
||||||
#### max_connections
|
#### max_connections
|
||||||
|
|
||||||
@ -47,4 +48,13 @@
|
|||||||
|
|
||||||
在打开新连接之前,连接中的最大多路复用流数量。
|
在打开新连接之前,连接中的最大多路复用流数量。
|
||||||
|
|
||||||
与 `max_connections` 和 `min_streams` 冲突。
|
与 `max_connections` 和 `min_streams` 冲突。
|
||||||
|
|
||||||
|
#### padding
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
|
||||||
|
需要 sing-box 服务器版本 1.3-beta9 或更高。
|
||||||
|
|
||||||
|
启用填充。
|
||||||
|
|
||||||
|
@ -8,3 +8,4 @@ Configuration examples for sing-box.
|
|||||||
* [Shadowsocks](./shadowsocks)
|
* [Shadowsocks](./shadowsocks)
|
||||||
* [ShadowTLS](./shadowtls)
|
* [ShadowTLS](./shadowtls)
|
||||||
* [Clash API](./clash-api)
|
* [Clash API](./clash-api)
|
||||||
|
* [WireGuard Direct](./wireguard-direct)
|
||||||
|
@ -8,3 +8,4 @@ sing-box 的配置示例。
|
|||||||
* [Shadowsocks](./shadowsocks)
|
* [Shadowsocks](./shadowsocks)
|
||||||
* [ShadowTLS](./shadowtls)
|
* [ShadowTLS](./shadowtls)
|
||||||
* [Clash API](./clash-api)
|
* [Clash API](./clash-api)
|
||||||
|
* [WireGuard Direct](./wireguard-direct)
|
||||||
|
90
docs/examples/wireguard-direct.md
Normal file
90
docs/examples/wireguard-direct.md
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
# WireGuard Direct
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"tag": "google",
|
||||||
|
"address": "tls://8.8.8.8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "local",
|
||||||
|
"address": "223.5.5.5",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"geoip": "cn",
|
||||||
|
"server": "direct"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"reverse_mapping": true
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "tun",
|
||||||
|
"tag": "tun",
|
||||||
|
"inet4_address": "172.19.0.1/30",
|
||||||
|
"auto_route": true,
|
||||||
|
"sniff": true,
|
||||||
|
"stack": "system"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "wireguard",
|
||||||
|
"tag": "wg",
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 2345,
|
||||||
|
"local_address": [
|
||||||
|
"172.19.0.1/128"
|
||||||
|
],
|
||||||
|
"private_key": "KLTnpPY03pig/WC3zR8U7VWmpANHPFh2/4pwICGJ5Fk=",
|
||||||
|
"peer_public_key": "uvNabcamf6Rs0vzmcw99jsjTJbxo6eWGOykSY66zsUk="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "block",
|
||||||
|
"tag": "block"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"ip_rules": [
|
||||||
|
{
|
||||||
|
"port": 53,
|
||||||
|
"action": "return"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"geoip": "cn",
|
||||||
|
"geosite": "cn",
|
||||||
|
"action": "return"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "direct",
|
||||||
|
"outbound": "wg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"protocol": "dns",
|
||||||
|
"outbound": "dns"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"geoip": "cn",
|
||||||
|
"geosite": "cn",
|
||||||
|
"outbound": "direct"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
20
docs/faq/fakeip.md
Normal file
20
docs/faq/fakeip.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# FakeIP
|
||||||
|
|
||||||
|
FakeIP refers to a type of behavior in a program that simultaneously hijacks both DNS and connection requests. It
|
||||||
|
responds to DNS requests with virtual results and restores mapping when accepting connections.
|
||||||
|
|
||||||
|
#### Advantage
|
||||||
|
|
||||||
|
* Retrieve the requested domain in places like IP routing (L3) where traffic detection is not possible to assist with routing.
|
||||||
|
* Decrease an RTT on the first TCP request to a domain (the most common reason).
|
||||||
|
|
||||||
|
#### Limitation
|
||||||
|
|
||||||
|
* Its mechanism breaks applications that depend on returning correct remote addresses.
|
||||||
|
* Only A and AAAA (IP) requests are supported, which may break applications that rely on other requests.
|
||||||
|
|
||||||
|
#### Recommendation
|
||||||
|
|
||||||
|
* Do not use if you do not need L3 routing.
|
||||||
|
* If using tun, make sure FakeIP ranges is included in the tun's routes.
|
||||||
|
* Enable `experimental.clash_api.store_fakeip` to persist FakeIP records, or use `dns.rules.rewrite_ttl` to avoid losing records after program restart in DNS cached environments.
|
19
docs/faq/fakeip.zh.md
Normal file
19
docs/faq/fakeip.zh.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# FakeIP
|
||||||
|
|
||||||
|
FakeIP 是指同时劫持 DNS 和连接请求的程序中的一种行为。它通过虚拟结果响应 DNS 请求,在接受连接时恢复映射。
|
||||||
|
|
||||||
|
#### 优点
|
||||||
|
|
||||||
|
* 在像 L3 路由这样无法进行流量探测的地方检索所请求的域名,以协助路由。
|
||||||
|
* 减少对一个域的第一个 TCP 请求的 RTT(这是最常见的原因)。
|
||||||
|
|
||||||
|
#### 限制
|
||||||
|
|
||||||
|
* 它的机制会破坏依赖于返回正确远程地址的应用程序。
|
||||||
|
* 仅支持 A 和 AAAA(IP)请求,这可能会破坏依赖于其他请求的应用程序。
|
||||||
|
|
||||||
|
#### 建议
|
||||||
|
|
||||||
|
* 如果不需要 L3 路由,请勿使用。
|
||||||
|
* 如果使用 tun,请确保 tun 路由中包含 FakeIP 地址范围。
|
||||||
|
* 启用 `experimental.clash_api.store_fakeip` 以持久化 FakeIP 记录,或者使用 `dns.rules.rewrite_ttl` 避免程序重启后在 DNS 被缓存的环境中丢失记录。
|
@ -11,12 +11,6 @@ it doesn't fit, because it compromises performance or design clarity, or because
|
|||||||
If it bothers you that sing-box is missing feature X, please forgive us and investigate the features that sing-box does
|
If it bothers you that sing-box is missing feature X, please forgive us and investigate the features that sing-box does
|
||||||
have. You might find that they compensate in interesting ways for the lack of X.
|
have. You might find that they compensate in interesting ways for the lack of X.
|
||||||
|
|
||||||
#### Fake IP
|
|
||||||
|
|
||||||
Fake IP (also called Fake DNS) is an invasive and imperfect DNS solution that breaks expected behavior, causes DNS leaks
|
|
||||||
and makes many software unusable. It is recommended by some software that lacks DNS processing and caching, but sing-box
|
|
||||||
does not need this.
|
|
||||||
|
|
||||||
#### Naive outbound
|
#### Naive outbound
|
||||||
|
|
||||||
NaïveProxy's main function is chromium's network stack, and it makes no sense to implement only its transport protocol.
|
NaïveProxy's main function is chromium's network stack, and it makes no sense to implement only its transport protocol.
|
||||||
|
@ -9,11 +9,6 @@
|
|||||||
|
|
||||||
如果 sing-box 缺少功能 X 让您感到困扰,请原谅我们并调查 sing-box 确实有的功能。 您可能会发现它们以有趣的方式弥补了 X 的缺失。
|
如果 sing-box 缺少功能 X 让您感到困扰,请原谅我们并调查 sing-box 确实有的功能。 您可能会发现它们以有趣的方式弥补了 X 的缺失。
|
||||||
|
|
||||||
#### Fake IP
|
|
||||||
|
|
||||||
Fake IP(也称 Fake DNS)是一种侵入性和不完善的 DNS 解决方案,它打破了预期的行为,导致 DNS 泄漏并使许多软件无法使用。
|
|
||||||
一些缺乏 DNS 处理和缓存的软件推荐使用它,但 sing-box 不需要。
|
|
||||||
|
|
||||||
#### Naive 出站
|
#### Naive 出站
|
||||||
|
|
||||||
NaïveProxy 的主要功能是 chromium 的网络栈,仅实现它的传输协议是舍本逐末的。
|
NaïveProxy 的主要功能是 chromium 的网络栈,仅实现它的传输协议是舍本逐末的。
|
||||||
|
@ -13,7 +13,7 @@ Experimental iOS client for sing-box.
|
|||||||
|
|
||||||
#### Note
|
#### Note
|
||||||
|
|
||||||
* User Agent in remote profile request is `SFA/$version ($version_code; sing-box $sing_box_version)`
|
* User Agent in remote profile request is `SFI/$version ($version_code; sing-box $sing_box_version)`
|
||||||
* Crash logs is located in `Settings` -> `View Service Log`
|
* Crash logs is located in `Settings` -> `View Service Log`
|
||||||
|
|
||||||
#### Privacy policy
|
#### Privacy policy
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package experimental
|
package experimental
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@ -8,7 +9,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClashServerConstructor = func(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error)
|
type ClashServerConstructor = func(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error)
|
||||||
|
|
||||||
var clashServerConstructor ClashServerConstructor
|
var clashServerConstructor ClashServerConstructor
|
||||||
|
|
||||||
@ -16,9 +17,9 @@ func RegisterClashServerConstructor(constructor ClashServerConstructor) {
|
|||||||
clashServerConstructor = constructor
|
clashServerConstructor = constructor
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClashServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
func NewClashServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
||||||
if clashServerConstructor == nil {
|
if clashServerConstructor == nil {
|
||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
return clashServerConstructor(router, logFactory, options)
|
return clashServerConstructor(ctx, router, logFactory, options)
|
||||||
}
|
}
|
||||||
|
78
experimental/clashapi/api_meta.go
Normal file
78
experimental/clashapi/api_meta.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package clashapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||||
|
"github.com/sagernet/websocket"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// API created by Clash.Meta
|
||||||
|
|
||||||
|
func (s *Server) setupMetaAPI(r chi.Router) {
|
||||||
|
r.Get("/memory", memory(s.trafficManager))
|
||||||
|
r.Mount("/group", groupRouter(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Memory struct {
|
||||||
|
Inuse uint64 `json:"inuse"`
|
||||||
|
OSLimit uint64 `json:"oslimit"` // maybe we need it in the future
|
||||||
|
}
|
||||||
|
|
||||||
|
func memory(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var wsConn *websocket.Conn
|
||||||
|
if websocket.IsWebSocketUpgrade(r) {
|
||||||
|
var err error
|
||||||
|
wsConn, err = upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if wsConn == nil {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
render.Status(r, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
tick := time.NewTicker(time.Second)
|
||||||
|
defer tick.Stop()
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
var err error
|
||||||
|
first := true
|
||||||
|
for range tick.C {
|
||||||
|
buf.Reset()
|
||||||
|
|
||||||
|
inuse := trafficManager.Snapshot().Memory
|
||||||
|
|
||||||
|
// make chat.js begin with zero
|
||||||
|
// this is shit var,but we need output 0 for first time
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
inuse = 0
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(buf).Encode(Memory{
|
||||||
|
Inuse: inuse,
|
||||||
|
OSLimit: 0,
|
||||||
|
}); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if wsConn == nil {
|
||||||
|
_, err = w.Write(buf.Bytes())
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
} else {
|
||||||
|
err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
136
experimental/clashapi/api_meta_group.go
Normal file
136
experimental/clashapi/api_meta_group.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package clashapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/badjson"
|
||||||
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
|
"github.com/sagernet/sing-box/outbound"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/batch"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
func groupRouter(server *Server) http.Handler {
|
||||||
|
r := chi.NewRouter()
|
||||||
|
r.Get("/", getGroups(server))
|
||||||
|
r.Route("/{name}", func(r chi.Router) {
|
||||||
|
r.Use(parseProxyName, findProxyByName(server.router))
|
||||||
|
r.Get("/", getGroup(server))
|
||||||
|
r.Get("/delay", getGroupDelay(server))
|
||||||
|
})
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGroups(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
groups := common.Map(common.Filter(server.router.Outbounds(), func(it adapter.Outbound) bool {
|
||||||
|
_, isGroup := it.(adapter.OutboundGroup)
|
||||||
|
return isGroup
|
||||||
|
}), func(it adapter.Outbound) *badjson.JSONObject {
|
||||||
|
return proxyInfo(server, it)
|
||||||
|
})
|
||||||
|
render.JSON(w, r, render.M{
|
||||||
|
"proxies": groups,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGroup(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
|
||||||
|
if _, ok := proxy.(adapter.OutboundGroup); ok {
|
||||||
|
render.JSON(w, r, proxyInfo(server, proxy))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
render.Status(r, http.StatusNotFound)
|
||||||
|
render.JSON(w, r, ErrNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
|
||||||
|
group, ok := proxy.(adapter.OutboundGroup)
|
||||||
|
if !ok {
|
||||||
|
render.Status(r, http.StatusNotFound)
|
||||||
|
render.JSON(w, r, ErrNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
query := r.URL.Query()
|
||||||
|
url := query.Get("url")
|
||||||
|
if strings.HasPrefix(url, "http://") {
|
||||||
|
url = ""
|
||||||
|
}
|
||||||
|
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
render.Status(r, http.StatusBadRequest)
|
||||||
|
render.JSON(w, r, ErrBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), time.Millisecond*time.Duration(timeout))
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var result map[string]uint16
|
||||||
|
if urlTestGroup, isURLTestGroup := group.(adapter.URLTestGroup); isURLTestGroup {
|
||||||
|
result, err = urlTestGroup.URLTest(ctx, url)
|
||||||
|
} else {
|
||||||
|
outbounds := common.FilterNotNil(common.Map(group.All(), func(it string) adapter.Outbound {
|
||||||
|
itOutbound, _ := server.router.Outbound(it)
|
||||||
|
return itOutbound
|
||||||
|
}))
|
||||||
|
b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))
|
||||||
|
checked := make(map[string]bool)
|
||||||
|
result = make(map[string]uint16)
|
||||||
|
var resultAccess sync.Mutex
|
||||||
|
for _, detour := range outbounds {
|
||||||
|
tag := detour.Tag()
|
||||||
|
realTag := outbound.RealTag(detour)
|
||||||
|
if checked[realTag] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
checked[realTag] = true
|
||||||
|
p, loaded := server.router.Outbound(realTag)
|
||||||
|
if !loaded {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b.Go(realTag, func() (any, error) {
|
||||||
|
t, err := urltest.URLTest(ctx, url, p)
|
||||||
|
if err != nil {
|
||||||
|
server.logger.Debug("outbound ", tag, " unavailable: ", err)
|
||||||
|
server.urlTestHistory.DeleteURLTestHistory(realTag)
|
||||||
|
} else {
|
||||||
|
server.logger.Debug("outbound ", tag, " available: ", t, "ms")
|
||||||
|
server.urlTestHistory.StoreURLTestHistory(realTag, &urltest.History{
|
||||||
|
Time: time.Now(),
|
||||||
|
Delay: t,
|
||||||
|
})
|
||||||
|
resultAccess.Lock()
|
||||||
|
result[tag] = t
|
||||||
|
resultAccess.Unlock()
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
b.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
render.Status(r, http.StatusGatewayTimeout)
|
||||||
|
render.JSON(w, r, newError(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.JSON(w, r, result)
|
||||||
|
}
|
||||||
|
}
|
@ -3,21 +3,28 @@ package clashapi
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cacheRouter() http.Handler {
|
func cacheRouter(router adapter.Router) http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Post("/fakeip/flush", flushFakeip)
|
r.Post("/fakeip/flush", flushFakeip(router))
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func flushFakeip(w http.ResponseWriter, r *http.Request) {
|
func flushFakeip(router adapter.Router) func(w http.ResponseWriter, r *http.Request) {
|
||||||
/*if err := cachefile.Cache().FlushFakeip(); err != nil {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
render.Status(r, http.StatusInternalServerError)
|
if cacheFile := router.ClashServer().CacheFile(); cacheFile != nil {
|
||||||
render.JSON(w, r, newError(err.Error()))
|
err := cacheFile.FakeIPReset()
|
||||||
return
|
if err != nil {
|
||||||
}*/
|
render.Status(r, http.StatusInternalServerError)
|
||||||
render.NoContent(w, r)
|
render.JSON(w, r, newError(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render.NoContent(w, r)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
77
experimental/clashapi/cachefile/fakeip.go
Normal file
77
experimental/clashapi/cachefile/fakeip.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package cachefile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
|
||||||
|
"go.etcd.io/bbolt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bucketFakeIP = []byte("fakeip")
|
||||||
|
keyMetadata = []byte("metadata")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *CacheFile) FakeIPMetadata() *adapter.FakeIPMetadata {
|
||||||
|
var metadata adapter.FakeIPMetadata
|
||||||
|
err := c.DB.View(func(tx *bbolt.Tx) error {
|
||||||
|
bucket := tx.Bucket(bucketFakeIP)
|
||||||
|
if bucket == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
metadataBinary := bucket.Get(keyMetadata)
|
||||||
|
if len(metadataBinary) == 0 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
return metadata.UnmarshalBinary(metadataBinary)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) FakeIPSaveMetadata(metadata *adapter.FakeIPMetadata) error {
|
||||||
|
return c.DB.Batch(func(tx *bbolt.Tx) error {
|
||||||
|
bucket, err := tx.CreateBucketIfNotExists(bucketFakeIP)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
metadataBinary, err := metadata.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return bucket.Put(keyMetadata, metadataBinary)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) FakeIPStore(address netip.Addr, domain string) error {
|
||||||
|
return c.DB.Batch(func(tx *bbolt.Tx) error {
|
||||||
|
bucket, err := tx.CreateBucketIfNotExists(bucketFakeIP)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return bucket.Put(address.AsSlice(), []byte(domain))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) FakeIPLoad(address netip.Addr) (string, bool) {
|
||||||
|
var domain string
|
||||||
|
_ = c.DB.View(func(tx *bbolt.Tx) error {
|
||||||
|
bucket := tx.Bucket(bucketFakeIP)
|
||||||
|
if bucket == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
domain = string(bucket.Get(address.AsSlice()))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return domain, domain != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) FakeIPReset() error {
|
||||||
|
return c.DB.Batch(func(tx *bbolt.Tx) error {
|
||||||
|
return tx.DeleteBucket(bucketFakeIP)
|
||||||
|
})
|
||||||
|
}
|
@ -6,6 +6,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/json"
|
"github.com/sagernet/sing-box/common/json"
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||||
"github.com/sagernet/websocket"
|
"github.com/sagernet/websocket"
|
||||||
@ -14,10 +15,10 @@ import (
|
|||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
func connectionRouter(trafficManager *trafficontrol.Manager) http.Handler {
|
func connectionRouter(router adapter.Router, trafficManager *trafficontrol.Manager) http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Get("/", getConnections(trafficManager))
|
r.Get("/", getConnections(trafficManager))
|
||||||
r.Delete("/", closeAllConnections(trafficManager))
|
r.Delete("/", closeAllConnections(router, trafficManager))
|
||||||
r.Delete("/{id}", closeConnection(trafficManager))
|
r.Delete("/{id}", closeConnection(trafficManager))
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@ -86,12 +87,13 @@ func closeConnection(trafficManager *trafficontrol.Manager) func(w http.Response
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeAllConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
|
func closeAllConnections(router adapter.Router, 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) {
|
||||||
snapshot := trafficManager.Snapshot()
|
snapshot := trafficManager.Snapshot()
|
||||||
for _, c := range snapshot.Connections {
|
for _, c := range snapshot.Connections {
|
||||||
c.Close()
|
c.Close()
|
||||||
}
|
}
|
||||||
|
router.ResetNetwork()
|
||||||
render.NoContent(w, r)
|
render.NoContent(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@ -133,7 +134,7 @@ func getProxies(server *Server, router adapter.Router) func(w http.ResponseWrite
|
|||||||
defaultTag = allProxies[0]
|
defaultTag = allProxies[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(allProxies, func(i, j int) bool {
|
sort.SliceStable(allProxies, func(i, j int) bool {
|
||||||
return allProxies[i] == defaultTag
|
return allProxies[i] == defaultTag
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -214,6 +215,9 @@ func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
|||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
query := r.URL.Query()
|
query := r.URL.Query()
|
||||||
url := query.Get("url")
|
url := query.Get("url")
|
||||||
|
if strings.HasPrefix(url, "http://") {
|
||||||
|
url = ""
|
||||||
|
}
|
||||||
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
|
timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.Status(r, http.StatusBadRequest)
|
render.Status(r, http.StatusBadRequest)
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
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"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
"github.com/sagernet/websocket"
|
"github.com/sagernet/websocket"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
@ -37,6 +38,7 @@ func init() {
|
|||||||
var _ adapter.ClashServer = (*Server)(nil)
|
var _ adapter.ClashServer = (*Server)(nil)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
|
ctx context.Context
|
||||||
router adapter.Router
|
router adapter.Router
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
httpServer *http.Server
|
httpServer *http.Server
|
||||||
@ -44,29 +46,38 @@ type Server struct {
|
|||||||
urlTestHistory *urltest.HistoryStorage
|
urlTestHistory *urltest.HistoryStorage
|
||||||
mode string
|
mode string
|
||||||
storeSelected bool
|
storeSelected bool
|
||||||
|
storeFakeIP bool
|
||||||
cacheFilePath string
|
cacheFilePath string
|
||||||
cacheFile adapter.ClashCacheFile
|
cacheFile adapter.ClashCacheFile
|
||||||
|
|
||||||
|
externalUI string
|
||||||
|
externalUIDownloadURL string
|
||||||
|
externalUIDownloadDetour string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
func NewServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
||||||
trafficManager := trafficontrol.NewManager()
|
trafficManager := trafficontrol.NewManager()
|
||||||
chiRouter := chi.NewRouter()
|
chiRouter := chi.NewRouter()
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
ctx: ctx,
|
||||||
router: router,
|
router: router,
|
||||||
logger: logFactory.NewLogger("clash-api"),
|
logger: logFactory.NewLogger("clash-api"),
|
||||||
httpServer: &http.Server{
|
httpServer: &http.Server{
|
||||||
Addr: options.ExternalController,
|
Addr: options.ExternalController,
|
||||||
Handler: chiRouter,
|
Handler: chiRouter,
|
||||||
},
|
},
|
||||||
trafficManager: trafficManager,
|
trafficManager: trafficManager,
|
||||||
urlTestHistory: urltest.NewHistoryStorage(),
|
urlTestHistory: urltest.NewHistoryStorage(),
|
||||||
mode: strings.ToLower(options.DefaultMode),
|
mode: strings.ToLower(options.DefaultMode),
|
||||||
|
storeSelected: options.StoreSelected,
|
||||||
|
storeFakeIP: options.StoreFakeIP,
|
||||||
|
externalUIDownloadURL: options.ExternalUIDownloadURL,
|
||||||
|
externalUIDownloadDetour: options.ExternalUIDownloadDetour,
|
||||||
}
|
}
|
||||||
if server.mode == "" {
|
if server.mode == "" {
|
||||||
server.mode = "rule"
|
server.mode = "rule"
|
||||||
}
|
}
|
||||||
if options.StoreSelected {
|
if options.StoreSelected || options.StoreFakeIP {
|
||||||
server.storeSelected = true
|
|
||||||
cachePath := os.ExpandEnv(options.CacheFile)
|
cachePath := os.ExpandEnv(options.CacheFile)
|
||||||
if cachePath == "" {
|
if cachePath == "" {
|
||||||
cachePath = "cache.db"
|
cachePath = "cache.db"
|
||||||
@ -74,7 +85,7 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
|||||||
if foundPath, loaded := C.FindPath(cachePath); loaded {
|
if foundPath, loaded := C.FindPath(cachePath); loaded {
|
||||||
cachePath = foundPath
|
cachePath = foundPath
|
||||||
} else {
|
} else {
|
||||||
cachePath = C.BasePath(cachePath)
|
cachePath = filemanager.BasePath(ctx, cachePath)
|
||||||
}
|
}
|
||||||
server.cacheFilePath = cachePath
|
server.cacheFilePath = cachePath
|
||||||
}
|
}
|
||||||
@ -94,17 +105,20 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
|||||||
r.Mount("/configs", configRouter(server, logFactory, server.logger))
|
r.Mount("/configs", configRouter(server, logFactory, server.logger))
|
||||||
r.Mount("/proxies", proxyRouter(server, router))
|
r.Mount("/proxies", proxyRouter(server, router))
|
||||||
r.Mount("/rules", ruleRouter(router))
|
r.Mount("/rules", ruleRouter(router))
|
||||||
r.Mount("/connections", connectionRouter(trafficManager))
|
r.Mount("/connections", connectionRouter(router, trafficManager))
|
||||||
r.Mount("/providers/proxies", proxyProviderRouter())
|
r.Mount("/providers/proxies", proxyProviderRouter())
|
||||||
r.Mount("/providers/rules", ruleProviderRouter())
|
r.Mount("/providers/rules", ruleProviderRouter())
|
||||||
r.Mount("/script", scriptRouter())
|
r.Mount("/script", scriptRouter())
|
||||||
r.Mount("/profile", profileRouter())
|
r.Mount("/profile", profileRouter())
|
||||||
r.Mount("/cache", cacheRouter())
|
r.Mount("/cache", cacheRouter(router))
|
||||||
r.Mount("/dns", dnsRouter(router))
|
r.Mount("/dns", dnsRouter(router))
|
||||||
|
|
||||||
|
server.setupMetaAPI(r)
|
||||||
})
|
})
|
||||||
if options.ExternalUI != "" {
|
if options.ExternalUI != "" {
|
||||||
|
server.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))
|
||||||
chiRouter.Group(func(r chi.Router) {
|
chiRouter.Group(func(r chi.Router) {
|
||||||
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(C.BasePath(os.ExpandEnv(options.ExternalUI)))))
|
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(server.externalUI)))
|
||||||
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
|
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
|
||||||
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
|
||||||
fs.ServeHTTP(w, r)
|
fs.ServeHTTP(w, r)
|
||||||
@ -126,6 +140,7 @@ func (s *Server) PreStart() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Start() error {
|
func (s *Server) Start() error {
|
||||||
|
s.checkAndDownloadExternalUI()
|
||||||
listener, err := net.Listen("tcp", s.httpServer.Addr)
|
listener, err := net.Listen("tcp", s.httpServer.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "external controller listen error")
|
return E.Cause(err, "external controller listen error")
|
||||||
@ -156,6 +171,10 @@ func (s *Server) StoreSelected() bool {
|
|||||||
return s.storeSelected
|
return s.storeSelected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) StoreFakeIP() bool {
|
||||||
|
return s.storeFakeIP
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) CacheFile() adapter.ClashCacheFile {
|
func (s *Server) CacheFile() adapter.ClashCacheFile {
|
||||||
return s.cacheFile
|
return s.cacheFile
|
||||||
}
|
}
|
||||||
@ -392,5 +411,5 @@ func getLogs(logFactory log.ObservableFactory) func(w http.ResponseWriter, r *ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
func version(w http.ResponseWriter, r *http.Request) {
|
func version(w http.ResponseWriter, r *http.Request) {
|
||||||
render.JSON(w, r, render.M{"version": "sing-box " + C.Version, "premium": true})
|
render.JSON(w, r, render.M{"version": "sing-box " + C.Version, "premium": true, "meta": true})
|
||||||
}
|
}
|
||||||
|
164
experimental/clashapi/server_resources.go
Normal file
164
experimental/clashapi/server_resources.go
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
package clashapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) checkAndDownloadExternalUI() {
|
||||||
|
if s.externalUI == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
entries, err := os.ReadDir(s.externalUI)
|
||||||
|
if err != nil {
|
||||||
|
os.MkdirAll(s.externalUI, 0o755)
|
||||||
|
}
|
||||||
|
if len(entries) == 0 {
|
||||||
|
err = s.downloadExternalUI()
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("download external ui error: ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) downloadExternalUI() error {
|
||||||
|
var downloadURL string
|
||||||
|
if s.externalUIDownloadURL != "" {
|
||||||
|
downloadURL = s.externalUIDownloadURL
|
||||||
|
} else {
|
||||||
|
downloadURL = "https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip"
|
||||||
|
}
|
||||||
|
s.logger.Info("downloading external ui")
|
||||||
|
var detour adapter.Outbound
|
||||||
|
if s.externalUIDownloadDetour != "" {
|
||||||
|
outbound, loaded := s.router.Outbound(s.externalUIDownloadDetour)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("detour outbound not found: ", s.externalUIDownloadDetour)
|
||||||
|
}
|
||||||
|
detour = outbound
|
||||||
|
} else {
|
||||||
|
detour = s.router.DefaultOutbound(N.NetworkTCP)
|
||||||
|
}
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
TLSHandshakeTimeout: 5 * time.Second,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defer httpClient.CloseIdleConnections()
|
||||||
|
response, err := httpClient.Get(downloadURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
return E.New("download external ui failed: ", response.Status)
|
||||||
|
}
|
||||||
|
err = s.downloadZIP(filepath.Base(downloadURL), response.Body, s.externalUI)
|
||||||
|
if err != nil {
|
||||||
|
removeAllInDirectory(s.externalUI)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) downloadZIP(name string, body io.Reader, output string) error {
|
||||||
|
tempFile, err := filemanager.CreateTemp(s.ctx, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.Remove(tempFile.Name())
|
||||||
|
_, err = io.Copy(tempFile, body)
|
||||||
|
tempFile.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reader, err := zip.OpenReader(tempFile.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
trimDir := zipIsInSingleDirectory(reader.File)
|
||||||
|
for _, file := range reader.File {
|
||||||
|
if file.FileInfo().IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pathElements := strings.Split(file.Name, "/")
|
||||||
|
if trimDir {
|
||||||
|
pathElements = pathElements[1:]
|
||||||
|
}
|
||||||
|
saveDirectory := output
|
||||||
|
if len(pathElements) > 1 {
|
||||||
|
saveDirectory = filepath.Join(saveDirectory, filepath.Join(pathElements[:len(pathElements)-1]...))
|
||||||
|
}
|
||||||
|
err = os.MkdirAll(saveDirectory, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
savePath := filepath.Join(saveDirectory, pathElements[len(pathElements)-1])
|
||||||
|
err = downloadZIPEntry(s.ctx, file, savePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadZIPEntry(ctx context.Context, zipFile *zip.File, savePath string) error {
|
||||||
|
saveFile, err := filemanager.Create(ctx, savePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer saveFile.Close()
|
||||||
|
reader, err := zipFile.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer reader.Close()
|
||||||
|
return common.Error(io.Copy(saveFile, reader))
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeAllInDirectory(directory string) {
|
||||||
|
dirEntries, err := os.ReadDir(directory)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, dirEntry := range dirEntries {
|
||||||
|
os.RemoveAll(filepath.Join(directory, dirEntry.Name()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func zipIsInSingleDirectory(files []*zip.File) bool {
|
||||||
|
var singleDirectory string
|
||||||
|
for _, file := range files {
|
||||||
|
if file.FileInfo().IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pathElements := strings.Split(file.Name, "/")
|
||||||
|
if len(pathElements) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if singleDirectory == "" {
|
||||||
|
singleDirectory = pathElements[0]
|
||||||
|
} else if singleDirectory != pathElements[0] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package trafficontrol
|
package trafficontrol
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi/compatible"
|
"github.com/sagernet/sing-box/experimental/clashapi/compatible"
|
||||||
@ -18,12 +19,15 @@ type Manager struct {
|
|||||||
connections compatible.Map[string, tracker]
|
connections compatible.Map[string, tracker]
|
||||||
ticker *time.Ticker
|
ticker *time.Ticker
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
|
// process *process.Process
|
||||||
|
memory uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager() *Manager {
|
func NewManager() *Manager {
|
||||||
manager := &Manager{
|
manager := &Manager{
|
||||||
ticker: time.NewTicker(time.Second),
|
ticker: time.NewTicker(time.Second),
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
|
// process: &process.Process{Pid: int32(os.Getpid())},
|
||||||
}
|
}
|
||||||
go manager.handle()
|
go manager.handle()
|
||||||
return manager
|
return manager
|
||||||
@ -58,10 +62,18 @@ func (m *Manager) Snapshot() *Snapshot {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
//if memoryInfo, err := m.process.MemoryInfo(); err == nil {
|
||||||
|
// m.memory = memoryInfo.RSS
|
||||||
|
//} else {
|
||||||
|
var memStats runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&memStats)
|
||||||
|
m.memory = memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased
|
||||||
|
|
||||||
return &Snapshot{
|
return &Snapshot{
|
||||||
UploadTotal: m.uploadTotal.Load(),
|
UploadTotal: m.uploadTotal.Load(),
|
||||||
DownloadTotal: m.downloadTotal.Load(),
|
DownloadTotal: m.downloadTotal.Load(),
|
||||||
Connections: connections,
|
Connections: connections,
|
||||||
|
Memory: m.memory,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,4 +112,5 @@ type Snapshot struct {
|
|||||||
DownloadTotal int64 `json:"downloadTotal"`
|
DownloadTotal int64 `json:"downloadTotal"`
|
||||||
UploadTotal int64 `json:"uploadTotal"`
|
UploadTotal int64 `json:"uploadTotal"`
|
||||||
Connections []tracker `json:"connections"`
|
Connections []tracker `json:"connections"`
|
||||||
|
Memory uint64 `json:"memory"`
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/experimental/trackerconn"
|
|
||||||
"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"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid/v5"
|
||||||
@ -115,13 +115,13 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad
|
|||||||
download := new(atomic.Int64)
|
download := new(atomic.Int64)
|
||||||
|
|
||||||
t := &tcpTracker{
|
t := &tcpTracker{
|
||||||
ExtendedConn: trackerconn.NewHook(conn, func(n int64) {
|
ExtendedConn: bufio.NewCounterConn(conn, []N.CountFunc{func(n int64) {
|
||||||
upload.Add(n)
|
upload.Add(n)
|
||||||
manager.PushUploaded(n)
|
manager.PushUploaded(n)
|
||||||
}, func(n int64) {
|
}}, []N.CountFunc{func(n int64) {
|
||||||
download.Add(n)
|
download.Add(n)
|
||||||
manager.PushDownloaded(n)
|
manager.PushDownloaded(n)
|
||||||
}),
|
}}),
|
||||||
manager: manager,
|
manager: manager,
|
||||||
trackerInfo: &trackerInfo{
|
trackerInfo: &trackerInfo{
|
||||||
UUID: uuid,
|
UUID: uuid,
|
||||||
@ -202,13 +202,13 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, route
|
|||||||
download := new(atomic.Int64)
|
download := new(atomic.Int64)
|
||||||
|
|
||||||
ut := &udpTracker{
|
ut := &udpTracker{
|
||||||
PacketConn: trackerconn.NewHookPacket(conn, func(n int64) {
|
PacketConn: bufio.NewCounterPacketConn(conn, []N.CountFunc{func(n int64) {
|
||||||
upload.Add(n)
|
upload.Add(n)
|
||||||
manager.PushUploaded(n)
|
manager.PushUploaded(n)
|
||||||
}, func(n int64) {
|
}}, []N.CountFunc{func(n int64) {
|
||||||
download.Add(n)
|
download.Add(n)
|
||||||
manager.PushDownloaded(n)
|
manager.PushDownloaded(n)
|
||||||
}),
|
}}),
|
||||||
manager: manager,
|
manager: manager,
|
||||||
trackerInfo: &trackerInfo{
|
trackerInfo: &trackerInfo{
|
||||||
UUID: uuid,
|
UUID: uuid,
|
||||||
|
@ -27,3 +27,27 @@ func RedirectStderr(path string) error {
|
|||||||
stderrFile = outputFile
|
stderrFile = outputFile
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RedirectStderrAsUser(path string, uid, gid int) error {
|
||||||
|
if stats, err := os.Stat(path); err == nil && stats.Size() > 0 {
|
||||||
|
_ = os.Rename(path, path+".old")
|
||||||
|
}
|
||||||
|
outputFile, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = outputFile.Chown(uid, gid)
|
||||||
|
if err != nil {
|
||||||
|
outputFile.Close()
|
||||||
|
os.Remove(outputFile.Name())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = unix.Dup2(int(outputFile.Fd()), int(os.Stderr.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
outputFile.Close()
|
||||||
|
os.Remove(outputFile.Name())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stderrFile = outputFile
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BoxService struct {
|
type BoxService struct {
|
||||||
@ -30,6 +31,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
ctx = filemanager.WithDefault(ctx, sBasePath, sTempPath, sUserID, sGroupID)
|
||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(box.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Options: options,
|
Options: options,
|
||||||
|
@ -1,13 +1,31 @@
|
|||||||
package libbox
|
package libbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetBasePath(path string) {
|
var (
|
||||||
C.SetBasePath(path)
|
sBasePath string
|
||||||
|
sTempPath string
|
||||||
|
sUserID int
|
||||||
|
sGroupID int
|
||||||
|
)
|
||||||
|
|
||||||
|
func Setup(basePath string, tempPath string, userID int, groupID int) {
|
||||||
|
sBasePath = basePath
|
||||||
|
sTempPath = tempPath
|
||||||
|
sUserID = userID
|
||||||
|
sGroupID = groupID
|
||||||
|
if sUserID == -1 {
|
||||||
|
sUserID = os.Getuid()
|
||||||
|
}
|
||||||
|
if sGroupID == -1 {
|
||||||
|
sGroupID = os.Getgid()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Version() string {
|
func Version() string {
|
||||||
|
@ -1,108 +0,0 @@
|
|||||||
package trackerconn
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func New(conn net.Conn, readCounter []*atomic.Int64, writeCounter []*atomic.Int64) *Conn {
|
|
||||||
return &Conn{bufio.NewExtendedConn(conn), readCounter, writeCounter}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHook(conn net.Conn, readCounter func(n int64), writeCounter func(n int64)) *HookConn {
|
|
||||||
return &HookConn{bufio.NewExtendedConn(conn), readCounter, writeCounter}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Conn struct {
|
|
||||||
N.ExtendedConn
|
|
||||||
readCounter []*atomic.Int64
|
|
||||||
writeCounter []*atomic.Int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Read(p []byte) (n int, err error) {
|
|
||||||
n, err = c.ExtendedConn.Read(p)
|
|
||||||
for _, counter := range c.readCounter {
|
|
||||||
counter.Add(int64(n))
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) ReadBuffer(buffer *buf.Buffer) error {
|
|
||||||
err := c.ExtendedConn.ReadBuffer(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, counter := range c.readCounter {
|
|
||||||
counter.Add(int64(buffer.Len()))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Write(p []byte) (n int, err error) {
|
|
||||||
n, err = c.ExtendedConn.Write(p)
|
|
||||||
for _, counter := range c.writeCounter {
|
|
||||||
counter.Add(int64(n))
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
|
||||||
dataLen := int64(buffer.Len())
|
|
||||||
err := c.ExtendedConn.WriteBuffer(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, counter := range c.writeCounter {
|
|
||||||
counter.Add(dataLen)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Upstream() any {
|
|
||||||
return c.ExtendedConn
|
|
||||||
}
|
|
||||||
|
|
||||||
type HookConn struct {
|
|
||||||
N.ExtendedConn
|
|
||||||
readCounter func(n int64)
|
|
||||||
writeCounter func(n int64)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *HookConn) Read(p []byte) (n int, err error) {
|
|
||||||
n, err = c.ExtendedConn.Read(p)
|
|
||||||
c.readCounter(int64(n))
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *HookConn) ReadBuffer(buffer *buf.Buffer) error {
|
|
||||||
err := c.ExtendedConn.ReadBuffer(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.readCounter(int64(buffer.Len()))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *HookConn) Write(p []byte) (n int, err error) {
|
|
||||||
n, err = c.ExtendedConn.Write(p)
|
|
||||||
c.writeCounter(int64(n))
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *HookConn) WriteBuffer(buffer *buf.Buffer) error {
|
|
||||||
dataLen := int64(buffer.Len())
|
|
||||||
err := c.ExtendedConn.WriteBuffer(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.writeCounter(dataLen)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *HookConn) Upstream() any {
|
|
||||||
return c.ExtendedConn
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
package trackerconn
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewPacket(conn N.PacketConn, readCounter []*atomic.Int64, writeCounter []*atomic.Int64) *PacketConn {
|
|
||||||
return &PacketConn{conn, readCounter, writeCounter}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHookPacket(conn N.PacketConn, readCounter func(n int64), writeCounter func(n int64)) *HookPacketConn {
|
|
||||||
return &HookPacketConn{conn, readCounter, writeCounter}
|
|
||||||
}
|
|
||||||
|
|
||||||
type PacketConn struct {
|
|
||||||
N.PacketConn
|
|
||||||
readCounter []*atomic.Int64
|
|
||||||
writeCounter []*atomic.Int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
|
||||||
destination, err = c.PacketConn.ReadPacket(buffer)
|
|
||||||
if err == nil {
|
|
||||||
for _, counter := range c.readCounter {
|
|
||||||
counter.Add(int64(buffer.Len()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
|
||||||
dataLen := int64(buffer.Len())
|
|
||||||
err := c.PacketConn.WritePacket(buffer, destination)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, counter := range c.writeCounter {
|
|
||||||
counter.Add(dataLen)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PacketConn) Upstream() any {
|
|
||||||
return c.PacketConn
|
|
||||||
}
|
|
||||||
|
|
||||||
type HookPacketConn struct {
|
|
||||||
N.PacketConn
|
|
||||||
readCounter func(n int64)
|
|
||||||
writeCounter func(n int64)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *HookPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
|
||||||
destination, err = c.PacketConn.ReadPacket(buffer)
|
|
||||||
if err == nil {
|
|
||||||
c.readCounter(int64(buffer.Len()))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *HookPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
|
||||||
dataLen := int64(buffer.Len())
|
|
||||||
err := c.PacketConn.WritePacket(buffer, destination)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.writeCounter(dataLen)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *HookPacketConn) Upstream() any {
|
|
||||||
return c.PacketConn
|
|
||||||
}
|
|
@ -10,9 +10,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/experimental/trackerconn"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
"github.com/sagernet/sing/common/atomic"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
@ -83,7 +83,7 @@ func (s *StatsService) RoutedConnection(inbound string, outbound string, user st
|
|||||||
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
|
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
|
||||||
}
|
}
|
||||||
s.access.Unlock()
|
s.access.Unlock()
|
||||||
return trackerconn.New(conn, readCounter, writeCounter)
|
return bufio.NewInt64CounterConn(conn, readCounter, writeCounter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatsService) RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn {
|
func (s *StatsService) RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn {
|
||||||
@ -109,7 +109,7 @@ func (s *StatsService) RoutedPacketConnection(inbound string, outbound string, u
|
|||||||
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
|
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
|
||||||
}
|
}
|
||||||
s.access.Unlock()
|
s.access.Unlock()
|
||||||
return trackerconn.NewPacket(conn, readCounter, writeCounter)
|
return bufio.NewInt64CounterPacketConn(conn, readCounter, writeCounter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {
|
func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {
|
||||||
|
24
go.mod
24
go.mod
@ -4,7 +4,7 @@ go 1.18
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
berty.tech/go-libtor v1.0.385
|
berty.tech/go-libtor v1.0.385
|
||||||
github.com/Dreamacro/clash v1.15.0
|
github.com/Dreamacro/clash v1.15.1
|
||||||
github.com/caddyserver/certmagic v0.17.2
|
github.com/caddyserver/certmagic v0.17.2
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
github.com/dustin/go-humanize v1.0.1
|
github.com/dustin/go-humanize v1.0.1
|
||||||
@ -13,11 +13,10 @@ require (
|
|||||||
github.com/go-chi/cors v1.2.1
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/go-chi/render v1.0.2
|
github.com/go-chi/render v1.0.2
|
||||||
github.com/gofrs/uuid/v5 v5.0.0
|
github.com/gofrs/uuid/v5 v5.0.0
|
||||||
github.com/hashicorp/yamux v0.1.1
|
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16
|
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/mholt/acmez v1.1.0
|
github.com/mholt/acmez v1.1.0
|
||||||
github.com/miekg/dns v1.1.53
|
github.com/miekg/dns v1.1.54
|
||||||
github.com/ooni/go-libtor v1.1.7
|
github.com/ooni/go-libtor v1.1.7
|
||||||
github.com/oschwald/maxminddb-golang v1.10.0
|
github.com/oschwald/maxminddb-golang v1.10.0
|
||||||
github.com/pires/go-proxyproto v0.7.0
|
github.com/pires/go-proxyproto v0.7.0
|
||||||
@ -25,30 +24,32 @@ require (
|
|||||||
github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035
|
github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035
|
||||||
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32
|
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||||
github.com/sagernet/sing v0.2.4
|
github.com/sagernet/sing v0.2.5-0.20230501044132-8365dd48a17a
|
||||||
github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc
|
github.com/sagernet/sing-dns v0.1.5-0.20230426113254-25d948c44223
|
||||||
|
github.com/sagernet/sing-mux v0.0.0-20230425130511-b0a6ffd8406f
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507
|
github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507
|
||||||
|
github.com/sagernet/sing-shadowsocks2 v0.0.0-20230501032827-681c9c4ee0e9
|
||||||
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b
|
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b
|
||||||
github.com/sagernet/sing-tun v0.1.4
|
github.com/sagernet/sing-tun v0.1.5-0.20230422121432-209ec123ca7b
|
||||||
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3
|
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
|
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
|
||||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c
|
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77
|
||||||
github.com/spf13/cobra v1.7.0
|
github.com/spf13/cobra v1.7.0
|
||||||
github.com/stretchr/testify v1.8.2
|
github.com/stretchr/testify v1.8.2
|
||||||
go.etcd.io/bbolt v1.3.7
|
go.etcd.io/bbolt v1.3.7
|
||||||
go.uber.org/zap v1.24.0
|
go.uber.org/zap v1.24.0
|
||||||
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35
|
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35
|
||||||
golang.org/x/crypto v0.8.0
|
golang.org/x/crypto v0.8.0
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
|
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
|
||||||
golang.org/x/net v0.9.0
|
golang.org/x/net v0.9.0
|
||||||
golang.org/x/sys v0.7.0
|
golang.org/x/sys v0.7.0
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
google.golang.org/grpc v1.54.0
|
google.golang.org/grpc v1.54.0
|
||||||
google.golang.org/protobuf v1.30.0
|
google.golang.org/protobuf v1.30.0
|
||||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c
|
gvisor.dev/gvisor v0.0.0-20230415003630-3981d5d5e523
|
||||||
)
|
)
|
||||||
|
|
||||||
//replace github.com/sagernet/sing => ../sing
|
//replace github.com/sagernet/sing => ../sing
|
||||||
@ -63,6 +64,7 @@ require (
|
|||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/google/btree v1.0.1 // indirect
|
github.com/google/btree v1.0.1 // indirect
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // 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
|
||||||
github.com/josharian/native v1.1.0 // indirect
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/klauspost/compress v1.15.15 // indirect
|
github.com/klauspost/compress v1.15.15 // indirect
|
||||||
@ -85,7 +87,7 @@ require (
|
|||||||
go.uber.org/multierr v1.6.0 // indirect
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
golang.org/x/mod v0.8.0 // indirect
|
golang.org/x/mod v0.8.0 // indirect
|
||||||
golang.org/x/text v0.9.0 // indirect
|
golang.org/x/text v0.9.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||||
golang.org/x/tools v0.6.0 // indirect
|
golang.org/x/tools v0.6.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
|
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||||
|
44
go.sum
44
go.sum
@ -1,7 +1,7 @@
|
|||||||
berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw=
|
berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw=
|
||||||
berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw=
|
berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw=
|
||||||
github.com/Dreamacro/clash v1.15.0 h1:mlpD950VEggXZBNahV66hyKDRxcczkj3vymoAt78KyE=
|
github.com/Dreamacro/clash v1.15.1 h1:AwwtrVYbeXg9ZCxpPxAurGXwaR3FxDdHv3rB/vAGXj4=
|
||||||
github.com/Dreamacro/clash v1.15.0/go.mod h1:WNH69bN11LiAdgdSr4hpkEuXVMfBbWyhEKMCTx9BtNE=
|
github.com/Dreamacro/clash v1.15.1/go.mod h1:WNH69bN11LiAdgdSr4hpkEuXVMfBbWyhEKMCTx9BtNE=
|
||||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
@ -70,8 +70,8 @@ github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczG
|
|||||||
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/mholt/acmez v1.1.0 h1:IQ9CGHKOHokorxnffsqDvmmE30mDenO1lptYZ1AYkHY=
|
github.com/mholt/acmez v1.1.0 h1:IQ9CGHKOHokorxnffsqDvmmE30mDenO1lptYZ1AYkHY=
|
||||||
github.com/mholt/acmez v1.1.0/go.mod h1:zwo5+fbLLTowAX8o8ETfQzbDtwGEXnPhkmGdKIP+bgs=
|
github.com/mholt/acmez v1.1.0/go.mod h1:zwo5+fbLLTowAX8o8ETfQzbDtwGEXnPhkmGdKIP+bgs=
|
||||||
github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
|
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
|
||||||
github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
||||||
@ -111,16 +111,20 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL
|
|||||||
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.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
||||||
github.com/sagernet/sing v0.2.4 h1:gC8BR5sglbJZX23RtMyFa8EETP9YEUADhfbEzU1yVbo=
|
github.com/sagernet/sing v0.2.5-0.20230501044132-8365dd48a17a h1:s2kkd/eR3mWGkYioknxhgQzG8uft4VRx9skhqxxeyVQ=
|
||||||
github.com/sagernet/sing v0.2.4/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
github.com/sagernet/sing v0.2.5-0.20230501044132-8365dd48a17a/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||||
github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc h1:hmbuqKv48SAjiKPoqtJGvS5pEHVPZjTHq9CPwQY2cZ4=
|
github.com/sagernet/sing-dns v0.1.5-0.20230426113254-25d948c44223 h1:L4eMuM07iSHY3UCknFnuFuHoe5clZuF2Xnf2wwA6Lwc=
|
||||||
github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc/go.mod h1:ZKuuqgsHRxDahYrzgSgy4vIAGGuKPlIf4hLcNzYzLkY=
|
github.com/sagernet/sing-dns v0.1.5-0.20230426113254-25d948c44223/go.mod h1:ZKuuqgsHRxDahYrzgSgy4vIAGGuKPlIf4hLcNzYzLkY=
|
||||||
|
github.com/sagernet/sing-mux v0.0.0-20230425130511-b0a6ffd8406f h1:iEpOTgBTjt0vZJVXMTqYq13XyIu/337TWbq6WZ3CMWc=
|
||||||
|
github.com/sagernet/sing-mux v0.0.0-20230425130511-b0a6ffd8406f/go.mod h1:pF+RnLvCAOhECrvauy6LYOpBakJ/vuaF1Wm4lPsWryI=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507 h1:bAHZCdWqJkb8LEW98+YsMVDXGRMUVjka8IC+St6ot88=
|
github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507 h1:bAHZCdWqJkb8LEW98+YsMVDXGRMUVjka8IC+St6ot88=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507/go.mod h1:UJjvQGw0lyYaDGIDvUraL16fwaAEH1WFw1Y6sUcMPog=
|
github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507/go.mod h1:UJjvQGw0lyYaDGIDvUraL16fwaAEH1WFw1Y6sUcMPog=
|
||||||
|
github.com/sagernet/sing-shadowsocks2 v0.0.0-20230501032827-681c9c4ee0e9 h1:0Dc1t9ao9EyvRil6l/950PLwND1qO1rgnxwbcctE8KE=
|
||||||
|
github.com/sagernet/sing-shadowsocks2 v0.0.0-20230501032827-681c9c4ee0e9/go.mod h1:Dpib342FFR68SZ3CSRYxk/zWbanAqRBrCxoLuda5I0A=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b h1:ouW/6IDCrxkBe19YSbdCd7buHix7b+UZ6BM4Zz74XF4=
|
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b h1:ouW/6IDCrxkBe19YSbdCd7buHix7b+UZ6BM4Zz74XF4=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b/go.mod h1:oG8bPerYI6cZ74KquY3DvA7ynECyrILPBnce6wtBqeI=
|
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b/go.mod h1:oG8bPerYI6cZ74KquY3DvA7ynECyrILPBnce6wtBqeI=
|
||||||
github.com/sagernet/sing-tun v0.1.4 h1:Fa6kgvuM2fPbPu3R97S8L8NgaD5lJq3wQorNuTb5oqo=
|
github.com/sagernet/sing-tun v0.1.5-0.20230422121432-209ec123ca7b h1:9NsciSJGwzdkXwVvT2c2g+RvkTVkANeBLr2l+soJ7LM=
|
||||||
github.com/sagernet/sing-tun v0.1.4/go.mod h1:7BrtP7NMp9FK5oVsZWg92b7yFrD+sM2+udapFurReyw=
|
github.com/sagernet/sing-tun v0.1.5-0.20230422121432-209ec123ca7b/go.mod h1:DD7Ce2Gt0GFc6I/1+Uw4D/aUlBsGqrQsC52CMK/V818=
|
||||||
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3 h1:BHOnxrbC929JonuKqFdJ7ZbDp7zs4oTlH5KFvKtWu9U=
|
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3 h1:BHOnxrbC929JonuKqFdJ7ZbDp7zs4oTlH5KFvKtWu9U=
|
||||||
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3/go.mod h1:yKrAr+dqZd64DxBXCHWrYicp+n4qbqO73mtwv3dck8U=
|
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3/go.mod h1:yKrAr+dqZd64DxBXCHWrYicp+n4qbqO73mtwv3dck8U=
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
||||||
@ -131,8 +135,8 @@ github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfI
|
|||||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 h1:g6QtRWQ2dKX7EQP++1JLNtw4C2TNxd4/ov8YUpOPOSo=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
|
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77/go.mod h1:pJDdXzZIwJ+2vmnT0TKzmf8meeum+e2mTDSehw79eE0=
|
||||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
@ -170,8 +174,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
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.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
@ -208,8 +212,8 @@ 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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
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.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
@ -219,8 +223,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde h1:ybF7AMzIUikL9x4LgwEmzhXtzRpKNqngme1VGDWz+Nk=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:mQqgjkW8GQQcJQsbBvK890TKqUK1DfKWkuBGbOkuMHQ=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
||||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
|
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
|
||||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
||||||
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
||||||
@ -236,7 +240,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
gvisor.dev/gvisor v0.0.0-20230415003630-3981d5d5e523 h1:zUQYeyyPLnSR6yMvLSOmLH37xDWCZ7BqlpE69fE5K3Q=
|
||||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
|
gvisor.dev/gvisor v0.0.0-20230415003630-3981d5d5e523/go.mod h1:pzr6sy8gDLfVmDAg8OYrlKvGEHw5C3PGTiBXBTCx76Q=
|
||||||
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
||||||
lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
|
lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
|
||||||
|
@ -9,6 +9,8 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-shadowsocks"
|
||||||
|
"github.com/sagernet/sing-shadowsocks/shadowaead"
|
||||||
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/auth"
|
"github.com/sagernet/sing/common/auth"
|
||||||
@ -25,7 +27,7 @@ var (
|
|||||||
|
|
||||||
type ShadowsocksMulti struct {
|
type ShadowsocksMulti struct {
|
||||||
myInboundAdapter
|
myInboundAdapter
|
||||||
service *shadowaead_2022.MultiService[int]
|
service shadowsocks.MultiService[int]
|
||||||
users []option.ShadowsocksUser
|
users []option.ShadowsocksUser
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,16 +51,26 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.
|
|||||||
} else {
|
} else {
|
||||||
udpTimeout = int64(C.UDPTimeout.Seconds())
|
udpTimeout = int64(C.UDPTimeout.Seconds())
|
||||||
}
|
}
|
||||||
if !common.Contains(shadowaead_2022.List, options.Method) {
|
var (
|
||||||
|
service shadowsocks.MultiService[int]
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if common.Contains(shadowaead_2022.List, options.Method) {
|
||||||
|
service, err = shadowaead_2022.NewMultiServiceWithPassword[int](
|
||||||
|
options.Method,
|
||||||
|
options.Password,
|
||||||
|
udpTimeout,
|
||||||
|
adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound),
|
||||||
|
router.TimeFunc(),
|
||||||
|
)
|
||||||
|
} else if common.Contains(shadowaead.List, options.Method) {
|
||||||
|
service, err = shadowaead.NewMultiService[int](
|
||||||
|
options.Method,
|
||||||
|
udpTimeout,
|
||||||
|
adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound))
|
||||||
|
} else {
|
||||||
return nil, E.New("unsupported method: " + options.Method)
|
return nil, E.New("unsupported method: " + options.Method)
|
||||||
}
|
}
|
||||||
service, err := shadowaead_2022.NewMultiServiceWithPassword[int](
|
|
||||||
options.Method,
|
|
||||||
options.Password,
|
|
||||||
udpTimeout,
|
|
||||||
adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound),
|
|
||||||
router.TimeFunc(),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,10 @@ import (
|
|||||||
"github.com/sagernet/sing/common/ranges"
|
"github.com/sagernet/sing/common/ranges"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.Inbound = (*Tun)(nil)
|
var (
|
||||||
|
_ adapter.Inbound = (*Tun)(nil)
|
||||||
|
_ tun.Router = (*Tun)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
type Tun struct {
|
type Tun struct {
|
||||||
tag string
|
tag string
|
||||||
@ -38,10 +41,6 @@ type Tun struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
tunName := options.InterfaceName
|
|
||||||
if tunName == "" {
|
|
||||||
tunName = tun.CalculateInterfaceName("")
|
|
||||||
}
|
|
||||||
tunMTU := options.MTU
|
tunMTU := options.MTU
|
||||||
if tunMTU == 0 {
|
if tunMTU == 0 {
|
||||||
tunMTU = 9000
|
tunMTU = 9000
|
||||||
@ -75,7 +74,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
inboundOptions: options.InboundOptions,
|
inboundOptions: options.InboundOptions,
|
||||||
tunOptions: tun.Options{
|
tunOptions: tun.Options{
|
||||||
Name: tunName,
|
Name: options.InterfaceName,
|
||||||
MTU: tunMTU,
|
MTU: tunMTU,
|
||||||
Inet4Address: common.Map(options.Inet4Address, option.ListenPrefix.Build),
|
Inet4Address: common.Map(options.Inet4Address, option.ListenPrefix.Build),
|
||||||
Inet6Address: common.Map(options.Inet6Address, option.ListenPrefix.Build),
|
Inet6Address: common.Map(options.Inet6Address, option.ListenPrefix.Build),
|
||||||
@ -141,12 +140,17 @@ func (t *Tun) Tag() string {
|
|||||||
|
|
||||||
func (t *Tun) Start() error {
|
func (t *Tun) Start() error {
|
||||||
if C.IsAndroid && t.platformInterface == nil {
|
if C.IsAndroid && t.platformInterface == nil {
|
||||||
|
t.logger.Trace("building android rules")
|
||||||
t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t)
|
t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t)
|
||||||
}
|
}
|
||||||
|
if t.tunOptions.Name == "" {
|
||||||
|
t.tunOptions.Name = tun.CalculateInterfaceName("")
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
tunInterface tun.Tun
|
tunInterface tun.Tun
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
t.logger.Trace("opening interface")
|
||||||
if t.platformInterface != nil {
|
if t.platformInterface != nil {
|
||||||
tunInterface, err = t.platformInterface.OpenTun(&t.tunOptions, t.platformOptions)
|
tunInterface, err = t.platformInterface.OpenTun(&t.tunOptions, t.platformOptions)
|
||||||
} else {
|
} else {
|
||||||
@ -155,7 +159,12 @@ func (t *Tun) Start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "configure tun interface")
|
return E.Cause(err, "configure tun interface")
|
||||||
}
|
}
|
||||||
|
t.logger.Trace("creating stack")
|
||||||
t.tunIf = tunInterface
|
t.tunIf = tunInterface
|
||||||
|
var tunRouter tun.Router
|
||||||
|
if len(t.router.IPRules()) > 0 {
|
||||||
|
tunRouter = t
|
||||||
|
}
|
||||||
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
|
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
|
||||||
Context: t.ctx,
|
Context: t.ctx,
|
||||||
Tun: tunInterface,
|
Tun: tunInterface,
|
||||||
@ -165,13 +174,16 @@ func (t *Tun) Start() error {
|
|||||||
Inet6Address: t.tunOptions.Inet6Address,
|
Inet6Address: t.tunOptions.Inet6Address,
|
||||||
EndpointIndependentNat: t.endpointIndependentNat,
|
EndpointIndependentNat: t.endpointIndependentNat,
|
||||||
UDPTimeout: t.udpTimeout,
|
UDPTimeout: t.udpTimeout,
|
||||||
|
Router: tunRouter,
|
||||||
Handler: t,
|
Handler: t,
|
||||||
Logger: t.logger,
|
Logger: t.logger,
|
||||||
ForwarderBindInterface: t.platformInterface != nil,
|
ForwarderBindInterface: t.platformInterface != nil,
|
||||||
|
InterfaceFinder: t.router.InterfaceFinder(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
t.logger.Trace("starting stack")
|
||||||
err = t.tunStack.Start()
|
err = t.tunStack.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -187,6 +199,21 @@ func (t *Tun) Close() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tun) RouteConnection(session tun.RouteSession, conn tun.RouteContext) tun.RouteAction {
|
||||||
|
ctx := log.ContextWithNewID(t.ctx)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
metadata.Inbound = t.tag
|
||||||
|
metadata.InboundType = C.TypeTun
|
||||||
|
metadata.IPVersion = session.IPVersion
|
||||||
|
metadata.Network = tun.NetworkName(session.Network)
|
||||||
|
metadata.Source = M.SocksaddrFromNetIP(session.Source)
|
||||||
|
metadata.Destination = M.SocksaddrFromNetIP(session.Destination)
|
||||||
|
metadata.InboundOptions = t.inboundOptions
|
||||||
|
t.logger.DebugContext(ctx, "incoming connection from ", metadata.Source)
|
||||||
|
t.logger.DebugContext(ctx, "incoming connection to ", metadata.Destination)
|
||||||
|
return t.router.RouteIPConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error {
|
func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error {
|
||||||
ctx = log.ContextWithNewID(ctx)
|
ctx = log.ContextWithNewID(ctx)
|
||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
package include
|
package include
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@ -11,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
experimental.RegisterClashServerConstructor(func(router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
experimental.RegisterClashServerConstructor(func(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
||||||
return nil, E.New(`clash api is not included in this build, rebuild with -tags with_clash_api`)
|
return nil, E.New(`clash api is not included in this build, rebuild with -tags with_clash_api`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
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"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
type factoryWithFile struct {
|
type factoryWithFile struct {
|
||||||
@ -36,6 +37,7 @@ func (f *observableFactoryWithFile) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
|
Context context.Context
|
||||||
Options option.LogOptions
|
Options option.LogOptions
|
||||||
Observable bool
|
Observable bool
|
||||||
DefaultWriter io.Writer
|
DefaultWriter io.Writer
|
||||||
@ -65,7 +67,7 @@ func New(options Options) (Factory, error) {
|
|||||||
logWriter = os.Stdout
|
logWriter = os.Stdout
|
||||||
default:
|
default:
|
||||||
var err error
|
var err error
|
||||||
logFile, err = os.OpenFile(C.BasePath(logOptions.Output), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
logFile, err = filemanager.OpenFile(options.Context, logOptions.Output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -48,12 +48,14 @@ nav:
|
|||||||
- configuration/dns/index.md
|
- configuration/dns/index.md
|
||||||
- DNS Server: configuration/dns/server.md
|
- DNS Server: configuration/dns/server.md
|
||||||
- DNS Rule: configuration/dns/rule.md
|
- DNS Rule: configuration/dns/rule.md
|
||||||
|
- FakeIP: configuration/dns/fakeip.md
|
||||||
- NTP:
|
- NTP:
|
||||||
- configuration/ntp/index.md
|
- configuration/ntp/index.md
|
||||||
- Route:
|
- Route:
|
||||||
- configuration/route/index.md
|
- configuration/route/index.md
|
||||||
- GeoIP: configuration/route/geoip.md
|
- GeoIP: configuration/route/geoip.md
|
||||||
- Geosite: configuration/route/geosite.md
|
- Geosite: configuration/route/geosite.md
|
||||||
|
- IP Route Rule: configuration/route/ip-rule.md
|
||||||
- Route Rule: configuration/route/rule.md
|
- Route Rule: configuration/route/rule.md
|
||||||
- Protocol Sniff: configuration/route/sniff.md
|
- Protocol Sniff: configuration/route/sniff.md
|
||||||
- Experimental:
|
- Experimental:
|
||||||
@ -102,6 +104,7 @@ nav:
|
|||||||
- URLTest: configuration/outbound/urltest.md
|
- URLTest: configuration/outbound/urltest.md
|
||||||
- FAQ:
|
- FAQ:
|
||||||
- faq/index.md
|
- faq/index.md
|
||||||
|
- FakeIP: faq/fakeip.md
|
||||||
- Known Issues: faq/known-issues.md
|
- Known Issues: faq/known-issues.md
|
||||||
- Examples:
|
- Examples:
|
||||||
- examples/index.md
|
- examples/index.md
|
||||||
@ -111,6 +114,7 @@ nav:
|
|||||||
- Shadowsocks: examples/shadowsocks.md
|
- Shadowsocks: examples/shadowsocks.md
|
||||||
- ShadowTLS: examples/shadowtls.md
|
- ShadowTLS: examples/shadowtls.md
|
||||||
- Clash API: examples/clash-api.md
|
- Clash API: examples/clash-api.md
|
||||||
|
- WireGuard Direct: examples/wireguard-direct.md
|
||||||
- Contributing:
|
- Contributing:
|
||||||
- contributing/index.md
|
- contributing/index.md
|
||||||
- Developing:
|
- Developing:
|
||||||
@ -143,6 +147,7 @@ extra:
|
|||||||
link: https://github.com/SagerNet/sing-box
|
link: https://github.com/SagerNet/sing-box
|
||||||
generator: false
|
generator: false
|
||||||
plugins:
|
plugins:
|
||||||
|
- search
|
||||||
- i18n:
|
- i18n:
|
||||||
default_language: en
|
default_language: en
|
||||||
languages:
|
languages:
|
||||||
@ -169,6 +174,7 @@ plugins:
|
|||||||
DNS Rule: DNS 规则
|
DNS Rule: DNS 规则
|
||||||
|
|
||||||
Route: 路由
|
Route: 路由
|
||||||
|
IP Route Rule: IP 路由规则
|
||||||
Route Rule: 路由规则
|
Route Rule: 路由规则
|
||||||
Protocol Sniff: 协议探测
|
Protocol Sniff: 协议探测
|
||||||
|
|
||||||
@ -187,4 +193,4 @@ plugins:
|
|||||||
Known Issues: 已知问题
|
Known Issues: 已知问题
|
||||||
Examples: 示例
|
Examples: 示例
|
||||||
Linux Server Installation: Linux 服务器安装
|
Linux Server Installation: Linux 服务器安装
|
||||||
DNS Hijack: DNS 劫持
|
DNS Hijack: DNS 劫持
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
type ClashAPIOptions struct {
|
type ClashAPIOptions struct {
|
||||||
ExternalController string `json:"external_controller,omitempty"`
|
ExternalController string `json:"external_controller,omitempty"`
|
||||||
ExternalUI string `json:"external_ui,omitempty"`
|
ExternalUI string `json:"external_ui,omitempty"`
|
||||||
Secret string `json:"secret,omitempty"`
|
ExternalUIDownloadURL string `json:"external_ui_download_url,omitempty"`
|
||||||
DefaultMode string `json:"default_mode,omitempty"`
|
ExternalUIDownloadDetour string `json:"external_ui_download_detour,omitempty"`
|
||||||
StoreSelected bool `json:"store_selected,omitempty"`
|
Secret string `json:"secret,omitempty"`
|
||||||
CacheFile string `json:"cache_file,omitempty"`
|
DefaultMode string `json:"default_mode,omitempty"`
|
||||||
|
StoreSelected bool `json:"store_selected,omitempty"`
|
||||||
|
StoreFakeIP bool `json:"store_fakeip,omitempty"`
|
||||||
|
CacheFile string `json:"cache_file,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SelectorOutboundOptions struct {
|
type SelectorOutboundOptions struct {
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DebugOptions struct {
|
type DebugOptions struct {
|
||||||
|
Listen string `json:"listen,omitempty"`
|
||||||
GCPercent *int `json:"gc_percent,omitempty"`
|
GCPercent *int `json:"gc_percent,omitempty"`
|
||||||
MaxStack *int `json:"max_stack,omitempty"`
|
MaxStack *int `json:"max_stack,omitempty"`
|
||||||
MaxThreads *int `json:"max_threads,omitempty"`
|
MaxThreads *int `json:"max_threads,omitempty"`
|
||||||
|
122
option/dns.go
122
option/dns.go
@ -1,27 +1,14 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/json"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DNSOptions struct {
|
type DNSOptions struct {
|
||||||
Servers []DNSServerOptions `json:"servers,omitempty"`
|
Servers []DNSServerOptions `json:"servers,omitempty"`
|
||||||
Rules []DNSRule `json:"rules,omitempty"`
|
Rules []DNSRule `json:"rules,omitempty"`
|
||||||
Final string `json:"final,omitempty"`
|
Final string `json:"final,omitempty"`
|
||||||
|
ReverseMapping bool `json:"reverse_mapping,omitempty"`
|
||||||
|
FakeIP *DNSFakeIPOptions `json:"fakeip,omitempty"`
|
||||||
DNSClientOptions
|
DNSClientOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSClientOptions struct {
|
|
||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
|
||||||
DisableExpire bool `json:"disable_expire,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DNSServerOptions struct {
|
type DNSServerOptions struct {
|
||||||
Tag string `json:"tag,omitempty"`
|
Tag string `json:"tag,omitempty"`
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
@ -32,96 +19,15 @@ type DNSServerOptions struct {
|
|||||||
Detour string `json:"detour,omitempty"`
|
Detour string `json:"detour,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type _DNSRule struct {
|
type DNSClientOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||||
DefaultOptions DefaultDNSRule `json:"-"`
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
LogicalOptions LogicalDNSRule `json:"-"`
|
DisableExpire bool `json:"disable_expire,omitempty"`
|
||||||
|
IndependentCache bool `json:"independent_cache,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSRule _DNSRule
|
type DNSFakeIPOptions struct {
|
||||||
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
func (r DNSRule) MarshalJSON() ([]byte, error) {
|
Inet4Range *ListenPrefix `json:"inet4_range,omitempty"`
|
||||||
var v any
|
Inet6Range *ListenPrefix `json:"inet6_range,omitempty"`
|
||||||
switch r.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
r.Type = ""
|
|
||||||
v = r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return nil, E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
return MarshallObjects((_DNSRule)(r), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
|
|
||||||
err := json.Unmarshal(bytes, (*_DNSRule)(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var v any
|
|
||||||
switch r.Type {
|
|
||||||
case "", C.RuleTypeDefault:
|
|
||||||
r.Type = C.RuleTypeDefault
|
|
||||||
v = &r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = &r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "dns route rule")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultDNSRule struct {
|
|
||||||
Inbound Listable[string] `json:"inbound,omitempty"`
|
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
|
||||||
QueryType Listable[DNSQueryType] `json:"query_type,omitempty"`
|
|
||||||
Network string `json:"network,omitempty"`
|
|
||||||
AuthUser Listable[string] `json:"auth_user,omitempty"`
|
|
||||||
Protocol Listable[string] `json:"protocol,omitempty"`
|
|
||||||
Domain Listable[string] `json:"domain,omitempty"`
|
|
||||||
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
|
||||||
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
|
||||||
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
|
||||||
Geosite Listable[string] `json:"geosite,omitempty"`
|
|
||||||
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
|
||||||
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
|
||||||
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
|
||||||
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
|
||||||
Port Listable[uint16] `json:"port,omitempty"`
|
|
||||||
PortRange Listable[string] `json:"port_range,omitempty"`
|
|
||||||
ProcessName Listable[string] `json:"process_name,omitempty"`
|
|
||||||
ProcessPath Listable[string] `json:"process_path,omitempty"`
|
|
||||||
PackageName Listable[string] `json:"package_name,omitempty"`
|
|
||||||
User Listable[string] `json:"user,omitempty"`
|
|
||||||
UserID Listable[int32] `json:"user_id,omitempty"`
|
|
||||||
Outbound Listable[string] `json:"outbound,omitempty"`
|
|
||||||
ClashMode string `json:"clash_mode,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Server string `json:"server,omitempty"`
|
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r DefaultDNSRule) IsValid() bool {
|
|
||||||
var defaultValue DefaultDNSRule
|
|
||||||
defaultValue.Invert = r.Invert
|
|
||||||
defaultValue.Server = r.Server
|
|
||||||
defaultValue.DisableCache = r.DisableCache
|
|
||||||
return !reflect.DeepEqual(r, defaultValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogicalDNSRule struct {
|
|
||||||
Mode string `json:"mode"`
|
|
||||||
Rules []DefaultDNSRule `json:"rules,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Server string `json:"server,omitempty"`
|
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r LogicalDNSRule) IsValid() bool {
|
|
||||||
return len(r.Rules) > 0 && common.All(r.Rules, DefaultDNSRule.IsValid)
|
|
||||||
}
|
}
|
||||||
|
@ -150,4 +150,5 @@ type MultiplexOptions struct {
|
|||||||
MaxConnections int `json:"max_connections,omitempty"`
|
MaxConnections int `json:"max_connections,omitempty"`
|
||||||
MinStreams int `json:"min_streams,omitempty"`
|
MinStreams int `json:"min_streams,omitempty"`
|
||||||
MaxStreams int `json:"max_streams,omitempty"`
|
MaxStreams int `json:"max_streams,omitempty"`
|
||||||
|
Padding bool `json:"padding,omitempty"`
|
||||||
}
|
}
|
||||||
|
101
option/route.go
101
option/route.go
@ -1,17 +1,9 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/json"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RouteOptions struct {
|
type RouteOptions struct {
|
||||||
GeoIP *GeoIPOptions `json:"geoip,omitempty"`
|
GeoIP *GeoIPOptions `json:"geoip,omitempty"`
|
||||||
Geosite *GeositeOptions `json:"geosite,omitempty"`
|
Geosite *GeositeOptions `json:"geosite,omitempty"`
|
||||||
|
IPRules []IPRule `json:"ip_rules,omitempty"`
|
||||||
Rules []Rule `json:"rules,omitempty"`
|
Rules []Rule `json:"rules,omitempty"`
|
||||||
Final string `json:"final,omitempty"`
|
Final string `json:"final,omitempty"`
|
||||||
FindProcess bool `json:"find_process,omitempty"`
|
FindProcess bool `json:"find_process,omitempty"`
|
||||||
@ -32,94 +24,3 @@ type GeositeOptions struct {
|
|||||||
DownloadURL string `json:"download_url,omitempty"`
|
DownloadURL string `json:"download_url,omitempty"`
|
||||||
DownloadDetour string `json:"download_detour,omitempty"`
|
DownloadDetour string `json:"download_detour,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type _Rule struct {
|
|
||||||
Type string `json:"type,omitempty"`
|
|
||||||
DefaultOptions DefaultRule `json:"-"`
|
|
||||||
LogicalOptions LogicalRule `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Rule _Rule
|
|
||||||
|
|
||||||
func (r Rule) MarshalJSON() ([]byte, error) {
|
|
||||||
var v any
|
|
||||||
switch r.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
r.Type = ""
|
|
||||||
v = r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return nil, E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
return MarshallObjects((_Rule)(r), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Rule) UnmarshalJSON(bytes []byte) error {
|
|
||||||
err := json.Unmarshal(bytes, (*_Rule)(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var v any
|
|
||||||
switch r.Type {
|
|
||||||
case "", C.RuleTypeDefault:
|
|
||||||
r.Type = C.RuleTypeDefault
|
|
||||||
v = &r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = &r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
err = UnmarshallExcluded(bytes, (*_Rule)(r), v)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "route rule")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultRule struct {
|
|
||||||
Inbound Listable[string] `json:"inbound,omitempty"`
|
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
|
||||||
Network string `json:"network,omitempty"`
|
|
||||||
AuthUser Listable[string] `json:"auth_user,omitempty"`
|
|
||||||
Protocol Listable[string] `json:"protocol,omitempty"`
|
|
||||||
Domain Listable[string] `json:"domain,omitempty"`
|
|
||||||
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
|
||||||
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
|
||||||
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
|
||||||
Geosite Listable[string] `json:"geosite,omitempty"`
|
|
||||||
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
|
||||||
GeoIP Listable[string] `json:"geoip,omitempty"`
|
|
||||||
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
|
||||||
IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
|
|
||||||
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
|
||||||
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
|
||||||
Port Listable[uint16] `json:"port,omitempty"`
|
|
||||||
PortRange Listable[string] `json:"port_range,omitempty"`
|
|
||||||
ProcessName Listable[string] `json:"process_name,omitempty"`
|
|
||||||
ProcessPath Listable[string] `json:"process_path,omitempty"`
|
|
||||||
PackageName Listable[string] `json:"package_name,omitempty"`
|
|
||||||
User Listable[string] `json:"user,omitempty"`
|
|
||||||
UserID Listable[int32] `json:"user_id,omitempty"`
|
|
||||||
ClashMode string `json:"clash_mode,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Outbound string `json:"outbound,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r DefaultRule) IsValid() bool {
|
|
||||||
var defaultValue DefaultRule
|
|
||||||
defaultValue.Invert = r.Invert
|
|
||||||
defaultValue.Outbound = r.Outbound
|
|
||||||
return !reflect.DeepEqual(r, defaultValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogicalRule struct {
|
|
||||||
Mode string `json:"mode"`
|
|
||||||
Rules []DefaultRule `json:"rules,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Outbound string `json:"outbound,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r LogicalRule) IsValid() bool {
|
|
||||||
return len(r.Rules) > 0 && common.All(r.Rules, DefaultRule.IsValid)
|
|
||||||
}
|
|
||||||
|
101
option/rule.go
Normal file
101
option/rule.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _Rule struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
DefaultOptions DefaultRule `json:"-"`
|
||||||
|
LogicalOptions LogicalRule `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rule _Rule
|
||||||
|
|
||||||
|
func (r Rule) MarshalJSON() ([]byte, error) {
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
r.Type = ""
|
||||||
|
v = r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
return MarshallObjects((_Rule)(r), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rule) UnmarshalJSON(bytes []byte) error {
|
||||||
|
err := json.Unmarshal(bytes, (*_Rule)(r))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case "", C.RuleTypeDefault:
|
||||||
|
r.Type = C.RuleTypeDefault
|
||||||
|
v = &r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = &r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
err = UnmarshallExcluded(bytes, (*_Rule)(r), v)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "route rule")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultRule struct {
|
||||||
|
Inbound Listable[string] `json:"inbound,omitempty"`
|
||||||
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
|
Network Listable[string] `json:"network,omitempty"`
|
||||||
|
AuthUser Listable[string] `json:"auth_user,omitempty"`
|
||||||
|
Protocol Listable[string] `json:"protocol,omitempty"`
|
||||||
|
Domain Listable[string] `json:"domain,omitempty"`
|
||||||
|
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
|
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
|
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
||||||
|
Geosite Listable[string] `json:"geosite,omitempty"`
|
||||||
|
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
||||||
|
GeoIP Listable[string] `json:"geoip,omitempty"`
|
||||||
|
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
|
IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
|
||||||
|
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
||||||
|
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
||||||
|
Port Listable[uint16] `json:"port,omitempty"`
|
||||||
|
PortRange Listable[string] `json:"port_range,omitempty"`
|
||||||
|
ProcessName Listable[string] `json:"process_name,omitempty"`
|
||||||
|
ProcessPath Listable[string] `json:"process_path,omitempty"`
|
||||||
|
PackageName Listable[string] `json:"package_name,omitempty"`
|
||||||
|
User Listable[string] `json:"user,omitempty"`
|
||||||
|
UserID Listable[int32] `json:"user_id,omitempty"`
|
||||||
|
ClashMode string `json:"clash_mode,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r DefaultRule) IsValid() bool {
|
||||||
|
var defaultValue DefaultRule
|
||||||
|
defaultValue.Invert = r.Invert
|
||||||
|
defaultValue.Outbound = r.Outbound
|
||||||
|
return !reflect.DeepEqual(r, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogicalRule struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Rules []DefaultRule `json:"rules,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r LogicalRule) IsValid() bool {
|
||||||
|
return len(r.Rules) > 0 && common.All(r.Rules, DefaultRule.IsValid)
|
||||||
|
}
|
107
option/rule_dns.go
Normal file
107
option/rule_dns.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _DNSRule struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
DefaultOptions DefaultDNSRule `json:"-"`
|
||||||
|
LogicalOptions LogicalDNSRule `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSRule _DNSRule
|
||||||
|
|
||||||
|
func (r DNSRule) MarshalJSON() ([]byte, error) {
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
r.Type = ""
|
||||||
|
v = r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
return MarshallObjects((_DNSRule)(r), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
|
||||||
|
err := json.Unmarshal(bytes, (*_DNSRule)(r))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case "", C.RuleTypeDefault:
|
||||||
|
r.Type = C.RuleTypeDefault
|
||||||
|
v = &r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = &r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "dns route rule")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultDNSRule struct {
|
||||||
|
Inbound Listable[string] `json:"inbound,omitempty"`
|
||||||
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
|
QueryType Listable[DNSQueryType] `json:"query_type,omitempty"`
|
||||||
|
Network Listable[string] `json:"network,omitempty"`
|
||||||
|
AuthUser Listable[string] `json:"auth_user,omitempty"`
|
||||||
|
Protocol Listable[string] `json:"protocol,omitempty"`
|
||||||
|
Domain Listable[string] `json:"domain,omitempty"`
|
||||||
|
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
|
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
|
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
||||||
|
Geosite Listable[string] `json:"geosite,omitempty"`
|
||||||
|
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
||||||
|
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
|
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
||||||
|
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
||||||
|
Port Listable[uint16] `json:"port,omitempty"`
|
||||||
|
PortRange Listable[string] `json:"port_range,omitempty"`
|
||||||
|
ProcessName Listable[string] `json:"process_name,omitempty"`
|
||||||
|
ProcessPath Listable[string] `json:"process_path,omitempty"`
|
||||||
|
PackageName Listable[string] `json:"package_name,omitempty"`
|
||||||
|
User Listable[string] `json:"user,omitempty"`
|
||||||
|
UserID Listable[int32] `json:"user_id,omitempty"`
|
||||||
|
Outbound Listable[string] `json:"outbound,omitempty"`
|
||||||
|
ClashMode string `json:"clash_mode,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Server string `json:"server,omitempty"`
|
||||||
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
|
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r DefaultDNSRule) IsValid() bool {
|
||||||
|
var defaultValue DefaultDNSRule
|
||||||
|
defaultValue.Invert = r.Invert
|
||||||
|
defaultValue.Server = r.Server
|
||||||
|
defaultValue.DisableCache = r.DisableCache
|
||||||
|
defaultValue.RewriteTTL = r.RewriteTTL
|
||||||
|
return !reflect.DeepEqual(r, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogicalDNSRule struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Rules []DefaultDNSRule `json:"rules,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Server string `json:"server,omitempty"`
|
||||||
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
|
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r LogicalDNSRule) IsValid() bool {
|
||||||
|
return len(r.Rules) > 0 && common.All(r.Rules, DefaultDNSRule.IsValid)
|
||||||
|
}
|
120
option/rule_ip.go
Normal file
120
option/rule_ip.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _IPRule struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
DefaultOptions DefaultIPRule `json:"-"`
|
||||||
|
LogicalOptions LogicalIPRule `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPRule _IPRule
|
||||||
|
|
||||||
|
func (r IPRule) MarshalJSON() ([]byte, error) {
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
r.Type = ""
|
||||||
|
v = r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
return MarshallObjects((_IPRule)(r), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IPRule) UnmarshalJSON(bytes []byte) error {
|
||||||
|
err := json.Unmarshal(bytes, (*_IPRule)(r))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case "", C.RuleTypeDefault:
|
||||||
|
r.Type = C.RuleTypeDefault
|
||||||
|
v = &r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = &r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
err = UnmarshallExcluded(bytes, (*_IPRule)(r), v)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "ip route rule")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultIPRule struct {
|
||||||
|
Inbound Listable[string] `json:"inbound,omitempty"`
|
||||||
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
|
Network Listable[string] `json:"network,omitempty"`
|
||||||
|
Domain Listable[string] `json:"domain,omitempty"`
|
||||||
|
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
|
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
|
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
||||||
|
Geosite Listable[string] `json:"geosite,omitempty"`
|
||||||
|
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
||||||
|
GeoIP Listable[string] `json:"geoip,omitempty"`
|
||||||
|
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
|
IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
|
||||||
|
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
||||||
|
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
||||||
|
Port Listable[uint16] `json:"port,omitempty"`
|
||||||
|
PortRange Listable[string] `json:"port_range,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Action RouteAction `json:"action,omitempty"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RouteAction tun.ActionType
|
||||||
|
|
||||||
|
func (a RouteAction) MarshalJSON() ([]byte, error) {
|
||||||
|
typeName, err := tun.ActionTypeName(tun.ActionType(a))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(typeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RouteAction) UnmarshalJSON(bytes []byte) error {
|
||||||
|
var value string
|
||||||
|
err := json.Unmarshal(bytes, &value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
actionType, err := tun.ParseActionType(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*a = RouteAction(actionType)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r DefaultIPRule) IsValid() bool {
|
||||||
|
var defaultValue DefaultIPRule
|
||||||
|
defaultValue.Invert = r.Invert
|
||||||
|
defaultValue.Outbound = r.Outbound
|
||||||
|
return !reflect.DeepEqual(r, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogicalIPRule struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Rules []DefaultIPRule `json:"rules,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Action RouteAction `json:"action,omitempty"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r LogicalIPRule) IsValid() bool {
|
||||||
|
return len(r.Rules) > 0 && common.All(r.Rules, DefaultIPRule.IsValid)
|
||||||
|
}
|
@ -4,7 +4,7 @@ type ShadowsocksInboundOptions struct {
|
|||||||
ListenOptions
|
ListenOptions
|
||||||
Network NetworkList `json:"network,omitempty"`
|
Network NetworkList `json:"network,omitempty"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password,omitempty"`
|
||||||
Users []ShadowsocksUser `json:"users,omitempty"`
|
Users []ShadowsocksUser `json:"users,omitempty"`
|
||||||
Destinations []ShadowsocksDestination `json:"destinations,omitempty"`
|
Destinations []ShadowsocksDestination `json:"destinations,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,9 @@ type SocksOutboundOptions struct {
|
|||||||
type HTTPOutboundOptions struct {
|
type HTTPOutboundOptions struct {
|
||||||
DialerOptions
|
DialerOptions
|
||||||
ServerOptions
|
ServerOptions
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
Headers map[string]Listable[string] `json:"headers,omitempty"`
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user