chore: embed hysteria, clean irrelevant codes, code from https://github.com/HyNetwork/hysteria

This commit is contained in:
Skyxim
2022-07-03 18:22:56 +08:00
parent 8ce9737f3d
commit 3cc1870aee
28 changed files with 3251 additions and 375 deletions

View File

@ -0,0 +1,422 @@
package core
import (
"bytes"
"context"
"crypto/tls"
"errors"
"fmt"
"github.com/Dreamacro/clash/transport/hysteria/obfs"
"github.com/Dreamacro/clash/transport/hysteria/pmtud_fix"
"github.com/Dreamacro/clash/transport/hysteria/transport"
"github.com/Dreamacro/clash/transport/hysteria/utils"
"github.com/lucas-clemente/quic-go"
"github.com/lucas-clemente/quic-go/congestion"
"github.com/lunixbochs/struc"
"math/rand"
"net"
"strconv"
"sync"
"time"
)
var (
ErrClosed = errors.New("closed")
)
type CongestionFactory func(refBPS uint64) congestion.CongestionControl
type Client struct {
transport *transport.ClientTransport
serverAddr string
protocol string
sendBPS, recvBPS uint64
auth []byte
congestionFactory CongestionFactory
obfuscator obfs.Obfuscator
tlsConfig *tls.Config
quicConfig *quic.Config
quicSession quic.Connection
reconnectMutex sync.Mutex
closed bool
udpSessionMutex sync.RWMutex
udpSessionMap map[uint32]chan *udpMessage
udpDefragger defragger
}
func NewClient(serverAddr string, protocol string, auth []byte, tlsConfig *tls.Config, quicConfig *quic.Config,
transport *transport.ClientTransport, sendBPS uint64, recvBPS uint64, congestionFactory CongestionFactory,
obfuscator obfs.Obfuscator) (*Client, error) {
quicConfig.DisablePathMTUDiscovery = quicConfig.DisablePathMTUDiscovery || pmtud_fix.DisablePathMTUDiscovery
c := &Client{
transport: transport,
serverAddr: serverAddr,
protocol: protocol,
sendBPS: sendBPS,
recvBPS: recvBPS,
auth: auth,
congestionFactory: congestionFactory,
obfuscator: obfuscator,
tlsConfig: tlsConfig,
quicConfig: quicConfig,
}
return c, nil
}
func (c *Client) connectToServer(dialer transport.PacketDialer) error {
qs, err := c.transport.QUICDial(c.protocol, c.serverAddr, c.tlsConfig, c.quicConfig, c.obfuscator, dialer)
if err != nil {
return err
}
// Control stream
ctx, ctxCancel := context.WithTimeout(context.Background(), protocolTimeout)
stream, err := qs.OpenStreamSync(ctx)
ctxCancel()
if err != nil {
_ = qs.CloseWithError(closeErrorCodeProtocol, "protocol error")
return err
}
ok, msg, err := c.handleControlStream(qs, stream)
if err != nil {
_ = qs.CloseWithError(closeErrorCodeProtocol, "protocol error")
return err
}
if !ok {
_ = qs.CloseWithError(closeErrorCodeAuth, "auth error")
return fmt.Errorf("auth error: %s", msg)
}
// All good
c.udpSessionMap = make(map[uint32]chan *udpMessage)
go c.handleMessage(qs)
c.quicSession = qs
return nil
}
func (c *Client) handleControlStream(qs quic.Connection, stream quic.Stream) (bool, string, error) {
// Send protocol version
_, err := stream.Write([]byte{protocolVersion})
if err != nil {
return false, "", err
}
// Send client hello
err = struc.Pack(stream, &clientHello{
Rate: transmissionRate{
SendBPS: c.sendBPS,
RecvBPS: c.recvBPS,
},
Auth: c.auth,
})
if err != nil {
return false, "", err
}
// Receive server hello
var sh serverHello
err = struc.Unpack(stream, &sh)
if err != nil {
return false, "", err
}
// Set the congestion accordingly
if sh.OK && c.congestionFactory != nil {
qs.SetCongestionControl(c.congestionFactory(sh.Rate.RecvBPS))
}
return sh.OK, sh.Message, nil
}
func (c *Client) handleMessage(qs quic.Connection) {
for {
msg, err := qs.ReceiveMessage()
if err != nil {
break
}
var udpMsg udpMessage
err = struc.Unpack(bytes.NewBuffer(msg), &udpMsg)
if err != nil {
continue
}
dfMsg := c.udpDefragger.Feed(udpMsg)
if dfMsg == nil {
continue
}
c.udpSessionMutex.RLock()
ch, ok := c.udpSessionMap[dfMsg.SessionID]
if ok {
select {
case ch <- dfMsg:
// OK
default:
// Silently drop the message when the channel is full
}
}
c.udpSessionMutex.RUnlock()
}
}
func (c *Client) openStreamWithReconnect(dialer transport.PacketDialer) (quic.Connection, quic.Stream, error) {
c.reconnectMutex.Lock()
defer c.reconnectMutex.Unlock()
if c.closed {
return nil, nil, ErrClosed
}
if c.quicSession == nil {
if err := c.connectToServer(dialer); err != nil {
// Still error, oops
return nil, nil, err
}
}
stream, err := c.quicSession.OpenStream()
if err == nil {
// All good
return c.quicSession, &wrappedQUICStream{stream}, nil
}
// Something is wrong
if nErr, ok := err.(net.Error); ok && nErr.Temporary() {
// Temporary error, just return
return nil, nil, err
}
// Permanent error, need to reconnect
if err := c.connectToServer(dialer); err != nil {
// Still error, oops
return nil, nil, err
}
// We are not going to try again even if it still fails the second time
stream, err = c.quicSession.OpenStream()
return c.quicSession, &wrappedQUICStream{stream}, err
}
func (c *Client) DialTCP(addr string, dialer transport.PacketDialer) (net.Conn, error) {
host, port, err := utils.SplitHostPort(addr)
if err != nil {
return nil, err
}
session, stream, err := c.openStreamWithReconnect(dialer)
if err != nil {
return nil, err
}
// Send request
err = struc.Pack(stream, &clientRequest{
UDP: false,
Host: host,
Port: port,
})
if err != nil {
_ = stream.Close()
return nil, err
}
// Read response
var sr serverResponse
err = struc.Unpack(stream, &sr)
if err != nil {
_ = stream.Close()
return nil, err
}
if !sr.OK {
_ = stream.Close()
return nil, fmt.Errorf("connection rejected: %s", sr.Message)
}
return &quicConn{
Orig: stream,
PseudoLocalAddr: session.LocalAddr(),
PseudoRemoteAddr: session.RemoteAddr(),
}, nil
}
func (c *Client) DialUDP(dialer transport.PacketDialer) (UDPConn, error) {
session, stream, err := c.openStreamWithReconnect(dialer)
if err != nil {
return nil, err
}
// Send request
err = struc.Pack(stream, &clientRequest{
UDP: true,
})
if err != nil {
_ = stream.Close()
return nil, err
}
// Read response
var sr serverResponse
err = struc.Unpack(stream, &sr)
if err != nil {
_ = stream.Close()
return nil, err
}
if !sr.OK {
_ = stream.Close()
return nil, fmt.Errorf("connection rejected: %s", sr.Message)
}
// Create a session in the map
c.udpSessionMutex.Lock()
nCh := make(chan *udpMessage, 1024)
// Store the current session map for CloseFunc below
// to ensures that we are adding and removing sessions on the same map,
// as reconnecting will reassign the map
sessionMap := c.udpSessionMap
sessionMap[sr.UDPSessionID] = nCh
c.udpSessionMutex.Unlock()
pktConn := &quicPktConn{
Session: session,
Stream: stream,
CloseFunc: func() {
c.udpSessionMutex.Lock()
if ch, ok := sessionMap[sr.UDPSessionID]; ok {
close(ch)
delete(sessionMap, sr.UDPSessionID)
}
c.udpSessionMutex.Unlock()
},
UDPSessionID: sr.UDPSessionID,
MsgCh: nCh,
}
go pktConn.Hold()
return pktConn, nil
}
func (c *Client) Close() error {
c.reconnectMutex.Lock()
defer c.reconnectMutex.Unlock()
err := c.quicSession.CloseWithError(closeErrorCodeGeneric, "")
c.closed = true
return err
}
type quicConn struct {
Orig quic.Stream
PseudoLocalAddr net.Addr
PseudoRemoteAddr net.Addr
}
func (w *quicConn) Read(b []byte) (n int, err error) {
return w.Orig.Read(b)
}
func (w *quicConn) Write(b []byte) (n int, err error) {
return w.Orig.Write(b)
}
func (w *quicConn) Close() error {
return w.Orig.Close()
}
func (w *quicConn) LocalAddr() net.Addr {
return w.PseudoLocalAddr
}
func (w *quicConn) RemoteAddr() net.Addr {
return w.PseudoRemoteAddr
}
func (w *quicConn) SetDeadline(t time.Time) error {
return w.Orig.SetDeadline(t)
}
func (w *quicConn) SetReadDeadline(t time.Time) error {
return w.Orig.SetReadDeadline(t)
}
func (w *quicConn) SetWriteDeadline(t time.Time) error {
return w.Orig.SetWriteDeadline(t)
}
type UDPConn interface {
ReadFrom() ([]byte, string, error)
WriteTo([]byte, string) error
Close() error
LocalAddr() net.Addr
SetDeadline(t time.Time) error
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
}
type quicPktConn struct {
Session quic.Connection
Stream quic.Stream
CloseFunc func()
UDPSessionID uint32
MsgCh <-chan *udpMessage
}
func (c *quicPktConn) Hold() {
// Hold the stream until it's closed
buf := make([]byte, 1024)
for {
_, err := c.Stream.Read(buf)
if err != nil {
break
}
}
_ = c.Close()
}
func (c *quicPktConn) ReadFrom() ([]byte, string, error) {
msg := <-c.MsgCh
if msg == nil {
// Closed
return nil, "", ErrClosed
}
return msg.Data, net.JoinHostPort(msg.Host, strconv.Itoa(int(msg.Port))), nil
}
func (c *quicPktConn) WriteTo(p []byte, addr string) error {
host, port, err := utils.SplitHostPort(addr)
if err != nil {
return err
}
msg := udpMessage{
SessionID: c.UDPSessionID,
Host: host,
Port: port,
FragCount: 1,
Data: p,
}
// try no frag first
var msgBuf bytes.Buffer
_ = struc.Pack(&msgBuf, &msg)
err = c.Session.SendMessage(msgBuf.Bytes())
if err != nil {
if errSize, ok := err.(quic.ErrMessageToLarge); ok {
// need to frag
msg.MsgID = uint16(rand.Intn(0xFFFF)) + 1 // msgID must be > 0 when fragCount > 1
fragMsgs := fragUDPMessage(msg, int(errSize))
for _, fragMsg := range fragMsgs {
msgBuf.Reset()
_ = struc.Pack(&msgBuf, &fragMsg)
err = c.Session.SendMessage(msgBuf.Bytes())
if err != nil {
return err
}
}
return nil
} else {
// some other error
return err
}
} else {
return nil
}
}
func (c *quicPktConn) Close() error {
c.CloseFunc()
return c.Stream.Close()
}
func (c *quicPktConn) LocalAddr() net.Addr {
return c.Session.LocalAddr()
}
func (c *quicPktConn) SetDeadline(t time.Time) error {
return c.Stream.SetDeadline(t)
}
func (c *quicPktConn) SetReadDeadline(t time.Time) error {
return c.Stream.SetReadDeadline(t)
}
func (c *quicPktConn) SetWriteDeadline(t time.Time) error {
return c.Stream.SetWriteDeadline(t)
}

