Skip to content

Commit

Permalink
chore: tuic server can handle V4 and V5 in same port
Browse files Browse the repository at this point in the history
  • Loading branch information
wwqgtxx committed Jun 21, 2023
1 parent 1d94546 commit 6d824c8
Show file tree
Hide file tree
Showing 11 changed files with 464 additions and 422 deletions.
8 changes: 4 additions & 4 deletions docs/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -905,9 +905,9 @@ listeners:
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
# token: # tuicV4填写(不可同时填写users
# token: # tuicV4填写(可以同时填写users
# - TOKEN
# users: # tuicV5填写(不可同时填写token
# users: # tuicV5填写(可以同时填写token
# 00000000-0000-0000-0000-000000000000: PASSWORD_0
# 00000000-0000-0000-0000-000000000001: PASSWORD_1
# certificate: ./server.crt
Expand Down Expand Up @@ -978,9 +978,9 @@ listeners:
# tuic-server:
# enable: true
# listen: 127.0.0.1:10443
# token: # tuicV4填写(不可同时填写users
# token: # tuicV4填写(可以同时填写users
# - TOKEN
# users: # tuicV5填写(不可同时填写token
# users: # tuicV5填写(可以同时填写token
# 00000000-0000-0000-0000-000000000000: PASSWORD_0
# 00000000-0000-0000-0000-000000000001: PASSWORD_1
# certificate: ./server.crt
Expand Down
51 changes: 17 additions & 34 deletions listener/tuic/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type Listener struct {
closed bool
config LC.TuicServer
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) {
Expand Down Expand Up @@ -102,42 +102,29 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet
return nil
}

var optionV4 *tuic.ServerOptionV4
var optionV5 *tuic.ServerOptionV5
option := &tuic.ServerOption{
HandleTcpFn: handleTcpFn,
HandleUdpFn: handleUdpFn,
TlsConfig: tlsConfig,
QuicConfig: quicConfig,
CongestionController: config.CongestionController,
AuthenticationTimeout: time.Duration(config.AuthenticationTimeout) * time.Millisecond,
MaxUdpRelayPacketSize: config.MaxUdpRelayPacketSize,
CWND: config.CWND,
}
if len(config.Token) > 0 {
tokens := make([][32]byte, len(config.Token))
for i, token := range config.Token {
tokens[i] = tuic.GenTKN(token)
}

optionV4 = &tuic.ServerOptionV4{
HandleTcpFn: handleTcpFn,
HandleUdpFn: handleUdpFn,
TlsConfig: tlsConfig,
QuicConfig: quicConfig,
Tokens: tokens,
CongestionController: config.CongestionController,
AuthenticationTimeout: time.Duration(config.AuthenticationTimeout) * time.Millisecond,
MaxUdpRelayPacketSize: config.MaxUdpRelayPacketSize,
CWND: config.CWND,
}
} else {
option.Tokens = tokens
}
if len(config.Users) > 0 {
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,
CWND: config.CWND,
}
option.Users = users
}

sl := &Listener{false, config, nil, nil}
Expand All @@ -157,12 +144,8 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet

sl.udpListeners = append(sl.udpListeners, ul)

var server tuic.Server
if optionV4 != nil {
server, err = tuic.NewServerV4(optionV4, ul)
} else {
server, err = tuic.NewServerV5(optionV5, ul)
}
var server *tuic.Server
server, err = tuic.NewServer(option, ul)
if err != nil {
return nil, err
}
Expand Down
11 changes: 8 additions & 3 deletions transport/tuic/common/type.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package common

