sing-quic/hysteria/protocol.go
2024-08-27 11:35:17 +08:00

254 lines
6.7 KiB
Go

package hysteria
import (
"encoding/binary"
"io"
"time"
"github.com/sagernet/quic-go"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
)
const (
MbpsToBps = 125000
MinSpeedBPS = 16384
DefaultALPN = "hysteria"
DefaultStreamReceiveWindow = 8388608 // 8MB
DefaultConnReceiveWindow = DefaultStreamReceiveWindow * 5 / 2 // 20MB
DefaultMaxIdleTimeout = 30 * time.Second
DefaultKeepAlivePeriod = 10 * time.Second
)
const (
ProtocolVersion = 3
ProtocolTimeout = 10 * time.Second
ErrorCodeGeneric = 0
ErrorCodeProtocolError = 1
ErrorCodeAuthError = 2
)
type ClientHello struct {
SendBPS uint64
RecvBPS uint64
Auth string
}
func WriteClientHello(stream io.Writer, hello ClientHello) error {
var requestLen int
requestLen += 1 // version
requestLen += 8 // sendBPS
requestLen += 8 // recvBPS
requestLen += 2 // auth len
requestLen += len(hello.Auth)
request := buf.NewSize(requestLen)
defer request.Release()
common.Must(
request.WriteByte(ProtocolVersion),
binary.Write(request, binary.BigEndian, hello.SendBPS),
binary.Write(request, binary.BigEndian, hello.RecvBPS),
binary.Write(request, binary.BigEndian, uint16(len(hello.Auth))),
common.Error(request.WriteString(hello.Auth)),
)
return common.Error(stream.Write(request.Bytes()))
}
func ReadClientHello(reader io.Reader) (*ClientHello, error) {
var version uint8
err := binary.Read(reader, binary.BigEndian, &version)
if err != nil {
return nil, err
}
if version != ProtocolVersion {
return nil, E.New("unsupported client version: ", version)
}
var clientHello ClientHello
err = binary.Read(reader, binary.BigEndian, &clientHello.SendBPS)
if err != nil {
return nil, err
}
err = binary.Read(reader, binary.BigEndian, &clientHello.RecvBPS)
if err != nil {
return nil, err
}
if clientHello.SendBPS == 0 || clientHello.RecvBPS == 0 {
return nil, E.New("invalid rate from client")
}
var authLen uint16
err = binary.Read(reader, binary.BigEndian, &authLen)
if err != nil {
return nil, err
}
authBytes := make([]byte, authLen)
_, err = io.ReadFull(reader, authBytes)
if err != nil {
return nil, err
}
clientHello.Auth = string(authBytes)
return &clientHello, nil
}
type ServerHello struct {
OK bool
SendBPS uint64
RecvBPS uint64
Message string
}
func ReadServerHello(stream io.Reader) (*ServerHello, error) {
var responseLen int
responseLen += 1 // ok
responseLen += 8 // sendBPS
responseLen += 8 // recvBPS
responseLen += 2 // message len
response := buf.NewSize(responseLen)
defer response.Release()
_, err := response.ReadFullFrom(stream, responseLen)
if err != nil {
return nil, err
}
var serverHello ServerHello
serverHello.OK = response.Byte(0) == 1
serverHello.SendBPS = binary.BigEndian.Uint64(response.Range(1, 9))
serverHello.RecvBPS = binary.BigEndian.Uint64(response.Range(9, 17))
messageLen := binary.BigEndian.Uint16(response.Range(17, 19))
if messageLen == 0 {
return &serverHello, nil
}
message := make([]byte, messageLen)
_, err = io.ReadFull(stream, message)
if err != nil {
return nil, err
}
serverHello.Message = string(message)
return &serverHello, nil
}
func WriteServerHello(stream io.Writer, hello ServerHello) error {
var responseLen int
responseLen += 1 // ok
responseLen += 8 // sendBPS
responseLen += 8 // recvBPS
responseLen += 2 // message len
responseLen += len(hello.Message)
response := buf.NewSize(responseLen)
defer response.Release()
if hello.OK {
common.Must(response.WriteByte(1))
} else {
common.Must(response.WriteByte(0))
}
common.Must(
binary.Write(response, binary.BigEndian, hello.SendBPS),
binary.Write(response, binary.BigEndian, hello.RecvBPS),
binary.Write(response, binary.BigEndian, uint16(len(hello.Message))),
common.Error(response.WriteString(hello.Message)),
)
return common.Error(stream.Write(response.Bytes()))
}
type ClientRequest struct {
UDP bool
Host string
Port uint16
}
func ReadClientRequest(stream io.Reader) (*ClientRequest, error) {
var clientRequest ClientRequest
err := binary.Read(stream, binary.BigEndian, &clientRequest.UDP)
if err != nil {
return nil, err
}
var hostLen uint16
err = binary.Read(stream, binary.BigEndian, &hostLen)
if err != nil {
return nil, err
}
host := make([]byte, hostLen)
_, err = io.ReadFull(stream, host)
if err != nil {
return nil, err
}
clientRequest.Host = string(host)
err = binary.Read(stream, binary.BigEndian, &clientRequest.Port)
if err != nil {
return nil, err
}
return &clientRequest, nil
}
func WriteClientRequest(request ClientRequest, payload []byte) *buf.Buffer {
var requestLen int
requestLen += 1 // udp
requestLen += 2 // host len
requestLen += len(request.Host)
requestLen += 2 // port
buffer := buf.NewSize(requestLen + len(payload))
if request.UDP {
common.Must(buffer.WriteByte(1))
} else {
common.Must(buffer.WriteByte(0))
}
common.Must(
binary.Write(buffer, binary.BigEndian, uint16(len(request.Host))),
common.Error(buffer.WriteString(request.Host)),
binary.Write(buffer, binary.BigEndian, request.Port),
common.Error(buffer.Write(payload)),
)
return buffer
}
type ServerResponse struct {
OK bool
UDPSessionID uint32
Message string
}
func ReadServerResponse(stream io.Reader) (*ServerResponse, error) {
var responseLen int
responseLen += 1 // ok
responseLen += 4 // udp session id
responseLen += 2 // message len
response := buf.NewSize(responseLen)
defer response.Release()
_, err := response.ReadFullFrom(stream, responseLen)
if err != nil {
return nil, err
}
var serverResponse ServerResponse
serverResponse.OK = response.Byte(0) == 1
serverResponse.UDPSessionID = binary.BigEndian.Uint32(response.Range(1, 5))
messageLen := binary.BigEndian.Uint16(response.Range(5, 7))
if messageLen == 0 {
return &serverResponse, nil
}
message := make([]byte, messageLen)
_, err = io.ReadFull(stream, message)
if err != nil {
return nil, err
}
serverResponse.Message = string(message)
return &serverResponse, nil
}
func WriteServerResponse(stream quic.Stream, response ServerResponse) error {
var responseLen int
responseLen += 1 // ok
responseLen += 4 // udp session id
responseLen += 2 // message len
responseLen += len(response.Message)
buffer := buf.NewSize(responseLen)
defer buffer.Release()
if response.OK {
common.Must(buffer.WriteByte(1))
} else {
common.Must(buffer.WriteByte(0))
}
common.Must(
binary.Write(buffer, binary.BigEndian, response.UDPSessionID),
binary.Write(buffer, binary.BigEndian, uint16(len(response.Message))),
common.Error(buffer.WriteString(response.Message)),
)
return common.Error(stream.Write(buffer.Bytes()))
}