Add cloudflared inbound support

This commit is contained in:
世界
2026-04-08 14:39:41 +08:00
parent 813b634d08
commit 1f1ba549fd
12 changed files with 360 additions and 6 deletions

View File

@@ -25,6 +25,7 @@ const (
TypeTUIC = "tuic"
TypeHysteria2 = "hysteria2"
TypeTailscale = "tailscale"
TypeCloudflared = "cloudflared"
TypeDERP = "derp"
TypeResolved = "resolved"
TypeSSMAPI = "ssm-api"
@@ -88,6 +89,8 @@ func ProxyDisplayName(proxyType string) string {
return "AnyTLS"
case TypeTailscale:
return "Tailscale"
case TypeCloudflared:
return "Cloudflared"
case TypeSelector:
return "Selector"
case TypeURLTest:

7
go.mod
View File

@@ -34,12 +34,13 @@ require (
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4
github.com/sagernet/sing v0.8.4
github.com/sagernet/sing-cloudflared v0.0.0-20260407120610-7715dc2523fa
github.com/sagernet/sing-mux v0.3.4
github.com/sagernet/sing-quic v0.6.1
github.com/sagernet/sing-shadowsocks v0.2.8
github.com/sagernet/sing-shadowsocks2 v0.2.1
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
github.com/sagernet/sing-tun v0.8.6
github.com/sagernet/sing-tun v0.8.7-0.20260402180740-11f6e77ec6c6
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
github.com/sagernet/smux v1.5.50-sing-box-mod.1
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7
@@ -70,6 +71,7 @@ require (
github.com/caddyserver/zerossl v0.1.5 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
github.com/coreos/go-oidc/v3 v3.17.0 // indirect
github.com/database64128/netx-go v0.1.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
@@ -79,6 +81,7 @@ require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/gaissmai/bart v0.18.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
@@ -99,6 +102,7 @@ require (
github.com/mdlayher/netlink v1.9.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pires/go-proxyproto v0.8.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
@@ -165,4 +169,5 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
zombiezen.com/go/capnproto2 v2.18.2+incompatible // indirect
)

16
go.sum
View File

@@ -28,6 +28,8 @@ github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
@@ -110,6 +112,8 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU=
github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk=
github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U=
@@ -142,6 +146,8 @@ github.com/openai/openai-go/v3 v3.26.0 h1:bRt6H/ozMNt/dDkN4gobnLqaEGrRGBzmbVs0xx
github.com/openai/openai-go/v3 v3.26.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
@@ -238,6 +244,8 @@ github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgj
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
github.com/sagernet/sing v0.8.4 h1:Fj+jlY3F8vhcRfz/G/P3Dwcs5wqnmyNPT7u1RVVmjFI=
github.com/sagernet/sing v0.8.4/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-cloudflared v0.0.0-20260407120610-7715dc2523fa h1:165HiOfgfofJIirEp1NGSmsoJAi+++WhR29IhtAu4A4=
github.com/sagernet/sing-cloudflared v0.0.0-20260407120610-7715dc2523fa/go.mod h1:bH2NKX+NpDTY1Zkxfboxw6MXB/ZywaNLmrDJYgKMJ2Y=
github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s=
github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
github.com/sagernet/sing-quic v0.6.1 h1:lx0tcm99wIA1RkyvILNzRSsMy1k7TTQYIhx71E/WBlw=
@@ -248,8 +256,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.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
github.com/sagernet/sing-tun v0.8.6 h1:NydXFikSXhiKqhahHKtuZ90HQPZFzlOFVRONmkr4C7I=
github.com/sagernet/sing-tun v0.8.6/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs=
github.com/sagernet/sing-tun v0.8.7-0.20260402180740-11f6e77ec6c6 h1:HV2I7DicF5Ar8v6F55f03W5FviBB7jgvLhJSDwbFvbk=
github.com/sagernet/sing-tun v0.8.7-0.20260402180740-11f6e77ec6c6/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs=
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=
@@ -294,6 +302,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s=
github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
@@ -401,3 +411,5 @@ lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
zombiezen.com/go/capnproto2 v2.18.2+incompatible h1:v3BD1zbruvffn7zjJUU5Pn8nZAB11bhZSQC4W+YnnKo=
zombiezen.com/go/capnproto2 v2.18.2+incompatible/go.mod h1:XO5Pr2SbXgqZwn0m0Ru54QBqpOf4K5AYBO+8LAOBQEQ=

12
include/cloudflared.go Normal file
View File

@@ -0,0 +1,12 @@
//go:build with_cloudflared
package include
import (
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/protocol/cloudflare"
)
func registerCloudflaredInbound(registry *inbound.Registry) {
cloudflare.RegisterInbound(registry)
}

View File

@@ -0,0 +1,20 @@
//go:build !with_cloudflared
package include
import (
"context"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
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"
)
func registerCloudflaredInbound(registry *inbound.Registry) {
inbound.Register[option.CloudflaredInboundOptions](registry, C.TypeCloudflared, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.CloudflaredInboundOptions) (adapter.Inbound, error) {
return nil, E.New(`Cloudflared is not included in this build, rebuild with -tags with_cloudflared`)
})
}

View File

@@ -64,6 +64,7 @@ func InboundRegistry() *inbound.Registry {
anytls.RegisterInbound(registry)
registerQUICInbounds(registry)
registerCloudflaredInbound(registry)
registerStubForRemovedInbounds(registry)
return registry

16
option/cloudflared.go Normal file
View File

@@ -0,0 +1,16 @@
package option
import "github.com/sagernet/sing/common/json/badoption"
type CloudflaredInboundOptions struct {
Token string `json:"token,omitempty"`
HAConnections int `json:"ha_connections,omitempty"`
Protocol string `json:"protocol,omitempty"`
PostQuantum bool `json:"post_quantum,omitempty"`
ControlDialer DialerOptions `json:"control_dialer,omitempty"`
TunnelDialer DialerOptions `json:"tunnel_dialer,omitempty"`
EdgeIPVersion int `json:"edge_ip_version,omitempty"`
DatagramVersion string `json:"datagram_version,omitempty"`
GracePeriod *badoption.Duration `json:"grace_period,omitempty"`
Region string `json:"region,omitempty"`
}

View File

@@ -0,0 +1,176 @@
//go:build with_cloudflared
package cloudflare
import (
"context"
"net"
"sync"
"time"
cloudflared "github.com/sagernet/sing-cloudflared"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
boxDialer "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"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json/badoption"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/pipe"
tun "github.com/sagernet/sing-tun"
)
func RegisterInbound(registry *inbound.Registry) {
inbound.Register[option.CloudflaredInboundOptions](registry, C.TypeCloudflared, NewInbound)
}
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.CloudflaredInboundOptions) (adapter.Inbound, error) {
controlDialer, err := boxDialer.NewWithOptions(boxDialer.Options{
Context: ctx,
Options: options.ControlDialer,
RemoteIsDomain: true,
})
if err != nil {
return nil, E.Cause(err, "build cloudflared control dialer")
}
tunnelDialer, err := boxDialer.NewWithOptions(boxDialer.Options{
Context: ctx,
Options: options.TunnelDialer,
})
if err != nil {
return nil, E.Cause(err, "build cloudflared tunnel dialer")
}
service, err := cloudflared.NewService(cloudflared.ServiceOptions{
Logger: logger,
ConnectionDialer: &routerDialer{router: router, tag: tag},
ControlDialer: controlDialer,
TunnelDialer: tunnelDialer,
ICMPHandler: &icmpRouterHandler{router: router, tag: tag},
ConnContext: func(ctx context.Context) context.Context {
return adapter.WithContext(ctx, &adapter.InboundContext{
Inbound: tag,
InboundType: C.TypeCloudflared,
})
},
Token: options.Token,
HAConnections: options.HAConnections,
Protocol: options.Protocol,
PostQuantum: options.PostQuantum,
EdgeIPVersion: options.EdgeIPVersion,
DatagramVersion: options.DatagramVersion,
GracePeriod: resolveGracePeriod(options.GracePeriod),
Region: options.Region,
})
if err != nil {
return nil, err
}
return &Inbound{
Adapter: inbound.NewAdapter(C.TypeCloudflared, tag),
service: service,
}, nil
}
type Inbound struct {
inbound.Adapter
service *cloudflared.Service
}
func (i *Inbound) Start(stage adapter.StartStage) error {
if stage != adapter.StartStateStart {
return nil
}
return i.service.Start()
}
func (i *Inbound) Close() error {
return i.service.Close()
}
func resolveGracePeriod(value *badoption.Duration) time.Duration {
if value == nil {
return 0
}
return time.Duration(*value)
}
// routerDialer bridges N.Dialer to the sing-box router for origin connections.
type routerDialer struct {
router adapter.Router
tag string
}
func (d *routerDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
input, output := pipe.Pipe()
done := make(chan struct{})
metadata := adapter.InboundContext{
Inbound: d.tag,
InboundType: C.TypeCloudflared,
Network: N.NetworkTCP,
Destination: destination,
}
var closeOnce sync.Once
closePipe := func() {
closeOnce.Do(func() {
common.Close(input, output)
})
}
go d.router.RouteConnectionEx(ctx, output, metadata, N.OnceClose(func(it error) {
closePipe()
close(done)
}))
return input, nil
}
func (d *routerDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
originDialer, ok := d.router.(routedOriginPacketDialer)
if !ok {
return nil, E.New("router does not support cloudflare routed packet dialing")
}
packetConn, err := originDialer.DialRoutePacketConnection(ctx, adapter.InboundContext{
Inbound: d.tag,
InboundType: C.TypeCloudflared,
Network: N.NetworkUDP,
Destination: destination,
UDPConnect: true,
})
if err != nil {
return nil, err
}
return bufio.NewNetPacketConn(packetConn), nil
}
type routedOriginPacketDialer interface {
DialRoutePacketConnection(ctx context.Context, metadata adapter.InboundContext) (N.PacketConn, error)
}
// icmpRouterHandler bridges cloudflared.ICMPHandler to router.PreMatch.
type icmpRouterHandler struct {
router adapter.Router
tag string
}
func (h *icmpRouterHandler) RouteICMPConnection(ctx context.Context, session tun.DirectRouteSession, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error) {
var ipVersion uint8
if session.Source.Is4() {
ipVersion = 4
} else {
ipVersion = 6
}
metadata := adapter.InboundContext{
Inbound: h.tag,
InboundType: C.TypeCloudflared,
IPVersion: ipVersion,
Network: N.NetworkICMP,
Source: M.SocksaddrFrom(session.Source, 0),
Destination: M.SocksaddrFrom(session.Destination, 0),
OriginDestination: M.SocksaddrFrom(session.Destination, 0),
}
return h.router.PreMatch(metadata, routeContext, timeout, false)
}

View File

@@ -1 +1 @@
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,badlinkname,tfogo_checklinkname0
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,with_naive_outbound,badlinkname,tfogo_checklinkname0

View File

@@ -1 +1 @@
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,badlinkname,tfogo_checklinkname0

View File

@@ -1 +1 @@
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0
with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0

109
route/dial.go Normal file
View File

@@ -0,0 +1,109 @@
package route
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
C "github.com/sagernet/sing-box/constant"
R "github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
)
// DialRoutePacketConnection dials a routed connected UDP packet connection for metadata.
func (r *Router) DialRoutePacketConnection(ctx context.Context, metadata adapter.InboundContext) (N.PacketConn, error) {
metadata.Network = N.NetworkUDP
metadata.UDPConnect = true
ctx = adapter.WithContext(ctx, &metadata)
selectedRule, selectedOutbound, err := r.selectRoutedOutbound(ctx, &metadata, N.NetworkUDP)
if err != nil {
return nil, err
}
var remoteConn net.Conn
if len(metadata.DestinationAddresses) > 0 || metadata.Destination.IsIP() {
remoteConn, err = dialer.DialSerialNetwork(
ctx,
selectedOutbound,
N.NetworkUDP,
metadata.Destination,
metadata.DestinationAddresses,
metadata.NetworkStrategy,
metadata.NetworkType,
metadata.FallbackNetworkType,
metadata.FallbackDelay,
)
} else {
remoteConn, err = selectedOutbound.DialContext(ctx, N.NetworkUDP, metadata.Destination)
}
if err != nil {
return nil, err
}
var packetConn N.PacketConn = bufio.NewUnbindPacketConn(remoteConn)
for _, tracker := range r.trackers {
packetConn = tracker.RoutedPacketConnection(ctx, packetConn, metadata, selectedRule, selectedOutbound)
}
if metadata.FakeIP {
packetConn = bufio.NewNATPacketConn(bufio.NewNetPacketConn(packetConn), metadata.OriginDestination, metadata.Destination)
}
return packetConn, nil
}
func (r *Router) selectRoutedOutbound(
ctx context.Context,
metadata *adapter.InboundContext,
network string,
) (adapter.Rule, adapter.Outbound, error) {
selectedRule, _, buffers, packetBuffers, err := r.matchRule(ctx, metadata, false, false, nil, nil)
if len(buffers) > 0 {
buf.ReleaseMulti(buffers)
}
if len(packetBuffers) > 0 {
N.ReleaseMultiPacketBuffer(packetBuffers)
}
if err != nil {
return nil, nil, err
}
var selectedOutbound adapter.Outbound
if selectedRule != nil {
switch action := selectedRule.Action().(type) {
case *R.RuleActionRoute:
var loaded bool
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
if !loaded {
return nil, nil, E.New("outbound not found: ", action.Outbound)
}
case *R.RuleActionBypass:
if action.Outbound != "" {
var loaded bool
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
if !loaded {
return nil, nil, E.New("outbound not found: ", action.Outbound)
}
}
case *R.RuleActionReject:
if action.Method == C.RuleActionRejectMethodReply {
return nil, nil, E.New("reject method `reply` is not supported for dialed connections")
}
return nil, nil, action.Error(ctx)
case *R.RuleActionHijackDNS:
return nil, nil, E.New("DNS hijack is not supported for dialed connections")
}
}
if selectedOutbound == nil {
selectedOutbound = r.outbound.Default()
}
if !common.Contains(selectedOutbound.Network(), network) {
return nil, nil, E.New(network, " is not supported by outbound: ", selectedOutbound.Tag())
}
return selectedRule, selectedOutbound, nil
}