mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-05-14 00:51:12 +03:00
Add tunnel
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ const (
|
||||
TypeVLESS = "vless"
|
||||
TypeTUIC = "tuic"
|
||||
TypeHysteria2 = "hysteria2"
|
||||
TypeTunnelClient = "tunnel_client"
|
||||
TypeTunnelServer = "tunnel_server"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -86,6 +88,10 @@ func ProxyDisplayName(proxyType string) string {
|
||||
return "Selector"
|
||||
case TypeURLTest:
|
||||
return "URLTest"
|
||||
case TypeTunnelClient:
|
||||
return "Tunnel Client"
|
||||
case TypeTunnelServer:
|
||||
return "Tunnel Server"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -26,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"
|
||||
@@ -88,6 +89,9 @@ func OutboundRegistry() *outbound.Registry {
|
||||
func EndpointRegistry() *endpoint.Registry {
|
||||
registry := endpoint.NewRegistry()
|
||||
|
||||
tunnel.RegisterServerEndpoint(registry)
|
||||
tunnel.RegisterClientEndpoint(registry)
|
||||
|
||||
registerWireGuardEndpoint(registry)
|
||||
|
||||
return registry
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
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")
|
||||
}
|
||||
@@ -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, " "), "]")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user