mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-06-25 03:43:12 +03:00
Compare commits
17 Commits
v1.11.13-e
...
v1.11.14-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
209b89a4a3 | ||
|
|
765111a552 | ||
|
|
9b8ab3e61e | ||
|
|
47f18e823a | ||
|
|
2d1b824b62 | ||
|
|
824eac453b | ||
|
|
85a1a8a53b | ||
|
|
ae9e7aa5f4 | ||
|
|
52b71c6f00 | ||
|
|
5d04673783 | ||
|
|
307c429715 | ||
|
|
6801b58d96 | ||
|
|
6272596ebc | ||
|
|
7c4c2d5ca8 | ||
|
|
d511698f3f | ||
|
|
cb435ea232 | ||
|
|
43a9016c83 |
@@ -42,14 +42,16 @@ type InboundManager interface {
|
||||
}
|
||||
|
||||
type InboundContext struct {
|
||||
Inbound string
|
||||
InboundType string
|
||||
IPVersion uint8
|
||||
Network string
|
||||
Source M.Socksaddr
|
||||
Destination M.Socksaddr
|
||||
User string
|
||||
Outbound string
|
||||
Inbound string
|
||||
InboundType string
|
||||
IPVersion uint8
|
||||
Network string
|
||||
Source M.Socksaddr
|
||||
Destination M.Socksaddr
|
||||
TunnelSource string
|
||||
TunnelDestination string
|
||||
User string
|
||||
Outbound string
|
||||
|
||||
// sniffer
|
||||
|
||||
|
||||
4
box.go
4
box.go
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental"
|
||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||
@@ -114,6 +115,9 @@ func New(options Options) (*Box, error) {
|
||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||
needV2RayAPI = true
|
||||
}
|
||||
if experimentalOptions.UnifiedDelay != nil && experimentalOptions.UnifiedDelay.Enabled {
|
||||
ctx = urltest.ContextWithIsUnifiedDelay(ctx)
|
||||
}
|
||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||
var defaultLogWriter io.Writer
|
||||
if platformInterface != nil {
|
||||
|
||||
Submodule clients/android updated: 320170a107...eb2e13a6f9
@@ -100,10 +100,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
} else if networkManager.AutoDetectInterface() {
|
||||
if platformInterface != nil {
|
||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||
if networkStrategy == nil {
|
||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||
defaultNetworkStrategy = true
|
||||
}
|
||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||
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 {
|
||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||
}
|
||||
if networkStrategy == nil {
|
||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||
defaultNetworkStrategy = true
|
||||
}
|
||||
bindFunc := networkManager.ProtectFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
|
||||
@@ -10,9 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
@@ -26,7 +24,9 @@ type slowOpenConn struct {
|
||||
destination M.Socksaddr
|
||||
conn net.Conn
|
||||
create chan struct{}
|
||||
done chan struct{}
|
||||
access sync.Mutex
|
||||
closeOnce sync.Once
|
||||
err error
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, des
|
||||
network: network,
|
||||
destination: destination,
|
||||
create: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -55,8 +56,8 @@ func (c *slowOpenConn) Read(b []byte) (n int, err error) {
|
||||
if c.err != nil {
|
||||
return 0, c.err
|
||||
}
|
||||
case <-c.ctx.Done():
|
||||
return 0, c.ctx.Err()
|
||||
case <-c.done:
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
}
|
||||
return c.conn.Read(b)
|
||||
@@ -74,12 +75,15 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
||||
return 0, c.err
|
||||
}
|
||||
return c.conn.Write(b)
|
||||
case <-c.done:
|
||||
return 0, os.ErrClosed
|
||||
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 {
|
||||
c.conn = nil
|
||||
c.err = E.Cause(err, "dial tcp fast open")
|
||||
c.err = err
|
||||
} else {
|
||||
c.conn = conn
|
||||
}
|
||||
n = len(b)
|
||||
close(c.create)
|
||||
@@ -87,7 +91,13 @@ func (c *slowOpenConn) Write(b []byte) (n int, err 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 {
|
||||
@@ -152,8 +162,8 @@ func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
||||
if c.err != nil {
|
||||
return 0, c.err
|
||||
}
|
||||
case <-c.ctx.Done():
|
||||
return 0, c.ctx.Err()
|
||||
case <-c.done:
|
||||
return 0, c.err
|
||||
}
|
||||
}
|
||||
return bufio.Copy(w, c.conn)
|
||||
|
||||
@@ -3,6 +3,7 @@ package interrupt
|
||||
import (
|
||||
"net"
|
||||
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
|
||||
@@ -73,3 +74,32 @@ func (c *PacketConn) WriterReplaceable() bool {
|
||||
func (c *PacketConn) Upstream() any {
|
||||
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"
|
||||
"sync"
|
||||
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"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}
|
||||
}
|
||||
|
||||
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) {
|
||||
g.access.Lock()
|
||||
defer g.access.Unlock()
|
||||
|
||||
@@ -76,6 +76,8 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
||||
// rup8(sizeof(xtcpcb_n))
|
||||
itemSize += 208
|
||||
}
|
||||
|
||||
var fallbackUDPProcess string
|
||||
// skip the first xinpgen(24 bytes) block
|
||||
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
||||
// 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]
|
||||
|
||||
var srcIP netip.Addr
|
||||
srcIsIPv4 := false
|
||||
switch {
|
||||
case flag&0x1 > 0 && isIPv4:
|
||||
// ipv4
|
||||
srcIP = netip.AddrFrom4(*(*[4]byte)(buf[inp+76 : inp+80]))
|
||||
srcIsIPv4 = true
|
||||
case flag&0x2 > 0 && !isIPv4:
|
||||
// ipv6
|
||||
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
|
||||
}
|
||||
|
||||
if ip != srcIP {
|
||||
continue
|
||||
if ip == srcIP {
|
||||
// xsocket_n.so_last_pid
|
||||
pid := readNativeUint32(buf[so+68 : so+72])
|
||||
return getExecPathFromPID(pid)
|
||||
}
|
||||
|
||||
// xsocket_n.so_last_pid
|
||||
pid := readNativeUint32(buf[so+68 : so+72])
|
||||
return getExecPathFromPID(pid)
|
||||
// udp packet connection may be not equal with srcIP
|
||||
if network == N.NetworkUDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 {
|
||||
pid := readNativeUint32(buf[so+68 : so+72])
|
||||
fallbackUDPProcess, _ = getExecPathFromPID(pid)
|
||||
}
|
||||
}
|
||||
|
||||
if network == N.NetworkUDP && len(fallbackUDPProcess) > 0 {
|
||||
return fallbackUDPProcess, nil
|
||||
}
|
||||
|
||||
return "", ErrNotFound
|
||||
|
||||
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
|
||||
}
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -24,27 +24,36 @@ func (c *Range) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (c *Range) UnmarshalJSON(content []byte) error {
|
||||
var rangeValue struct {
|
||||
From int32 `json:"from"`
|
||||
To int32 `json:"to"`
|
||||
}
|
||||
var stringValue string
|
||||
err := json.Unmarshal(content, &stringValue)
|
||||
if err != nil {
|
||||
return err
|
||||
if err == nil {
|
||||
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 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 {
|
||||
if rangeValue.From > rangeValue.To {
|
||||
return E.New("invalid range")
|
||||
}
|
||||
*c = Range{int32(from), int32(to)}
|
||||
*c = Range{rangeValue.From, rangeValue.To}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,13 @@ const (
|
||||
TypeTor = "tor"
|
||||
TypeSSH = "ssh"
|
||||
TypeShadowTLS = "shadowtls"
|
||||
TypeMieru = "mieru"
|
||||
TypeShadowsocksR = "shadowsocksr"
|
||||
TypeVLESS = "vless"
|
||||
TypeTUIC = "tuic"
|
||||
TypeHysteria2 = "hysteria2"
|
||||
TypeTunnelClient = "tunnel_client"
|
||||
TypeTunnelServer = "tunnel_server"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -79,10 +82,16 @@ func ProxyDisplayName(proxyType string) string {
|
||||
return "TUIC"
|
||||
case TypeHysteria2:
|
||||
return "Hysteria2"
|
||||
case TypeMieru:
|
||||
return "Mieru"
|
||||
case TypeSelector:
|
||||
return "Selector"
|
||||
case TypeURLTest:
|
||||
return "URLTest"
|
||||
case TypeTunnelClient:
|
||||
return "Tunnel Client"
|
||||
case TypeTunnelServer:
|
||||
return "Tunnel Server"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
### 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
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
| `shadowtls` | [ShadowTLS](./shadowtls/) |
|
||||
| `tuic` | [TUIC](./tuic/) |
|
||||
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
||||
| `mieru` | [Mieru](./mieru/) |
|
||||
| `tor` | [Tor](./tor/) |
|
||||
| `ssh` | [SSH](./ssh/) |
|
||||
| `dns` | [DNS](./dns/) |
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
| `shadowtls` | [ShadowTLS](./shadowtls/) |
|
||||
| `tuic` | [TUIC](./tuic/) |
|
||||
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
||||
| `mieru` | [Mieru](./mieru/) |
|
||||
| `tor` | [Tor](./tor/) |
|
||||
| `ssh` | [SSH](./ssh/) |
|
||||
| `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":{
|
||||
"level":"error"
|
||||
"log": {
|
||||
"level": "error"
|
||||
},
|
||||
"dns":{
|
||||
"servers":[
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"address":"local",
|
||||
"detour":"direct"
|
||||
"address": "local",
|
||||
"detour": "direct"
|
||||
}
|
||||
]
|
||||
},
|
||||
"endpoints":[
|
||||
"endpoints": [
|
||||
{
|
||||
"type":"wireguard",
|
||||
"tag":"wireguard-out",
|
||||
"system":false,
|
||||
"name":"",
|
||||
"mtu":1408,
|
||||
"address":[],
|
||||
"private_key":"",
|
||||
"listen_port":10000,
|
||||
"peers":[
|
||||
"type": "wireguard",
|
||||
"tag": "wireguard-out",
|
||||
"mtu": 1408,
|
||||
"address": null,
|
||||
"private_key": "",
|
||||
"listen_port": 10000,
|
||||
"peers": [
|
||||
{
|
||||
"address":"example.com",
|
||||
"port":10001,
|
||||
"public_key":"",
|
||||
"pre_shared_key":"",
|
||||
"allowed_ips":[],
|
||||
"persistent_keepalive_interval":0,
|
||||
"reserved":[
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
"address": "example.com",
|
||||
"port": 10001,
|
||||
"reserved": "AAAA"
|
||||
}
|
||||
],
|
||||
"udp_timeout":"",
|
||||
"workers":0,
|
||||
"amnezia":{
|
||||
"jc":120,
|
||||
"jmin":23,
|
||||
"jmax":911,
|
||||
"s1":0,
|
||||
"s2":0,
|
||||
"h1":1,
|
||||
"h2":2,
|
||||
"h3":3,
|
||||
"h4":4
|
||||
"udp_timeout": "5m0s",
|
||||
"amnezia": {
|
||||
"jc": 120,
|
||||
"jmin": 23,
|
||||
"jmax": 911,
|
||||
"h1": 1,
|
||||
"h2": 2,
|
||||
"h3": 3,
|
||||
"h4": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"inbounds":[
|
||||
"inbounds": [
|
||||
{
|
||||
"type":"mixed",
|
||||
"tag":"mixed-in",
|
||||
"listen_port":7897
|
||||
"type": "mixed",
|
||||
"tag": "mixed-in",
|
||||
"listen_port": 7897
|
||||
}
|
||||
],
|
||||
"outbounds":[
|
||||
"outbounds": [
|
||||
{
|
||||
"type":"direct",
|
||||
"tag":"direct"
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type":"dns",
|
||||
"tag":"dns-out"
|
||||
"type": "dns",
|
||||
"tag": "dns-out"
|
||||
}
|
||||
],
|
||||
"route":{
|
||||
"rules":[
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol":"dns",
|
||||
"outbound":"dns-out"
|
||||
"protocol": "dns",
|
||||
"outbound": "dns-out"
|
||||
},
|
||||
{
|
||||
"port":53,
|
||||
"outbound":"dns-out"
|
||||
"port": 53,
|
||||
"outbound": "dns-out"
|
||||
}
|
||||
],
|
||||
"final":"wireguard-out",
|
||||
"auto_detect_interface":true
|
||||
"final": "wireguard-out",
|
||||
"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":{
|
||||
"level":"error"
|
||||
"log": {
|
||||
"level": "error"
|
||||
},
|
||||
"dns":{
|
||||
"servers":[
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"address":"local",
|
||||
"detour":"direct"
|
||||
"address": "local",
|
||||
"detour": "direct"
|
||||
}
|
||||
]
|
||||
},
|
||||
"endpoints":[
|
||||
"endpoints": [
|
||||
{
|
||||
"type":"warp",
|
||||
"tag":"warp-out",
|
||||
"system":false,
|
||||
"name":"",
|
||||
"listen_port":10000,
|
||||
"udp_timeout":"",
|
||||
"workers":0,
|
||||
"profile":{
|
||||
"detour":"direct",
|
||||
"recreate":false,
|
||||
// for getting existing WARP device profile
|
||||
"id":"",
|
||||
"private_key":"",
|
||||
"auth_token":""
|
||||
"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
|
||||
},
|
||||
"amnezia":{
|
||||
"jc":120,
|
||||
"jmin":23,
|
||||
"jmax":911,
|
||||
"h1":1,
|
||||
"h2":2,
|
||||
"h3":3,
|
||||
"h4":4
|
||||
"profile": {
|
||||
"detour": "direct",
|
||||
// for getting existing WARP device profile
|
||||
"id": "",
|
||||
"private_key": "",
|
||||
"auth_token": ""
|
||||
}
|
||||
// Dial Fields
|
||||
}
|
||||
],
|
||||
"inbounds":[
|
||||
"inbounds": [
|
||||
{
|
||||
"type":"mixed",
|
||||
"tag":"mixed-in",
|
||||
"listen_port":7897
|
||||
"type": "mixed",
|
||||
"tag": "mixed-in",
|
||||
"listen_port": 7897
|
||||
}
|
||||
],
|
||||
"outbounds":[
|
||||
"outbounds": [
|
||||
{
|
||||
"type":"direct",
|
||||
"tag":"direct"
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type":"dns",
|
||||
"tag":"dns-out"
|
||||
"type": "dns",
|
||||
"tag": "dns-out"
|
||||
}
|
||||
],
|
||||
"route":{
|
||||
"rules":[
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol":"dns",
|
||||
"outbound":"dns-out"
|
||||
"protocol": "dns",
|
||||
"outbound": "dns-out"
|
||||
},
|
||||
{
|
||||
"port":53,
|
||||
"outbound":"dns-out"
|
||||
"port": 53,
|
||||
"outbound": "dns-out"
|
||||
}
|
||||
],
|
||||
"final":"warp-out",
|
||||
"auto_detect_interface":true
|
||||
"final": "warp-out",
|
||||
"auto_detect_interface": true
|
||||
},
|
||||
"experimental":{
|
||||
"cache_file":{
|
||||
"enabled":true,
|
||||
"store_warp_config":true
|
||||
"experimental": {
|
||||
"cache_file": {
|
||||
"enabled": true,
|
||||
"store_warp_config": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +1,98 @@
|
||||
{
|
||||
"log":{
|
||||
"level":"error"
|
||||
"log": {
|
||||
"level": "error"
|
||||
},
|
||||
"dns":{
|
||||
"servers":[
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"address":"local",
|
||||
"detour":"direct"
|
||||
"address": "local",
|
||||
"detour": "direct"
|
||||
}
|
||||
]
|
||||
},
|
||||
"inbounds":[
|
||||
"inbounds": [
|
||||
{
|
||||
"type":"mixed",
|
||||
"tag":"mixed-in",
|
||||
"listen_port":7897
|
||||
"type": "mixed",
|
||||
"tag": "mixed-in",
|
||||
"listen_port": 7897
|
||||
}
|
||||
],
|
||||
"outbounds":[
|
||||
"outbounds": [
|
||||
{
|
||||
"type":"direct",
|
||||
"tag":"direct"
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type":"vless",
|
||||
"tag":"vless-out",
|
||||
"server":"example.com",
|
||||
"server_port":443,
|
||||
"uuid":"3179dce2-2ff9-413c-85b4-c1d53ed41668",
|
||||
"tls":{
|
||||
"enabled":true,
|
||||
"server_name":"example.com",
|
||||
"alpn":[
|
||||
"h2" // h3 for QUIC
|
||||
]
|
||||
"type": "vless",
|
||||
"tag": "vless-out",
|
||||
"server": "example.com",
|
||||
"server_port": 443,
|
||||
"uuid": "3179dce2-2ff9-413c-85b4-c1d53ed41668",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.com",
|
||||
"alpn": "h2" // h3 for QUIC
|
||||
},
|
||||
"packet_encoding":"",
|
||||
"transport":{
|
||||
"type":"xhttp",
|
||||
"mode":"stream-up",
|
||||
"host":"example.com",
|
||||
"path":"/xhttp",
|
||||
"domain_strategy":"prefer_ipv4",
|
||||
"xmux":{
|
||||
"max_concurrency":"0-1",
|
||||
"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
|
||||
"transport": {
|
||||
"type": "xhttp",
|
||||
"mode": "stream-up",
|
||||
"host": "example.com",
|
||||
"path": "/xhttp",
|
||||
"domain_strategy": "prefer_ipv4",
|
||||
"xmux": {
|
||||
"max_concurrency": "0-1",
|
||||
"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
|
||||
},
|
||||
"download":{
|
||||
"server":"example.com",
|
||||
"server_port":443,
|
||||
"host":"example.com",
|
||||
"path":"/xhttp",
|
||||
"domain_strategy":"prefer_ipv4",
|
||||
"detour":"direct",
|
||||
"tls":{
|
||||
"enabled":true,
|
||||
"server_name":"example.com",
|
||||
"alpn":[
|
||||
"h2" // h3 for QUIC
|
||||
]
|
||||
"download": {
|
||||
"mode": "",
|
||||
"host": "example.com",
|
||||
"path": "/xhttp",
|
||||
"domain_strategy": "prefer_ipv4",
|
||||
"x_padding_bytes": "0-0",
|
||||
"sc_max_each_post_bytes": "0-0",
|
||||
"sc_min_posts_interval_ms": "0-0",
|
||||
"sc_stream_up_server_secs": "0-0",
|
||||
"xmux": {
|
||||
"max_concurrency": "0-1",
|
||||
"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":{
|
||||
"max_concurrency":"0-1",
|
||||
"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
|
||||
}
|
||||
"server": "example.com",
|
||||
"server_port": 443,
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.com",
|
||||
"alpn": "h2" // h3 for QUIC
|
||||
},
|
||||
"detour": "direct"
|
||||
}
|
||||
}
|
||||
},
|
||||
"packet_encoding": ""
|
||||
},
|
||||
{
|
||||
"type":"dns",
|
||||
"tag":"dns-out"
|
||||
"type": "dns",
|
||||
"tag": "dns-out"
|
||||
}
|
||||
],
|
||||
"route":{
|
||||
"rules":[
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol":"dns",
|
||||
"outbound":"dns-out"
|
||||
"protocol": "dns",
|
||||
"outbound": "dns-out"
|
||||
},
|
||||
{
|
||||
"port":53,
|
||||
"outbound":"dns-out"
|
||||
"port": 53,
|
||||
"outbound": "dns-out"
|
||||
}
|
||||
],
|
||||
"final":"vless-out",
|
||||
"auto_detect_interface":true
|
||||
"final": "vless-out",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
@@ -1,65 +1,63 @@
|
||||
{
|
||||
"log":{
|
||||
"level":"error"
|
||||
"log": {
|
||||
"level": "error"
|
||||
},
|
||||
"dns":{
|
||||
"servers":[
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"address":"local",
|
||||
"detour":"direct"
|
||||
"address": "local",
|
||||
"detour": "direct"
|
||||
}
|
||||
]
|
||||
},
|
||||
"inbounds":[
|
||||
"inbounds": [
|
||||
{
|
||||
"type":"vless",
|
||||
"tag":"vless-out",
|
||||
"listen":"0.0.0.0",
|
||||
"listen_port":443,
|
||||
"tls":{
|
||||
"enabled":true,
|
||||
"server_name":"example.com",
|
||||
"certificate_path":"/path/to/fullchain.pem",
|
||||
"key_path":"/path/to/privkey.pem",
|
||||
"alpn":[
|
||||
"h2" // h3 for QUIC
|
||||
]
|
||||
},
|
||||
"users":[
|
||||
"type": "vless",
|
||||
"tag": "vless-out",
|
||||
"listen": "0.0.0.0",
|
||||
"listen_port": 443,
|
||||
"users": [
|
||||
{
|
||||
"name":"user",
|
||||
"uuid":"3179dce2-2ff9-413c-85b4-c1d53ed41668"
|
||||
"name": "user",
|
||||
"uuid": "3179dce2-2ff9-413c-85b4-c1d53ed41668"
|
||||
}
|
||||
],
|
||||
"transport":{
|
||||
"type":"xhttp",
|
||||
"mode":"stream-up",
|
||||
"path":"/xhttp"
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.com",
|
||||
"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",
|
||||
"tag":"direct"
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type":"dns",
|
||||
"tag":"dns-out"
|
||||
"type": "dns",
|
||||
"tag": "dns-out"
|
||||
}
|
||||
],
|
||||
"route":{
|
||||
"rules":[
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol":"dns",
|
||||
"outbound":"dns-out"
|
||||
"protocol": "dns",
|
||||
"outbound": "dns-out"
|
||||
},
|
||||
{
|
||||
"port":53,
|
||||
"outbound":"dns-out"
|
||||
"port": 53,
|
||||
"outbound": "dns-out"
|
||||
}
|
||||
],
|
||||
"final":"direct",
|
||||
"auto_detect_interface":true
|
||||
"final": "direct",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,10 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
|
||||
cancel()
|
||||
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()
|
||||
return &BoxService{
|
||||
ctx: ctx,
|
||||
|
||||
18
go.mod
18
go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/sagernet/sing-box
|
||||
|
||||
go 1.24
|
||||
go 1.24.1
|
||||
|
||||
toolchain go1.24.3
|
||||
|
||||
@@ -8,6 +8,7 @@ require (
|
||||
github.com/caddyserver/certmagic v0.20.0
|
||||
github.com/cloudflare/circl v1.6.1
|
||||
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/render v1.0.3
|
||||
github.com/gofrs/uuid/v5 v5.3.2
|
||||
@@ -36,7 +37,7 @@ require (
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||
github.com/sagernet/sing-shadowtls v0.2.0
|
||||
github.com/sagernet/sing-tun v0.6.5
|
||||
github.com/sagernet/sing-tun v0.6.8
|
||||
github.com/sagernet/sing-vmess v0.2.3
|
||||
github.com/sagernet/smux v1.5.34-mod.2
|
||||
github.com/sagernet/utls v1.6.7
|
||||
@@ -47,11 +48,11 @@ require (
|
||||
go.uber.org/zap v1.27.0
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
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/net v0.40.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
|
||||
google.golang.org/grpc v1.72.1
|
||||
google.golang.org/protobuf v1.36.6
|
||||
@@ -59,6 +60,9 @@ 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/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.7 // indirect
|
||||
@@ -108,10 +112,14 @@ require (
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.uber.org/multierr v1.11.0 // 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/yaml.v3 v3.0.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/sing-dns => github.com/shtorm-7/sing-dns v0.4.5-extended-1.0.0
|
||||
|
||||
replace github.com/ameshkov/dnscrypt/v2 => github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0
|
||||
|
||||
44
go.sum
44
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/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/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
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/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/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/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/getlantern/wireguard-go v0.0.1-beta.5.0.20250303165430-793006c422ec h1:jXekDkSozctYj5JQlV1mCsIW+qHpIkgSFhOIpgRSB1I=
|
||||
@@ -133,8 +139,6 @@ 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.10 h1:Jey1tePgH9bjFuK1fQI3D9T+bPOQ4SdHMjuS4sYjDv4=
|
||||
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/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
||||
github.com/sagernet/sing-quic v0.4.3 h1:OZ/kGvSzjtYg+t0DY3F606hlT5LeiQQXDxfBopcRryQ=
|
||||
@@ -145,8 +149,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq
|
||||
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/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.5/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||
github.com/sagernet/sing-tun v0.6.8 h1:tr+LKHe09C2I9GfNuB2vnzaZm+ekoNlAhLLrdiLjtAA=
|
||||
github.com/sagernet/sing-tun v0.6.8/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||
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/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/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/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.5-extended-1.0.0 h1:iR0BTDEYwuYfQHwiySBhDOUHaipcizGb54OmC1C6Pz0=
|
||||
github.com/shtorm-7/sing-dns v0.4.5-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/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
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=
|
||||
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/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||
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/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
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/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
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-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
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/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
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.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
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.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
||||
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/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=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
|
||||
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 h1:dHDlF3CWxQkefK9IJx+O8ldY0gLygvrlYRBNbPqDWuY=
|
||||
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/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
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/group"
|
||||
"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/naive"
|
||||
"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/trojan"
|
||||
"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/vmess"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -75,6 +77,7 @@ func OutboundRegistry() *outbound.Registry {
|
||||
ssh.RegisterOutbound(registry)
|
||||
shadowtls.RegisterOutbound(registry)
|
||||
vless.RegisterOutbound(registry)
|
||||
mieru.RegisterOutbound(registry)
|
||||
|
||||
registerQUICOutbounds(registry)
|
||||
registerWireGuardOutbound(registry)
|
||||
@@ -86,6 +89,9 @@ func OutboundRegistry() *outbound.Registry {
|
||||
func EndpointRegistry() *endpoint.Registry {
|
||||
registry := endpoint.NewRegistry()
|
||||
|
||||
tunnel.RegisterServerEndpoint(registry)
|
||||
tunnel.RegisterClientEndpoint(registry)
|
||||
|
||||
registerWireGuardEndpoint(registry)
|
||||
|
||||
return registry
|
||||
|
||||
@@ -3,10 +3,11 @@ package option
|
||||
import "github.com/sagernet/sing/common/json/badoption"
|
||||
|
||||
type ExperimentalOptions struct {
|
||||
CacheFile *CacheFileOptions `json:"cache_file,omitempty"`
|
||||
ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"`
|
||||
V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"`
|
||||
Debug *DebugOptions `json:"debug,omitempty"`
|
||||
CacheFile *CacheFileOptions `json:"cache_file,omitempty"`
|
||||
ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"`
|
||||
V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"`
|
||||
UnifiedDelay *UnifiedDelayOptions `json:"unified_delay,omitempty"`
|
||||
Debug *DebugOptions `json:"debug,omitempty"`
|
||||
}
|
||||
|
||||
type CacheFileOptions struct {
|
||||
@@ -53,3 +54,7 @@ type V2RayStatsServiceOptions struct {
|
||||
Outbounds []string `json:"outbounds,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"`
|
||||
Port badoption.Listable[uint16] `json:"port,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"`
|
||||
ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
|
||||
ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
|
||||
|
||||
@@ -142,8 +142,9 @@ type RouteActionOptions struct {
|
||||
}
|
||||
|
||||
type RawRouteOptionsActionOptions struct {
|
||||
OverrideAddress string `json:"override_address,omitempty"`
|
||||
OverridePort uint16 `json:"override_port,omitempty"`
|
||||
OverrideAddress string `json:"override_address,omitempty"`
|
||||
OverridePort uint16 `json:"override_port,omitempty"`
|
||||
OverrideTunnelDestination string `json:"override_tunnel_destination,omitempty"`
|
||||
|
||||
NetworkStrategy *NetworkStrategy `json:"network_strategy,omitempty"`
|
||||
FallbackDelay uint32 `json:"fallback_delay,omitempty"`
|
||||
|
||||
@@ -89,6 +89,8 @@ type RawDefaultDNSRule struct {
|
||||
SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
|
||||
Port badoption.Listable[uint16] `json:"port,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"`
|
||||
ProcessPath badoption.Listable[string] `json:"process_path,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"`
|
||||
Port badoption.Listable[uint16] `json:"port,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"`
|
||||
ProcessPath badoption.Listable[string] `json:"process_path,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) {
|
||||
ctx = interrupt.ContextWithIsExternalConnection(ctx)
|
||||
selected := s.selected.Load()
|
||||
conn = s.interruptGroup.NewConn(conn, interrupt.IsExternalConnectionFromContext(ctx))
|
||||
if outboundHandler, isHandler := selected.(adapter.ConnectionHandlerEx); isHandler {
|
||||
outboundHandler.NewConnectionEx(ctx, conn, metadata, onClose)
|
||||
} 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) {
|
||||
ctx = interrupt.ContextWithIsExternalConnection(ctx)
|
||||
selected := s.selected.Load()
|
||||
conn = s.interruptGroup.NewSingPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx))
|
||||
if outboundHandler, isHandler := selected.(adapter.PacketConnectionHandlerEx); isHandler {
|
||||
outboundHandler.NewPacketConnectionEx(ctx, conn, metadata, onClose)
|
||||
} 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) {
|
||||
ctx = interrupt.ContextWithIsExternalConnection(ctx)
|
||||
conn = s.group.interruptGroup.NewConn(conn, interrupt.IsExternalConnectionFromContext(ctx))
|
||||
s.connection.NewConnection(ctx, s, conn, metadata, onClose)
|
||||
}
|
||||
|
||||
func (s *URLTest) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||
ctx = interrupt.ContextWithIsExternalConnection(ctx)
|
||||
conn = s.group.interruptGroup.NewSingPacketConn(conn, interrupt.IsExternalConnectionFromContext(ctx))
|
||||
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
|
||||
}
|
||||
151
protocol/tunnel/client.go
Normal file
151
protocol/tunnel/client.go
Normal file
@@ -0,0 +1,151 @@
|
||||
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"
|
||||
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 nil
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
@@ -185,31 +185,28 @@ func (w *WARPEndpoint) Start(stage adapter.StartStage) error {
|
||||
}
|
||||
|
||||
func (w *WARPEndpoint) Close() error {
|
||||
if err := w.isEndpointInitialized(); err != nil {
|
||||
return err
|
||||
if ok := w.isEndpointInitialized(); !ok {
|
||||
return E.New("endpoint not initialized")
|
||||
}
|
||||
return w.endpoint.Close()
|
||||
}
|
||||
|
||||
func (w *WARPEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
if err := w.isEndpointInitialized(); err != nil {
|
||||
return nil, err
|
||||
if ok := w.isEndpointInitialized(); !ok {
|
||||
return nil, E.New("endpoint not initialized")
|
||||
}
|
||||
return w.endpoint.DialContext(ctx, network, destination)
|
||||
}
|
||||
|
||||
func (w *WARPEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
if err := w.isEndpointInitialized(); err != nil {
|
||||
return nil, err
|
||||
if ok := w.isEndpointInitialized(); !ok {
|
||||
return nil, E.New("endpoint not initialized")
|
||||
}
|
||||
return w.endpoint.ListenPacket(ctx, destination)
|
||||
}
|
||||
|
||||
func (w *WARPEndpoint) isEndpointInitialized() error {
|
||||
func (w *WARPEndpoint) isEndpointInitialized() bool {
|
||||
w.mtx.Lock()
|
||||
defer w.mtx.Unlock()
|
||||
if w.endpoint == nil {
|
||||
return E.New("endpoint not initialized")
|
||||
}
|
||||
return nil
|
||||
return w.endpoint != nil
|
||||
}
|
||||
|
||||
12
route/dns.go
12
route/dns.go
@@ -31,7 +31,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 {
|
||||
metadata.Destination = M.Socksaddr{}
|
||||
for _, packet := range packetBuffers {
|
||||
@@ -45,10 +45,12 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB
|
||||
conn: conn,
|
||||
ctx: ctx,
|
||||
metadata: metadata,
|
||||
onClose: onClose,
|
||||
})
|
||||
return
|
||||
}
|
||||
err := dnsOutbound.NewDNSPacketConnection(ctx, r, conn, packetBuffers, metadata)
|
||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||
if err != nil && !E.IsClosedOrCanceled(err) {
|
||||
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection"))
|
||||
}
|
||||
@@ -85,8 +87,16 @@ type dnsHijacker struct {
|
||||
conn N.PacketConn
|
||||
ctx context.Context
|
||||
metadata adapter.InboundContext
|
||||
onClose N.CloseHandlerFunc
|
||||
}
|
||||
|
||||
func (h *dnsHijacker) NewPacketEx(buffer *buf.Buffer, destination M.Socksaddr) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
for _, buffer := range buffers {
|
||||
conn = bufio.NewCachedConn(conn, buffer)
|
||||
}
|
||||
r.hijackDNSStream(ctx, conn, metadata)
|
||||
N.CloseOnHandshakeFailure(conn, onClose, r.hijackDNSStream(ctx, conn, metadata))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -233,7 +233,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
||||
N.CloseOnHandshakeFailure(conn, onClose, action.Error(ctx))
|
||||
return nil
|
||||
case *rule.RuleActionHijackDNS:
|
||||
r.hijackDNSPacket(ctx, conn, packetBuffers, metadata)
|
||||
r.hijackDNSPacket(ctx, conn, packetBuffers, metadata, onClose)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -425,6 +425,9 @@ match:
|
||||
Fqdn: metadata.Destination.Fqdn,
|
||||
}
|
||||
}
|
||||
if routeOptions.OverrideTunnelDestination != "" {
|
||||
metadata.TunnelDestination = routeOptions.OverrideTunnelDestination
|
||||
}
|
||||
if routeOptions.NetworkStrategy != nil {
|
||||
metadata.NetworkStrategy = routeOptions.NetworkStrategy
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ func NewRuleAction(ctx context.Context, logger logger.ContextLogger, action opti
|
||||
RuleActionRouteOptions: RuleActionRouteOptions{
|
||||
OverrideAddress: M.ParseSocksaddrHostPort(action.RouteOptions.OverrideAddress, 0),
|
||||
OverridePort: action.RouteOptions.OverridePort,
|
||||
OverrideTunnelDestination: action.RouteOptions.OverrideTunnelDestination,
|
||||
NetworkStrategy: (*C.NetworkStrategy)(action.RouteOptions.NetworkStrategy),
|
||||
FallbackDelay: time.Duration(action.RouteOptions.FallbackDelay),
|
||||
UDPDisableDomainUnmapping: action.RouteOptions.UDPDisableDomainUnmapping,
|
||||
@@ -147,6 +148,7 @@ func (r *RuleActionRoute) String() string {
|
||||
type RuleActionRouteOptions struct {
|
||||
OverrideAddress M.Socksaddr
|
||||
OverridePort uint16
|
||||
OverrideTunnelDestination string
|
||||
NetworkStrategy *C.NetworkStrategy
|
||||
NetworkType []C.InterfaceType
|
||||
FallbackNetworkType []C.InterfaceType
|
||||
@@ -168,6 +170,9 @@ func (r *RuleActionRouteOptions) String() string {
|
||||
if r.OverridePort > 0 {
|
||||
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 {
|
||||
descriptions = append(descriptions, F.ToString("network-strategy=", r.NetworkStrategy))
|
||||
}
|
||||
|
||||
@@ -189,6 +189,16 @@ func NewDefaultRule(ctx context.Context, logger log.ContextLogger, options optio
|
||||
rule.destinationPortItems = append(rule.destinationPortItems, 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 {
|
||||
item := NewProcessItem(options.ProcessName)
|
||||
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.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 {
|
||||
item := NewProcessItem(options.ProcessName)
|
||||
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.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 {
|
||||
item := NewProcessItem(options.ProcessName)
|
||||
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, " "), "]")
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
||||
return requestURL
|
||||
}
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -123,7 +123,7 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
||||
return requestURL2
|
||||
}
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user