mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-04 21:05:17 +03:00
Compare commits
31 Commits
v1.11.13-e
...
v1.11.15-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89497dbfd5 | ||
|
|
8388abbb77 | ||
|
|
180b7c4134 | ||
|
|
deda2cca5e | ||
|
|
bc35aca017 | ||
|
|
281d52a1ea | ||
|
|
b8502759b5 | ||
|
|
209b89a4a3 | ||
|
|
6f804adf39 | ||
|
|
36db31c55a | ||
|
|
4dbbf59c82 | ||
|
|
832eb4458d | ||
|
|
2cf989d306 | ||
|
|
7d3ee29bd0 | ||
|
|
cba0e46aba | ||
|
|
765111a552 | ||
|
|
9b8ab3e61e | ||
|
|
47f18e823a | ||
|
|
2d1b824b62 | ||
|
|
824eac453b | ||
|
|
85a1a8a53b | ||
|
|
ae9e7aa5f4 | ||
|
|
52b71c6f00 | ||
|
|
5d04673783 | ||
|
|
307c429715 | ||
|
|
6801b58d96 | ||
|
|
6272596ebc | ||
|
|
7c4c2d5ca8 | ||
|
|
d511698f3f | ||
|
|
cb435ea232 | ||
|
|
43a9016c83 |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -468,11 +468,11 @@ jobs:
|
|||||||
- name: Setup Xcode stable
|
- name: Setup Xcode stable
|
||||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||||
run: |-
|
run: |-
|
||||||
sudo xcode-select -s /Applications/Xcode_16.2.app
|
sudo xcode-select -s /Applications/Xcode_16.4.app
|
||||||
- name: Setup Xcode beta
|
- name: Setup Xcode beta
|
||||||
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||||
run: |-
|
run: |-
|
||||||
sudo xcode-select -s /Applications/Xcode_16.2.app
|
sudo xcode-select -s /Applications/Xcode_16.4.app
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -1,6 +1,6 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
|
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls,with_acme
|
||||||
TAGS_GO121 = with_ech
|
TAGS_GO121 = with_ech
|
||||||
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
|
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
||||||
|
|||||||
@@ -42,22 +42,24 @@ type InboundManager interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type InboundContext struct {
|
type InboundContext struct {
|
||||||
Inbound string
|
Inbound string
|
||||||
InboundType string
|
InboundType string
|
||||||
IPVersion uint8
|
IPVersion uint8
|
||||||
Network string
|
Network string
|
||||||
Source M.Socksaddr
|
Source M.Socksaddr
|
||||||
Destination M.Socksaddr
|
Destination M.Socksaddr
|
||||||
User string
|
TunnelSource string
|
||||||
Outbound string
|
TunnelDestination string
|
||||||
|
User string
|
||||||
|
Outbound string
|
||||||
|
|
||||||
// sniffer
|
// sniffer
|
||||||
|
|
||||||
Protocol string
|
Protocol string
|
||||||
Domain string
|
Domain string
|
||||||
Client string
|
Client string
|
||||||
SniffContext any
|
SniffContext any
|
||||||
PacketSniffError error
|
SniffError error
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
|
|||||||
4
box.go
4
box.go
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||||
@@ -114,6 +115,9 @@ func New(options Options) (*Box, error) {
|
|||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
needV2RayAPI = true
|
needV2RayAPI = true
|
||||||
}
|
}
|
||||||
|
if experimentalOptions.UnifiedDelay != nil && experimentalOptions.UnifiedDelay.Enabled {
|
||||||
|
ctx = urltest.ContextWithIsUnifiedDelay(ctx)
|
||||||
|
}
|
||||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
var defaultLogWriter io.Writer
|
var defaultLogWriter io.Writer
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
|
|||||||
Submodule clients/android updated: 320170a107...7f1fa971e3
Submodule clients/apple updated: ae5818ee5a...f7883b0f3e
@@ -100,10 +100,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
} else if networkManager.AutoDetectInterface() {
|
} else if networkManager.AutoDetectInterface() {
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||||
if networkStrategy == nil {
|
|
||||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
|
||||||
defaultNetworkStrategy = true
|
|
||||||
}
|
|
||||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||||
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
||||||
@@ -115,6 +111,10 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
||||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||||
}
|
}
|
||||||
|
if networkStrategy == nil {
|
||||||
|
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||||
|
defaultNetworkStrategy = true
|
||||||
|
}
|
||||||
bindFunc := networkManager.ProtectFunc()
|
bindFunc := networkManager.ProtectFunc()
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
@@ -26,7 +24,9 @@ type slowOpenConn struct {
|
|||||||
destination M.Socksaddr
|
destination M.Socksaddr
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
create chan struct{}
|
create chan struct{}
|
||||||
|
done chan struct{}
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
|
closeOnce sync.Once
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +45,7 @@ func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, des
|
|||||||
network: network,
|
network: network,
|
||||||
destination: destination,
|
destination: destination,
|
||||||
create: make(chan struct{}),
|
create: make(chan struct{}),
|
||||||
|
done: make(chan struct{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,8 +56,8 @@ func (c *slowOpenConn) Read(b []byte) (n int, err error) {
|
|||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
case <-c.ctx.Done():
|
case <-c.done:
|
||||||
return 0, c.ctx.Err()
|
return 0, os.ErrClosed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c.conn.Read(b)
|
return c.conn.Read(b)
|
||||||
@@ -74,12 +75,15 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
return c.conn.Write(b)
|
return c.conn.Write(b)
|
||||||
|
case <-c.done:
|
||||||
|
return 0, os.ErrClosed
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
conn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.conn = nil
|
c.err = err
|
||||||
c.err = E.Cause(err, "dial tcp fast open")
|
} else {
|
||||||
|
c.conn = conn
|
||||||
}
|
}
|
||||||
n = len(b)
|
n = len(b)
|
||||||
close(c.create)
|
close(c.create)
|
||||||
@@ -87,7 +91,13 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Close() error {
|
func (c *slowOpenConn) Close() error {
|
||||||
return common.Close(c.conn)
|
c.closeOnce.Do(func() {
|
||||||
|
close(c.done)
|
||||||
|
if c.conn != nil {
|
||||||
|
c.conn.Close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) LocalAddr() net.Addr {
|
func (c *slowOpenConn) LocalAddr() net.Addr {
|
||||||
@@ -152,8 +162,8 @@ func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
case <-c.ctx.Done():
|
case <-c.done:
|
||||||
return 0, c.ctx.Err()
|
return 0, c.err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bufio.Copy(w, c.conn)
|
return bufio.Copy(w, c.conn)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package interrupt
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -73,3 +74,32 @@ func (c *PacketConn) WriterReplaceable() bool {
|
|||||||
func (c *PacketConn) Upstream() any {
|
func (c *PacketConn) Upstream() any {
|
||||||
return c.PacketConn
|
return c.PacketConn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SingPacketConn struct {
|
||||||
|
N.PacketConn
|
||||||
|
group *Group
|
||||||
|
element *list.Element[*groupConnItem]
|
||||||
|
}
|
||||||
|
|
||||||
|
/*func (c *SingPacketConn) MarkAsInternal() {
|
||||||
|
c.element.Value.internal = true
|
||||||
|
}*/
|
||||||
|
|
||||||
|
func (c *SingPacketConn) Close() error {
|
||||||
|
c.group.access.Lock()
|
||||||
|
defer c.group.access.Unlock()
|
||||||
|
c.group.connections.Remove(c.element)
|
||||||
|
return c.PacketConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SingPacketConn) ReaderReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SingPacketConn) WriterReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *SingPacketConn) Upstream() any {
|
||||||
|
return c.PacketConn
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,6 +37,13 @@ func (g *Group) NewPacketConn(conn net.PacketConn, isExternal bool) net.PacketCo
|
|||||||
return &PacketConn{PacketConn: conn, group: g, element: item}
|
return &PacketConn{PacketConn: conn, group: g, element: item}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Group) NewSingPacketConn(conn N.PacketConn, isExternal bool) N.PacketConn {
|
||||||
|
g.access.Lock()
|
||||||
|
defer g.access.Unlock()
|
||||||
|
item := g.connections.PushBack(&groupConnItem{conn, isExternal})
|
||||||
|
return &SingPacketConn{PacketConn: conn, group: g, element: item}
|
||||||
|
}
|
||||||
|
|
||||||
func (g *Group) Interrupt(interruptExternalConnections bool) {
|
func (g *Group) Interrupt(interruptExternalConnections bool) {
|
||||||
g.access.Lock()
|
g.access.Lock()
|
||||||
defer g.access.Unlock()
|
defer g.access.Unlock()
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
|||||||
// rup8(sizeof(xtcpcb_n))
|
// rup8(sizeof(xtcpcb_n))
|
||||||
itemSize += 208
|
itemSize += 208
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fallbackUDPProcess string
|
||||||
// skip the first xinpgen(24 bytes) block
|
// skip the first xinpgen(24 bytes) block
|
||||||
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
||||||
// offset of xinpcb_n and xsocket_n
|
// offset of xinpcb_n and xsocket_n
|
||||||
@@ -90,10 +92,12 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
|||||||
flag := buf[inp+44]
|
flag := buf[inp+44]
|
||||||
|
|
||||||
var srcIP netip.Addr
|
var srcIP netip.Addr
|
||||||
|
srcIsIPv4 := false
|
||||||
switch {
|
switch {
|
||||||
case flag&0x1 > 0 && isIPv4:
|
case flag&0x1 > 0 && isIPv4:
|
||||||
// ipv4
|
// ipv4
|
||||||
srcIP = netip.AddrFrom4(*(*[4]byte)(buf[inp+76 : inp+80]))
|
srcIP = netip.AddrFrom4(*(*[4]byte)(buf[inp+76 : inp+80]))
|
||||||
|
srcIsIPv4 = true
|
||||||
case flag&0x2 > 0 && !isIPv4:
|
case flag&0x2 > 0 && !isIPv4:
|
||||||
// ipv6
|
// ipv6
|
||||||
srcIP = netip.AddrFrom16(*(*[16]byte)(buf[inp+64 : inp+80]))
|
srcIP = netip.AddrFrom16(*(*[16]byte)(buf[inp+64 : inp+80]))
|
||||||
@@ -101,13 +105,21 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ip != srcIP {
|
if ip == srcIP {
|
||||||
continue
|
// xsocket_n.so_last_pid
|
||||||
|
pid := readNativeUint32(buf[so+68 : so+72])
|
||||||
|
return getExecPathFromPID(pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// xsocket_n.so_last_pid
|
// udp packet connection may be not equal with srcIP
|
||||||
pid := readNativeUint32(buf[so+68 : so+72])
|
if network == N.NetworkUDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 {
|
||||||
return getExecPathFromPID(pid)
|
pid := readNativeUint32(buf[so+68 : so+72])
|
||||||
|
fallbackUDPProcess, _ = getExecPathFromPID(pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if network == N.NetworkUDP && len(fallbackUDPProcess) > 0 {
|
||||||
|
return fallbackUDPProcess, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", ErrNotFound
|
return "", ErrNotFound
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ package tls
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/libdns/alidns"
|
"github.com/libdns/alidns"
|
||||||
@@ -37,7 +37,38 @@ func (w *acmeWrapper) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
|
type acmeLogWriter struct {
|
||||||
|
logger logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *acmeLogWriter) Write(p []byte) (n int, err error) {
|
||||||
|
logLine := strings.ReplaceAll(string(p), " ", ": ")
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(logLine, "error: "):
|
||||||
|
w.logger.Error(logLine[7:])
|
||||||
|
case strings.HasPrefix(logLine, "warn: "):
|
||||||
|
w.logger.Warn(logLine[6:])
|
||||||
|
case strings.HasPrefix(logLine, "info: "):
|
||||||
|
w.logger.Info(logLine[6:])
|
||||||
|
case strings.HasPrefix(logLine, "debug: "):
|
||||||
|
w.logger.Debug(logLine[7:])
|
||||||
|
default:
|
||||||
|
w.logger.Debug(logLine)
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *acmeLogWriter) Sync() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encoderConfig() zapcore.EncoderConfig {
|
||||||
|
config := zap.NewProductionEncoderConfig()
|
||||||
|
config.TimeKey = zapcore.OmitKey
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
|
||||||
var acmeServer string
|
var acmeServer string
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
case "", "letsencrypt":
|
case "", "letsencrypt":
|
||||||
@@ -58,14 +89,15 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
|
|||||||
} else {
|
} else {
|
||||||
storage = certmagic.Default.Storage
|
storage = certmagic.Default.Storage
|
||||||
}
|
}
|
||||||
|
zapLogger := zap.New(zapcore.NewCore(
|
||||||
|
zapcore.NewConsoleEncoder(encoderConfig()),
|
||||||
|
&acmeLogWriter{logger: logger},
|
||||||
|
zap.DebugLevel,
|
||||||
|
))
|
||||||
config := &certmagic.Config{
|
config := &certmagic.Config{
|
||||||
DefaultServerName: options.DefaultServerName,
|
DefaultServerName: options.DefaultServerName,
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
Logger: zap.New(zapcore.NewCore(
|
Logger: zapLogger,
|
||||||
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
|
|
||||||
os.Stderr,
|
|
||||||
zap.InfoLevel,
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
acmeConfig := certmagic.ACMEIssuer{
|
acmeConfig := certmagic.ACMEIssuer{
|
||||||
CA: acmeServer,
|
CA: acmeServer,
|
||||||
@@ -75,7 +107,7 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
|
|||||||
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
|
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
|
||||||
AltHTTPPort: int(options.AlternativeHTTPPort),
|
AltHTTPPort: int(options.AlternativeHTTPPort),
|
||||||
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
||||||
Logger: config.Logger,
|
Logger: zapLogger,
|
||||||
}
|
}
|
||||||
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
|
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
|
||||||
var solver certmagic.DNS01Solver
|
var solver certmagic.DNS01Solver
|
||||||
@@ -103,6 +135,7 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
|
|||||||
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
|
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
|
||||||
return config, nil
|
return config, nil
|
||||||
},
|
},
|
||||||
|
Logger: zapLogger,
|
||||||
})
|
})
|
||||||
config = certmagic.New(cache, *config)
|
config = certmagic.New(cache, *config)
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
|
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
|
||||||
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
var err error
|
var err error
|
||||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
|
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
13
common/urltest/context.go
Normal file
13
common/urltest/context.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package urltest
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
type contextKeyIsUnifiedDelay struct{}
|
||||||
|
|
||||||
|
func ContextWithIsUnifiedDelay(ctx context.Context) context.Context {
|
||||||
|
return context.WithValue(ctx, contextKeyIsUnifiedDelay{}, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsUnifiedDelayFromContext(ctx context.Context) bool {
|
||||||
|
return ctx.Value(contextKeyIsUnifiedDelay{}) != nil
|
||||||
|
}
|
||||||
@@ -122,6 +122,15 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
|
if IsUnifiedDelayFromContext(ctx) {
|
||||||
|
second := time.Now()
|
||||||
|
resp, err = client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
start = second
|
||||||
|
}
|
||||||
t = uint16(time.Since(start) / time.Millisecond)
|
t = uint16(time.Since(start) / time.Millisecond)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,27 +24,36 @@ func (c *Range) MarshalJSON() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Range) UnmarshalJSON(content []byte) error {
|
func (c *Range) UnmarshalJSON(content []byte) error {
|
||||||
|
var rangeValue struct {
|
||||||
|
From int32 `json:"from"`
|
||||||
|
To int32 `json:"to"`
|
||||||
|
}
|
||||||
var stringValue string
|
var stringValue string
|
||||||
err := json.Unmarshal(content, &stringValue)
|
err := json.Unmarshal(content, &stringValue)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
return err
|
parts := strings.Split(stringValue, "-")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return E.New("invalid length of range parts")
|
||||||
|
}
|
||||||
|
from, err := strconv.ParseInt(parts[0], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
to, err := strconv.ParseInt(parts[1], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rangeValue.From, rangeValue.To = int32(from), int32(to)
|
||||||
|
} else {
|
||||||
|
err := json.Unmarshal(content, &rangeValue)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
parts := strings.Split(stringValue, "-")
|
if rangeValue.From > rangeValue.To {
|
||||||
if len(parts) != 2 {
|
|
||||||
return E.New("invalid length of range parts")
|
|
||||||
}
|
|
||||||
from, err := strconv.ParseInt(parts[0], 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
to, err := strconv.ParseInt(parts[1], 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if from > to {
|
|
||||||
return E.New("invalid range")
|
return E.New("invalid range")
|
||||||
}
|
}
|
||||||
*c = Range{int32(from), int32(to)}
|
*c = Range{rangeValue.From, rangeValue.To}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,10 +20,13 @@ const (
|
|||||||
TypeTor = "tor"
|
TypeTor = "tor"
|
||||||
TypeSSH = "ssh"
|
TypeSSH = "ssh"
|
||||||
TypeShadowTLS = "shadowtls"
|
TypeShadowTLS = "shadowtls"
|
||||||
|
TypeMieru = "mieru"
|
||||||
TypeShadowsocksR = "shadowsocksr"
|
TypeShadowsocksR = "shadowsocksr"
|
||||||
TypeVLESS = "vless"
|
TypeVLESS = "vless"
|
||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
TypeHysteria2 = "hysteria2"
|
TypeHysteria2 = "hysteria2"
|
||||||
|
TypeTunnelClient = "tunnel_client"
|
||||||
|
TypeTunnelServer = "tunnel_server"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -79,10 +82,16 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "TUIC"
|
return "TUIC"
|
||||||
case TypeHysteria2:
|
case TypeHysteria2:
|
||||||
return "Hysteria2"
|
return "Hysteria2"
|
||||||
|
case TypeMieru:
|
||||||
|
return "Mieru"
|
||||||
case TypeSelector:
|
case TypeSelector:
|
||||||
return "Selector"
|
return "Selector"
|
||||||
case TypeURLTest:
|
case TypeURLTest:
|
||||||
return "URLTest"
|
return "URLTest"
|
||||||
|
case TypeTunnelClient:
|
||||||
|
return "Tunnel Client"
|
||||||
|
case TypeTunnelServer:
|
||||||
|
return "Tunnel Server"
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,20 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### 1.11.15
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||||
|
violated the rules (TestFlight users are not affected)._
|
||||||
|
|
||||||
|
### 1.11.14
|
||||||
|
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||||
|
violated the rules (TestFlight users are not affected)._
|
||||||
|
|
||||||
### 1.11.13
|
### 1.11.13
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ icon: material/new-box
|
|||||||
{
|
{
|
||||||
"external_controller": "0.0.0.0:9090",
|
"external_controller": "0.0.0.0:9090",
|
||||||
"external_ui": "dashboard"
|
"external_ui": "dashboard"
|
||||||
// external_ui_download_detour: "direct"
|
// "external_ui_download_detour": "direct"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ icon: material/new-box
|
|||||||
{
|
{
|
||||||
"external_controller": "0.0.0.0:9090",
|
"external_controller": "0.0.0.0:9090",
|
||||||
"external_ui": "dashboard"
|
"external_ui": "dashboard"
|
||||||
// external_ui_download_detour: "direct"
|
// "external_ui_download_detour": "direct"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
| `shadowtls` | [ShadowTLS](./shadowtls/) |
|
| `shadowtls` | [ShadowTLS](./shadowtls/) |
|
||||||
| `tuic` | [TUIC](./tuic/) |
|
| `tuic` | [TUIC](./tuic/) |
|
||||||
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
||||||
|
| `mieru` | [Mieru](./mieru/) |
|
||||||
| `tor` | [Tor](./tor/) |
|
| `tor` | [Tor](./tor/) |
|
||||||
| `ssh` | [SSH](./ssh/) |
|
| `ssh` | [SSH](./ssh/) |
|
||||||
| `dns` | [DNS](./dns/) |
|
| `dns` | [DNS](./dns/) |
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
| `shadowtls` | [ShadowTLS](./shadowtls/) |
|
| `shadowtls` | [ShadowTLS](./shadowtls/) |
|
||||||
| `tuic` | [TUIC](./tuic/) |
|
| `tuic` | [TUIC](./tuic/) |
|
||||||
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
||||||
|
| `mieru` | [Mieru](./mieru/) |
|
||||||
| `tor` | [Tor](./tor/) |
|
| `tor` | [Tor](./tor/) |
|
||||||
| `ssh` | [SSH](./ssh/) |
|
| `ssh` | [SSH](./ssh/) |
|
||||||
| `dns` | [DNS](./dns/) |
|
| `dns` | [DNS](./dns/) |
|
||||||
|
|||||||
71
docs/configuration/outbound/mieru.md
Normal file
71
docs/configuration/outbound/mieru.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "mieru",
|
||||||
|
"tag": "mieru-out",
|
||||||
|
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 1080,
|
||||||
|
"server_ports": [
|
||||||
|
"9000-9010",
|
||||||
|
"9020-9030"
|
||||||
|
],
|
||||||
|
"transport": "TCP",
|
||||||
|
"username": "asdf",
|
||||||
|
"password": "hjkl",
|
||||||
|
"multiplexing": "MULTIPLEXING_LOW",
|
||||||
|
|
||||||
|
... // Dial Fields
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### server
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
The server address.
|
||||||
|
|
||||||
|
#### server_port
|
||||||
|
|
||||||
|
The server port.
|
||||||
|
|
||||||
|
Must set at least one field between `server_port` and `server_ports`.
|
||||||
|
|
||||||
|
#### server_ports
|
||||||
|
|
||||||
|
Server port range list.
|
||||||
|
|
||||||
|
Must set at least one field between `server_port` and `server_ports`.
|
||||||
|
|
||||||
|
#### transport
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
Transmission protocol. The only allowed value is `TCP`.
|
||||||
|
|
||||||
|
#### username
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
mieru user name.
|
||||||
|
|
||||||
|
#### password
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
mieru password.
|
||||||
|
|
||||||
|
#### multiplexing
|
||||||
|
|
||||||
|
Multiplexing level. Supported values are `MULTIPLEXING_OFF`, `MULTIPLEXING_LOW`, `MULTIPLEXING_MIDDLE`, `MULTIPLEXING_HIGH`. `MULTIPLEXING_OFF` disables multiplexing.
|
||||||
|
|
||||||
|
### Dial Fields
|
||||||
|
|
||||||
|
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||||
71
docs/configuration/outbound/mieru.zh.md
Normal file
71
docs/configuration/outbound/mieru.zh.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
|
### 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "mieru",
|
||||||
|
"tag": "mieru-out",
|
||||||
|
|
||||||
|
"server": "127.0.0.1",
|
||||||
|
"server_port": 1080,
|
||||||
|
"server_ports": [
|
||||||
|
"9000-9010",
|
||||||
|
"9020-9030"
|
||||||
|
],
|
||||||
|
"transport": "TCP",
|
||||||
|
"username": "asdf",
|
||||||
|
"password": "hjkl",
|
||||||
|
"multiplexing": "MULTIPLEXING_LOW",
|
||||||
|
|
||||||
|
... // 拨号字段
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
|
#### server
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
服务器地址。
|
||||||
|
|
||||||
|
#### server_port
|
||||||
|
|
||||||
|
服务器端口。
|
||||||
|
|
||||||
|
必须填写 `server_port` 和 `server_ports` 中至少一项。
|
||||||
|
|
||||||
|
#### server_ports
|
||||||
|
|
||||||
|
服务器端口范围列表。
|
||||||
|
|
||||||
|
必须填写 `server_port` 和 `server_ports` 中至少一项。
|
||||||
|
|
||||||
|
#### transport
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
通信协议。仅可设为 `TCP`。
|
||||||
|
|
||||||
|
#### username
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
mieru 用户名。
|
||||||
|
|
||||||
|
#### password
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
mieru 密码。
|
||||||
|
|
||||||
|
#### multiplexing
|
||||||
|
|
||||||
|
多路复用设置。可以设为 `MULTIPLEXING_OFF`,`MULTIPLEXING_LOW`,`MULTIPLEXING_MIDDLE`,`MULTIPLEXING_HIGH`。其中 `MULTIPLEXING_OFF` 会关闭多路复用功能。
|
||||||
|
|
||||||
|
### 拨号字段
|
||||||
|
|
||||||
|
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
||||||
@@ -1,84 +1,71 @@
|
|||||||
{
|
{
|
||||||
"log":{
|
"log": {
|
||||||
"level":"error"
|
"level": "error"
|
||||||
},
|
},
|
||||||
"dns":{
|
"dns": {
|
||||||
"servers":[
|
"servers": [
|
||||||
{
|
{
|
||||||
"address":"local",
|
"address": "local",
|
||||||
"detour":"direct"
|
"detour": "direct"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"endpoints":[
|
"endpoints": [
|
||||||
{
|
{
|
||||||
"type":"wireguard",
|
"type": "wireguard",
|
||||||
"tag":"wireguard-out",
|
"tag": "wireguard-out",
|
||||||
"system":false,
|
"mtu": 1408,
|
||||||
"name":"",
|
"address": null,
|
||||||
"mtu":1408,
|
"private_key": "",
|
||||||
"address":[],
|
"listen_port": 10000,
|
||||||
"private_key":"",
|
"peers": [
|
||||||
"listen_port":10000,
|
|
||||||
"peers":[
|
|
||||||
{
|
{
|
||||||
"address":"example.com",
|
"address": "example.com",
|
||||||
"port":10001,
|
"port": 10001,
|
||||||
"public_key":"",
|
"reserved": "AAAA"
|
||||||
"pre_shared_key":"",
|
|
||||||
"allowed_ips":[],
|
|
||||||
"persistent_keepalive_interval":0,
|
|
||||||
"reserved":[
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"udp_timeout":"",
|
"udp_timeout": "5m0s",
|
||||||
"workers":0,
|
"amnezia": {
|
||||||
"amnezia":{
|
"jc": 120,
|
||||||
"jc":120,
|
"jmin": 23,
|
||||||
"jmin":23,
|
"jmax": 911,
|
||||||
"jmax":911,
|
"h1": 1,
|
||||||
"s1":0,
|
"h2": 2,
|
||||||
"s2":0,
|
"h3": 3,
|
||||||
"h1":1,
|
"h4": 4
|
||||||
"h2":2,
|
|
||||||
"h3":3,
|
|
||||||
"h4":4
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"inbounds":[
|
"inbounds": [
|
||||||
{
|
{
|
||||||
"type":"mixed",
|
"type": "mixed",
|
||||||
"tag":"mixed-in",
|
"tag": "mixed-in",
|
||||||
"listen_port":7897
|
"listen_port": 7897
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outbounds":[
|
"outbounds": [
|
||||||
{
|
{
|
||||||
"type":"direct",
|
"type": "direct",
|
||||||
"tag":"direct"
|
"tag": "direct"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type":"dns",
|
"type": "dns",
|
||||||
"tag":"dns-out"
|
"tag": "dns-out"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route":{
|
"route": {
|
||||||
"rules":[
|
"rules": [
|
||||||
{
|
{
|
||||||
"protocol":"dns",
|
"protocol": "dns",
|
||||||
"outbound":"dns-out"
|
"outbound": "dns-out"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"port":53,
|
"port": 53,
|
||||||
"outbound":"dns-out"
|
"outbound": "dns-out"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"final":"wireguard-out",
|
"final": "wireguard-out",
|
||||||
"auto_detect_interface":true
|
"auto_detect_interface": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
56
examples/mieru/client.json
Normal file
56
examples/mieru/client.json
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "error"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"address": "local",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "mixed",
|
||||||
|
"tag": "mixed-in",
|
||||||
|
"listen_port": 7897
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "mieru",
|
||||||
|
"tag": "mieru-out",
|
||||||
|
"server": "example.com",
|
||||||
|
"server_port": 27017,
|
||||||
|
"server_ports": "27017-27019",
|
||||||
|
"transport": "TCP",
|
||||||
|
"username": "username",
|
||||||
|
"password": "password",
|
||||||
|
"multiplexing": "MULTIPLEXING_LOW"
|
||||||
|
// Dial Fields
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"protocol": "dns",
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": 53,
|
||||||
|
"outbound": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": "mieru-out",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
44
examples/sdns/client.json
Normal file
44
examples/sdns/client.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "error"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"address": "sdns://AQMAAAAAAAAAETk0LjE0MC4xNS4xNTo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "mixed",
|
||||||
|
"tag": "mixed-in",
|
||||||
|
"listen_port": 7897
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"protocol": "dns",
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": 53,
|
||||||
|
"outbound": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": "direct",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
64
examples/tunnel/client->server/client.json
Normal file
64
examples/tunnel/client->server/client.json
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "error"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"address": "local",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"type": "tunnel_client",
|
||||||
|
"tag": "tunnel",
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
|
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe",
|
||||||
|
"outbound": {
|
||||||
|
"type": "vless",
|
||||||
|
"tag": "vless-out",
|
||||||
|
"server": "0.0.0.0",
|
||||||
|
"server_port": 8000,
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
|
"network": "tcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "mixed",
|
||||||
|
"tag": "mixed-in",
|
||||||
|
"listen_port": 7897
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"protocol": "dns",
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": 53,
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"outbound": "tunnel",
|
||||||
|
"override_tunnel_destination": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": "direct-out",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
62
examples/tunnel/client->server/server.json
Normal file
62
examples/tunnel/client->server/server.json
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "error"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"address": "local",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"type": "tunnel_server",
|
||||||
|
"tag": "tunnel",
|
||||||
|
"uuid": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13",
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
|
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inbound": {
|
||||||
|
"type": "vless",
|
||||||
|
"tag": "vless-in",
|
||||||
|
"listen": "0.0.0.0",
|
||||||
|
"listen_port": 8000,
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "vless",
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"protocol": "dns",
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": 53,
|
||||||
|
"outbound": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": "direct-out",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
64
examples/tunnel/client1->server->client2/client1.json
Normal file
64
examples/tunnel/client1->server->client2/client1.json
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "error"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"address": "local",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"type": "tunnel_client",
|
||||||
|
"tag": "tunnel",
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
|
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe",
|
||||||
|
"outbound": {
|
||||||
|
"type": "vless",
|
||||||
|
"tag": "vless-out",
|
||||||
|
"server": "0.0.0.0",
|
||||||
|
"server_port": 8000,
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
|
"network": "tcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "mixed",
|
||||||
|
"tag": "mixed-in",
|
||||||
|
"listen_port": 7897
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"protocol": "dns",
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": 53,
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"outbound": "tunnel",
|
||||||
|
"override_tunnel_destination": "487f6073-3300-4819-a07d-39652e45fb4d"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": "direct-out",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
53
examples/tunnel/client1->server->client2/client2.json
Normal file
53
examples/tunnel/client1->server->client2/client2.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "error"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"address": "local",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"type": "tunnel_client",
|
||||||
|
"tag": "tunnel",
|
||||||
|
"uuid": "487f6073-3300-4819-a07d-39652e45fb4d",
|
||||||
|
"key": "3d74d616-2502-4c17-9cc3-92c366550f4f",
|
||||||
|
"outbound": {
|
||||||
|
"type": "vless",
|
||||||
|
"tag": "vless-out",
|
||||||
|
"server": "0.0.0.0",
|
||||||
|
"server_port": 8000,
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
|
"network": "tcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"protocol": "dns",
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": 53,
|
||||||
|
"outbound": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": "direct-out",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
77
examples/tunnel/client1->server->client2/server.json
Normal file
77
examples/tunnel/client1->server->client2/server.json
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "error"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"address": "local",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"type": "tunnel_server",
|
||||||
|
"tag": "tunnel",
|
||||||
|
"uuid": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13",
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
|
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "487f6073-3300-4819-a07d-39652e45fb4d",
|
||||||
|
"key": "3d74d616-2502-4c17-9cc3-92c366550f4f"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inbound": {
|
||||||
|
"type": "vless",
|
||||||
|
"tag": "vless-in",
|
||||||
|
"listen": "0.0.0.0",
|
||||||
|
"listen_port": 8000,
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "vless",
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"protocol": "dns",
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": 53,
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tunnel_source": [
|
||||||
|
"9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
|
"487f6073-3300-4819-a07d-39652e45fb4d"
|
||||||
|
],
|
||||||
|
"tunnel_destination": [
|
||||||
|
"9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
|
"487f6073-3300-4819-a07d-39652e45fb4d"
|
||||||
|
],
|
||||||
|
"outbound": "tunnel"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": "direct-out",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "error"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"address": "local",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "mixed",
|
||||||
|
"tag": "mixed-in",
|
||||||
|
"listen_port": 7897
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "vless",
|
||||||
|
"tag": "vless-out",
|
||||||
|
"server": "0.0.0.0",
|
||||||
|
"server_port": 8000,
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
|
"network": "tcp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"protocol": "dns",
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": 53,
|
||||||
|
"outbound": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": "vless-out",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "error"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"address": "local",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"type": "tunnel_server",
|
||||||
|
"tag": "tunnel",
|
||||||
|
"uuid": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13",
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
|
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inbound": {
|
||||||
|
"type": "vless",
|
||||||
|
"tag": "vless-in",
|
||||||
|
"listen": "0.0.0.0",
|
||||||
|
"listen_port": 8000,
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "vless",
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"protocol": "dns",
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": 53,
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inbound": "vless-in",
|
||||||
|
"outbound": "tunnel",
|
||||||
|
"override_tunnel_destination": "9b65b7e1-04c8-4717-8f45-2aa61fd25937"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": "direct-out",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "error"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"address": "local",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"type": "tunnel_client",
|
||||||
|
"tag": "tunnel",
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
|
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe",
|
||||||
|
"outbound": {
|
||||||
|
"type": "vless",
|
||||||
|
"tag": "vless-out",
|
||||||
|
"server": "0.0.0.0",
|
||||||
|
"server_port": 8000,
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
|
"network": "tcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"protocol": "dns",
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": 53,
|
||||||
|
"outbound": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": "direct-out",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
53
examples/tunnel/server->client/client.json
Normal file
53
examples/tunnel/server->client/client.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "error"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"address": "local",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"type": "tunnel_client",
|
||||||
|
"tag": "tunnel",
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
|
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe",
|
||||||
|
"outbound": {
|
||||||
|
"type": "vless",
|
||||||
|
"tag": "vless-out",
|
||||||
|
"server": "0.0.0.0",
|
||||||
|
"server_port": 8000,
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
|
"network": "tcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"protocol": "dns",
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": 53,
|
||||||
|
"outbound": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": "direct-out",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
73
examples/tunnel/server->client/server.json
Normal file
73
examples/tunnel/server->client/server.json
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "error"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"address": "local",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"type": "tunnel_server",
|
||||||
|
"tag": "tunnel",
|
||||||
|
"uuid": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13",
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
|
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inbound": {
|
||||||
|
"type": "vless",
|
||||||
|
"tag": "vless-in",
|
||||||
|
"listen": "0.0.0.0",
|
||||||
|
"listen_port": 8000,
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "vless",
|
||||||
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "mixed",
|
||||||
|
"tag": "mixed-in",
|
||||||
|
"listen_port": 7897
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"protocol": "dns",
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": 53,
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"outbound": "tunnel",
|
||||||
|
"override_tunnel_destination": "9b65b7e1-04c8-4717-8f45-2aa61fd25937"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": "direct-out",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
79
examples/unified_delay/client.json
Normal file
79
examples/unified_delay/client.json
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "error"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"address": "local",
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"endpoints": [
|
||||||
|
{
|
||||||
|
"type": "warp",
|
||||||
|
"tag": "warp-out",
|
||||||
|
"listen_port": 10000,
|
||||||
|
"udp_timeout": "5m0s",
|
||||||
|
"amnezia": {
|
||||||
|
"jc": 120,
|
||||||
|
"jmin": 23,
|
||||||
|
"jmax": 911,
|
||||||
|
"h1": 1,
|
||||||
|
"h2": 2,
|
||||||
|
"h3": 3,
|
||||||
|
"h4": 4
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "mixed",
|
||||||
|
"tag": "mixed-in",
|
||||||
|
"listen_port": 7897
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "selector",
|
||||||
|
"tag": "select-out",
|
||||||
|
"outbounds": [
|
||||||
|
"direct",
|
||||||
|
"warp-out"
|
||||||
|
],
|
||||||
|
"default": "direct",
|
||||||
|
"interrupt_exist_connections": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"protocol": "dns",
|
||||||
|
"outbound": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"port": 53,
|
||||||
|
"outbound": "dns-out"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"final": "direct",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
},
|
||||||
|
"experimental": {
|
||||||
|
"unified_delay": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,79 +1,75 @@
|
|||||||
{
|
{
|
||||||
"log":{
|
"log": {
|
||||||
"level":"error"
|
"level": "error"
|
||||||
},
|
},
|
||||||
"dns":{
|
"dns": {
|
||||||
"servers":[
|
"servers": [
|
||||||
{
|
{
|
||||||
"address":"local",
|
"address": "local",
|
||||||
"detour":"direct"
|
"detour": "direct"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"endpoints":[
|
"endpoints": [
|
||||||
{
|
{
|
||||||
"type":"warp",
|
"type": "warp",
|
||||||
"tag":"warp-out",
|
"tag": "warp-out",
|
||||||
"system":false,
|
"listen_port": 10000,
|
||||||
"name":"",
|
"udp_timeout": "5m0s",
|
||||||
"listen_port":10000,
|
"amnezia": {
|
||||||
"udp_timeout":"",
|
"jc": 120,
|
||||||
"workers":0,
|
"jmin": 23,
|
||||||
"profile":{
|
"jmax": 911,
|
||||||
"detour":"direct",
|
"h1": 1,
|
||||||
"recreate":false,
|
"h2": 2,
|
||||||
// for getting existing WARP device profile
|
"h3": 3,
|
||||||
"id":"",
|
"h4": 4
|
||||||
"private_key":"",
|
|
||||||
"auth_token":""
|
|
||||||
},
|
},
|
||||||
"amnezia":{
|
"profile": {
|
||||||
"jc":120,
|
"detour": "direct",
|
||||||
"jmin":23,
|
// for getting existing WARP device profile
|
||||||
"jmax":911,
|
"id": "",
|
||||||
"h1":1,
|
"private_key": "",
|
||||||
"h2":2,
|
"auth_token": ""
|
||||||
"h3":3,
|
|
||||||
"h4":4
|
|
||||||
}
|
}
|
||||||
// Dial Fields
|
// Dial Fields
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"inbounds":[
|
"inbounds": [
|
||||||
{
|
{
|
||||||
"type":"mixed",
|
"type": "mixed",
|
||||||
"tag":"mixed-in",
|
"tag": "mixed-in",
|
||||||
"listen_port":7897
|
"listen_port": 7897
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outbounds":[
|
"outbounds": [
|
||||||
{
|
{
|
||||||
"type":"direct",
|
"type": "direct",
|
||||||
"tag":"direct"
|
"tag": "direct"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type":"dns",
|
"type": "dns",
|
||||||
"tag":"dns-out"
|
"tag": "dns-out"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route":{
|
"route": {
|
||||||
"rules":[
|
"rules": [
|
||||||
{
|
{
|
||||||
"protocol":"dns",
|
"protocol": "dns",
|
||||||
"outbound":"dns-out"
|
"outbound": "dns-out"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"port":53,
|
"port": 53,
|
||||||
"outbound":"dns-out"
|
"outbound": "dns-out"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"final":"warp-out",
|
"final": "warp-out",
|
||||||
"auto_detect_interface":true
|
"auto_detect_interface": true
|
||||||
},
|
},
|
||||||
"experimental":{
|
"experimental": {
|
||||||
"cache_file":{
|
"cache_file": {
|
||||||
"enabled":true,
|
"enabled": true,
|
||||||
"store_warp_config":true
|
"store_warp_config": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,97 +1,98 @@
|
|||||||
{
|
{
|
||||||
"log":{
|
"log": {
|
||||||
"level":"error"
|
"level": "error"
|
||||||
},
|
},
|
||||||
"dns":{
|
"dns": {
|
||||||
"servers":[
|
"servers": [
|
||||||
{
|
{
|
||||||
"address":"local",
|
"address": "local",
|
||||||
"detour":"direct"
|
"detour": "direct"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"inbounds":[
|
"inbounds": [
|
||||||
{
|
{
|
||||||
"type":"mixed",
|
"type": "mixed",
|
||||||
"tag":"mixed-in",
|
"tag": "mixed-in",
|
||||||
"listen_port":7897
|
"listen_port": 7897
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outbounds":[
|
"outbounds": [
|
||||||
{
|
{
|
||||||
"type":"direct",
|
"type": "direct",
|
||||||
"tag":"direct"
|
"tag": "direct"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type":"vless",
|
"type": "vless",
|
||||||
"tag":"vless-out",
|
"tag": "vless-out",
|
||||||
"server":"example.com",
|
"server": "example.com",
|
||||||
"server_port":443,
|
"server_port": 443,
|
||||||
"uuid":"3179dce2-2ff9-413c-85b4-c1d53ed41668",
|
"uuid": "3179dce2-2ff9-413c-85b4-c1d53ed41668",
|
||||||
"tls":{
|
"tls": {
|
||||||
"enabled":true,
|
"enabled": true,
|
||||||
"server_name":"example.com",
|
"server_name": "example.com",
|
||||||
"alpn":[
|
"alpn": "h2" // h3 for QUIC
|
||||||
"h2" // h3 for QUIC
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"packet_encoding":"",
|
"transport": {
|
||||||
"transport":{
|
"type": "xhttp",
|
||||||
"type":"xhttp",
|
"mode": "stream-up",
|
||||||
"mode":"stream-up",
|
"host": "example.com",
|
||||||
"host":"example.com",
|
"path": "/xhttp",
|
||||||
"path":"/xhttp",
|
"domain_strategy": "prefer_ipv4",
|
||||||
"domain_strategy":"prefer_ipv4",
|
"xmux": {
|
||||||
"xmux":{
|
"max_concurrency": "0-1",
|
||||||
"max_concurrency":"0-1",
|
"max_connections": "0-1",
|
||||||
"max_connections":"0-1",
|
"c_max_reuse_times": "0-1",
|
||||||
"c_max_reuse_times":"0-1",
|
"h_max_request_times": "0-1",
|
||||||
"h_max_request_times":"0-1",
|
"h_max_reusable_secs": "0-1",
|
||||||
"h_max_reusable_secs":"0-1",
|
"h_keep_alive_period": 60
|
||||||
"h_keep_alive_period":60
|
|
||||||
},
|
},
|
||||||
"download":{
|
"download": {
|
||||||
"server":"example.com",
|
"mode": "",
|
||||||
"server_port":443,
|
"host": "example.com",
|
||||||
"host":"example.com",
|
"path": "/xhttp",
|
||||||
"path":"/xhttp",
|
"domain_strategy": "prefer_ipv4",
|
||||||
"domain_strategy":"prefer_ipv4",
|
"x_padding_bytes": "0-0",
|
||||||
"detour":"direct",
|
"sc_max_each_post_bytes": "0-0",
|
||||||
"tls":{
|
"sc_min_posts_interval_ms": "0-0",
|
||||||
"enabled":true,
|
"sc_stream_up_server_secs": "0-0",
|
||||||
"server_name":"example.com",
|
"xmux": {
|
||||||
"alpn":[
|
"max_concurrency": "0-1",
|
||||||
"h2" // h3 for QUIC
|
"max_connections": "0-1",
|
||||||
]
|
"c_max_reuse_times": "0-1",
|
||||||
|
"h_max_request_times": "0-1",
|
||||||
|
"h_max_reusable_secs": "0-1",
|
||||||
|
"h_keep_alive_period": 60
|
||||||
},
|
},
|
||||||
"xmux":{
|
"server": "example.com",
|
||||||
"max_concurrency":"0-1",
|
"server_port": 443,
|
||||||
"max_connections":"0-1",
|
"tls": {
|
||||||
"c_max_reuse_times":"0-1",
|
"enabled": true,
|
||||||
"h_max_request_times":"0-1",
|
"server_name": "example.com",
|
||||||
"h_max_reusable_secs":"0-1",
|
"alpn": "h2" // h3 for QUIC
|
||||||
"h_keep_alive_period":60
|
},
|
||||||
}
|
"detour": "direct"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"packet_encoding": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type":"dns",
|
"type": "dns",
|
||||||
"tag":"dns-out"
|
"tag": "dns-out"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route":{
|
"route": {
|
||||||
"rules":[
|
"rules": [
|
||||||
{
|
{
|
||||||
"protocol":"dns",
|
"protocol": "dns",
|
||||||
"outbound":"dns-out"
|
"outbound": "dns-out"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"port":53,
|
"port": 53,
|
||||||
"outbound":"dns-out"
|
"outbound": "dns-out"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"final":"vless-out",
|
"final": "vless-out",
|
||||||
"auto_detect_interface":true
|
"auto_detect_interface": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,65 +1,63 @@
|
|||||||
{
|
{
|
||||||
"log":{
|
"log": {
|
||||||
"level":"error"
|
"level": "error"
|
||||||
},
|
},
|
||||||
"dns":{
|
"dns": {
|
||||||
"servers":[
|
"servers": [
|
||||||
{
|
{
|
||||||
"address":"local",
|
"address": "local",
|
||||||
"detour":"direct"
|
"detour": "direct"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"inbounds":[
|
"inbounds": [
|
||||||
{
|
{
|
||||||
"type":"vless",
|
"type": "vless",
|
||||||
"tag":"vless-out",
|
"tag": "vless-out",
|
||||||
"listen":"0.0.0.0",
|
"listen": "0.0.0.0",
|
||||||
"listen_port":443,
|
"listen_port": 443,
|
||||||
"tls":{
|
"users": [
|
||||||
"enabled":true,
|
|
||||||
"server_name":"example.com",
|
|
||||||
"certificate_path":"/path/to/fullchain.pem",
|
|
||||||
"key_path":"/path/to/privkey.pem",
|
|
||||||
"alpn":[
|
|
||||||
"h2" // h3 for QUIC
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"users":[
|
|
||||||
{
|
{
|
||||||
"name":"user",
|
"name": "user",
|
||||||
"uuid":"3179dce2-2ff9-413c-85b4-c1d53ed41668"
|
"uuid": "3179dce2-2ff9-413c-85b4-c1d53ed41668"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"transport":{
|
"tls": {
|
||||||
"type":"xhttp",
|
"enabled": true,
|
||||||
"mode":"stream-up",
|
"server_name": "example.com",
|
||||||
"path":"/xhttp"
|
"alpn": "h2", // h3 for QUIC
|
||||||
|
"certificate_path": "/path/to/fullchain.pem",
|
||||||
|
"key_path": "/path/to/privkey.pem"
|
||||||
|
},
|
||||||
|
"transport": {
|
||||||
|
"type": "xhttp",
|
||||||
|
"mode": "stream-up",
|
||||||
|
"path": "/xhttp",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outbounds":[
|
"outbounds": [
|
||||||
{
|
{
|
||||||
"type":"direct",
|
"type": "direct",
|
||||||
"tag":"direct"
|
"tag": "direct"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type":"dns",
|
"type": "dns",
|
||||||
"tag":"dns-out"
|
"tag": "dns-out"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route":{
|
"route": {
|
||||||
"rules":[
|
"rules": [
|
||||||
{
|
{
|
||||||
"protocol":"dns",
|
"protocol": "dns",
|
||||||
"outbound":"dns-out"
|
"outbound": "dns-out"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"port":53,
|
"port": 53,
|
||||||
"outbound":"dns-out"
|
"outbound": "dns-out"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"final":"direct",
|
"final": "direct",
|
||||||
"auto_detect_interface":true
|
"auto_detect_interface": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,10 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
|
|||||||
cancel()
|
cancel()
|
||||||
return nil, E.Cause(err, "create service")
|
return nil, E.Cause(err, "create service")
|
||||||
}
|
}
|
||||||
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
|
if experimentalOptions.UnifiedDelay != nil && experimentalOptions.UnifiedDelay.Enabled {
|
||||||
|
ctx = urltest.ContextWithIsUnifiedDelay(ctx)
|
||||||
|
}
|
||||||
runtimeDebug.FreeOSMemory()
|
runtimeDebug.FreeOSMemory()
|
||||||
return &BoxService{
|
return &BoxService{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
|||||||
22
go.mod
22
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module github.com/sagernet/sing-box
|
module github.com/sagernet/sing-box
|
||||||
|
|
||||||
go 1.24
|
go 1.24.1
|
||||||
|
|
||||||
toolchain go1.24.3
|
toolchain go1.24.3
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@ require (
|
|||||||
github.com/caddyserver/certmagic v0.20.0
|
github.com/caddyserver/certmagic v0.20.0
|
||||||
github.com/cloudflare/circl v1.6.1
|
github.com/cloudflare/circl v1.6.1
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
|
github.com/enfein/mieru/v3 v3.13.0
|
||||||
github.com/go-chi/chi/v5 v5.2.1
|
github.com/go-chi/chi/v5 v5.2.1
|
||||||
github.com/go-chi/render v1.0.3
|
github.com/go-chi/render v1.0.3
|
||||||
github.com/gofrs/uuid/v5 v5.3.2
|
github.com/gofrs/uuid/v5 v5.3.2
|
||||||
@@ -30,13 +31,13 @@ require (
|
|||||||
github.com/sagernet/quic-go v0.49.0-beta.1
|
github.com/sagernet/quic-go v0.49.0-beta.1
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||||
github.com/sagernet/sing v0.6.10
|
github.com/sagernet/sing v0.6.10
|
||||||
github.com/sagernet/sing-dns v0.4.5
|
github.com/sagernet/sing-dns v0.4.6
|
||||||
github.com/sagernet/sing-mux v0.3.2
|
github.com/sagernet/sing-mux v0.3.2
|
||||||
github.com/sagernet/sing-quic v0.4.3
|
github.com/sagernet/sing-quic v0.4.4
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||||
github.com/sagernet/sing-shadowtls v0.2.0
|
github.com/sagernet/sing-shadowtls v0.2.0
|
||||||
github.com/sagernet/sing-tun v0.6.5
|
github.com/sagernet/sing-tun v0.6.9
|
||||||
github.com/sagernet/sing-vmess v0.2.3
|
github.com/sagernet/sing-vmess v0.2.3
|
||||||
github.com/sagernet/smux v1.5.34-mod.2
|
github.com/sagernet/smux v1.5.34-mod.2
|
||||||
github.com/sagernet/utls v1.6.7
|
github.com/sagernet/utls v1.6.7
|
||||||
@@ -47,11 +48,11 @@ require (
|
|||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.38.0
|
golang.org/x/crypto v0.38.0
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
|
||||||
golang.org/x/mod v0.24.0
|
golang.org/x/mod v0.24.0
|
||||||
golang.org/x/net v0.40.0
|
golang.org/x/net v0.40.0
|
||||||
golang.org/x/sys v0.33.0
|
golang.org/x/sys v0.33.0
|
||||||
golang.org/x/time v0.7.0
|
golang.org/x/time v0.11.0
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
google.golang.org/grpc v1.72.1
|
google.golang.org/grpc v1.72.1
|
||||||
google.golang.org/protobuf v1.36.6
|
google.golang.org/protobuf v1.36.6
|
||||||
@@ -59,6 +60,9 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/AdguardTeam/golibs v0.32.7 // indirect
|
||||||
|
github.com/ameshkov/dnscrypt/v2 v2.4.0 // indirect
|
||||||
|
github.com/ameshkov/dnsstamps v1.0.3 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.9.7 // indirect
|
github.com/onsi/ginkgo/v2 v2.9.7 // indirect
|
||||||
@@ -108,10 +112,14 @@ require (
|
|||||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/blake3 v1.4.1 // indirect
|
lukechampine.com/blake3 v1.4.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/sagernet/wireguard-go => github.com/getlantern/wireguard-go v0.0.1-beta.5.0.20250303165430-793006c422ec
|
replace github.com/sagernet/wireguard-go => github.com/getlantern/wireguard-go v0.0.1-beta.5.0.20250303165430-793006c422ec
|
||||||
|
|
||||||
|
replace github.com/sagernet/sing-dns => github.com/shtorm-7/sing-dns v0.4.6-extended-1.0.0
|
||||||
|
|
||||||
|
replace github.com/ameshkov/dnscrypt/v2 => github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0
|
||||||
|
|||||||
48
go.sum
48
go.sum
@@ -1,5 +1,9 @@
|
|||||||
|
github.com/AdguardTeam/golibs v0.32.7 h1:3dmGlAVgmvquCCwHsvEl58KKcRAK3z1UnjMnwSIeDH4=
|
||||||
|
github.com/AdguardTeam/golibs v0.32.7/go.mod h1:bE8KV1zqTzgZjmjFyBJ9f9O5DEKO717r7e57j1HclJA=
|
||||||
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/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
||||||
|
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||||
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
|
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
|
||||||
@@ -16,6 +20,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU=
|
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU=
|
||||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
|
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
|
||||||
|
github.com/enfein/mieru/v3 v3.13.0 h1:eGyxLGkb+lut9ebmx+BGwLJ5UMbEc/wGIYO0AXEKy98=
|
||||||
|
github.com/enfein/mieru/v3 v3.13.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
github.com/getlantern/wireguard-go v0.0.1-beta.5.0.20250303165430-793006c422ec h1:jXekDkSozctYj5JQlV1mCsIW+qHpIkgSFhOIpgRSB1I=
|
github.com/getlantern/wireguard-go v0.0.1-beta.5.0.20250303165430-793006c422ec h1:jXekDkSozctYj5JQlV1mCsIW+qHpIkgSFhOIpgRSB1I=
|
||||||
@@ -133,20 +139,18 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4Wk
|
|||||||
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing v0.6.10 h1:Jey1tePgH9bjFuK1fQI3D9T+bPOQ4SdHMjuS4sYjDv4=
|
github.com/sagernet/sing v0.6.10 h1:Jey1tePgH9bjFuK1fQI3D9T+bPOQ4SdHMjuS4sYjDv4=
|
||||||
github.com/sagernet/sing v0.6.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.6.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-dns v0.4.5 h1:D9REN14qx2FTrZRBrtFLL99f2CuFzQ9S7mIf8uV5hZI=
|
|
||||||
github.com/sagernet/sing-dns v0.4.5/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8=
|
|
||||||
github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE=
|
github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE=
|
||||||
github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
||||||
github.com/sagernet/sing-quic v0.4.3 h1:OZ/kGvSzjtYg+t0DY3F606hlT5LeiQQXDxfBopcRryQ=
|
github.com/sagernet/sing-quic v0.4.4 h1:qqOCLnzHbqKkj/wBcXEI3rhSyqoGlqDdv2S6mz2d/JA=
|
||||||
github.com/sagernet/sing-quic v0.4.3/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY=
|
github.com/sagernet/sing-quic v0.4.4/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk=
|
github.com/sagernet/sing-shadowtls v0.2.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo=
|
github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo=
|
||||||
github.com/sagernet/sing-tun v0.6.5 h1:nGfD6GNq/r0tEjdZHOV3BS6fydSmd4kBAokU5rffssg=
|
github.com/sagernet/sing-tun v0.6.9 h1:uP8O4Q7U9QesjWumgxd2S9fjT3c6aEPWl5RB6uBdVB8=
|
||||||
github.com/sagernet/sing-tun v0.6.5/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
github.com/sagernet/sing-tun v0.6.9/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||||
github.com/sagernet/sing-vmess v0.2.3 h1:z6Ym8dnZG7k1fP3+54vz8G0tvRVJeOoTFFeUPwXTD44=
|
github.com/sagernet/sing-vmess v0.2.3 h1:z6Ym8dnZG7k1fP3+54vz8G0tvRVJeOoTFFeUPwXTD44=
|
||||||
github.com/sagernet/sing-vmess v0.2.3/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA=
|
github.com/sagernet/sing-vmess v0.2.3/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA=
|
||||||
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
||||||
@@ -155,6 +159,10 @@ github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8=
|
|||||||
github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM=
|
github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM=
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
|
||||||
|
github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0 h1:e5s7RKBd2rIPR0StbvZ2vTVtJ5jDTsTk5wtIIapZTRg=
|
||||||
|
github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0/go.mod h1:WpEFV2uhebXb8Jhes/5/fSdpmhGV8TL22RDaeWwV6hI=
|
||||||
|
github.com/shtorm-7/sing-dns v0.4.6-extended-1.0.0 h1:/fyqTYciGYTEbDDySW22t9vY5bSr+lCeLZfjVlv9qgw=
|
||||||
|
github.com/shtorm-7/sing-dns v0.4.6-extended-1.0.0/go.mod h1:HWuvH9L2ZwuffhzX5J5ZOjrhLQ8DB5qT9ie/YTXW1nU=
|
||||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||||
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=
|
||||||
@@ -191,16 +199,16 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
|||||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
@@ -214,8 +222,8 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/W
|
|||||||
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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
@@ -240,8 +248,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.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
||||||
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
||||||
@@ -251,8 +259,8 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
|
|||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf h1:dHDlF3CWxQkefK9IJx+O8ldY0gLygvrlYRBNbPqDWuY=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||||
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
||||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/protocol/dns"
|
"github.com/sagernet/sing-box/protocol/dns"
|
||||||
"github.com/sagernet/sing-box/protocol/group"
|
"github.com/sagernet/sing-box/protocol/group"
|
||||||
"github.com/sagernet/sing-box/protocol/http"
|
"github.com/sagernet/sing-box/protocol/http"
|
||||||
|
"github.com/sagernet/sing-box/protocol/mieru"
|
||||||
"github.com/sagernet/sing-box/protocol/mixed"
|
"github.com/sagernet/sing-box/protocol/mixed"
|
||||||
"github.com/sagernet/sing-box/protocol/naive"
|
"github.com/sagernet/sing-box/protocol/naive"
|
||||||
"github.com/sagernet/sing-box/protocol/redirect"
|
"github.com/sagernet/sing-box/protocol/redirect"
|
||||||
@@ -25,6 +26,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/protocol/tor"
|
"github.com/sagernet/sing-box/protocol/tor"
|
||||||
"github.com/sagernet/sing-box/protocol/trojan"
|
"github.com/sagernet/sing-box/protocol/trojan"
|
||||||
"github.com/sagernet/sing-box/protocol/tun"
|
"github.com/sagernet/sing-box/protocol/tun"
|
||||||
|
"github.com/sagernet/sing-box/protocol/tunnel"
|
||||||
"github.com/sagernet/sing-box/protocol/vless"
|
"github.com/sagernet/sing-box/protocol/vless"
|
||||||
"github.com/sagernet/sing-box/protocol/vmess"
|
"github.com/sagernet/sing-box/protocol/vmess"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -75,6 +77,7 @@ func OutboundRegistry() *outbound.Registry {
|
|||||||
ssh.RegisterOutbound(registry)
|
ssh.RegisterOutbound(registry)
|
||||||
shadowtls.RegisterOutbound(registry)
|
shadowtls.RegisterOutbound(registry)
|
||||||
vless.RegisterOutbound(registry)
|
vless.RegisterOutbound(registry)
|
||||||
|
mieru.RegisterOutbound(registry)
|
||||||
|
|
||||||
registerQUICOutbounds(registry)
|
registerQUICOutbounds(registry)
|
||||||
registerWireGuardOutbound(registry)
|
registerWireGuardOutbound(registry)
|
||||||
@@ -86,6 +89,9 @@ func OutboundRegistry() *outbound.Registry {
|
|||||||
func EndpointRegistry() *endpoint.Registry {
|
func EndpointRegistry() *endpoint.Registry {
|
||||||
registry := endpoint.NewRegistry()
|
registry := endpoint.NewRegistry()
|
||||||
|
|
||||||
|
tunnel.RegisterServerEndpoint(registry)
|
||||||
|
tunnel.RegisterClientEndpoint(registry)
|
||||||
|
|
||||||
registerWireGuardEndpoint(registry)
|
registerWireGuardEndpoint(registry)
|
||||||
|
|
||||||
return registry
|
return registry
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ package option
|
|||||||
import "github.com/sagernet/sing/common/json/badoption"
|
import "github.com/sagernet/sing/common/json/badoption"
|
||||||
|
|
||||||
type ExperimentalOptions struct {
|
type ExperimentalOptions struct {
|
||||||
CacheFile *CacheFileOptions `json:"cache_file,omitempty"`
|
CacheFile *CacheFileOptions `json:"cache_file,omitempty"`
|
||||||
ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"`
|
ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"`
|
||||||
V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"`
|
V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"`
|
||||||
Debug *DebugOptions `json:"debug,omitempty"`
|
UnifiedDelay *UnifiedDelayOptions `json:"unified_delay,omitempty"`
|
||||||
|
Debug *DebugOptions `json:"debug,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CacheFileOptions struct {
|
type CacheFileOptions struct {
|
||||||
@@ -53,3 +54,7 @@ type V2RayStatsServiceOptions struct {
|
|||||||
Outbounds []string `json:"outbounds,omitempty"`
|
Outbounds []string `json:"outbounds,omitempty"`
|
||||||
Users []string `json:"users,omitempty"`
|
Users []string `json:"users,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UnifiedDelayOptions struct {
|
||||||
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
|
}
|
||||||
|
|||||||
13
option/mieru.go
Normal file
13
option/mieru.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import "github.com/sagernet/sing/common/json/badoption"
|
||||||
|
|
||||||
|
type MieruOutboundOptions struct {
|
||||||
|
DialerOptions
|
||||||
|
ServerOptions
|
||||||
|
ServerPortRanges badoption.Listable[string] `json:"server_ports,omitempty"`
|
||||||
|
Transport string `json:"transport,omitempty"`
|
||||||
|
UserName string `json:"username,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
Multiplexing string `json:"multiplexing,omitempty"`
|
||||||
|
}
|
||||||
@@ -88,6 +88,8 @@ type RawDefaultRule struct {
|
|||||||
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
||||||
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
||||||
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
||||||
|
TunnelSource badoption.Listable[string] `json:"tunnel_source,omitempty"`
|
||||||
|
TunnelDestination badoption.Listable[string] `json:"tunnel_destination,omitempty"`
|
||||||
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
||||||
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
||||||
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
||||||
|
|||||||
@@ -142,8 +142,9 @@ type RouteActionOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RawRouteOptionsActionOptions struct {
|
type RawRouteOptionsActionOptions struct {
|
||||||
OverrideAddress string `json:"override_address,omitempty"`
|
OverrideAddress string `json:"override_address,omitempty"`
|
||||||
OverridePort uint16 `json:"override_port,omitempty"`
|
OverridePort uint16 `json:"override_port,omitempty"`
|
||||||
|
OverrideTunnelDestination string `json:"override_tunnel_destination,omitempty"`
|
||||||
|
|
||||||
NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"`
|
NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"`
|
||||||
FallbackDelay uint32 `json:"fallback_delay,omitempty"`
|
FallbackDelay uint32 `json:"fallback_delay,omitempty"`
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ type RawDefaultDNSRule struct {
|
|||||||
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
||||||
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
||||||
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
||||||
|
TunnelSource badoption.Listable[string] `json:"tunnel_source,omitempty"`
|
||||||
|
TunnelDestination badoption.Listable[string] `json:"tunnel_destination,omitempty"`
|
||||||
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
||||||
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
||||||
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
||||||
|
|||||||
@@ -194,6 +194,8 @@ type DefaultHeadlessRule struct {
|
|||||||
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
||||||
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
Port badoption.Listable[uint16] `json:"port,omitempty"`
|
||||||
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
PortRange badoption.Listable[string] `json:"port_range,omitempty"`
|
||||||
|
TunnelSource badoption.Listable[string] `json:"tunnel_source,omitempty"`
|
||||||
|
TunnelDestination badoption.Listable[string] `json:"tunnel_destination,omitempty"`
|
||||||
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
|
||||||
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
||||||
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
||||||
|
|||||||
21
option/tunnel.go
Normal file
21
option/tunnel.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import "github.com/sagernet/sing/common/json/badoption"
|
||||||
|
|
||||||
|
type TunnelClientEndpointOptions struct {
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
Outbound Outbound `json:"outbound"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TunnelServerEndpointOptions struct {
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
Users []TunnelUser `json:"users"`
|
||||||
|
Inbound Inbound `json:"inbound"`
|
||||||
|
ConnectTimeout badoption.Duration `json:"connect_timeout,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TunnelUser struct {
|
||||||
|
UUID string `json:"uuid"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
}
|
||||||
@@ -157,6 +157,7 @@ func (s *Selector) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
|||||||
func (s *Selector) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
func (s *Selector) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
ctx = interrupt.ContextWithIsExternalConnection(ctx)
|
ctx = interrupt.ContextWithIsExternalConnection(ctx)
|
||||||
selected := s.selected.Load()
|
selected := s.selected.Load()
|
||||||
|
conn = s.interruptGroup.NewConn(conn, interrupt.IsExternalConnectionFromContext(ctx))
|
||||||
if outboundHandler, isHandler := selected.(adapter.ConnectionHandlerEx); isHandler {
|
if outboundHandler, isHandler := selected.(adapter.ConnectionHandlerEx); isHandler {
|
||||||
outboundHandler.NewConnectionEx(ctx, conn, metadata, onClose)
|
outboundHandler.NewConnectionEx(ctx, conn, metadata, onClose)
|
||||||
} else {
|
} else {
|
||||||
@@ -167,6 +168,7 @@ func (s *Selector) NewConnectionEx(ctx context.Context, conn net.Conn, metadata
|
|||||||
func (s *Selector) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
func (s *Selector) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
ctx = interrupt.ContextWithIsExternalConnection(ctx)
|
ctx = interrupt.ContextWithIsExternalConnection(ctx)
|
||||||
selected := s.selected.Load()
|
selected := s.selected.Load()
|
||||||
|
conn = s.interruptGroup.NewSingPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx))
|
||||||
if outboundHandler, isHandler := selected.(adapter.PacketConnectionHandlerEx); isHandler {
|
if outboundHandler, isHandler := selected.(adapter.PacketConnectionHandlerEx); isHandler {
|
||||||
outboundHandler.NewPacketConnectionEx(ctx, conn, metadata, onClose)
|
outboundHandler.NewPacketConnectionEx(ctx, conn, metadata, onClose)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -162,11 +162,13 @@ func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (ne
|
|||||||
|
|
||||||
func (s *URLTest) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
func (s *URLTest) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
ctx = interrupt.ContextWithIsExternalConnection(ctx)
|
ctx = interrupt.ContextWithIsExternalConnection(ctx)
|
||||||
|
conn = s.group.interruptGroup.NewConn(conn, interrupt.IsExternalConnectionFromContext(ctx))
|
||||||
s.connection.NewConnection(ctx, s, conn, metadata, onClose)
|
s.connection.NewConnection(ctx, s, conn, metadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *URLTest) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
func (s *URLTest) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
ctx = interrupt.ContextWithIsExternalConnection(ctx)
|
ctx = interrupt.ContextWithIsExternalConnection(ctx)
|
||||||
|
conn = s.group.interruptGroup.NewSingPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx))
|
||||||
s.connection.NewPacketConnection(ctx, s, conn, metadata, onClose)
|
s.connection.NewPacketConnection(ctx, s, conn, metadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
245
protocol/mieru/outbound.go
Normal file
245
protocol/mieru/outbound.go
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
package mieru
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"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"
|
||||||
|
|
||||||
|
mieruclient "github.com/enfein/mieru/v3/apis/client"
|
||||||
|
mierucommon "github.com/enfein/mieru/v3/apis/common"
|
||||||
|
mierumodel "github.com/enfein/mieru/v3/apis/model"
|
||||||
|
mierupb "github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Outbound struct {
|
||||||
|
outbound.Adapter
|
||||||
|
dialer N.Dialer
|
||||||
|
logger log.ContextLogger
|
||||||
|
client mieruclient.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterOutbound(registry *outbound.Registry) {
|
||||||
|
outbound.Register(registry, C.TypeMieru, NewOutbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.MieruOutboundOptions) (adapter.Outbound, error) {
|
||||||
|
outboundDialer, err := dialer.New(ctx, options.DialerOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := buildMieruClientConfig(options, mieruDialer{dialer: outboundDialer})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to build mieru client config: %w", err)
|
||||||
|
}
|
||||||
|
c := mieruclient.NewClient()
|
||||||
|
if err := c.Store(config); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to store mieru client config: %w", err)
|
||||||
|
}
|
||||||
|
if err := c.Start(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to start mieru client: %w", err)
|
||||||
|
}
|
||||||
|
logger.InfoContext(ctx, "mieru client is started")
|
||||||
|
|
||||||
|
return &Outbound{
|
||||||
|
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeMieru, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions),
|
||||||
|
dialer: outboundDialer,
|
||||||
|
logger: logger,
|
||||||
|
client: c,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
ctx, metadata := adapter.ExtendContext(ctx)
|
||||||
|
metadata.Outbound = o.Tag()
|
||||||
|
metadata.Destination = destination
|
||||||
|
switch N.NetworkName(network) {
|
||||||
|
case N.NetworkTCP:
|
||||||
|
o.logger.InfoContext(ctx, "outbound connection to ", destination)
|
||||||
|
d, err := socksAddrToNetAddrSpec(destination, "tcp")
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "failed to convert destination address")
|
||||||
|
}
|
||||||
|
return o.client.DialContext(ctx, d)
|
||||||
|
case N.NetworkUDP:
|
||||||
|
o.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination)
|
||||||
|
d, err := socksAddrToNetAddrSpec(destination, "udp")
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "failed to convert destination address")
|
||||||
|
}
|
||||||
|
streamConn, err := o.client.DialContext(ctx, d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &streamer{
|
||||||
|
PacketConn: mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(streamConn)),
|
||||||
|
Remote: destination,
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
ctx, metadata := adapter.ExtendContext(ctx)
|
||||||
|
metadata.Outbound = o.Tag()
|
||||||
|
metadata.Destination = destination
|
||||||
|
o.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination)
|
||||||
|
d, err := socksAddrToNetAddrSpec(destination, "udp")
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "failed to convert destination address")
|
||||||
|
}
|
||||||
|
streamConn, err := o.client.DialContext(ctx, d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(streamConn)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Outbound) Close() error {
|
||||||
|
return common.Close(o.client)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mieruDialer is an adapter to mieru dialer interface.
|
||||||
|
type mieruDialer struct {
|
||||||
|
dialer N.Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md mieruDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
addr := M.ParseSocksaddr(address)
|
||||||
|
return md.dialer.DialContext(ctx, network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ mierucommon.Dialer = (*mieruDialer)(nil)
|
||||||
|
|
||||||
|
// streamer converts a net.PacketConn to a net.Conn.
|
||||||
|
type streamer struct {
|
||||||
|
net.PacketConn
|
||||||
|
Remote net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ net.Conn = (*streamer)(nil)
|
||||||
|
|
||||||
|
func (s *streamer) Read(b []byte) (n int, err error) {
|
||||||
|
n, _, err = s.PacketConn.ReadFrom(b)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *streamer) Write(b []byte) (n int, err error) {
|
||||||
|
return s.WriteTo(b, s.Remote)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *streamer) RemoteAddr() net.Addr {
|
||||||
|
return s.Remote
|
||||||
|
}
|
||||||
|
|
||||||
|
// socksAddrToNetAddrSpec converts a Socksaddr object to NetAddrSpec, and overrides the network.
|
||||||
|
func socksAddrToNetAddrSpec(sa M.Socksaddr, network string) (mierumodel.NetAddrSpec, error) {
|
||||||
|
var nas mierumodel.NetAddrSpec
|
||||||
|
if err := nas.From(sa); err != nil {
|
||||||
|
return nas, err
|
||||||
|
}
|
||||||
|
nas.Net = network
|
||||||
|
return nas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildMieruClientConfig(options option.MieruOutboundOptions, dialer mieruDialer) (*mieruclient.ClientConfig, error) {
|
||||||
|
if err := validateMieruOptions(options); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to validate mieru options: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
transportProtocol := mierupb.TransportProtocol_TCP.Enum()
|
||||||
|
server := &mierupb.ServerEndpoint{}
|
||||||
|
if options.ServerPort != 0 {
|
||||||
|
server.PortBindings = append(server.PortBindings, &mierupb.PortBinding{
|
||||||
|
Port: proto.Int32(int32(options.ServerPort)),
|
||||||
|
Protocol: transportProtocol,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, pr := range options.ServerPortRanges {
|
||||||
|
server.PortBindings = append(server.PortBindings, &mierupb.PortBinding{
|
||||||
|
PortRange: proto.String(pr),
|
||||||
|
Protocol: transportProtocol,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if M.IsDomainName(options.Server) {
|
||||||
|
server.DomainName = proto.String(options.Server)
|
||||||
|
} else {
|
||||||
|
server.IpAddress = proto.String(options.Server)
|
||||||
|
}
|
||||||
|
config := &mieruclient.ClientConfig{
|
||||||
|
Profile: &mierupb.ClientProfile{
|
||||||
|
ProfileName: proto.String("sing-box"),
|
||||||
|
User: &mierupb.User{
|
||||||
|
Name: proto.String(options.UserName),
|
||||||
|
Password: proto.String(options.Password),
|
||||||
|
},
|
||||||
|
Servers: []*mierupb.ServerEndpoint{server},
|
||||||
|
},
|
||||||
|
Dialer: dialer,
|
||||||
|
}
|
||||||
|
if multiplexing, ok := mierupb.MultiplexingLevel_value[options.Multiplexing]; ok {
|
||||||
|
config.Profile.Multiplexing = &mierupb.MultiplexingConfig{
|
||||||
|
Level: mierupb.MultiplexingLevel(multiplexing).Enum(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateMieruOptions(options option.MieruOutboundOptions) error {
|
||||||
|
if options.Server == "" {
|
||||||
|
return fmt.Errorf("server is empty")
|
||||||
|
}
|
||||||
|
if options.ServerPort == 0 && len(options.ServerPortRanges) == 0 {
|
||||||
|
return fmt.Errorf("either server_port or server_ports must be set")
|
||||||
|
}
|
||||||
|
for _, pr := range options.ServerPortRanges {
|
||||||
|
begin, end, err := beginAndEndPortFromPortRange(pr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid server_ports format")
|
||||||
|
}
|
||||||
|
if begin < 1 || begin > 65535 {
|
||||||
|
return fmt.Errorf("begin port must be between 1 and 65535")
|
||||||
|
}
|
||||||
|
if end < 1 || end > 65535 {
|
||||||
|
return fmt.Errorf("end port must be between 1 and 65535")
|
||||||
|
}
|
||||||
|
if begin > end {
|
||||||
|
return fmt.Errorf("begin port must be less than or equal to end port")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.Transport != "TCP" {
|
||||||
|
return fmt.Errorf("transport must be TCP")
|
||||||
|
}
|
||||||
|
if options.UserName == "" {
|
||||||
|
return fmt.Errorf("username is empty")
|
||||||
|
}
|
||||||
|
if options.Password == "" {
|
||||||
|
return fmt.Errorf("password is empty")
|
||||||
|
}
|
||||||
|
if options.Multiplexing != "" {
|
||||||
|
if _, ok := mierupb.MultiplexingLevel_value[options.Multiplexing]; !ok {
|
||||||
|
return fmt.Errorf("invalid multiplexing level: %s", options.Multiplexing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func beginAndEndPortFromPortRange(portRange string) (int, int, error) {
|
||||||
|
var begin, end int
|
||||||
|
_, err := fmt.Sscanf(portRange, "%d-%d", &begin, &end)
|
||||||
|
return begin, end, err
|
||||||
|
}
|
||||||
152
protocol/tunnel/client.go
Normal file
152
protocol/tunnel/client.go
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
package tunnel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterClientEndpoint(registry *endpoint.Registry) {
|
||||||
|
endpoint.Register[option.TunnelClientEndpointOptions](registry, C.TypeTunnelClient, NewClientEndpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientEndpoint struct {
|
||||||
|
outbound.Adapter
|
||||||
|
ctx context.Context
|
||||||
|
outbound adapter.Outbound
|
||||||
|
router adapter.ConnectionRouterEx
|
||||||
|
logger logger.ContextLogger
|
||||||
|
uuid uuid.UUID
|
||||||
|
key uuid.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClientEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunnelClientEndpointOptions) (adapter.Endpoint, error) {
|
||||||
|
clientUUID, err := uuid.FromString(options.UUID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clientKey, err := uuid.FromString(options.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client := &ClientEndpoint{
|
||||||
|
Adapter: outbound.NewAdapter(C.TypeTunnelClient, tag, []string{N.NetworkTCP}, []string{}),
|
||||||
|
ctx: ctx,
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
uuid: clientUUID,
|
||||||
|
key: clientKey,
|
||||||
|
}
|
||||||
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
|
outbound, err := outboundRegistry.CreateOutbound(ctx, router, logger, options.Outbound.Tag, options.Outbound.Type, options.Outbound.Options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client.outbound = outbound
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientEndpoint) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStatePostStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for range 5 {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
err := c.startInboundConn()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.ErrorContext(c.ctx, err)
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
if network != N.NetworkTCP {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
var destinationUUID *uuid.UUID
|
||||||
|
if metadata := adapter.ContextFrom(ctx); metadata != nil {
|
||||||
|
if metadata.TunnelDestination != "" {
|
||||||
|
uuid, err := uuid.FromString(metadata.TunnelDestination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
destinationUUID = &uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if destinationUUID == nil {
|
||||||
|
return nil, E.New("tunnel destination not set")
|
||||||
|
}
|
||||||
|
if *destinationUUID == c.uuid {
|
||||||
|
return nil, E.New("routing loop")
|
||||||
|
}
|
||||||
|
conn, err := c.outbound.DialContext(ctx, N.NetworkTCP, Destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = WriteRequest(conn, &Request{UUID: c.key, Command: CommandTCP, DestinationUUID: *destinationUUID, Destination: destination})
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientEndpoint) Close() error {
|
||||||
|
return common.Close(c.outbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientEndpoint) startInboundConn() error {
|
||||||
|
conn, err := c.outbound.DialContext(c.ctx, N.NetworkTCP, Destination)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = WriteRequest(conn, &Request{UUID: c.key, Command: CommandInbound, Destination: Destination})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
request, err := ReadRequest(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go c.connHandler(conn, request)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientEndpoint) connHandler(conn net.Conn, request *Request) {
|
||||||
|
metadata := adapter.InboundContext{
|
||||||
|
Source: M.ParseSocksaddr(conn.RemoteAddr().String()),
|
||||||
|
Destination: request.Destination,
|
||||||
|
}
|
||||||
|
if request.UUID == c.uuid {
|
||||||
|
c.logger.ErrorContext(c.ctx, "routing loop")
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
metadata.TunnelSource = request.UUID.String()
|
||||||
|
c.router.RouteConnectionEx(c.ctx, conn, metadata, func(it error) {})
|
||||||
|
}
|
||||||
91
protocol/tunnel/protocol.go
Normal file
91
protocol/tunnel/protocol.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package tunnel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Version = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CommandInbound = 1
|
||||||
|
CommandTCP = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var Destination = M.Socksaddr{
|
||||||
|
Fqdn: "sp.tunnel.sing-box.arpa",
|
||||||
|
Port: 444,
|
||||||
|
}
|
||||||
|
|
||||||
|
var AddressSerializer = M.NewSerializer(
|
||||||
|
M.AddressFamilyByte(0x01, M.AddressFamilyIPv4),
|
||||||
|
M.AddressFamilyByte(0x03, M.AddressFamilyIPv6),
|
||||||
|
M.AddressFamilyByte(0x02, M.AddressFamilyFqdn),
|
||||||
|
M.PortThenAddress(),
|
||||||
|
)
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
UUID uuid.UUID
|
||||||
|
Command byte
|
||||||
|
DestinationUUID uuid.UUID
|
||||||
|
Destination M.Socksaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadRequest(reader io.Reader) (*Request, error) {
|
||||||
|
var request Request
|
||||||
|
var version uint8
|
||||||
|
err := binary.Read(reader, binary.BigEndian, &version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if version != Version {
|
||||||
|
return nil, E.New("unknown version: ", version)
|
||||||
|
}
|
||||||
|
_, err = io.ReadFull(reader, request.UUID[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &request.Command)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = io.ReadFull(reader, request.DestinationUUID[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
request.Destination, err = AddressSerializer.ReadAddrPort(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &request, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteRequest(writer io.Writer, request *Request) error {
|
||||||
|
var requestLen int
|
||||||
|
requestLen += 1 // version
|
||||||
|
requestLen += 16 // UUID
|
||||||
|
requestLen += 16 // destinationUUID
|
||||||
|
requestLen += 1 // command
|
||||||
|
requestLen += AddressSerializer.AddrPortLen(request.Destination)
|
||||||
|
buffer := buf.NewSize(requestLen)
|
||||||
|
defer buffer.Release()
|
||||||
|
common.Must(
|
||||||
|
buffer.WriteByte(Version),
|
||||||
|
common.Error(buffer.Write(request.UUID[:])),
|
||||||
|
buffer.WriteByte(request.Command),
|
||||||
|
common.Error(buffer.Write(request.DestinationUUID[:])),
|
||||||
|
)
|
||||||
|
err := AddressSerializer.WriteAddrPort(buffer, request.Destination)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return common.Error(writer.Write(buffer.Bytes()))
|
||||||
|
}
|
||||||
41
protocol/tunnel/router.go
Normal file
41
protocol/tunnel/router.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package tunnel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Router struct {
|
||||||
|
adapter.Router
|
||||||
|
logger logger.ContextLogger
|
||||||
|
handler func(context.Context, net.Conn, adapter.InboundContext, N.CloseHandlerFunc) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouter(router adapter.Router, logger logger.ContextLogger, handler func(context.Context, net.Conn, adapter.InboundContext, N.CloseHandlerFunc) error) *Router {
|
||||||
|
return &Router{Router: router, logger: logger, handler: handler}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
|
return r.handler(ctx, conn, metadata, func(error) {})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
|
if err := r.handler(ctx, conn, metadata, onClose); err != nil {
|
||||||
|
r.logger.ErrorContext(ctx, err)
|
||||||
|
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
|
r.logger.ErrorContext(ctx, os.ErrInvalid)
|
||||||
|
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||||
|
}
|
||||||
203
protocol/tunnel/server.go
Normal file
203
protocol/tunnel/server.go
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
package tunnel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterServerEndpoint(registry *endpoint.Registry) {
|
||||||
|
endpoint.Register[option.TunnelServerEndpointOptions](registry, C.TypeTunnelServer, NewServerEndpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerEndpoint struct {
|
||||||
|
outbound.Adapter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
inbound adapter.Inbound
|
||||||
|
router adapter.Router
|
||||||
|
uuid uuid.UUID
|
||||||
|
users map[uuid.UUID]uuid.UUID
|
||||||
|
keys map[uuid.UUID]uuid.UUID
|
||||||
|
conns map[uuid.UUID]chan net.Conn
|
||||||
|
timeout time.Duration
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunnelServerEndpointOptions) (adapter.Endpoint, error) {
|
||||||
|
serverUUID, err := uuid.FromString(options.UUID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
server := &ServerEndpoint{
|
||||||
|
Adapter: outbound.NewAdapter(C.TypeTunnelServer, tag, []string{N.NetworkTCP}, []string{}),
|
||||||
|
logger: logger,
|
||||||
|
router: router,
|
||||||
|
uuid: serverUUID,
|
||||||
|
}
|
||||||
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
|
inbound, err := inboundRegistry.Create(ctx, NewRouter(router, logger, server.connHandler), logger, options.Inbound.Tag, options.Inbound.Type, options.Inbound.Options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
server.inbound = inbound
|
||||||
|
server.users = make(map[uuid.UUID]uuid.UUID, len(options.Users))
|
||||||
|
server.keys = make(map[uuid.UUID]uuid.UUID, len(options.Users))
|
||||||
|
server.conns = make(map[uuid.UUID]chan net.Conn)
|
||||||
|
for _, user := range options.Users {
|
||||||
|
key, err := uuid.FromString(user.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
uuid, err := uuid.FromString(user.UUID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
server.users[key] = uuid
|
||||||
|
server.keys[uuid] = key
|
||||||
|
server.conns[uuid] = make(chan net.Conn, 10)
|
||||||
|
}
|
||||||
|
if options.ConnectTimeout != 0 {
|
||||||
|
server.timeout = time.Duration(options.ConnectTimeout)
|
||||||
|
} else {
|
||||||
|
server.timeout = C.TCPConnectTimeout
|
||||||
|
}
|
||||||
|
return server, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerEndpoint) Start(stage adapter.StartStage) error {
|
||||||
|
return s.inbound.Start(stage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
if network != N.NetworkTCP {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
var sourceUUID *uuid.UUID
|
||||||
|
var ch chan net.Conn
|
||||||
|
if metadata := adapter.ContextFrom(ctx); metadata != nil {
|
||||||
|
if metadata.TunnelDestination != "" {
|
||||||
|
tunnelDestination, err := uuid.FromString(metadata.TunnelDestination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.mtx.Lock()
|
||||||
|
var ok bool
|
||||||
|
ch, ok = s.conns[tunnelDestination]
|
||||||
|
if !ok {
|
||||||
|
return nil, E.New("user ", metadata.TunnelDestination, " not found")
|
||||||
|
}
|
||||||
|
s.mtx.Unlock()
|
||||||
|
}
|
||||||
|
if metadata.TunnelSource != "" {
|
||||||
|
tunnelSource, err := uuid.FromString(metadata.TunnelSource)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sourceUUID = &tunnelSource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ch == nil {
|
||||||
|
return nil, E.New("tunnel destination not set")
|
||||||
|
}
|
||||||
|
if sourceUUID == nil {
|
||||||
|
sourceUUID = &s.uuid
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, s.timeout)
|
||||||
|
defer cancel()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case conn := <-ch:
|
||||||
|
err := WriteRequest(conn, &Request{UUID: *sourceUUID, Command: CommandTCP, Destination: destination})
|
||||||
|
if err != nil {
|
||||||
|
s.logger.ErrorContext(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerEndpoint) Close() error {
|
||||||
|
return common.Close(s.inbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ServerEndpoint) connHandler(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {
|
||||||
|
if metadata.Destination != Destination {
|
||||||
|
s.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
request, err := ReadRequest(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if request.Command == CommandInbound {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
uuid, ok := s.users[request.UUID]
|
||||||
|
if !ok {
|
||||||
|
return E.New("key ", request.UUID.String(), " not found")
|
||||||
|
}
|
||||||
|
ch := s.conns[uuid]
|
||||||
|
select {
|
||||||
|
case ch <- conn:
|
||||||
|
default:
|
||||||
|
oldConn := <-ch
|
||||||
|
oldConn.Close()
|
||||||
|
ch <- conn
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if request.Command == CommandTCP {
|
||||||
|
sourceUUID, ok := s.users[request.UUID]
|
||||||
|
if !ok {
|
||||||
|
return E.New("key ", request.UUID, " not found")
|
||||||
|
}
|
||||||
|
if sourceUUID == request.DestinationUUID {
|
||||||
|
return E.New("routing loop on ", sourceUUID)
|
||||||
|
}
|
||||||
|
s.mtx.Lock()
|
||||||
|
if request.DestinationUUID != s.uuid {
|
||||||
|
_, ok = s.keys[request.DestinationUUID]
|
||||||
|
if !ok {
|
||||||
|
return E.New("user ", sourceUUID, " not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.mtx.Unlock()
|
||||||
|
metadata.Inbound = s.Tag()
|
||||||
|
metadata.InboundType = C.TypeTunnelServer
|
||||||
|
metadata.Destination = request.Destination
|
||||||
|
metadata.TunnelSource = sourceUUID.String()
|
||||||
|
metadata.TunnelDestination = request.DestinationUUID.String()
|
||||||
|
s.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return E.New("command ", request.Command, " not found")
|
||||||
|
}
|
||||||
@@ -205,6 +205,10 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.
|
|||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
metadata.Source = source
|
metadata.Source = source
|
||||||
metadata.Destination = destination
|
metadata.Destination = destination
|
||||||
|
//nolint:staticcheck
|
||||||
|
metadata.InboundDetour = h.listener.ListenOptions().Detour
|
||||||
|
//nolint:staticcheck
|
||||||
|
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
|
||||||
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||||
(*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose)
|
(*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,6 +219,10 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.
|
|||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
metadata.Source = source
|
metadata.Source = source
|
||||||
metadata.Destination = destination
|
metadata.Destination = destination
|
||||||
|
//nolint:staticcheck
|
||||||
|
metadata.InboundDetour = h.listener.ListenOptions().Detour
|
||||||
|
//nolint:staticcheck
|
||||||
|
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
|
||||||
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||||
(*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose)
|
(*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,31 +185,28 @@ func (w *WARPEndpoint) Start(stage adapter.StartStage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *WARPEndpoint) Close() error {
|
func (w *WARPEndpoint) Close() error {
|
||||||
if err := w.isEndpointInitialized(); err != nil {
|
if ok := w.isEndpointInitialized(); !ok {
|
||||||
return err
|
return E.New("endpoint not initialized")
|
||||||
}
|
}
|
||||||
return w.endpoint.Close()
|
return w.endpoint.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WARPEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (w *WARPEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
if err := w.isEndpointInitialized(); err != nil {
|
if ok := w.isEndpointInitialized(); !ok {
|
||||||
return nil, err
|
return nil, E.New("endpoint not initialized")
|
||||||
}
|
}
|
||||||
return w.endpoint.DialContext(ctx, network, destination)
|
return w.endpoint.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WARPEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (w *WARPEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
if err := w.isEndpointInitialized(); err != nil {
|
if ok := w.isEndpointInitialized(); !ok {
|
||||||
return nil, err
|
return nil, E.New("endpoint not initialized")
|
||||||
}
|
}
|
||||||
return w.endpoint.ListenPacket(ctx, destination)
|
return w.endpoint.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WARPEndpoint) isEndpointInitialized() error {
|
func (w *WARPEndpoint) isEndpointInitialized() bool {
|
||||||
w.mtx.Lock()
|
w.mtx.Lock()
|
||||||
defer w.mtx.Unlock()
|
defer w.mtx.Unlock()
|
||||||
if w.endpoint == nil {
|
return w.endpoint != nil
|
||||||
return E.New("endpoint not initialized")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
17
route/dns.go
17
route/dns.go
@@ -2,15 +2,14 @@ package route
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
dnsOutbound "github.com/sagernet/sing-box/protocol/dns"
|
dnsOutbound "github.com/sagernet/sing-box/protocol/dns"
|
||||||
|
R "github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@@ -31,7 +30,7 @@ func (r *Router) hijackDNSStream(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetBuffers []*N.PacketBuffer, metadata adapter.InboundContext) {
|
func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetBuffers []*N.PacketBuffer, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
if natConn, isNatConn := conn.(udpnat.Conn); isNatConn {
|
if natConn, isNatConn := conn.(udpnat.Conn); isNatConn {
|
||||||
metadata.Destination = M.Socksaddr{}
|
metadata.Destination = M.Socksaddr{}
|
||||||
for _, packet := range packetBuffers {
|
for _, packet := range packetBuffers {
|
||||||
@@ -45,10 +44,12 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB
|
|||||||
conn: conn,
|
conn: conn,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
|
onClose: onClose,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := dnsOutbound.NewDNSPacketConnection(ctx, r, conn, packetBuffers, metadata)
|
err := dnsOutbound.NewDNSPacketConnection(ctx, r, conn, packetBuffers, metadata)
|
||||||
|
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||||
if err != nil && !E.IsClosedOrCanceled(err) {
|
if err != nil && !E.IsClosedOrCanceled(err) {
|
||||||
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection"))
|
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection"))
|
||||||
}
|
}
|
||||||
@@ -56,7 +57,7 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB
|
|||||||
|
|
||||||
func ExchangeDNSPacket(ctx context.Context, router *Router, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) {
|
func ExchangeDNSPacket(ctx context.Context, router *Router, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) {
|
||||||
err := exchangeDNSPacket(ctx, router, conn, buffer, metadata, destination)
|
err := exchangeDNSPacket(ctx, router, conn, buffer, metadata, destination)
|
||||||
if err != nil && !errors.Is(err, tun.ErrDrop) && !E.IsClosedOrCanceled(err) {
|
if err != nil && !R.IsRejected(err) && !E.IsClosedOrCanceled(err) {
|
||||||
router.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection"))
|
router.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,8 +86,16 @@ type dnsHijacker struct {
|
|||||||
conn N.PacketConn
|
conn N.PacketConn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
metadata adapter.InboundContext
|
metadata adapter.InboundContext
|
||||||
|
onClose N.CloseHandlerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *dnsHijacker) NewPacketEx(buffer *buf.Buffer, destination M.Socksaddr) {
|
func (h *dnsHijacker) NewPacketEx(buffer *buf.Buffer, destination M.Socksaddr) {
|
||||||
go ExchangeDNSPacket(h.ctx, h.router, h.conn, buffer, h.metadata, destination)
|
go ExchangeDNSPacket(h.ctx, h.router, h.conn, buffer, h.metadata, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *dnsHijacker) Close() error {
|
||||||
|
if h.onClose != nil {
|
||||||
|
h.onClose(nil)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/common/sniff"
|
"github.com/sagernet/sing-box/common/sniff"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/route/rule"
|
R "github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing-mux"
|
"github.com/sagernet/sing-mux"
|
||||||
"github.com/sagernet/sing-vmess"
|
"github.com/sagernet/sing-vmess"
|
||||||
@@ -51,7 +51,7 @@ func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata
|
|||||||
err := r.routeConnection(ctx, conn, metadata, onClose)
|
err := r.routeConnection(ctx, conn, metadata, onClose)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||||
if E.IsClosedOrCanceled(err) {
|
if E.IsClosedOrCanceled(err) || R.IsRejected(err) {
|
||||||
r.logger.DebugContext(ctx, "connection closed: ", err)
|
r.logger.DebugContext(ctx, "connection closed: ", err)
|
||||||
} else {
|
} else {
|
||||||
r.logger.ErrorContext(ctx, err)
|
r.logger.ErrorContext(ctx, err)
|
||||||
@@ -101,7 +101,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
var selectedOutbound adapter.Outbound
|
var selectedOutbound adapter.Outbound
|
||||||
if selectedRule != nil {
|
if selectedRule != nil {
|
||||||
switch action := selectedRule.Action().(type) {
|
switch action := selectedRule.Action().(type) {
|
||||||
case *rule.RuleActionRoute:
|
case *R.RuleActionRoute:
|
||||||
var loaded bool
|
var loaded bool
|
||||||
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
|
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
@@ -112,15 +112,15 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
buf.ReleaseMulti(buffers)
|
buf.ReleaseMulti(buffers)
|
||||||
return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag())
|
return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag())
|
||||||
}
|
}
|
||||||
case *rule.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
buf.ReleaseMulti(buffers)
|
buf.ReleaseMulti(buffers)
|
||||||
N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx))
|
N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx))
|
||||||
return nil
|
return nil
|
||||||
case *rule.RuleActionHijackDNS:
|
case *R.RuleActionHijackDNS:
|
||||||
for _, buffer := range buffers {
|
for _, buffer := range buffers {
|
||||||
conn = bufio.NewCachedConn(conn, buffer)
|
conn = bufio.NewCachedConn(conn, buffer)
|
||||||
}
|
}
|
||||||
r.hijackDNSStream(ctx, conn, metadata)
|
N.CloseOnHandshakeFailure(conn, onClose, r.hijackDNSStream(ctx, conn, metadata))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,7 +154,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
if E.IsClosedOrCanceled(err) {
|
if E.IsClosedOrCanceled(err) || R.IsRejected(err) {
|
||||||
r.logger.DebugContext(ctx, "connection closed: ", err)
|
r.logger.DebugContext(ctx, "connection closed: ", err)
|
||||||
} else {
|
} else {
|
||||||
r.logger.ErrorContext(ctx, err)
|
r.logger.ErrorContext(ctx, err)
|
||||||
@@ -171,7 +171,7 @@ func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
|||||||
err := r.routePacketConnection(ctx, conn, metadata, onClose)
|
err := r.routePacketConnection(ctx, conn, metadata, onClose)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||||
if E.IsClosedOrCanceled(err) {
|
if E.IsClosedOrCanceled(err) || R.IsRejected(err) {
|
||||||
r.logger.DebugContext(ctx, "connection closed: ", err)
|
r.logger.DebugContext(ctx, "connection closed: ", err)
|
||||||
} else {
|
} else {
|
||||||
r.logger.ErrorContext(ctx, err)
|
r.logger.ErrorContext(ctx, err)
|
||||||
@@ -217,7 +217,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
var selectReturn bool
|
var selectReturn bool
|
||||||
if selectedRule != nil {
|
if selectedRule != nil {
|
||||||
switch action := selectedRule.Action().(type) {
|
switch action := selectedRule.Action().(type) {
|
||||||
case *rule.RuleActionRoute:
|
case *R.RuleActionRoute:
|
||||||
var loaded bool
|
var loaded bool
|
||||||
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
|
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
@@ -228,12 +228,12 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
N.ReleaseMultiPacketBuffer(packetBuffers)
|
N.ReleaseMultiPacketBuffer(packetBuffers)
|
||||||
return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag())
|
return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag())
|
||||||
}
|
}
|
||||||
case *rule.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
N.ReleaseMultiPacketBuffer(packetBuffers)
|
N.ReleaseMultiPacketBuffer(packetBuffers)
|
||||||
N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx))
|
N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx))
|
||||||
return nil
|
return nil
|
||||||
case *rule.RuleActionHijackDNS:
|
case *R.RuleActionHijackDNS:
|
||||||
r.hijackDNSPacket(ctx, conn, packetBuffers, metadata)
|
r.hijackDNSPacket(ctx, conn, packetBuffers, metadata, onClose)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,7 +271,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext) error {
|
|||||||
if selectedRule == nil {
|
if selectedRule == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
rejectAction, isReject := selectedRule.Action().(*rule.RuleActionReject)
|
rejectAction, isReject := selectedRule.Action().(*R.RuleActionReject)
|
||||||
if !isReject {
|
if !isReject {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -346,7 +346,7 @@ func (r *Router) matchRule(
|
|||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
if metadata.InboundOptions != common.DefaultValue[option.InboundOptions]() {
|
if metadata.InboundOptions != common.DefaultValue[option.InboundOptions]() {
|
||||||
if !preMatch && metadata.InboundOptions.SniffEnabled {
|
if !preMatch && metadata.InboundOptions.SniffEnabled {
|
||||||
newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{
|
newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &R.RuleActionSniff{
|
||||||
OverrideDestination: metadata.InboundOptions.SniffOverrideDestination,
|
OverrideDestination: metadata.InboundOptions.SniffOverrideDestination,
|
||||||
Timeout: time.Duration(metadata.InboundOptions.SniffTimeout),
|
Timeout: time.Duration(metadata.InboundOptions.SniffTimeout),
|
||||||
}, inputConn, inputPacketConn, nil)
|
}, inputConn, inputPacketConn, nil)
|
||||||
@@ -361,7 +361,7 @@ func (r *Router) matchRule(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS {
|
if dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS {
|
||||||
fatalErr = r.actionResolve(ctx, metadata, &rule.RuleActionResolve{
|
fatalErr = r.actionResolve(ctx, metadata, &R.RuleActionResolve{
|
||||||
Strategy: dns.DomainStrategy(metadata.InboundOptions.DomainStrategy),
|
Strategy: dns.DomainStrategy(metadata.InboundOptions.DomainStrategy),
|
||||||
})
|
})
|
||||||
if fatalErr != nil {
|
if fatalErr != nil {
|
||||||
@@ -398,11 +398,11 @@ match:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var routeOptions *rule.RuleActionRouteOptions
|
var routeOptions *R.RuleActionRouteOptions
|
||||||
switch action := currentRule.Action().(type) {
|
switch action := currentRule.Action().(type) {
|
||||||
case *rule.RuleActionRoute:
|
case *R.RuleActionRoute:
|
||||||
routeOptions = &action.RuleActionRouteOptions
|
routeOptions = &action.RuleActionRouteOptions
|
||||||
case *rule.RuleActionRouteOptions:
|
case *R.RuleActionRouteOptions:
|
||||||
routeOptions = action
|
routeOptions = action
|
||||||
}
|
}
|
||||||
if routeOptions != nil {
|
if routeOptions != nil {
|
||||||
@@ -425,6 +425,9 @@ match:
|
|||||||
Fqdn: metadata.Destination.Fqdn,
|
Fqdn: metadata.Destination.Fqdn,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if routeOptions.OverrideTunnelDestination != "" {
|
||||||
|
metadata.TunnelDestination = routeOptions.OverrideTunnelDestination
|
||||||
|
}
|
||||||
if routeOptions.NetworkStrategy != nil {
|
if routeOptions.NetworkStrategy != nil {
|
||||||
metadata.NetworkStrategy = routeOptions.NetworkStrategy
|
metadata.NetworkStrategy = routeOptions.NetworkStrategy
|
||||||
}
|
}
|
||||||
@@ -448,7 +451,7 @@ match:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch action := currentRule.Action().(type) {
|
switch action := currentRule.Action().(type) {
|
||||||
case *rule.RuleActionSniff:
|
case *R.RuleActionSniff:
|
||||||
if !preMatch {
|
if !preMatch {
|
||||||
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers)
|
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers)
|
||||||
if newErr != nil {
|
if newErr != nil {
|
||||||
@@ -465,7 +468,7 @@ match:
|
|||||||
selectedRuleIndex = currentRuleIndex
|
selectedRuleIndex = currentRuleIndex
|
||||||
break match
|
break match
|
||||||
}
|
}
|
||||||
case *rule.RuleActionResolve:
|
case *R.RuleActionResolve:
|
||||||
fatalErr = r.actionResolve(ctx, metadata, action)
|
fatalErr = r.actionResolve(ctx, metadata, action)
|
||||||
if fatalErr != nil {
|
if fatalErr != nil {
|
||||||
return
|
return
|
||||||
@@ -485,7 +488,7 @@ match:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) actionSniff(
|
func (r *Router) actionSniff(
|
||||||
ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionSniff,
|
ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionSniff,
|
||||||
inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer,
|
inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer,
|
||||||
) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) {
|
) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) {
|
||||||
if sniff.Skip(metadata) {
|
if sniff.Skip(metadata) {
|
||||||
@@ -498,6 +501,9 @@ func (r *Router) actionSniff(
|
|||||||
if inputConn != nil {
|
if inputConn != nil {
|
||||||
if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 {
|
if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 {
|
||||||
return
|
return
|
||||||
|
} else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
|
||||||
|
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
var streamSniffers []sniff.StreamSniffer
|
var streamSniffers []sniff.StreamSniffer
|
||||||
if len(action.StreamSniffers) > 0 {
|
if len(action.StreamSniffers) > 0 {
|
||||||
@@ -522,6 +528,7 @@ func (r *Router) actionSniff(
|
|||||||
action.Timeout,
|
action.Timeout,
|
||||||
streamSniffers...,
|
streamSniffers...,
|
||||||
)
|
)
|
||||||
|
metadata.SniffError = err
|
||||||
if err == nil {
|
if err == nil {
|
||||||
//goland:noinspection GoDeprecation
|
//goland:noinspection GoDeprecation
|
||||||
if action.OverrideDestination && M.IsDomainName(metadata.Domain) {
|
if action.OverrideDestination && M.IsDomainName(metadata.Domain) {
|
||||||
@@ -546,8 +553,8 @@ func (r *Router) actionSniff(
|
|||||||
} else if inputPacketConn != nil {
|
} else if inputPacketConn != nil {
|
||||||
if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 {
|
if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 {
|
||||||
return
|
return
|
||||||
} else if metadata.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrNeedMoreData) {
|
} else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
|
||||||
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.PacketSniffError)
|
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var packetSniffers []sniff.PacketSniffer
|
var packetSniffers []sniff.PacketSniffer
|
||||||
@@ -594,7 +601,7 @@ func (r *Router) actionSniff(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if len(packetBuffers) > 0 || metadata.PacketSniffError != nil {
|
if len(packetBuffers) > 0 || metadata.SniffError != nil {
|
||||||
err = sniff.PeekPacket(
|
err = sniff.PeekPacket(
|
||||||
ctx,
|
ctx,
|
||||||
metadata,
|
metadata,
|
||||||
@@ -614,7 +621,7 @@ func (r *Router) actionSniff(
|
|||||||
Destination: destination,
|
Destination: destination,
|
||||||
}
|
}
|
||||||
packetBuffers = append(packetBuffers, packetBuffer)
|
packetBuffers = append(packetBuffers, packetBuffer)
|
||||||
metadata.PacketSniffError = err
|
metadata.SniffError = err
|
||||||
if errors.Is(err, sniff.ErrNeedMoreData) {
|
if errors.Is(err, sniff.ErrNeedMoreData) {
|
||||||
// TODO: replace with generic message when there are more multi-packet protocols
|
// TODO: replace with generic message when there are more multi-packet protocols
|
||||||
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
|
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
|
||||||
@@ -645,7 +652,7 @@ func (r *Router) actionSniff(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionResolve) error {
|
func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionResolve) error {
|
||||||
if metadata.Destination.IsFqdn() {
|
if metadata.Destination.IsFqdn() {
|
||||||
metadata.DNSServer = action.Server
|
metadata.DNSServer = action.Server
|
||||||
addresses, err := r.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, action.Strategy)
|
addresses, err := r.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, action.Strategy)
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
|
|||||||
Question: []mDNS.Question{message.Question[0]},
|
Question: []mDNS.Question{message.Question[0]},
|
||||||
}, nil
|
}, nil
|
||||||
case C.RuleActionRejectMethodDrop:
|
case C.RuleActionRejectMethodDrop:
|
||||||
return nil, tun.ErrDrop
|
return nil, &R.RejectedError{Cause: tun.ErrDrop}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -289,7 +289,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, strategy dns.DomainS
|
|||||||
case C.RuleActionRejectMethodDefault:
|
case C.RuleActionRejectMethodDefault:
|
||||||
return nil, nil
|
return nil, nil
|
||||||
case C.RuleActionRejectMethodDrop:
|
case C.RuleActionRejectMethodDrop:
|
||||||
return nil, tun.ErrDrop
|
return nil, &R.RejectedError{Cause: tun.ErrDrop}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package rule
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -33,6 +34,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
|
|||||||
RuleActionRouteOptions: RuleActionRouteOptions{
|
RuleActionRouteOptions: RuleActionRouteOptions{
|
||||||
OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptions.OverrideAddress, 0),
|
OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptions.OverrideAddress, 0),
|
||||||
OverridePort: action.RouteOptions.OverridePort,
|
OverridePort: action.RouteOptions.OverridePort,
|
||||||
|
OverrideTunnelDestination: action.RouteOptions.OverrideTunnelDestination,
|
||||||
NetworkStrategy: (*C.NetworkStrategy)(action.RouteOptions.NetworkStrategy),
|
NetworkStrategy: (*C.NetworkStrategy)(action.RouteOptions.NetworkStrategy),
|
||||||
FallbackDelay: time.Duration(action.RouteOptions.FallbackDelay),
|
FallbackDelay: time.Duration(action.RouteOptions.FallbackDelay),
|
||||||
UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping,
|
UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping,
|
||||||
@@ -147,6 +149,7 @@ func (r *RuleActionRoute) String() string {
|
|||||||
type RuleActionRouteOptions struct {
|
type RuleActionRouteOptions struct {
|
||||||
OverrideAddress M.Socksaddr
|
OverrideAddress M.Socksaddr
|
||||||
OverridePort uint16
|
OverridePort uint16
|
||||||
|
OverrideTunnelDestination string
|
||||||
NetworkStrategy *C.NetworkStrategy
|
NetworkStrategy *C.NetworkStrategy
|
||||||
NetworkType []C.InterfaceType
|
NetworkType []C.InterfaceType
|
||||||
FallbackNetworkType []C.InterfaceType
|
FallbackNetworkType []C.InterfaceType
|
||||||
@@ -168,6 +171,9 @@ func (r *RuleActionRouteOptions) String() string {
|
|||||||
if r.OverridePort > 0 {
|
if r.OverridePort > 0 {
|
||||||
descriptions = append(descriptions, F.ToString("override-port=", r.OverridePort))
|
descriptions = append(descriptions, F.ToString("override-port=", r.OverridePort))
|
||||||
}
|
}
|
||||||
|
if r.OverrideTunnelDestination != "" {
|
||||||
|
descriptions = append(descriptions, F.ToString("override-tunnel-destination=", r.OverrideTunnelDestination))
|
||||||
|
}
|
||||||
if r.NetworkStrategy != nil {
|
if r.NetworkStrategy != nil {
|
||||||
descriptions = append(descriptions, F.ToString("network-strategy=", r.NetworkStrategy))
|
descriptions = append(descriptions, F.ToString("network-strategy=", r.NetworkStrategy))
|
||||||
}
|
}
|
||||||
@@ -250,6 +256,23 @@ func (r *RuleActionDirect) String() string {
|
|||||||
return "direct" + r.description
|
return "direct" + r.description
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RejectedError struct {
|
||||||
|
Cause error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RejectedError) Error() string {
|
||||||
|
return "rejected"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RejectedError) Unwrap() error {
|
||||||
|
return r.Cause
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsRejected(err error) bool {
|
||||||
|
var rejected *RejectedError
|
||||||
|
return errors.As(err, &rejected)
|
||||||
|
}
|
||||||
|
|
||||||
type RuleActionReject struct {
|
type RuleActionReject struct {
|
||||||
Method string
|
Method string
|
||||||
NoDrop bool
|
NoDrop bool
|
||||||
@@ -273,9 +296,9 @@ func (r *RuleActionReject) Error(ctx context.Context) error {
|
|||||||
var returnErr error
|
var returnErr error
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case C.RuleActionRejectMethodDefault:
|
case C.RuleActionRejectMethodDefault:
|
||||||
returnErr = syscall.ECONNREFUSED
|
returnErr = &RejectedError{syscall.ECONNREFUSED}
|
||||||
case C.RuleActionRejectMethodDrop:
|
case C.RuleActionRejectMethodDrop:
|
||||||
return tun.ErrDrop
|
return &RejectedError{tun.ErrDrop}
|
||||||
default:
|
default:
|
||||||
panic(F.ToString("unknown reject method: ", r.Method))
|
panic(F.ToString("unknown reject method: ", r.Method))
|
||||||
}
|
}
|
||||||
@@ -293,7 +316,7 @@ func (r *RuleActionReject) Error(ctx context.Context) error {
|
|||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
r.logger.DebugContext(ctx, "dropped due to flooding")
|
r.logger.DebugContext(ctx, "dropped due to flooding")
|
||||||
}
|
}
|
||||||
return tun.ErrDrop
|
return &RejectedError{tun.ErrDrop}
|
||||||
}
|
}
|
||||||
return returnErr
|
return returnErr
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,6 +189,16 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
|
|||||||
rule.destinationPortItems = append(rule.destinationPortItems, item)
|
rule.destinationPortItems = append(rule.destinationPortItems, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
|
if len(options.TunnelSource) > 0 {
|
||||||
|
item := NewTunnelSourceItem(options.TunnelSource)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.TunnelDestination) > 0 {
|
||||||
|
item := NewTunnelDestinationItem(options.TunnelDestination)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
if len(options.ProcessName) > 0 {
|
if len(options.ProcessName) > 0 {
|
||||||
item := NewProcessItem(options.ProcessName)
|
item := NewProcessItem(options.ProcessName)
|
||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
|
|||||||
@@ -180,6 +180,16 @@ func NewDefaultDNSRule(ctx context.Context, logger log.ContextLogger, options op
|
|||||||
rule.destinationPortItems = append(rule.destinationPortItems, item)
|
rule.destinationPortItems = append(rule.destinationPortItems, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
|
if len(options.TunnelSource) > 0 {
|
||||||
|
item := NewTunnelSourceItem(options.TunnelSource)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.TunnelDestination) > 0 {
|
||||||
|
item := NewTunnelDestinationItem(options.TunnelDestination)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
if len(options.ProcessName) > 0 {
|
if len(options.ProcessName) > 0 {
|
||||||
item := NewProcessItem(options.ProcessName)
|
item := NewProcessItem(options.ProcessName)
|
||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
|
|||||||
@@ -121,6 +121,16 @@ func NewDefaultHeadlessRule(ctx context.Context, options option.DefaultHeadlessR
|
|||||||
rule.destinationPortItems = append(rule.destinationPortItems, item)
|
rule.destinationPortItems = append(rule.destinationPortItems, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
|
if len(options.TunnelSource) > 0 {
|
||||||
|
item := NewTunnelSourceItem(options.TunnelSource)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.TunnelDestination) > 0 {
|
||||||
|
item := NewTunnelDestinationItem(options.TunnelDestination)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
if len(options.ProcessName) > 0 {
|
if len(options.ProcessName) > 0 {
|
||||||
item := NewProcessItem(options.ProcessName)
|
item := NewProcessItem(options.ProcessName)
|
||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
|
|||||||
35
route/rule/rule_item_tunnel_destination.go
Normal file
35
route/rule/rule_item_tunnel_destination.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RuleItem = (*TunnelDestinationItem)(nil)
|
||||||
|
|
||||||
|
type TunnelDestinationItem struct {
|
||||||
|
destinations []string
|
||||||
|
destinationMap map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTunnelDestinationItem(destinations []string) *TunnelDestinationItem {
|
||||||
|
rule := &TunnelDestinationItem{destinations, make(map[string]bool)}
|
||||||
|
for _, destination := range destinations {
|
||||||
|
rule.destinationMap[destination] = true
|
||||||
|
}
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TunnelDestinationItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
return r.destinationMap[metadata.TunnelDestination]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TunnelDestinationItem) String() string {
|
||||||
|
if len(r.destinations) == 1 {
|
||||||
|
return F.ToString("tunnel_destination=", r.destinations[0])
|
||||||
|
} else {
|
||||||
|
return F.ToString("tunnel_destination=[", strings.Join(r.destinations, " "), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
35
route/rule/rule_item_tunnel_source.go
Normal file
35
route/rule/rule_item_tunnel_source.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RuleItem = (*TunnelSourceItem)(nil)
|
||||||
|
|
||||||
|
type TunnelSourceItem struct {
|
||||||
|
sources []string
|
||||||
|
sourceMap map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTunnelSourceItem(sources []string) *TunnelSourceItem {
|
||||||
|
rule := &TunnelSourceItem{sources, make(map[string]bool)}
|
||||||
|
for _, source := range sources {
|
||||||
|
rule.sourceMap[source] = true
|
||||||
|
}
|
||||||
|
return rule
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TunnelSourceItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
return r.sourceMap[metadata.TunnelSource]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *TunnelSourceItem) String() string {
|
||||||
|
if len(r.sources) == 1 {
|
||||||
|
return F.ToString("tunnel_source=", r.sources[0])
|
||||||
|
} else {
|
||||||
|
return F.ToString("tunnel_source=[", strings.Join(r.sources, " "), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,9 @@ type HTTPConn struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTP1Conn(conn net.Conn, request *http.Request) *HTTPConn {
|
func NewHTTP1Conn(conn net.Conn, request *http.Request) *HTTPConn {
|
||||||
|
if request.Header.Get("Host") == "" {
|
||||||
|
request.Header.Set("Host", request.Host)
|
||||||
|
}
|
||||||
return &HTTPConn{
|
return &HTTPConn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
request: request,
|
request: request,
|
||||||
@@ -89,9 +92,6 @@ func (c *HTTPConn) writeRequest(payload []byte) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if c.request.Header.Get("Host") == "" {
|
|
||||||
c.request.Header.Set("Host", c.request.Host)
|
|
||||||
}
|
|
||||||
for key, value := range c.request.Header {
|
for key, value := range c.request.Header {
|
||||||
_, err = writer.Write([]byte(F.ToString(key, ": ", strings.Join(value, ", "), CRLF)))
|
_, err = writer.Write([]byte(F.ToString(key, ": ", strings.Join(value, ", "), CRLF)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
return requestURL
|
return requestURL
|
||||||
}
|
}
|
||||||
if dest.IsFqdn() {
|
if dest.IsFqdn() {
|
||||||
addresses, err := router.Lookup(ctx, dest.AddrString(), dns.DomainStrategy(options.DomainStrategy))
|
addresses, err := router.Lookup(ctx, dest.Fqdn, dns.DomainStrategy(options.DomainStrategy))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -87,9 +87,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
return xmuxClient.XmuxConn.(DialerClient), xmuxClient
|
return xmuxClient.XmuxConn.(DialerClient), xmuxClient
|
||||||
}
|
}
|
||||||
getRequestURL2 := getRequestURL
|
getRequestURL2 := getRequestURL
|
||||||
getHTTPClient2 := func() (DialerClient, *XmuxClient) {
|
getHTTPClient2 := getHTTPClient
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if options.Download != nil {
|
if options.Download != nil {
|
||||||
options2 := options.Download
|
options2 := options.Download
|
||||||
dialer2 := dialer
|
dialer2 := dialer
|
||||||
@@ -123,7 +121,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
return requestURL2
|
return requestURL2
|
||||||
}
|
}
|
||||||
if dest2.IsFqdn() {
|
if dest2.IsFqdn() {
|
||||||
addresses2, err := router.Lookup(ctx, dest2.AddrString(), dns.DomainStrategy(options2.DomainStrategy))
|
addresses2, err := router.Lookup(ctx, dest2.Fqdn, dns.DomainStrategy(options2.DomainStrategy))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user