View File

@ -0,0 +1,67 @@
package core
func fragUDPMessage(m udpMessage, maxSize int) []udpMessage {
if m.Size() <= maxSize {
return []udpMessage{m}
}
fullPayload := m.Data
maxPayloadSize := maxSize - m.HeaderSize()
off := 0
fragID := uint8(0)
fragCount := uint8((len(fullPayload) + maxPayloadSize - 1) / maxPayloadSize) // round up
var frags []udpMessage
for off < len(fullPayload) {
payloadSize := len(fullPayload) - off
if payloadSize > maxPayloadSize {
payloadSize = maxPayloadSize
}
frag := m
frag.FragID = fragID
frag.FragCount = fragCount
frag.DataLen = uint16(payloadSize)
frag.Data = fullPayload[off : off+payloadSize]
frags = append(frags, frag)
off += payloadSize
fragID++
}
return frags
}
type defragger struct {
msgID uint16
frags []*udpMessage
count uint8
}
func (d *defragger) Feed(m udpMessage) *udpMessage {
if m.FragCount <= 1 {
return &m
}
if m.FragID >= m.FragCount {
// wtf is this?
return nil
}
if m.MsgID != d.msgID {
// new message, clear previous state
d.msgID = m.MsgID
d.frags = make([]*udpMessage, m.FragCount)
d.count = 1
d.frags[m.FragID] = &m
} else if d.frags[m.FragID] == nil {
d.frags[m.FragID] = &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...)
}
m.DataLen = uint16(len(data))
m.Data = data
m.FragID = 0
m.FragCount = 1
return &m
}
}
return nil
}