import (
"bufio"
"context"
"errors"
"net"
"time"

N "github.com/Dreamacro/clash/common/net"
C "github.com/Dreamacro/clash/constant"

"github.com/metacubex/quic-go"
Expand All @@ -28,9 +30,12 @@ type Client interface {
Close()
}

type Server interface {
Serve() error
Close() error
type ServerHandler interface {
AuthOk() bool
HandleTimeout()
HandleStream(conn *N.BufferedConn) (err error)
HandleMessage(message []byte) (err error)
HandleUniStream(reader *bufio.Reader) (err error)
}

type UdpRelayMode uint8
Expand Down
234 changes: 234 additions & 0 deletions transport/tuic/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
package tuic

import (
"bufio"
"context"
"crypto/tls"
"net"
"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"
v4 "github.com/Dreamacro/clash/transport/tuic/v4"
v5 "github.com/Dreamacro/clash/transport/tuic/v5"

"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
Tokens [][32]byte // V4 special
Users map[[16]byte]string // V5 special
CongestionController string
AuthenticationTimeout time.Duration
MaxUdpRelayPacketSize int
CWND int
}

type Server struct {
*ServerOption
optionV4 *v4.ServerOption
optionV5 *v5.ServerOption
listener *quic.EarlyListener
}

func (s *Server) Serve() error {
for {
conn, err := s.listener.Accept(context.Background())
if err != nil {
return err
}
common.SetCongestionController(conn, s.CongestionController, s.CWND)
h := &serverHandler{
Server: s,
quicConn: conn,
uuid: utils.NewUUIDV4(),
}
if h.optionV4 != nil {
h.v4Handler = v4.NewServerHandler(h.optionV4, conn, h.uuid)
}
if h.optionV5 != nil {
h.v5Handler = v5.NewServerHandler(h.optionV5, conn, h.uuid)
}
go h.handle()
}
}

func (s *Server) Close() error {
return s.listener.Close()
}

type serverHandler struct {
*Server
quicConn quic.EarlyConnection
uuid uuid.UUID

v4Handler common.ServerHandler
v5Handler common.ServerHandler
}

func (s *serverHandler) handle() {
go func() {
_ = s.handleUniStream()
}()
go func() {
_ = s.handleStream()
}()
go func() {
_ = s.handleMessage()
}()

<-s.quicConn.HandshakeComplete()
time.AfterFunc(s.AuthenticationTimeout, func() {
if s.v4Handler != nil {
if s.v4Handler.AuthOk() {
return
}
}

if s.v5Handler != nil {
if s.v5Handler.AuthOk() {
return
}
}

if s.v4Handler != nil {
s.v4Handler.HandleTimeout()
}

if s.v5Handler != nil {
s.v5Handler.HandleTimeout()
}
})
}

func (s *serverHandler) handleMessage() (err error) {
for {
var message []byte
message, err = s.quicConn.ReceiveMessage()
if err != nil {
return err
}
go func() (err error) {
if len(message) > 0 {
switch message[0] {
case v4.VER:
if s.v4Handler != nil {
return s.v4Handler.HandleMessage(message)
}
case v5.VER:
if s.v5Handler != nil {
return s.v5Handler.HandleMessage(message)
}
}
}
return
}()
}
}

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)

verBytes, err := conn.Peek(1)
if err != nil {
_ = conn.Close()
return err
}

switch verBytes[0] {
case v4.VER:
if s.v4Handler != nil {
return s.v4Handler.HandleStream(conn)
}
case v5.VER:
if s.v5Handler != nil {
return s.v5Handler.HandleStream(conn)
}
}
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)
verBytes, err := reader.Peek(1)
if err != nil {
return err
}

switch verBytes[0] {
case v4.VER:
if s.v4Handler != nil {
return s.v4Handler.HandleUniStream(reader)
}
case v5.VER:
if s.v5Handler != nil {
return s.v5Handler.HandleUniStream(reader)
}
}
return
}()
}
}

func NewServer(option *ServerOption, pc net.PacketConn) (*Server, error) {
listener, err := quic.ListenEarly(pc, option.TlsConfig, option.QuicConfig)
if err != nil {
return nil, err
}
server := &Server{
ServerOption: option,
listener: listener,
}
if len(option.Tokens) > 0 {
server.optionV4 = &v4.ServerOption{
HandleTcpFn: option.HandleTcpFn,
HandleUdpFn: option.HandleUdpFn,
Tokens: option.Tokens,
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
}
}
if len(option.Users) > 0 {
server.optionV5 = &v5.ServerOption{
HandleTcpFn: option.HandleTcpFn,
HandleUdpFn: option.HandleUdpFn,
Users: option.Users,
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
}
}
return server, nil
}
15 changes: 0 additions & 15 deletions transport/tuic/tuic.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
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"
Expand All @@ -26,19 +24,6 @@ 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

Expand Down
Loading

0 comments on commit 6d824c8

Please sign in to comment.