chore: tuic server can handle V4 and V5 in same port
This commit is contained in:
@ -3,18 +3,13 @@ package v5
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/Dreamacro/clash/adapter/inbound"
|
||||
"github.com/Dreamacro/clash/common/atomic"
|
||||
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"
|
||||
@ -27,123 +22,72 @@ 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
|
||||
CWND 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{
|
||||
func NewServerHandler(option *ServerOption, quicConn quic.EarlyConnection, uuid uuid.UUID) common.ServerHandler {
|
||||
return &serverHandler{
|
||||
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, s.CWND)
|
||||
h := &serverHandler{
|
||||
Server: s,
|
||||
quicConn: conn,
|
||||
uuid: utils.NewUUIDV4(),
|
||||
authCh: make(chan struct{}),
|
||||
}
|
||||
go h.handle()
|
||||
quicConn: quicConn,
|
||||
uuid: uuid,
|
||||
authCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Close() error {
|
||||
return s.listener.Close()
|
||||
}
|
||||
|
||||
type serverHandler struct {
|
||||
*Server
|
||||
*ServerOption
|
||||
quicConn quic.EarlyConnection
|
||||
uuid uuid.UUID
|
||||
|
||||
authCh chan struct{}
|
||||
authOk bool
|
||||
authUUID string
|
||||
authOk atomic.Bool
|
||||
authUUID atomic.TypedValue[string]
|
||||
authOnce sync.Once
|
||||
|
||||
udpInputMap sync.Map
|
||||
}
|
||||
|
||||
func (s *serverHandler) handle() {
|
||||
go func() {
|
||||
_ = s.handleUniStream()
|
||||
}()
|
||||
go func() {
|
||||
_ = s.handleStream()
|
||||
}()
|
||||
go func() {
|
||||
_ = s.handleMessage()
|
||||
}()
|
||||
func (s *serverHandler) AuthOk() bool {
|
||||
return s.authOk.Load()
|
||||
}
|
||||
|
||||
<-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) HandleTimeout() {
|
||||
s.authOnce.Do(func() {
|
||||
_ = s.quicConn.CloseWithError(AuthenticationTimeout, "AuthenticationTimeout")
|
||||
s.authOk.Store(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) {
|
||||
reader := bytes.NewBuffer(message)
|
||||
commandHead, err := ReadCommandHead(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch commandHead.TYPE {
|
||||
case PacketType:
|
||||
var packet Packet
|
||||
packet, err = ReadPacketWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.parsePacket(packet, common.NATIVE)
|
||||
case HeartbeatType:
|
||||
var heartbeat Heartbeat
|
||||
heartbeat, err = ReadHeartbeatWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
heartbeat.BytesLen()
|
||||
}
|
||||
return
|
||||
}()
|
||||
func (s *serverHandler) HandleMessage(message []byte) (err error) {
|
||||
reader := bytes.NewBuffer(message)
|
||||
commandHead, err := ReadCommandHead(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch commandHead.TYPE {
|
||||
case PacketType:
|
||||
var packet Packet
|
||||
packet, err = ReadPacketWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return s.parsePacket(packet, common.NATIVE)
|
||||
case HeartbeatType:
|
||||
var heartbeat Heartbeat
|
||||
heartbeat, err = ReadHeartbeatWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
heartbeat.BytesLen()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *serverHandler) parsePacket(packet Packet, udpRelayMode common.UdpRelayMode) (err error) {
|
||||
<-s.authCh
|
||||
if !s.authOk {
|
||||
if !s.authOk.Load() {
|
||||
return
|
||||
}
|
||||
var assocId uint16
|
||||
@ -175,108 +119,79 @@ func (s *serverHandler) parsePacket(packet Packet, udpRelayMode common.UdpRelayM
|
||||
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))
|
||||
}, inbound.WithInUser(s.authUUID.Load()))
|
||||
}
|
||||
|
||||
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) HandleStream(conn *N.BufferedConn) (err error) {
|
||||
connect, err := ReadConnect(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
<-s.authCh
|
||||
if !s.authOk.Load() {
|
||||
return conn.Close()
|
||||
}
|
||||
|
||||
err = s.HandleTcpFn(conn, connect.ADDR.SocksAddr(), inbound.WithInUser(s.authUUID.Load()))
|
||||
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())
|
||||
func (s *serverHandler) HandleUniStream(reader *bufio.Reader) (err error) {
|
||||
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 err
|
||||
return
|
||||
}
|
||||
go func() (err error) {
|
||||
defer func() {
|
||||
stream.CancelRead(0)
|
||||
}()
|
||||
reader := bufio.NewReader(stream)
|
||||
commandHead, err := ReadCommandHead(reader)
|
||||
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
|
||||
}
|
||||
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, common.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)
|
||||
}
|
||||
if token == authenticate.TOKEN {
|
||||
authOk = true
|
||||
authUUID = authenticate.UUID
|
||||
}
|
||||
}
|
||||
s.authOnce.Do(func() {
|
||||
if !authOk {
|
||||
_ = s.quicConn.CloseWithError(AuthenticationFailed, "AuthenticationFailed")
|
||||
}
|
||||
s.authOk.Store(authOk)
|
||||
s.authUUID.Store(authUUID.String())
|
||||
close(s.authCh)
|
||||
})
|
||||
case PacketType:
|
||||
var packet Packet
|
||||
packet, err = ReadPacketWithHead(commandHead, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}()
|
||||
}
|
||||
return s.parsePacket(packet, common.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)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type serverUDPInput struct {
|
||||
|
Reference in New Issue
Block a user