View File

@ -0,0 +1,346 @@
package core
import (
"reflect"
"testing"
)
func Test_fragUDPMessage(t *testing.T) {
type args struct {
m udpMessage
maxSize int
}
tests := []struct {
name string
args args
want []udpMessage
}{
{
"no frag",
args{
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 123,
FragID: 0,
FragCount: 1,
DataLen: 5,
Data: []byte("hello"),
},
100,
},
[]udpMessage{
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 123,
FragID: 0,
FragCount: 1,
DataLen: 5,
Data: []byte("hello"),
},
},
},
{
"2 frags",
args{
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 123,
FragID: 0,
FragCount: 1,
DataLen: 5,
Data: []byte("hello"),
},
22,
},
[]udpMessage{
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 123,
FragID: 0,
FragCount: 2,
DataLen: 4,
Data: []byte("hell"),
},
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 123,
FragID: 1,
FragCount: 2,
DataLen: 1,
Data: []byte("o"),
},
},
},
{
"4 frags",
args{
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 123,
FragID: 0,
FragCount: 1,
DataLen: 20,
Data: []byte("wow wow wow lol lmao"),
},
23,
},
[]udpMessage{
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 123,
FragID: 0,
FragCount: 4,
DataLen: 5,
Data: []byte("wow w"),
},
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 123,
FragID: 1,
FragCount: 4,
DataLen: 5,
Data: []byte("ow wo"),
},
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 123,
FragID: 2,
FragCount: 4,
DataLen: 5,
Data: []byte("w lol"),
},
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 123,
FragID: 3,
FragCount: 4,
DataLen: 5,
Data: []byte(" lmao"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := fragUDPMessage(tt.args.m, tt.args.maxSize); !reflect.DeepEqual(got, tt.want) {
t.Errorf("fragUDPMessage() = %v, want %v", got, tt.want)
}
})
}
}
func Test_defragger_Feed(t *testing.T) {
d := &defragger{}
type args struct {
m udpMessage
}
tests := []struct {
name string
args args
want *udpMessage
}{
{
"no frag",
args{
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 123,
FragID: 0,
FragCount: 1,
DataLen: 5,
Data: []byte("hello"),
},
},
&udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 123,
FragID: 0,
FragCount: 1,
DataLen: 5,
Data: []byte("hello"),
},
},
{
"frag 1 - 1/3",
args{
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 666,
FragID: 0,
FragCount: 3,
DataLen: 5,
Data: []byte("hello"),
},
},
nil,
},
{
"frag 1 - 2/3",
args{
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 666,
FragID: 1,
FragCount: 3,
DataLen: 8,
Data: []byte(" shitty "),
},
},
nil,
},
{
"frag 1 - 3/3",
args{
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 666,
FragID: 2,
FragCount: 3,
DataLen: 7,
Data: []byte("world!!"),
},
},
&udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 666,
FragID: 0,
FragCount: 1,
DataLen: 20,
Data: []byte("hello shitty world!!"),
},
},
{
"frag 2 - 1/2",
args{
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 777,
FragID: 0,
FragCount: 2,
DataLen: 5,
Data: []byte("hello"),
},
},
nil,
},
{
"frag 3 - 2/2",
args{
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 778,
FragID: 1,
FragCount: 2,
DataLen: 5,
Data: []byte(" moto"),
},
},
nil,
},
{
"frag 2 - 2/2",
args{
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 777,
FragID: 1,
FragCount: 2,
DataLen: 5,
Data: []byte(" moto"),
},
},
nil,
},
{
"frag 2 - 1/2 re",
args{
udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 777,
FragID: 0,
FragCount: 2,
DataLen: 5,
Data: []byte("hello"),
},
},
&udpMessage{
SessionID: 123,
HostLen: 4,
Host: "test",
Port: 123,
MsgID: 777,
FragID: 0,
FragCount: 1,
DataLen: 10,
Data: []byte("hello moto"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := d.Feed(tt.args.m); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Feed() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -0,0 +1,76 @@
package core
import (
"time"
)
const (
protocolVersion = uint8(3)
protocolVersionV2 = uint8(2)
protocolTimeout = 10 * time.Second
closeErrorCodeGeneric = 0
closeErrorCodeProtocol = 1
closeErrorCodeAuth = 2
)
type transmissionRate struct {
SendBPS uint64
RecvBPS uint64
}
type clientHello struct {
Rate transmissionRate
AuthLen uint16 `struc:"sizeof=Auth"`
Auth []byte
}
type serverHello struct {
OK bool
Rate transmissionRate
MessageLen uint16 `struc:"sizeof=Message"`
Message string
}
type clientRequest struct {
UDP bool
HostLen uint16 `struc:"sizeof=Host"`
Host string
Port uint16
}
type serverResponse struct {
OK bool
UDPSessionID uint32
MessageLen uint16 `struc:"sizeof=Message"`
Message string
}
type udpMessage struct {
SessionID uint32
HostLen uint16 `struc:"sizeof=Host"`
Host string
Port uint16
MsgID uint16 // doesn't matter when not fragmented, but must not be 0 when fragmented
FragID uint8 // doesn't matter when not fragmented, starts at 0 when fragmented
FragCount uint8 // must be 1 when not fragmented
DataLen uint16 `struc:"sizeof=Data"`
Data []byte
}
func (m udpMessage) HeaderSize() int {
return 4 + 2 + len(m.Host) + 2 + 2 + 1 + 1 + 2
}
func (m udpMessage) Size() int {
return m.HeaderSize() + len(m.Data)
}
type udpMessageV2 struct {
SessionID uint32
HostLen uint16 `struc:"sizeof=Host"`
Host string
Port uint16
DataLen uint16 `struc:"sizeof=Data"`
Data []byte
}

View File

@ -0,0 +1,54 @@
package core
import (
"context"
"github.com/lucas-clemente/quic-go"
"time"
)
// Handle stream close properly
// Ref: https://github.com/libp2p/go-libp2p-quic-transport/blob/master/stream.go
type wrappedQUICStream struct {
Stream quic.Stream
}
func (s *wrappedQUICStream) StreamID() quic.StreamID {
return s.Stream.StreamID()
}
func (s *wrappedQUICStream) Read(p []byte) (n int, err error) {
return s.Stream.Read(p)
}
func (s *wrappedQUICStream) CancelRead(code quic.StreamErrorCode) {
s.Stream.CancelRead(code)
}
func (s *wrappedQUICStream) SetReadDeadline(t time.Time) error {
return s.Stream.SetReadDeadline(t)
}
func (s *wrappedQUICStream) Write(p []byte) (n int, err error) {
return s.Stream.Write(p)
}
func (s *wrappedQUICStream) Close() error {
s.Stream.CancelRead(0)
return s.Stream.Close()
}
func (s *wrappedQUICStream) CancelWrite(code quic.StreamErrorCode) {
s.Stream.CancelWrite(code)
}
func (s *wrappedQUICStream) Context() context.Context {
return s.Stream.Context()
}
func (s *wrappedQUICStream) SetWriteDeadline(t time.Time) error {
return s.Stream.SetWriteDeadline(t)
}
func (s *wrappedQUICStream) SetDeadline(t time.Time) error {
return s.Stream.SetDeadline(t)
}