feat: support tuicV5
This commit is contained in:
parent
5e20fedf5f
commit
e914317bef
21 changed files with 2103 additions and 251 deletions
|
@ -13,13 +13,14 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/metacubex/quic-go"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"github.com/Dreamacro/clash/component/proxydialer"
|
"github.com/Dreamacro/clash/component/proxydialer"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/transport/tuic"
|
"github.com/Dreamacro/clash/transport/tuic"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
"github.com/metacubex/quic-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Tuic struct {
|
type Tuic struct {
|
||||||
|
@ -33,7 +34,9 @@ type TuicOption struct {
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
Token string `proxy:"token"`
|
Token string `proxy:"token,omitempty"`
|
||||||
|
UUID string `proxy:"uuid,omitempty"`
|
||||||
|
Password string `proxy:"password,omitempty"`
|
||||||
Ip string `proxy:"ip,omitempty"`
|
Ip string `proxy:"ip,omitempty"`
|
||||||
HeartbeatInterval int `proxy:"heartbeat-interval,omitempty"`
|
HeartbeatInterval int `proxy:"heartbeat-interval,omitempty"`
|
||||||
ALPN []string `proxy:"alpn,omitempty"`
|
ALPN []string `proxy:"alpn,omitempty"`
|
||||||
|
@ -184,14 +187,19 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||||
option.MaxOpenStreams = 100
|
option.MaxOpenStreams = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packetOverHead := tuic.PacketOverHeadV4
|
||||||
|
if len(option.Token) == 0 {
|
||||||
|
packetOverHead = tuic.PacketOverHeadV5
|
||||||
|
}
|
||||||
|
|
||||||
if option.MaxDatagramFrameSize == 0 {
|
if option.MaxDatagramFrameSize == 0 {
|
||||||
option.MaxDatagramFrameSize = option.MaxUdpRelayPacketSize + tuic.PacketOverHead
|
option.MaxDatagramFrameSize = option.MaxUdpRelayPacketSize + packetOverHead
|
||||||
}
|
}
|
||||||
|
|
||||||
if option.MaxDatagramFrameSize > 1400 {
|
if option.MaxDatagramFrameSize > 1400 {
|
||||||
option.MaxDatagramFrameSize = 1400
|
option.MaxDatagramFrameSize = 1400
|
||||||
}
|
}
|
||||||
option.MaxUdpRelayPacketSize = option.MaxDatagramFrameSize - tuic.PacketOverHead
|
option.MaxUdpRelayPacketSize = option.MaxDatagramFrameSize - packetOverHead
|
||||||
|
|
||||||
// ensure server's incoming stream can handle correctly, increase to 1.1x
|
// ensure server's incoming stream can handle correctly, increase to 1.1x
|
||||||
quicMaxOpenStreams := int64(option.MaxOpenStreams)
|
quicMaxOpenStreams := int64(option.MaxOpenStreams)
|
||||||
|
@ -222,8 +230,8 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||||
}
|
}
|
||||||
if option.DisableSni {
|
if option.DisableSni {
|
||||||
tlsConfig.ServerName = ""
|
tlsConfig.ServerName = ""
|
||||||
|
tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
|
||||||
}
|
}
|
||||||
tkn := tuic.GenTKN(option.Token)
|
|
||||||
|
|
||||||
t := &Tuic{
|
t := &Tuic{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
|
@ -249,20 +257,38 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||||
if clientMaxOpenStreams < 1 {
|
if clientMaxOpenStreams < 1 {
|
||||||
clientMaxOpenStreams = 1
|
clientMaxOpenStreams = 1
|
||||||
}
|
}
|
||||||
clientOption := &tuic.ClientOption{
|
|
||||||
TlsConfig: tlsConfig,
|
|
||||||
QuicConfig: quicConfig,
|
|
||||||
Token: tkn,
|
|
||||||
UdpRelayMode: option.UdpRelayMode,
|
|
||||||
CongestionController: option.CongestionController,
|
|
||||||
ReduceRtt: option.ReduceRtt,
|
|
||||||
RequestTimeout: time.Duration(option.RequestTimeout) * time.Millisecond,
|
|
||||||
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
|
|
||||||
FastOpen: option.FastOpen,
|
|
||||||
MaxOpenStreams: clientMaxOpenStreams,
|
|
||||||
}
|
|
||||||
|
|
||||||
t.client = tuic.NewPoolClient(clientOption)
|
if len(option.Token) > 0 {
|
||||||
|
tkn := tuic.GenTKN(option.Token)
|
||||||
|
clientOption := &tuic.ClientOptionV4{
|
||||||
|
TlsConfig: tlsConfig,
|
||||||
|
QuicConfig: quicConfig,
|
||||||
|
Token: tkn,
|
||||||
|
UdpRelayMode: option.UdpRelayMode,
|
||||||
|
CongestionController: option.CongestionController,
|
||||||
|
ReduceRtt: option.ReduceRtt,
|
||||||
|
RequestTimeout: time.Duration(option.RequestTimeout) * time.Millisecond,
|
||||||
|
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
|
||||||
|
FastOpen: option.FastOpen,
|
||||||
|
MaxOpenStreams: clientMaxOpenStreams,
|
||||||
|
}
|
||||||
|
|
||||||
|
t.client = tuic.NewPoolClientV4(clientOption)
|
||||||
|
} else {
|
||||||
|
clientOption := &tuic.ClientOptionV5{
|
||||||
|
TlsConfig: tlsConfig,
|
||||||
|
QuicConfig: quicConfig,
|
||||||
|
Uuid: uuid.FromStringOrNil(option.UUID),
|
||||||
|
Password: option.Password,
|
||||||
|
UdpRelayMode: option.UdpRelayMode,
|
||||||
|
CongestionController: option.CongestionController,
|
||||||
|
ReduceRtt: option.ReduceRtt,
|
||||||
|
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
|
||||||
|
MaxOpenStreams: clientMaxOpenStreams,
|
||||||
|
}
|
||||||
|
|
||||||
|
t.client = tuic.NewPoolClientV5(clientOption)
|
||||||
|
}
|
||||||
|
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -219,16 +219,17 @@ type RawTun struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawTuicServer struct {
|
type RawTuicServer struct {
|
||||||
Enable bool `yaml:"enable" json:"enable"`
|
Enable bool `yaml:"enable" json:"enable"`
|
||||||
Listen string `yaml:"listen" json:"listen"`
|
Listen string `yaml:"listen" json:"listen"`
|
||||||
Token []string `yaml:"token" json:"token"`
|
Token []string `yaml:"token" json:"token"`
|
||||||
Certificate string `yaml:"certificate" json:"certificate"`
|
Users map[string]string `yaml:"users" json:"users,omitempty"`
|
||||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
Certificate string `yaml:"certificate" json:"certificate"`
|
||||||
CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||||
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
||||||
AuthenticationTimeout int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||||
ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
|
AuthenticationTimeout int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
||||||
MaxUdpRelayPacketSize int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
|
ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
|
||||||
|
MaxUdpRelayPacketSize int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawConfig struct {
|
type RawConfig struct {
|
||||||
|
@ -355,6 +356,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
||||||
TuicServer: RawTuicServer{
|
TuicServer: RawTuicServer{
|
||||||
Enable: false,
|
Enable: false,
|
||||||
Token: nil,
|
Token: nil,
|
||||||
|
Users: nil,
|
||||||
Certificate: "",
|
Certificate: "",
|
||||||
PrivateKey: "",
|
PrivateKey: "",
|
||||||
Listen: "",
|
Listen: "",
|
||||||
|
@ -1294,6 +1296,7 @@ func parseTuicServer(rawTuic RawTuicServer, general *General) error {
|
||||||
Enable: rawTuic.Enable,
|
Enable: rawTuic.Enable,
|
||||||
Listen: rawTuic.Listen,
|
Listen: rawTuic.Listen,
|
||||||
Token: rawTuic.Token,
|
Token: rawTuic.Token,
|
||||||
|
Users: rawTuic.Users,
|
||||||
Certificate: rawTuic.Certificate,
|
Certificate: rawTuic.Certificate,
|
||||||
PrivateKey: rawTuic.PrivateKey,
|
PrivateKey: rawTuic.PrivateKey,
|
||||||
CongestionController: rawTuic.CongestionController,
|
CongestionController: rawTuic.CongestionController,
|
||||||
|
|
|
@ -661,7 +661,11 @@ proxies: # socks5
|
||||||
server: www.example.com
|
server: www.example.com
|
||||||
port: 10443
|
port: 10443
|
||||||
type: tuic
|
type: tuic
|
||||||
|
# tuicV4必须填写token (不可同时填写uuid和password)
|
||||||
token: TOKEN
|
token: TOKEN
|
||||||
|
# tuicV5必须填写uuid和password(不可同时填写token)
|
||||||
|
uuid: 00000000-0000-0000-0000-000000000001
|
||||||
|
password: PASSWORD_1
|
||||||
# ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server'
|
# ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server'
|
||||||
# heartbeat-interval: 10000
|
# heartbeat-interval: 10000
|
||||||
# alpn: [h3]
|
# alpn: [h3]
|
||||||
|
@ -899,8 +903,11 @@ listeners:
|
||||||
listen: 0.0.0.0
|
listen: 0.0.0.0
|
||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||||
# token:
|
# token: # tuicV4填写(不可同时填写users)
|
||||||
# - TOKEN
|
# - TOKEN
|
||||||
|
# users: # tuicV5填写(不可同时填写token)
|
||||||
|
# 00000000-0000-0000-0000-000000000000: PASSWORD_0
|
||||||
|
# 00000000-0000-0000-0000-000000000001: PASSWORD_1
|
||||||
# certificate: ./server.crt
|
# certificate: ./server.crt
|
||||||
# private-key: ./server.key
|
# private-key: ./server.key
|
||||||
# congestion-controller: bbr
|
# congestion-controller: bbr
|
||||||
|
@ -970,8 +977,11 @@ listeners:
|
||||||
# tuic-server:
|
# tuic-server:
|
||||||
# enable: true
|
# enable: true
|
||||||
# listen: 127.0.0.1:10443
|
# listen: 127.0.0.1:10443
|
||||||
# token:
|
# token: # tuicV4填写(不可同时填写users)
|
||||||
# - TOKEN
|
# - TOKEN
|
||||||
|
# users: # tuicV5填写(不可同时填写token)
|
||||||
|
# 00000000-0000-0000-0000-000000000000: PASSWORD_0
|
||||||
|
# 00000000-0000-0000-0000-000000000001: PASSWORD_1
|
||||||
# certificate: ./server.crt
|
# certificate: ./server.crt
|
||||||
# private-key: ./server.key
|
# private-key: ./server.key
|
||||||
# congestion-controller: bbr
|
# congestion-controller: bbr
|
||||||
|
|
|
@ -84,16 +84,17 @@ type tunSchema struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type tuicServerSchema struct {
|
type tuicServerSchema struct {
|
||||||
Enable bool `yaml:"enable" json:"enable"`
|
Enable bool `yaml:"enable" json:"enable"`
|
||||||
Listen *string `yaml:"listen" json:"listen"`
|
Listen *string `yaml:"listen" json:"listen"`
|
||||||
Token *[]string `yaml:"token" json:"token"`
|
Token *[]string `yaml:"token" json:"token"`
|
||||||
Certificate *string `yaml:"certificate" json:"certificate"`
|
Users *map[string]string `yaml:"users" json:"users,omitempty"`
|
||||||
PrivateKey *string `yaml:"private-key" json:"private-key"`
|
Certificate *string `yaml:"certificate" json:"certificate"`
|
||||||
CongestionController *string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
PrivateKey *string `yaml:"private-key" json:"private-key"`
|
||||||
MaxIdleTime *int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
CongestionController *string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
||||||
AuthenticationTimeout *int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
MaxIdleTime *int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||||
ALPN *[]string `yaml:"alpn" json:"alpn,omitempty"`
|
AuthenticationTimeout *int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
||||||
MaxUdpRelayPacketSize *int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
|
ALPN *[]string `yaml:"alpn" json:"alpn,omitempty"`
|
||||||
|
MaxUdpRelayPacketSize *int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConfigs(w http.ResponseWriter, r *http.Request) {
|
func getConfigs(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -186,6 +187,9 @@ func pointerOrDefaultTuicServer(p *tuicServerSchema, def LC.TuicServer) LC.TuicS
|
||||||
if p.Token != nil {
|
if p.Token != nil {
|
||||||
def.Token = *p.Token
|
def.Token = *p.Token
|
||||||
}
|
}
|
||||||
|
if p.Users != nil {
|
||||||
|
def.Users = *p.Users
|
||||||
|
}
|
||||||
if p.Certificate != nil {
|
if p.Certificate != nil {
|
||||||
def.Certificate = *p.Certificate
|
def.Certificate = *p.Certificate
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,17 +5,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type TuicServer struct {
|
type TuicServer struct {
|
||||||
Enable bool `yaml:"enable" json:"enable"`
|
Enable bool `yaml:"enable" json:"enable"`
|
||||||
Listen string `yaml:"listen" json:"listen"`
|
Listen string `yaml:"listen" json:"listen"`
|
||||||
Token []string `yaml:"token" json:"token"`
|
Token []string `yaml:"token" json:"token,omitempty"`
|
||||||
Certificate string `yaml:"certificate" json:"certificate"`
|
Users map[string]string `yaml:"users" json:"users,omitempty"`
|
||||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
Certificate string `yaml:"certificate" json:"certificate"`
|
||||||
CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||||
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
CongestionController string `yaml:"congestion-controller" json:"congestion-controller,omitempty"`
|
||||||
AuthenticationTimeout int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||||
ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
|
AuthenticationTimeout int `yaml:"authentication-timeout" json:"authentication-timeout,omitempty"`
|
||||||
MaxUdpRelayPacketSize int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
|
ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
|
||||||
MaxDatagramFrameSize int `yaml:"max-datagram-frame-size" json:"max-datagram-frame-size,omitempty"`
|
MaxUdpRelayPacketSize int `yaml:"max-udp-relay-packet-size" json:"max-udp-relay-packet-size,omitempty"`
|
||||||
|
MaxDatagramFrameSize int `yaml:"max-datagram-frame-size" json:"max-datagram-frame-size,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t TuicServer) String() string {
|
func (t TuicServer) String() string {
|
||||||
|
|
|
@ -9,14 +9,15 @@ import (
|
||||||
|
|
||||||
type TuicOption struct {
|
type TuicOption struct {
|
||||||
BaseOption
|
BaseOption
|
||||||
Token []string `inbound:"token"`
|
Token []string `inbound:"token,omitempty"`
|
||||||
Certificate string `inbound:"certificate"`
|
Users map[string]string `inbound:"users,omitempty"`
|
||||||
PrivateKey string `inbound:"private-key"`
|
Certificate string `inbound:"certificate"`
|
||||||
CongestionController string `inbound:"congestion-controller,omitempty"`
|
PrivateKey string `inbound:"private-key"`
|
||||||
MaxIdleTime int `inbound:"max-idle-time,omitempty"`
|
CongestionController string `inbound:"congestion-controller,omitempty"`
|
||||||
AuthenticationTimeout int `inbound:"authentication-timeout,omitempty"`
|
MaxIdleTime int `inbound:"max-idle-time,omitempty"`
|
||||||
ALPN []string `inbound:"alpn,omitempty"`
|
AuthenticationTimeout int `inbound:"authentication-timeout,omitempty"`
|
||||||
MaxUdpRelayPacketSize int `inbound:"max-udp-relay-packet-size,omitempty"`
|
ALPN []string `inbound:"alpn,omitempty"`
|
||||||
|
MaxUdpRelayPacketSize int `inbound:"max-udp-relay-packet-size,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o TuicOption) Equal(config C.InboundConfig) bool {
|
func (o TuicOption) Equal(config C.InboundConfig) bool {
|
||||||
|
@ -42,6 +43,7 @@ func NewTuic(options *TuicOption) (*Tuic, error) {
|
||||||
Enable: true,
|
Enable: true,
|
||||||
Listen: base.RawAddress(),
|
Listen: base.RawAddress(),
|
||||||
Token: options.Token,
|
Token: options.Token,
|
||||||
|
Users: options.Users,
|
||||||
Certificate: options.Certificate,
|
Certificate: options.Certificate,
|
||||||
PrivateKey: options.PrivateKey,
|
PrivateKey: options.PrivateKey,
|
||||||
CongestionController: options.CongestionController,
|
CongestionController: options.CongestionController,
|
||||||
|
|
|
@ -6,8 +6,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/metacubex/quic-go"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
CN "github.com/Dreamacro/clash/common/net"
|
CN "github.com/Dreamacro/clash/common/net"
|
||||||
"github.com/Dreamacro/clash/common/sockopt"
|
"github.com/Dreamacro/clash/common/sockopt"
|
||||||
|
@ -16,6 +14,10 @@ import (
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
"github.com/Dreamacro/clash/transport/tuic"
|
"github.com/Dreamacro/clash/transport/tuic"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
"github.com/metacubex/quic-go"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ServerMaxIncomingStreams = (1 << 32) - 1
|
const ServerMaxIncomingStreams = (1 << 32) - 1
|
||||||
|
@ -24,7 +26,7 @@ type Listener struct {
|
||||||
closed bool
|
closed bool
|
||||||
config LC.TuicServer
|
config LC.TuicServer
|
||||||
udpListeners []net.PacketConn
|
udpListeners []net.PacketConn
|
||||||
servers []*tuic.Server
|
servers []tuic.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, additions ...inbound.Addition) (*Listener, error) {
|
func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, additions ...inbound.Addition) (*Listener, error) {
|
||||||
|
@ -59,39 +61,77 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet
|
||||||
quicConfig.InitialConnectionReceiveWindow = tuic.DefaultConnectionReceiveWindow / 10
|
quicConfig.InitialConnectionReceiveWindow = tuic.DefaultConnectionReceiveWindow / 10
|
||||||
quicConfig.MaxConnectionReceiveWindow = tuic.DefaultConnectionReceiveWindow
|
quicConfig.MaxConnectionReceiveWindow = tuic.DefaultConnectionReceiveWindow
|
||||||
|
|
||||||
|
packetOverHead := tuic.PacketOverHeadV4
|
||||||
|
if len(config.Token) == 0 {
|
||||||
|
packetOverHead = tuic.PacketOverHeadV5
|
||||||
|
}
|
||||||
|
|
||||||
if config.MaxUdpRelayPacketSize == 0 {
|
if config.MaxUdpRelayPacketSize == 0 {
|
||||||
config.MaxUdpRelayPacketSize = 1500
|
config.MaxUdpRelayPacketSize = 1500
|
||||||
}
|
}
|
||||||
maxDatagramFrameSize := config.MaxUdpRelayPacketSize + tuic.PacketOverHead
|
maxDatagramFrameSize := config.MaxUdpRelayPacketSize + packetOverHead
|
||||||
if maxDatagramFrameSize > 1400 {
|
if maxDatagramFrameSize > 1400 {
|
||||||
maxDatagramFrameSize = 1400
|
maxDatagramFrameSize = 1400
|
||||||
}
|
}
|
||||||
config.MaxUdpRelayPacketSize = maxDatagramFrameSize - tuic.PacketOverHead
|
config.MaxUdpRelayPacketSize = maxDatagramFrameSize - packetOverHead
|
||||||
quicConfig.MaxDatagramFrameSize = int64(maxDatagramFrameSize)
|
quicConfig.MaxDatagramFrameSize = int64(maxDatagramFrameSize)
|
||||||
|
|
||||||
tokens := make([][32]byte, len(config.Token))
|
handleTcpFn := func(conn net.Conn, addr socks5.Addr, _additions ...inbound.Addition) error {
|
||||||
for i, token := range config.Token {
|
newAdditions := additions
|
||||||
tokens[i] = tuic.GenTKN(token)
|
if len(_additions) > 0 {
|
||||||
|
newAdditions = slices.Clone(additions)
|
||||||
|
newAdditions = append(newAdditions, _additions...)
|
||||||
|
}
|
||||||
|
tcpIn <- inbound.NewSocket(addr, conn, C.TUIC, newAdditions...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
handleUdpFn := func(addr socks5.Addr, packet C.UDPPacket, _additions ...inbound.Addition) error {
|
||||||
|
newAdditions := additions
|
||||||
|
if len(_additions) > 0 {
|
||||||
|
newAdditions = slices.Clone(additions)
|
||||||
|
newAdditions = append(newAdditions, _additions...)
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case udpIn <- inbound.NewPacket(addr, packet, C.TUIC, newAdditions...):
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
option := &tuic.ServerOption{
|
var optionV4 *tuic.ServerOptionV4
|
||||||
HandleTcpFn: func(conn net.Conn, addr socks5.Addr) error {
|
var optionV5 *tuic.ServerOptionV5
|
||||||
tcpIn <- inbound.NewSocket(addr, conn, C.TUIC, additions...)
|
if len(config.Token) > 0 {
|
||||||
return nil
|
tokens := make([][32]byte, len(config.Token))
|
||||||
},
|
for i, token := range config.Token {
|
||||||
HandleUdpFn: func(addr socks5.Addr, packet C.UDPPacket) error {
|
tokens[i] = tuic.GenTKN(token)
|
||||||
select {
|
}
|
||||||
case udpIn <- inbound.NewPacket(addr, packet, C.TUIC, additions...):
|
|
||||||
default:
|
optionV4 = &tuic.ServerOptionV4{
|
||||||
}
|
HandleTcpFn: handleTcpFn,
|
||||||
return nil
|
HandleUdpFn: handleUdpFn,
|
||||||
},
|
TlsConfig: tlsConfig,
|
||||||
TlsConfig: tlsConfig,
|
QuicConfig: quicConfig,
|
||||||
QuicConfig: quicConfig,
|
Tokens: tokens,
|
||||||
Tokens: tokens,
|
CongestionController: config.CongestionController,
|
||||||
CongestionController: config.CongestionController,
|
AuthenticationTimeout: time.Duration(config.AuthenticationTimeout) * time.Millisecond,
|
||||||
AuthenticationTimeout: time.Duration(config.AuthenticationTimeout) * time.Millisecond,
|
MaxUdpRelayPacketSize: config.MaxUdpRelayPacketSize,
|
||||||
MaxUdpRelayPacketSize: config.MaxUdpRelayPacketSize,
|
}
|
||||||
|
} else {
|
||||||
|
users := make(map[[16]byte]string)
|
||||||
|
for _uuid, password := range config.Users {
|
||||||
|
users[uuid.FromStringOrNil(_uuid)] = password
|
||||||
|
}
|
||||||
|
|
||||||
|
optionV5 = &tuic.ServerOptionV5{
|
||||||
|
HandleTcpFn: handleTcpFn,
|
||||||
|
HandleUdpFn: handleUdpFn,
|
||||||
|
TlsConfig: tlsConfig,
|
||||||
|
QuicConfig: quicConfig,
|
||||||
|
Users: users,
|
||||||
|
CongestionController: config.CongestionController,
|
||||||
|
AuthenticationTimeout: time.Duration(config.AuthenticationTimeout) * time.Millisecond,
|
||||||
|
MaxUdpRelayPacketSize: config.MaxUdpRelayPacketSize,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sl := &Listener{false, config, nil, nil}
|
sl := &Listener{false, config, nil, nil}
|
||||||
|
@ -111,7 +151,12 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet
|
||||||
|
|
||||||
sl.udpListeners = append(sl.udpListeners, ul)
|
sl.udpListeners = append(sl.udpListeners, ul)
|
||||||
|
|
||||||
server, err := tuic.NewServer(option, ul)
|
var server tuic.Server
|
||||||
|
if optionV4 != nil {
|
||||||
|
server, err = tuic.NewServerV4(optionV4, ul)
|
||||||
|
} else {
|
||||||
|
server, err = tuic.NewServerV5(optionV5, ul)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
44
transport/tuic/common/congestion.go
Normal file
44
transport/tuic/common/congestion.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Dreamacro/clash/transport/tuic/congestion"
|
||||||
|
|
||||||
|
"github.com/metacubex/quic-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultStreamReceiveWindow = 15728640 // 15 MB/s
|
||||||
|
DefaultConnectionReceiveWindow = 67108864 // 64 MB/s
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetCongestionController(quicConn quic.Connection, cc string) {
|
||||||
|
switch cc {
|
||||||
|
case "cubic":
|
||||||
|
quicConn.SetCongestionControl(
|
||||||
|
congestion.NewCubicSender(
|
||||||
|
congestion.DefaultClock{},
|
||||||
|
congestion.GetInitialPacketSize(quicConn.RemoteAddr()),
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
case "new_reno":
|
||||||
|
quicConn.SetCongestionControl(
|
||||||
|
congestion.NewCubicSender(
|
||||||
|
congestion.DefaultClock{},
|
||||||
|
congestion.GetInitialPacketSize(quicConn.RemoteAddr()),
|
||||||
|
true,
|
||||||
|
nil,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
case "bbr":
|
||||||
|
quicConn.SetCongestionControl(
|
||||||
|
congestion.NewBBRSender(
|
||||||
|
congestion.DefaultClock{},
|
||||||
|
congestion.GetInitialPacketSize(quicConn.RemoteAddr()),
|
||||||
|
congestion.InitialCongestionWindow*congestion.InitialMaxDatagramSize,
|
||||||
|
congestion.DefaultBBRMaxCongestionWindow*congestion.InitialMaxDatagramSize,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
67
transport/tuic/common/stream.go
Normal file
67
transport/tuic/common/stream.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/metacubex/quic-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type quicStreamConn struct {
|
||||||
|
quic.Stream
|
||||||
|
lock sync.Mutex
|
||||||
|
lAddr net.Addr
|
||||||
|
rAddr net.Addr
|
||||||
|
|
||||||
|
closeDeferFn func()
|
||||||
|
|
||||||
|
closeOnce sync.Once
|
||||||
|
closeErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *quicStreamConn) Write(p []byte) (n int, err error) {
|
||||||
|
q.lock.Lock()
|
||||||
|
defer q.lock.Unlock()
|
||||||
|
return q.Stream.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *quicStreamConn) Close() error {
|
||||||
|
q.closeOnce.Do(func() {
|
||||||
|
q.closeErr = q.close()
|
||||||
|
})
|
||||||
|
return q.closeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *quicStreamConn) close() error {
|
||||||
|
if q.closeDeferFn != nil {
|
||||||
|
defer q.closeDeferFn()
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/cloudflare/cloudflared/commit/ed2bac026db46b239699ac5ce4fcf122d7cab2cd
|
||||||
|
// Make sure a possible writer does not block the lock forever. We need it, so we can close the writer
|
||||||
|
// side of the stream safely.
|
||||||
|
_ = q.Stream.SetWriteDeadline(time.Now())
|
||||||
|
|
||||||
|
// This lock is eventually acquired despite Write also acquiring it, because we set a deadline to writes.
|
||||||
|
q.lock.Lock()
|
||||||
|
defer q.lock.Unlock()
|
||||||
|
|
||||||
|
// We have to clean up the receiving stream ourselves since the Close in the bottom does not handle that.
|
||||||
|
q.Stream.CancelRead(0)
|
||||||
|
return q.Stream.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *quicStreamConn) LocalAddr() net.Addr {
|
||||||
|
return q.lAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *quicStreamConn) RemoteAddr() net.Addr {
|
||||||
|
return q.rAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ net.Conn = (*quicStreamConn)(nil)
|
||||||
|
|
||||||
|
func NewQuicStreamConn(stream quic.Stream, lAddr, rAddr net.Addr, closeDeferFn func()) net.Conn {
|
||||||
|
return &quicStreamConn{Stream: stream, lAddr: lAddr, rAddr: rAddr, closeDeferFn: closeDeferFn}
|
||||||
|
}
|
34
transport/tuic/common/type.go
Normal file
34
transport/tuic/common/type.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
|
"github.com/metacubex/quic-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ClientClosed = errors.New("tuic: client closed")
|
||||||
|
TooManyOpenStreams = errors.New("tuic: too many open streams")
|
||||||
|
)
|
||||||
|
|
||||||
|
type DialFunc func(ctx context.Context, dialer C.Dialer) (transport *quic.Transport, addr net.Addr, err error)
|
||||||
|
|
||||||
|
type Client interface {
|
||||||
|
DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.Conn, error)
|
||||||
|
ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.PacketConn, error)
|
||||||
|
OpenStreams() int64
|
||||||
|
DialerRef() C.Dialer
|
||||||
|
LastVisited() time.Time
|
||||||
|
SetLastVisited(last time.Time)
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server interface {
|
||||||
|
Serve() error
|
||||||
|
Close() error
|
||||||
|
}
|
|
@ -23,15 +23,14 @@ type dialResult struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type PoolClient struct {
|
type PoolClient struct {
|
||||||
*ClientOption
|
newClientOptionV4 *ClientOptionV4
|
||||||
|
newClientOptionV5 *ClientOptionV5
|
||||||
newClientOption *ClientOption
|
dialResultMap map[C.Dialer]dialResult
|
||||||
dialResultMap map[C.Dialer]dialResult
|
dialResultMutex *sync.Mutex
|
||||||
dialResultMutex *sync.Mutex
|
tcpClients *list.List[Client]
|
||||||
tcpClients *list.List[*Client]
|
tcpClientsMutex *sync.Mutex
|
||||||
tcpClientsMutex *sync.Mutex
|
udpClients *list.List[Client]
|
||||||
udpClients *list.List[*Client]
|
udpClientsMutex *sync.Mutex
|
||||||
udpClientsMutex *sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *PoolClient) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.Conn, error) {
|
func (t *PoolClient) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.Conn, error) {
|
||||||
|
@ -99,7 +98,7 @@ func (t *PoolClient) forceClose() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *PoolClient) newClient(udp bool, dialer C.Dialer) *Client {
|
func (t *PoolClient) newClient(udp bool, dialer C.Dialer) (client Client) {
|
||||||
clients := t.tcpClients
|
clients := t.tcpClients
|
||||||
clientsMutex := t.tcpClientsMutex
|
clientsMutex := t.tcpClientsMutex
|
||||||
if udp {
|
if udp {
|
||||||
|
@ -110,22 +109,26 @@ func (t *PoolClient) newClient(udp bool, dialer C.Dialer) *Client {
|
||||||
clientsMutex.Lock()
|
clientsMutex.Lock()
|
||||||
defer clientsMutex.Unlock()
|
defer clientsMutex.Unlock()
|
||||||
|
|
||||||
client := NewClient(t.newClientOption, udp)
|
if t.newClientOptionV4 != nil {
|
||||||
client.dialerRef = dialer
|
client = NewClientV4(t.newClientOptionV4, udp, dialer)
|
||||||
client.lastVisited = time.Now()
|
} else {
|
||||||
|
client = NewClientV5(t.newClientOptionV5, udp, dialer)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.SetLastVisited(time.Now())
|
||||||
|
|
||||||
clients.PushFront(client)
|
clients.PushFront(client)
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *PoolClient) getClient(udp bool, dialer C.Dialer) *Client {
|
func (t *PoolClient) getClient(udp bool, dialer C.Dialer) Client {
|
||||||
clients := t.tcpClients
|
clients := t.tcpClients
|
||||||
clientsMutex := t.tcpClientsMutex
|
clientsMutex := t.tcpClientsMutex
|
||||||
if udp {
|
if udp {
|
||||||
clients = t.udpClients
|
clients = t.udpClients
|
||||||
clientsMutex = t.udpClientsMutex
|
clientsMutex = t.udpClientsMutex
|
||||||
}
|
}
|
||||||
var bestClient *Client
|
var bestClient Client
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
clientsMutex.Lock()
|
clientsMutex.Lock()
|
||||||
|
@ -138,11 +141,11 @@ func (t *PoolClient) getClient(udp bool, dialer C.Dialer) *Client {
|
||||||
it = next
|
it = next
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if client.dialerRef == dialer {
|
if client.DialerRef() == dialer {
|
||||||
if bestClient == nil {
|
if bestClient == nil {
|
||||||
bestClient = client
|
bestClient = client
|
||||||
} else {
|
} else {
|
||||||
if client.openStreams.Load() < bestClient.openStreams.Load() {
|
if client.OpenStreams() < bestClient.OpenStreams() {
|
||||||
bestClient = client
|
bestClient = client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,7 +155,7 @@ func (t *PoolClient) getClient(udp bool, dialer C.Dialer) *Client {
|
||||||
}()
|
}()
|
||||||
for it := clients.Front(); it != nil; {
|
for it := clients.Front(); it != nil; {
|
||||||
client := it.Value
|
client := it.Value
|
||||||
if client != bestClient && client.openStreams.Load() == 0 && time.Now().Sub(client.lastVisited) > 30*time.Minute {
|
if client != bestClient && client.OpenStreams() == 0 && time.Now().Sub(client.LastVisited()) > 30*time.Minute {
|
||||||
client.Close()
|
client.Close()
|
||||||
next := it.Next()
|
next := it.Next()
|
||||||
clients.Remove(it)
|
clients.Remove(it)
|
||||||
|
@ -165,25 +168,40 @@ func (t *PoolClient) getClient(udp bool, dialer C.Dialer) *Client {
|
||||||
if bestClient == nil {
|
if bestClient == nil {
|
||||||
return t.newClient(udp, dialer)
|
return t.newClient(udp, dialer)
|
||||||
} else {
|
} else {
|
||||||
bestClient.lastVisited = time.Now()
|
bestClient.SetLastVisited(time.Now())
|
||||||
return bestClient
|
return bestClient
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPoolClient(clientOption *ClientOption) *PoolClient {
|
func NewPoolClientV4(clientOption *ClientOptionV4) *PoolClient {
|
||||||
p := &PoolClient{
|
p := &PoolClient{
|
||||||
ClientOption: clientOption,
|
|
||||||
dialResultMap: make(map[C.Dialer]dialResult),
|
dialResultMap: make(map[C.Dialer]dialResult),
|
||||||
dialResultMutex: &sync.Mutex{},
|
dialResultMutex: &sync.Mutex{},
|
||||||
tcpClients: list.New[*Client](),
|
tcpClients: list.New[Client](),
|
||||||
tcpClientsMutex: &sync.Mutex{},
|
tcpClientsMutex: &sync.Mutex{},
|
||||||
udpClients: list.New[*Client](),
|
udpClients: list.New[Client](),
|
||||||
udpClientsMutex: &sync.Mutex{},
|
udpClientsMutex: &sync.Mutex{},
|
||||||
}
|
}
|
||||||
newClientOption := *clientOption
|
newClientOption := *clientOption
|
||||||
p.newClientOption = &newClientOption
|
p.newClientOptionV4 = &newClientOption
|
||||||
runtime.SetFinalizer(p, closeClientPool)
|
runtime.SetFinalizer(p, closeClientPool)
|
||||||
log.Debugln("New Tuic PoolClient at %p", p)
|
log.Debugln("New TuicV4 PoolClient at %p", p)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPoolClientV5(clientOption *ClientOptionV5) *PoolClient {
|
||||||
|
p := &PoolClient{
|
||||||
|
dialResultMap: make(map[C.Dialer]dialResult),
|
||||||
|
dialResultMutex: &sync.Mutex{},
|
||||||
|
tcpClients: list.New[Client](),
|
||||||
|
tcpClientsMutex: &sync.Mutex{},
|
||||||
|
udpClients: list.New[Client](),
|
||||||
|
udpClientsMutex: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
newClientOption := *clientOption
|
||||||
|
p.newClientOptionV5 = &newClientOption
|
||||||
|
runtime.SetFinalizer(p, closeClientPool)
|
||||||
|
log.Debugln("New TuicV5 PoolClient at %p", p)
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
47
transport/tuic/tuic.go
Normal file
47
transport/tuic/tuic.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package tuic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/tuic/common"
|
||||||
|
v4 "github.com/Dreamacro/clash/transport/tuic/v4"
|
||||||
|
v5 "github.com/Dreamacro/clash/transport/tuic/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientOptionV4 = v4.ClientOption
|
||||||
|
type ClientOptionV5 = v5.ClientOption
|
||||||
|
|
||||||
|
type Client = common.Client
|
||||||
|
|
||||||
|
func NewClientV4(clientOption *ClientOptionV4, udp bool, dialerRef C.Dialer) Client {
|
||||||
|
return v4.NewClient(clientOption, udp, dialerRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClientV5(clientOption *ClientOptionV5, udp bool, dialerRef C.Dialer) Client {
|
||||||
|
return v5.NewClient(clientOption, udp, dialerRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DialFunc = common.DialFunc
|
||||||
|
|
||||||
|
var TooManyOpenStreams = common.TooManyOpenStreams
|
||||||
|
|
||||||
|
type ServerOptionV4 = v4.ServerOption
|
||||||
|
type ServerOptionV5 = v5.ServerOption
|
||||||
|
|
||||||
|
type Server = common.Server
|
||||||
|
|
||||||
|
func NewServerV4(option *ServerOptionV4, pc net.PacketConn) (Server, error) {
|
||||||
|
return v4.NewServer(option, pc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServerV5(option *ServerOptionV5, pc net.PacketConn) (Server, error) {
|
||||||
|
return v5.NewServer(option, pc)
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultStreamReceiveWindow = common.DefaultStreamReceiveWindow
|
||||||
|
const DefaultConnectionReceiveWindow = common.DefaultConnectionReceiveWindow
|
||||||
|
|
||||||
|
var GenTKN = v4.GenTKN
|
||||||
|
var PacketOverHeadV4 = v4.PacketOverHead
|
||||||
|
var PacketOverHeadV5 = v5.PacketOverHead
|
|
@ -1,4 +1,4 @@
|
||||||
package tuic
|
package v4
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
@ -13,23 +13,18 @@ import (
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
atomic2 "github.com/Dreamacro/clash/common/atomic"
|
||||||
"github.com/Dreamacro/clash/common/buf"
|
"github.com/Dreamacro/clash/common/buf"
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
"github.com/Dreamacro/clash/transport/tuic/common"
|
||||||
|
|
||||||
"github.com/metacubex/quic-go"
|
"github.com/metacubex/quic-go"
|
||||||
"github.com/zhangyunhao116/fastrand"
|
"github.com/zhangyunhao116/fastrand"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
ClientClosed = errors.New("tuic: client closed")
|
|
||||||
TooManyOpenStreams = errors.New("tuic: too many open streams")
|
|
||||||
)
|
|
||||||
|
|
||||||
type DialFunc func(ctx context.Context, dialer C.Dialer) (transport *quic.Transport, addr net.Addr, err error)
|
|
||||||
|
|
||||||
type ClientOption struct {
|
type ClientOption struct {
|
||||||
TlsConfig *tls.Config
|
TlsConfig *tls.Config
|
||||||
QuicConfig *quic.Config
|
QuicConfig *quic.Config
|
||||||
|
@ -57,10 +52,26 @@ type clientImpl struct {
|
||||||
|
|
||||||
// only ready for PoolClient
|
// only ready for PoolClient
|
||||||
dialerRef C.Dialer
|
dialerRef C.Dialer
|
||||||
lastVisited time.Time
|
lastVisited atomic2.TypedValue[time.Time]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *clientImpl) getQuicConn(ctx context.Context, dialer C.Dialer, dialFn DialFunc) (quic.Connection, error) {
|
func (t *clientImpl) OpenStreams() int64 {
|
||||||
|
return t.openStreams.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *clientImpl) DialerRef() C.Dialer {
|
||||||
|
return t.dialerRef
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *clientImpl) LastVisited() time.Time {
|
||||||
|
return t.lastVisited.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *clientImpl) SetLastVisited(last time.Time) {
|
||||||
|
t.lastVisited.Store(last)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *clientImpl) getQuicConn(ctx context.Context, dialer C.Dialer, dialFn common.DialFunc) (quic.Connection, error) {
|
||||||
t.connMutex.Lock()
|
t.connMutex.Lock()
|
||||||
defer t.connMutex.Unlock()
|
defer t.connMutex.Unlock()
|
||||||
if t.quicConn != nil {
|
if t.quicConn != nil {
|
||||||
|
@ -80,7 +91,7 @@ func (t *clientImpl) getQuicConn(ctx context.Context, dialer C.Dialer, dialFn Di
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
SetCongestionController(quicConn, t.CongestionController)
|
common.SetCongestionController(quicConn, t.CongestionController)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_ = t.sendAuthentication(quicConn)
|
_ = t.sendAuthentication(quicConn)
|
||||||
|
@ -237,11 +248,11 @@ func (t *clientImpl) forceClose(quicConn quic.Connection, err error) {
|
||||||
func (t *clientImpl) Close() {
|
func (t *clientImpl) Close() {
|
||||||
t.closed.Store(true)
|
t.closed.Store(true)
|
||||||
if t.openStreams.Load() == 0 {
|
if t.openStreams.Load() == 0 {
|
||||||
t.forceClose(nil, ClientClosed)
|
t.forceClose(nil, common.ClientClosed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *clientImpl) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.Conn, error) {
|
func (t *clientImpl) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.Conn, error) {
|
||||||
quicConn, err := t.getQuicConn(ctx, dialer, dialFn)
|
quicConn, err := t.getQuicConn(ctx, dialer, dialFn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -249,9 +260,9 @@ func (t *clientImpl) DialContextWithDialer(ctx context.Context, metadata *C.Meta
|
||||||
openStreams := t.openStreams.Add(1)
|
openStreams := t.openStreams.Add(1)
|
||||||
if openStreams >= t.MaxOpenStreams {
|
if openStreams >= t.MaxOpenStreams {
|
||||||
t.openStreams.Add(-1)
|
t.openStreams.Add(-1)
|
||||||
return nil, TooManyOpenStreams
|
return nil, common.TooManyOpenStreams
|
||||||
}
|
}
|
||||||
stream, err := func() (stream *quicStreamConn, err error) {
|
stream, err := func() (stream net.Conn, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
t.deferQuicConn(quicConn, err)
|
t.deferQuicConn(quicConn, err)
|
||||||
}()
|
}()
|
||||||
|
@ -265,19 +276,19 @@ func (t *clientImpl) DialContextWithDialer(ctx context.Context, metadata *C.Meta
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
stream = &quicStreamConn{
|
stream = common.NewQuicStreamConn(
|
||||||
Stream: quicStream,
|
quicStream,
|
||||||
lAddr: quicConn.LocalAddr(),
|
quicConn.LocalAddr(),
|
||||||
rAddr: quicConn.RemoteAddr(),
|
quicConn.RemoteAddr(),
|
||||||
closeDeferFn: func() {
|
func() {
|
||||||
time.AfterFunc(C.DefaultTCPTimeout, func() {
|
time.AfterFunc(C.DefaultTCPTimeout, func() {
|
||||||
openStreams := t.openStreams.Add(-1)
|
openStreams := t.openStreams.Add(-1)
|
||||||
if openStreams == 0 && t.closed.Load() {
|
if openStreams == 0 && t.closed.Load() {
|
||||||
t.forceClose(quicConn, ClientClosed)
|
t.forceClose(quicConn, common.ClientClosed)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}
|
)
|
||||||
_, err = buf.WriteTo(stream)
|
_, err = buf.WriteTo(stream)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = stream.Close()
|
_ = stream.Close()
|
||||||
|
@ -361,7 +372,7 @@ func (conn *earlyConn) WriterReplaceable() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *clientImpl) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.PacketConn, error) {
|
func (t *clientImpl) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.PacketConn, error) {
|
||||||
quicConn, err := t.getQuicConn(ctx, dialer, dialFn)
|
quicConn, err := t.getQuicConn(ctx, dialer, dialFn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -369,7 +380,7 @@ func (t *clientImpl) ListenPacketWithDialer(ctx context.Context, metadata *C.Met
|
||||||
openStreams := t.openStreams.Add(1)
|
openStreams := t.openStreams.Add(1)
|
||||||
if openStreams >= t.MaxOpenStreams {
|
if openStreams >= t.MaxOpenStreams {
|
||||||
t.openStreams.Add(-1)
|
t.openStreams.Add(-1)
|
||||||
return nil, TooManyOpenStreams
|
return nil, common.TooManyOpenStreams
|
||||||
}
|
}
|
||||||
|
|
||||||
pipe1, pipe2 := net.Pipe()
|
pipe1, pipe2 := net.Pipe()
|
||||||
|
@ -393,7 +404,7 @@ func (t *clientImpl) ListenPacketWithDialer(ctx context.Context, metadata *C.Met
|
||||||
time.AfterFunc(C.DefaultUDPTimeout, func() {
|
time.AfterFunc(C.DefaultUDPTimeout, func() {
|
||||||
openStreams := t.openStreams.Add(-1)
|
openStreams := t.openStreams.Add(-1)
|
||||||
if openStreams == 0 && t.closed.Load() {
|
if openStreams == 0 && t.closed.Load() {
|
||||||
t.forceClose(quicConn, ClientClosed)
|
t.forceClose(quicConn, common.ClientClosed)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -405,7 +416,7 @@ type Client struct {
|
||||||
*clientImpl // use an independent pointer to let Finalizer can work no matter somewhere handle an influence in clientImpl inner
|
*clientImpl // use an independent pointer to let Finalizer can work no matter somewhere handle an influence in clientImpl inner
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Client) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.Conn, error) {
|
func (t *Client) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.Conn, error) {
|
||||||
conn, err := t.clientImpl.DialContextWithDialer(ctx, metadata, dialer, dialFn)
|
conn, err := t.clientImpl.DialContextWithDialer(ctx, metadata, dialer, dialFn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -413,7 +424,7 @@ func (t *Client) DialContextWithDialer(ctx context.Context, metadata *C.Metadata
|
||||||
return N.NewRefConn(conn, t), err
|
return N.NewRefConn(conn, t), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Client) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.PacketConn, error) {
|
func (t *Client) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.PacketConn, error) {
|
||||||
pc, err := t.clientImpl.ListenPacketWithDialer(ctx, metadata, dialer, dialFn)
|
pc, err := t.clientImpl.ListenPacketWithDialer(ctx, metadata, dialer, dialFn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -422,21 +433,22 @@ func (t *Client) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadat
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Client) forceClose() {
|
func (t *Client) forceClose() {
|
||||||
t.clientImpl.forceClose(nil, ClientClosed)
|
t.clientImpl.forceClose(nil, common.ClientClosed)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(clientOption *ClientOption, udp bool) *Client {
|
func NewClient(clientOption *ClientOption, udp bool, dialerRef C.Dialer) *Client {
|
||||||
ci := &clientImpl{
|
ci := &clientImpl{
|
||||||
ClientOption: clientOption,
|
ClientOption: clientOption,
|
||||||
udp: udp,
|
udp: udp,
|
||||||
|
dialerRef: dialerRef,
|
||||||
}
|
}
|
||||||
c := &Client{ci}
|
c := &Client{ci}
|
||||||
runtime.SetFinalizer(c, closeClient)
|
runtime.SetFinalizer(c, closeClient)
|
||||||
log.Debugln("New Tuic Client at %p", c)
|
log.Debugln("New TuicV4 Client at %p", c)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeClient(client *Client) {
|
func closeClient(client *Client) {
|
||||||
log.Debugln("Close Tuic Client at %p", client)
|
log.Debugln("Close TuicV4 Client at %p", client)
|
||||||
client.forceClose()
|
client.forceClose()
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package tuic
|
package v4
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
@ -10,100 +10,8 @@ import (
|
||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
"github.com/Dreamacro/clash/transport/tuic/congestion"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
DefaultStreamReceiveWindow = 15728640 // 15 MB/s
|
|
||||||
DefaultConnectionReceiveWindow = 67108864 // 64 MB/s
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetCongestionController(quicConn quic.Connection, cc string) {
|
|
||||||
switch cc {
|
|
||||||
case "cubic":
|
|
||||||
quicConn.SetCongestionControl(
|
|
||||||
congestion.NewCubicSender(
|
|
||||||
congestion.DefaultClock{},
|
|
||||||
congestion.GetInitialPacketSize(quicConn.RemoteAddr()),
|
|
||||||
false,
|
|
||||||
nil,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
case "new_reno":
|
|
||||||
quicConn.SetCongestionControl(
|
|
||||||
congestion.NewCubicSender(
|
|
||||||
congestion.DefaultClock{},
|
|
||||||
congestion.GetInitialPacketSize(quicConn.RemoteAddr()),
|
|
||||||
true,
|
|
||||||
nil,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
case "bbr":
|
|
||||||
quicConn.SetCongestionControl(
|
|
||||||
congestion.NewBBRSender(
|
|
||||||
congestion.DefaultClock{},
|
|
||||||
congestion.GetInitialPacketSize(quicConn.RemoteAddr()),
|
|
||||||
congestion.InitialCongestionWindow*congestion.InitialMaxDatagramSize,
|
|
||||||
congestion.DefaultBBRMaxCongestionWindow*congestion.InitialMaxDatagramSize,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type quicStreamConn struct {
|
|
||||||
quic.Stream
|
|
||||||
lock sync.Mutex
|
|
||||||
lAddr net.Addr
|
|
||||||
rAddr net.Addr
|
|
||||||
|
|
||||||
closeDeferFn func()
|
|
||||||
|
|
||||||
closeOnce sync.Once
|
|
||||||
closeErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *quicStreamConn) Write(p []byte) (n int, err error) {
|
|
||||||
q.lock.Lock()
|
|
||||||
defer q.lock.Unlock()
|
|
||||||
return q.Stream.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *quicStreamConn) Close() error {
|
|
||||||
q.closeOnce.Do(func() {
|
|
||||||
q.closeErr = q.close()
|
|
||||||
})
|
|
||||||
return q.closeErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *quicStreamConn) close() error {
|
|
||||||
if q.closeDeferFn != nil {
|
|
||||||
defer q.closeDeferFn()
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/cloudflare/cloudflared/commit/ed2bac026db46b239699ac5ce4fcf122d7cab2cd
|
|
||||||
// Make sure a possible writer does not block the lock forever. We need it, so we can close the writer
|
|
||||||
// side of the stream safely.
|
|
||||||
_ = q.Stream.SetWriteDeadline(time.Now())
|
|
||||||
|
|
||||||
// This lock is eventually acquired despite Write also acquiring it, because we set a deadline to writes.
|
|
||||||
q.lock.Lock()
|
|
||||||
defer q.lock.Unlock()
|
|
||||||
|
|
||||||
// We have to clean up the receiving stream ourselves since the Close in the bottom does not handle that.
|
|
||||||
q.Stream.CancelRead(0)
|
|
||||||
return q.Stream.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *quicStreamConn) LocalAddr() net.Addr {
|
|
||||||
return q.lAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *quicStreamConn) RemoteAddr() net.Addr {
|
|
||||||
return q.rAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ net.Conn = (*quicStreamConn)(nil)
|
|
||||||
|
|
||||||
type quicStreamPacketConn struct {
|
type quicStreamPacketConn struct {
|
||||||
connId uint32
|
connId uint32
|
||||||
quicConn quic.Connection
|
quicConn quic.Connection
|
|
@ -1,4 +1,4 @@
|
||||||
package tuic
|
package v4
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
|
@ -1,4 +1,4 @@
|
||||||
package tuic
|
package v4
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
@ -11,19 +11,21 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
"github.com/Dreamacro/clash/common/utils"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
"github.com/Dreamacro/clash/transport/tuic/common"
|
||||||
|
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid/v5"
|
||||||
"github.com/metacubex/quic-go"
|
"github.com/metacubex/quic-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServerOption struct {
|
type ServerOption struct {
|
||||||
HandleTcpFn func(conn net.Conn, addr socks5.Addr) error
|
HandleTcpFn func(conn net.Conn, addr socks5.Addr, additions ...inbound.Addition) error
|
||||||
HandleUdpFn func(addr socks5.Addr, packet C.UDPPacket) error
|
HandleUdpFn func(addr socks5.Addr, packet C.UDPPacket, additions ...inbound.Addition) error
|
||||||
|
|
||||||
TlsConfig *tls.Config
|
TlsConfig *tls.Config
|
||||||
QuicConfig *quic.Config
|
QuicConfig *quic.Config
|
||||||
|
@ -55,7 +57,7 @@ func (s *Server) Serve() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
SetCongestionController(conn, s.CongestionController)
|
common.SetCongestionController(conn, s.CongestionController)
|
||||||
h := &serverHandler{
|
h := &serverHandler{
|
||||||
Server: s,
|
Server: s,
|
||||||
quicConn: conn,
|
quicConn: conn,
|
||||||
|
@ -162,11 +164,12 @@ func (s *serverHandler) handleStream() (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
go func() (err error) {
|
go func() (err error) {
|
||||||
stream := &quicStreamConn{
|
stream := common.NewQuicStreamConn(
|
||||||
Stream: quicStream,
|
quicStream,
|
||||||
lAddr: s.quicConn.LocalAddr(),
|
s.quicConn.LocalAddr(),
|
||||||
rAddr: s.quicConn.RemoteAddr(),
|
s.quicConn.RemoteAddr(),
|
||||||
}
|
nil,
|
||||||
|
)
|
||||||
conn := N.NewBufferedConn(stream)
|
conn := N.NewBufferedConn(stream)
|
||||||
connect, err := ReadConnect(conn)
|
connect, err := ReadConnect(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -224,18 +227,18 @@ func (s *serverHandler) handleUniStream() (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ok := false
|
authOk := false
|
||||||
for _, tkn := range s.Tokens {
|
for _, tkn := range s.Tokens {
|
||||||
if authenticate.TKN == tkn {
|
if authenticate.TKN == tkn {
|
||||||
ok = true
|
authOk = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.authOnce.Do(func() {
|
s.authOnce.Do(func() {
|
||||||
if !ok {
|
if !authOk {
|
||||||
_ = s.quicConn.CloseWithError(AuthenticationFailed, "AuthenticationFailed")
|
_ = s.quicConn.CloseWithError(AuthenticationFailed, "AuthenticationFailed")
|
||||||
}
|
}
|
||||||
s.authOk = ok
|
s.authOk = authOk
|
||||||
close(s.authCh)
|
close(s.authCh)
|
||||||
})
|
})
|
||||||
case PacketType:
|
case PacketType:
|
386
transport/tuic/v5/client.go
Normal file
386
transport/tuic/v5/client.go
Normal file
|
@ -0,0 +1,386 @@
|
||||||
|
package v5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
atomic2 "github.com/Dreamacro/clash/common/atomic"
|
||||||
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
"github.com/Dreamacro/clash/transport/tuic/common"
|
||||||
|
|
||||||
|
"github.com/metacubex/quic-go"
|
||||||
|
"github.com/zhangyunhao116/fastrand"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientOption struct {
|
||||||
|
TlsConfig *tls.Config
|
||||||
|
QuicConfig *quic.Config
|
||||||
|
Uuid [16]byte
|
||||||
|
Password string
|
||||||
|
UdpRelayMode string
|
||||||
|
CongestionController string
|
||||||
|
ReduceRtt bool
|
||||||
|
MaxUdpRelayPacketSize int
|
||||||
|
MaxOpenStreams int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientImpl struct {
|
||||||
|
*ClientOption
|
||||||
|
udp bool
|
||||||
|
|
||||||
|
quicConn quic.Connection
|
||||||
|
connMutex sync.Mutex
|
||||||
|
|
||||||
|
openStreams atomic.Int64
|
||||||
|
closed atomic.Bool
|
||||||
|
|
||||||
|
udpInputMap sync.Map
|
||||||
|
|
||||||
|
// only ready for PoolClient
|
||||||
|
dialerRef C.Dialer
|
||||||
|
lastVisited atomic2.TypedValue[time.Time]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *clientImpl) OpenStreams() int64 {
|
||||||
|
return t.openStreams.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *clientImpl) DialerRef() C.Dialer {
|
||||||
|
return t.dialerRef
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *clientImpl) LastVisited() time.Time {
|
||||||
|
return t.lastVisited.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *clientImpl) SetLastVisited(last time.Time) {
|
||||||
|
t.lastVisited.Store(last)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *clientImpl) getQuicConn(ctx context.Context, dialer C.Dialer, dialFn common.DialFunc) (quic.Connection, error) {
|
||||||
|
t.connMutex.Lock()
|
||||||
|
defer t.connMutex.Unlock()
|
||||||
|
if t.quicConn != nil {
|
||||||
|
return t.quicConn, nil
|
||||||
|
}
|
||||||
|
transport, addr, err := dialFn(ctx, dialer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var quicConn quic.Connection
|
||||||
|
if t.ReduceRtt {
|
||||||
|
quicConn, err = transport.DialEarly(ctx, addr, t.TlsConfig, t.QuicConfig)
|
||||||
|
} else {
|
||||||
|
quicConn, err = transport.Dial(ctx, addr, t.TlsConfig, t.QuicConfig)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
common.SetCongestionController(quicConn, t.CongestionController)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_ = t.sendAuthentication(quicConn)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if t.udp {
|
||||||
|
go func() {
|
||||||
|
_ = t.parseUDP(quicConn)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
t.quicConn = quicConn
|
||||||
|
t.openStreams.Store(0)
|
||||||
|
return quicConn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *clientImpl) sendAuthentication(quicConn quic.Connection) (err error) {
|
||||||
|
defer func() {
|
||||||
|
t.deferQuicConn(quicConn, err)
|
||||||
|
}()
|
||||||
|
stream, err := quicConn.OpenUniStream()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf := pool.GetBuffer()
|
||||||
|
defer pool.PutBuffer(buf)
|
||||||
|
token, err := GenToken(quicConn.ConnectionState(), t.Uuid, t.Password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = NewAuthenticate(t.Uuid, token).WriteTo(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = buf.WriteTo(stream)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = stream.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *clientImpl) parseUDP(quicConn quic.Connection) (err error) {
|
||||||
|
defer func() {
|
||||||
|
t.deferQuicConn(quicConn, err)
|
||||||
|
}()
|
||||||
|
switch t.UdpRelayMode {
|
||||||
|
case "quic":
|
||||||
|
for {
|
||||||
|
var stream quic.ReceiveStream
|
||||||
|
stream, err = quicConn.AcceptUniStream(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() (err error) {
|
||||||
|
var assocId uint16
|
||||||
|
defer func() {
|
||||||
|
t.deferQuicConn(quicConn, err)
|
||||||
|
if err != nil && assocId != 0 {
|
||||||
|
if val, ok := t.udpInputMap.LoadAndDelete(assocId); ok {
|
||||||
|
if conn, ok := val.(net.Conn); ok {
|
||||||
|
_ = conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.CancelRead(0)
|
||||||
|
}()
|
||||||
|
reader := bufio.NewReader(stream)
|
||||||
|
packet, err := ReadPacket(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assocId = packet.ASSOC_ID
|
||||||
|
if val, ok := t.udpInputMap.Load(assocId); ok {
|
||||||
|
if conn, ok := val.(net.Conn); ok {
|
||||||
|
writer := bufio.NewWriterSize(conn, packet.BytesLen())
|
||||||
|
_ = packet.WriteTo(writer)
|
||||||
|
_ = writer.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
default: // native
|
||||||
|
for {
|
||||||
|
var message []byte
|
||||||
|
message, err = quicConn.ReceiveMessage()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() (err error) {
|
||||||
|
var assocId uint16
|
||||||
|
defer func() {
|
||||||
|
t.deferQuicConn(quicConn, err)
|
||||||
|
if err != nil && assocId != 0 {
|
||||||
|
if val, ok := t.udpInputMap.LoadAndDelete(assocId); ok {
|
||||||
|
if conn, ok := val.(net.Conn); ok {
|
||||||
|
_ = conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
buffer := bytes.NewBuffer(message)
|
||||||
|
packet, err := ReadPacket(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assocId = packet.ASSOC_ID
|
||||||
|
if val, ok := t.udpInputMap.Load(assocId); ok {
|
||||||
|
if conn, ok := val.(net.Conn); ok {
|
||||||
|
_, _ = conn.Write(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *clientImpl) deferQuicConn(quicConn quic.Connection, err error) {
|
||||||
|
var netError net.Error
|
||||||
|
if err != nil && errors.As(err, &netError) {
|
||||||
|
t.forceClose(quicConn, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *clientImpl) forceClose(quicConn quic.Connection, err error) {
|
||||||
|
t.connMutex.Lock()
|
||||||
|
defer t.connMutex.Unlock()
|
||||||
|
if quicConn == nil {
|
||||||
|
quicConn = t.quicConn
|
||||||
|
}
|
||||||
|
if quicConn != nil {
|
||||||
|
if quicConn == t.quicConn {
|
||||||
|
t.quicConn = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errStr := ""
|
||||||
|
if err != nil {
|
||||||
|
errStr = err.Error()
|
||||||
|
}
|
||||||
|
if quicConn != nil {
|
||||||
|
_ = quicConn.CloseWithError(ProtocolError, errStr)
|
||||||
|
}
|
||||||
|
udpInputMap := &t.udpInputMap
|
||||||
|
udpInputMap.Range(func(key, value any) bool {
|
||||||
|
if conn, ok := value.(net.Conn); ok {
|
||||||
|
_ = conn.Close()
|
||||||
|
}
|
||||||
|
udpInputMap.Delete(key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *clientImpl) Close() {
|
||||||
|
t.closed.Store(true)
|
||||||
|
if t.openStreams.Load() == 0 {
|
||||||
|
t.forceClose(nil, common.ClientClosed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *clientImpl) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.Conn, error) {
|
||||||
|
quicConn, err := t.getQuicConn(ctx, dialer, dialFn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
openStreams := t.openStreams.Add(1)
|
||||||
|
if openStreams >= t.MaxOpenStreams {
|
||||||
|
t.openStreams.Add(-1)
|
||||||
|
return nil, common.TooManyOpenStreams
|
||||||
|
}
|
||||||
|
stream, err := func() (stream net.Conn, err error) {
|
||||||
|
defer func() {
|
||||||
|
t.deferQuicConn(quicConn, err)
|
||||||
|
}()
|
||||||
|
buf := pool.GetBuffer()
|
||||||
|
defer pool.PutBuffer(buf)
|
||||||
|
err = NewConnect(NewAddress(metadata)).WriteTo(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
quicStream, err := quicConn.OpenStream()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stream = common.NewQuicStreamConn(
|
||||||
|
quicStream,
|
||||||
|
quicConn.LocalAddr(),
|
||||||
|
quicConn.RemoteAddr(),
|
||||||
|
func() {
|
||||||
|
time.AfterFunc(C.DefaultTCPTimeout, func() {
|
||||||
|
openStreams := t.openStreams.Add(-1)
|
||||||
|
if openStreams == 0 && t.closed.Load() {
|
||||||
|
t.forceClose(quicConn, common.ClientClosed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
_, err = buf.WriteTo(stream)
|
||||||
|
if err != nil {
|
||||||
|
_ = stream.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return stream, err
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *clientImpl) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.PacketConn, error) {
|
||||||
|
quicConn, err := t.getQuicConn(ctx, dialer, dialFn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
openStreams := t.openStreams.Add(1)
|
||||||
|
if openStreams >= t.MaxOpenStreams {
|
||||||
|
t.openStreams.Add(-1)
|
||||||
|
return nil, common.TooManyOpenStreams
|
||||||
|
}
|
||||||
|
|
||||||
|
pipe1, pipe2 := net.Pipe()
|
||||||
|
var connId uint16
|
||||||
|
for {
|
||||||
|
connId = uint16(fastrand.Intn(0xFFFF))
|
||||||
|
_, loaded := t.udpInputMap.LoadOrStore(connId, pipe1)
|
||||||
|
if !loaded {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pc := &quicStreamPacketConn{
|
||||||
|
connId: connId,
|
||||||
|
quicConn: quicConn,
|
||||||
|
inputConn: N.NewBufferedConn(pipe2),
|
||||||
|
udpRelayMode: t.UdpRelayMode,
|
||||||
|
maxUdpRelayPacketSize: t.MaxUdpRelayPacketSize,
|
||||||
|
deferQuicConnFn: t.deferQuicConn,
|
||||||
|
closeDeferFn: func() {
|
||||||
|
t.udpInputMap.Delete(connId)
|
||||||
|
time.AfterFunc(C.DefaultUDPTimeout, func() {
|
||||||
|
openStreams := t.openStreams.Add(-1)
|
||||||
|
if openStreams == 0 && t.closed.Load() {
|
||||||
|
t.forceClose(quicConn, common.ClientClosed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return pc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
*clientImpl // use an independent pointer to let Finalizer can work no matter somewhere handle an influence in clientImpl inner
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Client) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.Conn, error) {
|
||||||
|
conn, err := t.clientImpl.DialContextWithDialer(ctx, metadata, dialer, dialFn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return N.NewRefConn(conn, t), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Client) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.PacketConn, error) {
|
||||||
|
pc, err := t.clientImpl.ListenPacketWithDialer(ctx, metadata, dialer, dialFn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return N.NewRefPacketConn(pc, t), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Client) forceClose() {
|
||||||
|
t.clientImpl.forceClose(nil, common.ClientClosed)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(clientOption *ClientOption, udp bool, dialerRef C.Dialer) *Client {
|
||||||
|
ci := &clientImpl{
|
||||||
|
ClientOption: clientOption,
|
||||||
|
udp: udp,
|
||||||
|
dialerRef: dialerRef,
|
||||||
|
}
|
||||||
|
c := &Client{ci}
|
||||||
|
runtime.SetFinalizer(c, closeClient)
|
||||||
|
log.Debugln("New TuicV5 Client at %p", c)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeClient(client *Client) {
|
||||||
|
log.Debugln("Close TuicV5 Client at %p", client)
|
||||||
|
client.forceClose()
|
||||||
|
}
|
80
transport/tuic/v5/frag.go
Normal file
80
transport/tuic/v5/frag.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
package v5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/metacubex/quic-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fragWriteNative(quicConn quic.Connection, packet Packet, buf *bytes.Buffer, fragSize int) (err error) {
|
||||||
|
fullPayload := packet.DATA
|
||||||
|
off := 0
|
||||||
|
fragID := uint8(0)
|
||||||
|
fragCount := uint8((len(fullPayload) + fragSize - 1) / fragSize) // round up
|
||||||
|
packet.FRAG_TOTAL = fragCount
|
||||||
|
for off < len(fullPayload) {
|
||||||
|
payloadSize := len(fullPayload) - off
|
||||||
|
if payloadSize > fragSize {
|
||||||
|
payloadSize = fragSize
|
||||||
|
}
|
||||||
|
frag := packet
|
||||||
|
frag.FRAG_ID = fragID
|
||||||
|
frag.SIZE = uint16(payloadSize)
|
||||||
|
frag.DATA = fullPayload[off : off+payloadSize]
|
||||||
|
off += payloadSize
|
||||||
|
fragID++
|
||||||
|
buf.Reset()
|
||||||
|
err = frag.WriteTo(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data := buf.Bytes()
|
||||||
|
err = quicConn.SendMessage(data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
packet.ADDR.TYPE = AtypNone // avoid "fragment 2/2: address in non-first fragment"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type deFragger struct {
|
||||||
|
pkgID uint16
|
||||||
|
frags []*Packet
|
||||||
|
count uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deFragger) Feed(m Packet) *Packet {
|
||||||
|
if m.FRAG_TOTAL <= 1 {
|
||||||
|
return &m
|
||||||
|
}
|
||||||
|
if m.FRAG_ID >= m.FRAG_TOTAL {
|
||||||
|
// wtf is this?
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if d.count == 0 || m.PKT_ID != d.pkgID {
|
||||||
|
// new message, clear previous state
|
||||||
|
d.pkgID = m.PKT_ID
|
||||||
|
d.frags = make([]*Packet, m.FRAG_TOTAL)
|
||||||
|
d.count = 1
|
||||||
|
d.frags[m.FRAG_ID] = &m
|
||||||
|
} else if d.frags[m.FRAG_ID] == nil {
|
||||||
|
d.frags[m.FRAG_ID] = &m
|
||||||
|
d.count++
|
||||||
|
if int(d.count) == len(d.frags) {
|
||||||
|
// all fragments received, assemble
|
||||||
|
var data []byte
|
||||||
|
for _, frag := range d.frags {
|
||||||
|
data = append(data, frag.DATA...)
|
||||||
|
}
|
||||||
|
p := d.frags[0] // recover from first fragment
|
||||||
|
p.SIZE = uint16(len(data))
|
||||||
|
p.DATA = data
|
||||||
|
p.FRAG_ID = 0
|
||||||
|
p.FRAG_TOTAL = 1
|
||||||
|
d.count = 0
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
208
transport/tuic/v5/packet.go
Normal file
208
transport/tuic/v5/packet.go
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
package v5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
|
||||||
|
"github.com/metacubex/quic-go"
|
||||||
|
"github.com/zhangyunhao116/fastrand"
|
||||||
|
)
|
||||||
|
|
||||||
|
type quicStreamPacketConn struct {
|
||||||
|
connId uint16
|
||||||
|
quicConn quic.Connection
|
||||||
|
inputConn *N.BufferedConn
|
||||||
|
|
||||||
|
udpRelayMode string
|
||||||
|
maxUdpRelayPacketSize int
|
||||||
|
|
||||||
|
deferQuicConnFn func(quicConn quic.Connection, err error)
|
||||||
|
closeDeferFn func()
|
||||||
|
writeClosed *atomic.Bool
|
||||||
|
|
||||||
|
closeOnce sync.Once
|
||||||
|
closeErr error
|
||||||
|
closed bool
|
||||||
|
|
||||||
|
deFragger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *quicStreamPacketConn) Close() error {
|
||||||
|
q.closeOnce.Do(func() {
|
||||||
|
q.closed = true
|
||||||
|
q.closeErr = q.close()
|
||||||
|
})
|
||||||
|
return q.closeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *quicStreamPacketConn) close() (err error) {
|
||||||
|
if q.closeDeferFn != nil {
|
||||||
|
defer q.closeDeferFn()
|
||||||
|
}
|
||||||
|
if q.deferQuicConnFn != nil {
|
||||||
|
defer func() {
|
||||||
|
q.deferQuicConnFn(q.quicConn, err)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if q.inputConn != nil {
|
||||||
|
_ = q.inputConn.Close()
|
||||||
|
q.inputConn = nil
|
||||||
|
|
||||||
|
buf := pool.GetBuffer()
|
||||||
|
defer pool.PutBuffer(buf)
|
||||||
|
err = NewDissociate(q.connId).WriteTo(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var stream quic.SendStream
|
||||||
|
stream, err = q.quicConn.OpenUniStream()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = buf.WriteTo(stream)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = stream.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *quicStreamPacketConn) SetDeadline(t time.Time) error {
|
||||||
|
//TODO implement me
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *quicStreamPacketConn) SetReadDeadline(t time.Time) error {
|
||||||
|
if q.inputConn != nil {
|
||||||
|
return q.inputConn.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *quicStreamPacketConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
//TODO implement me
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *quicStreamPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||||
|
if q.inputConn != nil {
|
||||||
|
for {
|
||||||
|
var packet Packet
|
||||||
|
packet, err = ReadPacket(q.inputConn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if packetPtr := q.deFragger.Feed(packet); packetPtr != nil {
|
||||||
|
n = copy(p, packet.DATA)
|
||||||
|
addr = packetPtr.ADDR.UDPAddr()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = net.ErrClosed
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *quicStreamPacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) {
|
||||||
|
if q.inputConn != nil {
|
||||||
|
for {
|
||||||
|
var packet Packet
|
||||||
|
packet, err = ReadPacket(q.inputConn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if packetPtr := q.deFragger.Feed(packet); packetPtr != nil {
|
||||||
|
data = packetPtr.DATA
|
||||||
|
addr = packetPtr.ADDR.UDPAddr()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = net.ErrClosed
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
|
if len(p) > 0xffff { // uint16 max
|
||||||
|
return 0, quic.ErrMessageTooLarge(0xffff)
|
||||||
|
}
|
||||||
|
if q.closed {
|
||||||
|
return 0, net.ErrClosed
|
||||||
|
}
|
||||||
|
if q.writeClosed != nil && q.writeClosed.Load() {
|
||||||
|
_ = q.Close()
|
||||||
|
return 0, net.ErrClosed
|
||||||
|
}
|
||||||
|
if q.deferQuicConnFn != nil {
|
||||||
|
defer func() {
|
||||||
|
q.deferQuicConnFn(q.quicConn, err)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
buf := pool.GetBuffer()
|
||||||
|
defer pool.PutBuffer(buf)
|
||||||
|
address, err := NewAddressNetAddr(addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pktId := uint16(fastrand.Uint32())
|
||||||
|
packet := NewPacket(q.connId, pktId, 1, 0, uint16(len(p)), address, p)
|
||||||
|
switch q.udpRelayMode {
|
||||||
|
case "quic":
|
||||||
|
err = packet.WriteTo(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var stream quic.SendStream
|
||||||
|
stream, err = q.quicConn.OpenUniStream()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer stream.Close()
|
||||||
|
_, err = buf.WriteTo(stream)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default: // native
|
||||||
|
if len(p) > q.maxUdpRelayPacketSize {
|
||||||
|
err = fragWriteNative(q.quicConn, packet, buf, q.maxUdpRelayPacketSize)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = packet.WriteTo(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data := buf.Bytes()
|
||||||
|
err = q.quicConn.SendMessage(data)
|
||||||
|
|
||||||
|
var tooLarge quic.ErrMessageTooLarge
|
||||||
|
if errors.As(err, &tooLarge) {
|
||||||
|
err = fragWriteNative(q.quicConn, packet, buf, int(tooLarge)-PacketOverHead)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n = len(p)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *quicStreamPacketConn) LocalAddr() net.Addr {
|
||||||
|
return q.quicConn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ net.PacketConn = (*quicStreamPacketConn)(nil)
|
651
transport/tuic/v5/protocol.go
Normal file
651
transport/tuic/v5/protocol.go
Normal file
|
@ -0,0 +1,651 @@
|
||||||
|
package v5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/common/utils"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
|
||||||
|
"github.com/metacubex/quic-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BufferedReader interface {
|
||||||
|
io.Reader
|
||||||
|
io.ByteReader
|
||||||
|
}
|
||||||
|
|
||||||
|
type BufferedWriter interface {
|
||||||
|
io.Writer
|
||||||
|
io.ByteWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
AuthenticateType = CommandType(0x00)
|
||||||
|
ConnectType = CommandType(0x01)
|
||||||
|
PacketType = CommandType(0x02)
|
||||||
|
DissociateType = CommandType(0x03)
|
||||||
|
HeartbeatType = CommandType(0x04)
|
||||||
|
ResponseType = CommandType(0xff)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c CommandType) String() string {
|
||||||
|
switch c {
|
||||||
|
case AuthenticateType:
|
||||||
|
return "Authenticate"
|
||||||
|
case ConnectType:
|
||||||
|
return "Connect"
|
||||||
|
case PacketType:
|
||||||
|
return "Packet"
|
||||||
|
case DissociateType:
|
||||||
|
return "Dissociate"
|
||||||
|
case HeartbeatType:
|
||||||
|
return "Heartbeat"
|
||||||
|
case ResponseType:
|
||||||
|
return "Response"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("UnknowCommand: %#x", byte(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CommandType) BytesLen() int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandHead struct {
|
||||||
|
VER byte
|
||||||
|
TYPE CommandType
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommandHead(TYPE CommandType) CommandHead {
|
||||||
|
return CommandHead{
|
||||||
|
VER: 0x05,
|
||||||
|
TYPE: TYPE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadCommandHead(reader BufferedReader) (c CommandHead, err error) {
|
||||||
|
c.VER, err = reader.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
TYPE, err := reader.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.TYPE = CommandType(TYPE)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CommandHead) WriteTo(writer BufferedWriter) (err error) {
|
||||||
|
err = writer.WriteByte(c.VER)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = writer.WriteByte(byte(c.TYPE))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CommandHead) BytesLen() int {
|
||||||
|
return 1 + c.TYPE.BytesLen()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Authenticate struct {
|
||||||
|
CommandHead
|
||||||
|
UUID [16]byte
|
||||||
|
TOKEN [32]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthenticate(UUID [16]byte, TOKEN [32]byte) Authenticate {
|
||||||
|
return Authenticate{
|
||||||
|
CommandHead: NewCommandHead(AuthenticateType),
|
||||||
|
UUID: UUID,
|
||||||
|
TOKEN: TOKEN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadAuthenticateWithHead(head CommandHead, reader BufferedReader) (c Authenticate, err error) {
|
||||||
|
c.CommandHead = head
|
||||||
|
if c.CommandHead.TYPE != AuthenticateType {
|
||||||
|
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = io.ReadFull(reader, c.UUID[:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = io.ReadFull(reader, c.TOKEN[:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadAuthenticate(reader BufferedReader) (c Authenticate, err error) {
|
||||||
|
head, err := ReadCommandHead(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return ReadAuthenticateWithHead(head, reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenToken(state quic.ConnectionState, uuid [16]byte, password string) (token [32]byte, err error) {
|
||||||
|
var tokenBytes []byte
|
||||||
|
tokenBytes, err = state.TLS.ExportKeyingMaterial(utils.StringFromImmutableBytes(uuid[:]), utils.ImmutableBytesFromString(password), 32)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
copy(token[:], tokenBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Authenticate) WriteTo(writer BufferedWriter) (err error) {
|
||||||
|
err = c.CommandHead.WriteTo(writer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = writer.Write(c.UUID[:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = writer.Write(c.TOKEN[:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Authenticate) BytesLen() int {
|
||||||
|
return c.CommandHead.BytesLen() + 16 + 32
|
||||||
|
}
|
||||||
|
|
||||||
|
type Connect struct {
|
||||||
|
CommandHead
|
||||||
|
ADDR Address
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConnect(ADDR Address) Connect {
|
||||||
|
return Connect{
|
||||||
|
CommandHead: NewCommandHead(ConnectType),
|
||||||
|
ADDR: ADDR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadConnectWithHead(head CommandHead, reader BufferedReader) (c Connect, err error) {
|
||||||
|
c.CommandHead = head
|
||||||
|
if c.CommandHead.TYPE != ConnectType {
|
||||||
|
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.ADDR, err = ReadAddress(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadConnect(reader BufferedReader) (c Connect, err error) {
|
||||||
|
head, err := ReadCommandHead(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return ReadConnectWithHead(head, reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Connect) WriteTo(writer BufferedWriter) (err error) {
|
||||||
|
err = c.CommandHead.WriteTo(writer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = c.ADDR.WriteTo(writer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Connect) BytesLen() int {
|
||||||
|
return c.CommandHead.BytesLen() + c.ADDR.BytesLen()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Packet struct {
|
||||||
|
CommandHead
|
||||||
|
ASSOC_ID uint16
|
||||||
|
PKT_ID uint16
|
||||||
|
FRAG_TOTAL uint8
|
||||||
|
FRAG_ID uint8
|
||||||
|
SIZE uint16
|
||||||
|
ADDR Address
|
||||||
|
DATA []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPacket(ASSOC_ID uint16, PKT_ID uint16, FRGA_TOTAL uint8, FRAG_ID uint8, SIZE uint16, ADDR Address, DATA []byte) Packet {
|
||||||
|
return Packet{
|
||||||
|
CommandHead: NewCommandHead(PacketType),
|
||||||
|
ASSOC_ID: ASSOC_ID,
|
||||||
|
PKT_ID: PKT_ID,
|
||||||
|
FRAG_ID: FRAG_ID,
|
||||||
|
FRAG_TOTAL: FRGA_TOTAL,
|
||||||
|
SIZE: SIZE,
|
||||||
|
ADDR: ADDR,
|
||||||
|
DATA: DATA,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadPacketWithHead(head CommandHead, reader BufferedReader) (c Packet, err error) {
|
||||||
|
c.CommandHead = head
|
||||||
|
if c.CommandHead.TYPE != PacketType {
|
||||||
|
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &c.ASSOC_ID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &c.PKT_ID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &c.FRAG_TOTAL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &c.FRAG_ID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &c.SIZE)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.ADDR, err = ReadAddress(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.DATA = make([]byte, c.SIZE)
|
||||||
|
_, err = io.ReadFull(reader, c.DATA)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadPacket(reader BufferedReader) (c Packet, err error) {
|
||||||
|
head, err := ReadCommandHead(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return ReadPacketWithHead(head, reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Packet) WriteTo(writer BufferedWriter) (err error) {
|
||||||
|
err = c.CommandHead.WriteTo(writer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, c.ASSOC_ID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, c.PKT_ID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, c.FRAG_TOTAL)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, c.FRAG_ID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, c.SIZE)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = c.ADDR.WriteTo(writer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = writer.Write(c.DATA)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Packet) BytesLen() int {
|
||||||
|
return c.CommandHead.BytesLen() + 4 + 2 + c.ADDR.BytesLen() + len(c.DATA)
|
||||||
|
}
|
||||||
|
|
||||||
|
var PacketOverHead = NewPacket(0, 0, 0, 0, 0, NewAddressAddrPort(netip.AddrPortFrom(netip.IPv6Unspecified(), 0)), nil).BytesLen()
|
||||||
|
|
||||||
|
type Dissociate struct {
|
||||||
|
CommandHead
|
||||||
|
ASSOC_ID uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDissociate(ASSOC_ID uint16) Dissociate {
|
||||||
|
return Dissociate{
|
||||||
|
CommandHead: NewCommandHead(DissociateType),
|
||||||
|
ASSOC_ID: ASSOC_ID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadDissociateWithHead(head CommandHead, reader BufferedReader) (c Dissociate, err error) {
|
||||||
|
c.CommandHead = head
|
||||||
|
if c.CommandHead.TYPE != DissociateType {
|
||||||
|
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &c.ASSOC_ID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadDissociate(reader BufferedReader) (c Dissociate, err error) {
|
||||||
|
head, err := ReadCommandHead(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return ReadDissociateWithHead(head, reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Dissociate) WriteTo(writer BufferedWriter) (err error) {
|
||||||
|
err = c.CommandHead.WriteTo(writer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, c.ASSOC_ID)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Dissociate) BytesLen() int {
|
||||||
|
return c.CommandHead.BytesLen() + 4
|
||||||
|
}
|
||||||
|
|
||||||
|
type Heartbeat struct {
|
||||||
|
CommandHead
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHeartbeat() Heartbeat {
|
||||||
|
return Heartbeat{
|
||||||
|
CommandHead: NewCommandHead(HeartbeatType),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadHeartbeatWithHead(head CommandHead, reader BufferedReader) (c Heartbeat, err error) {
|
||||||
|
c.CommandHead = head
|
||||||
|
if c.CommandHead.TYPE != HeartbeatType {
|
||||||
|
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadHeartbeat(reader BufferedReader) (c Heartbeat, err error) {
|
||||||
|
head, err := ReadCommandHead(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return ReadHeartbeatWithHead(head, reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
CommandHead
|
||||||
|
REP byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResponse(REP byte) Response {
|
||||||
|
return Response{
|
||||||
|
CommandHead: NewCommandHead(ResponseType),
|
||||||
|
REP: REP,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResponseSucceed() Response {
|
||||||
|
return NewResponse(0x00)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResponseFailed() Response {
|
||||||
|
return NewResponse(0xff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadResponseWithHead(head CommandHead, reader BufferedReader) (c Response, err error) {
|
||||||
|
c.CommandHead = head
|
||||||
|
if c.CommandHead.TYPE != ResponseType {
|
||||||
|
err = fmt.Errorf("error command type: %s", c.CommandHead.TYPE)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.REP, err = reader.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadResponse(reader BufferedReader) (c Response, err error) {
|
||||||
|
head, err := ReadCommandHead(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return ReadResponseWithHead(head, reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Response) WriteTo(writer BufferedWriter) (err error) {
|
||||||
|
err = c.CommandHead.WriteTo(writer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = writer.WriteByte(c.REP)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Response) IsSucceed() bool {
|
||||||
|
return c.REP == 0x00
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Response) IsFailed() bool {
|
||||||
|
return c.REP == 0xff
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Response) BytesLen() int {
|
||||||
|
return c.CommandHead.BytesLen() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr types
|
||||||
|
const (
|
||||||
|
AtypDomainName byte = 0
|
||||||
|
AtypIPv4 byte = 1
|
||||||
|
AtypIPv6 byte = 2
|
||||||
|
AtypNone byte = 255 // Address type None is used in Packet commands that is not the first fragment of a UDP packet.
|
||||||
|
)
|
||||||
|
|
||||||
|
type Address struct {
|
||||||
|
TYPE byte
|
||||||
|
ADDR []byte
|
||||||
|
PORT uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAddress(metadata *C.Metadata) Address {
|
||||||
|
var addrType byte
|
||||||
|
var addr []byte
|
||||||
|
switch metadata.AddrType() {
|
||||||
|
case socks5.AtypIPv4:
|
||||||
|
addrType = AtypIPv4
|
||||||
|
addr = metadata.DstIP.AsSlice()
|
||||||
|
case socks5.AtypIPv6:
|
||||||
|
addrType = AtypIPv6
|
||||||
|
addr = metadata.DstIP.AsSlice()
|
||||||
|
case socks5.AtypDomainName:
|
||||||
|
addrType = AtypDomainName
|
||||||
|
addr = make([]byte, len(metadata.Host)+1)
|
||||||
|
addr[0] = byte(len(metadata.Host))
|
||||||
|
copy(addr[1:], metadata.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
|
||||||
|
|
||||||
|
return Address{
|
||||||
|
TYPE: addrType,
|
||||||
|
ADDR: addr,
|
||||||
|
PORT: uint16(port),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAddressNetAddr(addr net.Addr) (Address, error) {
|
||||||
|
if addr, ok := addr.(interface{ AddrPort() netip.AddrPort }); ok {
|
||||||
|
if addrPort := addr.AddrPort(); addrPort.IsValid() { // sing's M.Socksaddr maybe return an invalid AddrPort if it's a DomainName
|
||||||
|
return NewAddressAddrPort(addrPort), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addrStr := addr.String()
|
||||||
|
if addrPort, err := netip.ParseAddrPort(addrStr); err == nil {
|
||||||
|
return NewAddressAddrPort(addrPort), nil
|
||||||
|
}
|
||||||
|
metadata := &C.Metadata{}
|
||||||
|
if err := metadata.SetRemoteAddress(addrStr); err != nil {
|
||||||
|
return Address{}, err
|
||||||
|
}
|
||||||
|
return NewAddress(metadata), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAddressAddrPort(addrPort netip.AddrPort) Address {
|
||||||
|
var addrType byte
|
||||||
|
port := addrPort.Port()
|
||||||
|
addr := addrPort.Addr().Unmap()
|
||||||
|
if addr.Is4() {
|
||||||
|
addrType = AtypIPv4
|
||||||
|
} else {
|
||||||
|
addrType = AtypIPv6
|
||||||
|
}
|
||||||
|
return Address{
|
||||||
|
TYPE: addrType,
|
||||||
|
ADDR: addr.AsSlice(),
|
||||||
|
PORT: port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadAddress(reader BufferedReader) (c Address, err error) {
|
||||||
|
c.TYPE, err = reader.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch c.TYPE {
|
||||||
|
case AtypIPv4:
|
||||||
|
c.ADDR = make([]byte, net.IPv4len)
|
||||||
|
_, err = io.ReadFull(reader, c.ADDR)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case AtypIPv6:
|
||||||
|
c.ADDR = make([]byte, net.IPv6len)
|
||||||
|
_, err = io.ReadFull(reader, c.ADDR)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case AtypDomainName:
|
||||||
|
var addrLen byte
|
||||||
|
addrLen, err = reader.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.ADDR = make([]byte, addrLen+1)
|
||||||
|
c.ADDR[0] = addrLen
|
||||||
|
_, err = io.ReadFull(reader, c.ADDR[1:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.TYPE == AtypNone {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &c.PORT)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Address) WriteTo(writer BufferedWriter) (err error) {
|
||||||
|
err = writer.WriteByte(c.TYPE)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.TYPE == AtypNone {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = writer.Write(c.ADDR[:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, c.PORT)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Address) String() string {
|
||||||
|
switch c.TYPE {
|
||||||
|
case AtypDomainName:
|
||||||
|
return net.JoinHostPort(string(c.ADDR[1:]), strconv.Itoa(int(c.PORT)))
|
||||||
|
default:
|
||||||
|
addr, _ := netip.AddrFromSlice(c.ADDR)
|
||||||
|
addrPort := netip.AddrPortFrom(addr, c.PORT)
|
||||||
|
return addrPort.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Address) SocksAddr() socks5.Addr {
|
||||||
|
addr := make([]byte, 1+len(c.ADDR)+2)
|
||||||
|
switch c.TYPE {
|
||||||
|
case AtypIPv4:
|
||||||
|
addr[0] = socks5.AtypIPv4
|
||||||
|
case AtypIPv6:
|
||||||
|
addr[0] = socks5.AtypIPv6
|
||||||
|
case AtypDomainName:
|
||||||
|
addr[0] = socks5.AtypDomainName
|
||||||
|
}
|
||||||
|
copy(addr[1:], c.ADDR)
|
||||||
|
binary.BigEndian.PutUint16(addr[len(addr)-2:], c.PORT)
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Address) UDPAddr() *net.UDPAddr {
|
||||||
|
return &net.UDPAddr{
|
||||||
|
IP: c.ADDR,
|
||||||
|
Port: int(c.PORT),
|
||||||
|
Zone: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Address) BytesLen() int {
|
||||||
|
return 1 + len(c.ADDR) + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProtocolError = quic.ApplicationErrorCode(0xfffffff0)
|
||||||
|
AuthenticationFailed = quic.ApplicationErrorCode(0xfffffff1)
|
||||||
|
AuthenticationTimeout = quic.ApplicationErrorCode(0xfffffff2)
|
||||||
|
BadCommand = quic.ApplicationErrorCode(0xfffffff3)
|
||||||
|
)
|
303
transport/tuic/v5/server.go
Normal file
303
transport/tuic/v5/server.go
Normal file
|
@ -0,0 +1,303 @@
|
||||||
|
package v5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
|
"github.com/Dreamacro/clash/common/utils"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
|
"github.com/Dreamacro/clash/transport/tuic/common"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
"github.com/metacubex/quic-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerOption struct {
|
||||||
|
HandleTcpFn func(conn net.Conn, addr socks5.Addr, additions ...inbound.Addition) error
|
||||||
|
HandleUdpFn func(addr socks5.Addr, packet C.UDPPacket, additions ...inbound.Addition) error
|
||||||
|
|
||||||
|
TlsConfig *tls.Config
|
||||||
|
QuicConfig *quic.Config
|
||||||
|
Users map[[16]byte]string
|
||||||
|
CongestionController string
|
||||||
|
AuthenticationTimeout time.Duration
|
||||||
|
MaxUdpRelayPacketSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
*ServerOption
|
||||||
|
listener *quic.EarlyListener
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(option *ServerOption, pc net.PacketConn) (*Server, error) {
|
||||||
|
listener, err := quic.ListenEarly(pc, option.TlsConfig, option.QuicConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Server{
|
||||||
|
ServerOption: option,
|
||||||
|
listener: listener,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Serve() error {
|
||||||
|
for {
|
||||||
|
conn, err := s.listener.Accept(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
common.SetCongestionController(conn, s.CongestionController)
|
||||||
|
h := &serverHandler{
|
||||||
|
Server: s,
|
||||||
|
quicConn: conn,
|
||||||
|
uuid: utils.NewUUIDV4(),
|
||||||
|
authCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
go h.handle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Close() error {
|
||||||
|
return s.listener.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverHandler struct {
|
||||||
|
*Server
|
||||||
|
quicConn quic.EarlyConnection
|
||||||
|
uuid uuid.UUID
|
||||||
|
|
||||||
|
authCh chan struct{}
|
||||||
|
authOk bool
|
||||||
|
authUUID string
|
||||||
|
authOnce sync.Once
|
||||||
|
|
||||||
|
udpInputMap sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serverHandler) handle() {
|
||||||
|
go func() {
|
||||||
|
_ = s.handleUniStream()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
_ = s.handleStream()
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
_ = s.handleMessage()
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-s.quicConn.HandshakeComplete()
|
||||||
|
time.AfterFunc(s.AuthenticationTimeout, func() {
|
||||||
|
s.authOnce.Do(func() {
|
||||||
|
_ = s.quicConn.CloseWithError(AuthenticationTimeout, "AuthenticationTimeout")
|
||||||
|
s.authOk = false
|
||||||
|
close(s.authCh)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serverHandler) handleMessage() (err error) {
|
||||||
|
for {
|
||||||
|
var message []byte
|
||||||
|
message, err = s.quicConn.ReceiveMessage()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() (err error) {
|
||||||
|
buffer := bytes.NewBuffer(message)
|
||||||
|
packet, err := ReadPacket(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return s.parsePacket(packet, "native")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serverHandler) parsePacket(packet Packet, udpRelayMode string) (err error) {
|
||||||
|
<-s.authCh
|
||||||
|
if !s.authOk {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var assocId uint16
|
||||||
|
|
||||||
|
assocId = packet.ASSOC_ID
|
||||||
|
|
||||||
|
v, _ := s.udpInputMap.LoadOrStore(assocId, &serverUDPInput{})
|
||||||
|
input := v.(*serverUDPInput)
|
||||||
|
if input.writeClosed.Load() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
packetPtr := input.Feed(packet)
|
||||||
|
if packetPtr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pc := &quicStreamPacketConn{
|
||||||
|
connId: assocId,
|
||||||
|
quicConn: s.quicConn,
|
||||||
|
inputConn: nil,
|
||||||
|
udpRelayMode: udpRelayMode,
|
||||||
|
maxUdpRelayPacketSize: s.MaxUdpRelayPacketSize,
|
||||||
|
deferQuicConnFn: nil,
|
||||||
|
closeDeferFn: nil,
|
||||||
|
writeClosed: &input.writeClosed,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.HandleUdpFn(packetPtr.ADDR.SocksAddr(), &serverUDPPacket{
|
||||||
|
pc: pc,
|
||||||
|
packet: packetPtr,
|
||||||
|
rAddr: N.NewCustomAddr("tuic", fmt.Sprintf("tuic-%s-%d", s.uuid, assocId), s.quicConn.RemoteAddr()), // for tunnel's handleUDPConn
|
||||||
|
}, inbound.WithInUser(s.authUUID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serverHandler) handleStream() (err error) {
|
||||||
|
for {
|
||||||
|
var quicStream quic.Stream
|
||||||
|
quicStream, err = s.quicConn.AcceptStream(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() (err error) {
|
||||||
|
stream := common.NewQuicStreamConn(
|
||||||
|
quicStream,
|
||||||
|
s.quicConn.LocalAddr(),
|
||||||
|
s.quicConn.RemoteAddr(),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
conn := N.NewBufferedConn(stream)
|
||||||
|
connect, err := ReadConnect(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
<-s.authCh
|
||||||
|
if !s.authOk {
|
||||||
|
return conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.HandleTcpFn(conn, connect.ADDR.SocksAddr(), inbound.WithInUser(s.authUUID))
|
||||||
|
if err != nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serverHandler) handleUniStream() (err error) {
|
||||||
|
for {
|
||||||
|
var stream quic.ReceiveStream
|
||||||
|
stream, err = s.quicConn.AcceptUniStream(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() (err error) {
|
||||||
|
defer func() {
|
||||||
|
stream.CancelRead(0)
|
||||||
|
}()
|
||||||
|
reader := bufio.NewReader(stream)
|
||||||
|
commandHead, err := ReadCommandHead(reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch commandHead.TYPE {
|
||||||
|
case AuthenticateType:
|
||||||
|
var authenticate Authenticate
|
||||||
|
authenticate, err = ReadAuthenticateWithHead(commandHead, reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
authOk := false
|
||||||
|
var authUUID uuid.UUID
|
||||||
|
var token [32]byte
|
||||||
|
if password, ok := s.Users[authenticate.UUID]; ok {
|
||||||
|
token, err = GenToken(s.quicConn.ConnectionState(), authenticate.UUID, password)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if token == authenticate.TOKEN {
|
||||||
|
authOk = true
|
||||||
|
authUUID = authenticate.UUID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.authOnce.Do(func() {
|
||||||
|
if !authOk {
|
||||||
|
_ = s.quicConn.CloseWithError(AuthenticationFailed, "AuthenticationFailed")
|
||||||
|
}
|
||||||
|
s.authOk = authOk
|
||||||
|
s.authUUID = authUUID.String()
|
||||||
|
close(s.authCh)
|
||||||
|
})
|
||||||
|
case PacketType:
|
||||||
|
var packet Packet
|
||||||
|
packet, err = ReadPacketWithHead(commandHead, reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return s.parsePacket(packet, "quic")
|
||||||
|
case DissociateType:
|
||||||
|
var disassociate Dissociate
|
||||||
|
disassociate, err = ReadDissociateWithHead(commandHead, reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v, loaded := s.udpInputMap.LoadAndDelete(disassociate.ASSOC_ID); loaded {
|
||||||
|
input := v.(*serverUDPInput)
|
||||||
|
input.writeClosed.Store(true)
|
||||||
|
}
|
||||||
|
case HeartbeatType:
|
||||||
|
var heartbeat Heartbeat
|
||||||
|
heartbeat, err = ReadHeartbeatWithHead(commandHead, reader)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
heartbeat.BytesLen()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverUDPInput struct {
|
||||||
|
writeClosed atomic.Bool
|
||||||
|
deFragger
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverUDPPacket struct {
|
||||||
|
pc *quicStreamPacketConn
|
||||||
|
packet *Packet
|
||||||
|
rAddr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serverUDPPacket) InAddr() net.Addr {
|
||||||
|
return s.pc.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serverUDPPacket) LocalAddr() net.Addr {
|
||||||
|
return s.rAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serverUDPPacket) Data() []byte {
|
||||||
|
return s.packet.DATA
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serverUDPPacket) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||||
|
return s.pc.WriteTo(b, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serverUDPPacket) Drop() {
|
||||||
|
s.packet.DATA = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ C.UDPPacket = (*serverUDPPacket)(nil)
|
||||||
|
var _ C.UDPPacketInAddr = (*serverUDPPacket)(nil)
|
Loading…
Reference in a new issue