Update BBR and Hysteria congestion control & Migrate legacy Hysteria protocol to library

This commit is contained in:
世界 2023-10-21 12:00:00 +08:00
parent 3b161ab30c
commit 31c294d998
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
17 changed files with 214 additions and 1402 deletions

6
go.mod
View file

@ -24,12 +24,12 @@ require (
github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a
github.com/sagernet/gomobile v0.0.0-20230915142329-c6740b6d2950
github.com/sagernet/gvisor v0.0.0-20230930141345-5fef6f2e17ab
github.com/sagernet/quic-go v0.0.0-20230919101909-0cc6c5dcecee
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028
github.com/sagernet/sing-dns v0.1.10
github.com/sagernet/sing-mux v0.1.3
github.com/sagernet/sing-quic v0.1.2
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6
github.com/sagernet/sing-shadowsocks v0.2.5
github.com/sagernet/sing-shadowsocks2 v0.1.4
github.com/sagernet/sing-shadowtls v0.1.4
@ -45,7 +45,6 @@ require (
go.uber.org/zap v1.26.0
go4.org/netipx v0.0.0-20230824141953-6213f710f925
golang.org/x/crypto v0.14.0
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
golang.org/x/net v0.17.0
golang.org/x/sys v0.13.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
@ -85,6 +84,7 @@ require (
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/mod v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect

8
go.sum
View file

@ -104,8 +104,8 @@ github.com/sagernet/gvisor v0.0.0-20230930141345-5fef6f2e17ab h1:u+xQoi/Yc6bNUvT
github.com/sagernet/gvisor v0.0.0-20230930141345-5fef6f2e17ab/go.mod h1:3akUhSHSVtLuJaYcW5JPepUraBOW06Ibz2HKwaK5rOk=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/quic-go v0.0.0-20230919101909-0cc6c5dcecee h1:ykuhl9jCS638N+jw1vC9AvT9bbQn6xRNScP2FWPV9dM=
github.com/sagernet/quic-go v0.0.0-20230919101909-0cc6c5dcecee/go.mod h1:0CfhWwZAeXGYM9+Nkkw1zcQtFHQC8KWjbpeDv7pu8iw=
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460 h1:dAe4OIJAtE0nHOzTHhAReQteh3+sa63rvXbuIpbeOTY=
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460/go.mod h1:uJGpmJCOcMQqMlHKc3P1Vz6uygmpz4bPeVIoOhdVQnM=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
@ -116,8 +116,8 @@ github.com/sagernet/sing-dns v0.1.10 h1:iIU7nRBlUYj+fF2TaktGIvRiTFFrHwSMedLQsvlT
github.com/sagernet/sing-dns v0.1.10/go.mod h1:vtUimtf7Nq9EdvD5WTpfCr69KL1M7bcgOVKiYBiAY/c=
github.com/sagernet/sing-mux v0.1.3 h1:fAf7PZa2A55mCeh0KKM02f1k2Y4vEmxuZZ/51ahkkLA=
github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0=
github.com/sagernet/sing-quic v0.1.2 h1:+u9CRf0KHi5HgXmJ3eB0CtqpWXtF0lx2QlWq+ZFZ+XY=
github.com/sagernet/sing-quic v0.1.2/go.mod h1:H1TX0/y9UUM43wyaLQ+qjg2+o901ibYtwWX2rWG+a3o=
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6 h1:w+TUbIZKZFSdf/AUa/y33kY9xaLeNGz/tBNcNhqpqfg=
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6/go.mod h1:1M7xP4802K9Kz6BQ7LlA7UeCapWvWlH1Htmk2bAqkWc=
github.com/sagernet/sing-shadowsocks v0.2.5 h1:qxIttos4xu6ii7MTVJYA8EFQR7Q3KG6xMqmLJIFtBaY=
github.com/sagernet/sing-shadowsocks v0.2.5/go.mod h1:MGWGkcU2xW2G2mfArT9/QqpVLOGU+dBaahZCtPHdt7A=
github.com/sagernet/sing-shadowsocks2 v0.1.4 h1:vht2M8t3m5DTgXR2j24KbYOygG5aOp+MUhpQnAux728=

View file

@ -4,104 +4,38 @@ package inbound
import (
"context"
"sync"
"net"
"github.com/sagernet/quic-go"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/humanize"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/hysteria"
"github.com/sagernet/sing-quic"
hyCC "github.com/sagernet/sing-quic/hysteria2/congestion"
"github.com/sagernet/sing-quic/hysteria"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"golang.org/x/exp/slices"
)
var _ adapter.Inbound = (*Hysteria)(nil)
type Hysteria struct {
myInboundAdapter
quicConfig *quic.Config
tlsConfig tls.ServerConfig
authKey []string
authUser []string
xplusKey []byte
sendBPS uint64
recvBPS uint64
listener qtls.Listener
udpAccess sync.RWMutex
udpSessionId uint32
udpSessions map[uint32]chan *hysteria.UDPMessage
udpDefragger hysteria.Defragger
service *hysteria.Service[int]
userNameList []string
}
func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (*Hysteria, error) {
options.UDPFragmentDefault = true
quicConfig := &quic.Config{
InitialStreamReceiveWindow: options.ReceiveWindowConn,
MaxStreamReceiveWindow: options.ReceiveWindowConn,
InitialConnectionReceiveWindow: options.ReceiveWindowClient,
MaxConnectionReceiveWindow: options.ReceiveWindowClient,
MaxIncomingStreams: int64(options.MaxConnClient),
KeepAlivePeriod: hysteria.KeepAlivePeriod,
DisablePathMTUDiscovery: options.DisableMTUDiscovery || !(C.IsLinux || C.IsWindows),
EnableDatagrams: true,
if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired
}
if options.ReceiveWindowConn == 0 {
quicConfig.InitialStreamReceiveWindow = hysteria.DefaultStreamReceiveWindow
quicConfig.MaxStreamReceiveWindow = hysteria.DefaultStreamReceiveWindow
}
if options.ReceiveWindowClient == 0 {
quicConfig.InitialConnectionReceiveWindow = hysteria.DefaultConnectionReceiveWindow
quicConfig.MaxConnectionReceiveWindow = hysteria.DefaultConnectionReceiveWindow
}
if quicConfig.MaxIncomingStreams == 0 {
quicConfig.MaxIncomingStreams = hysteria.DefaultMaxIncomingStreams
}
authKey := common.Map(options.Users, func(it option.HysteriaUser) string {
if len(it.Auth) > 0 {
return string(it.Auth)
} else {
return it.AuthString
}
})
authUser := common.Map(options.Users, func(it option.HysteriaUser) string {
return it.Name
})
var xplus []byte
if options.Obfs != "" {
xplus = []byte(options.Obfs)
}
var up, down uint64
if len(options.Up) > 0 {
up = hysteria.StringToBps(options.Up)
if up == 0 {
return nil, E.New("invalid up speed format: ", options.Up)
}
} else {
up = uint64(options.UpMbps) * hysteria.MbpsToBps
}
if len(options.Down) > 0 {
down = hysteria.StringToBps(options.Down)
if down == 0 {
return nil, E.New("invalid down speed format: ", options.Down)
}
} else {
down = uint64(options.DownMbps) * hysteria.MbpsToBps
}
if up < hysteria.MinSpeedBPS {
return nil, E.New("invalid up speed")
}
if down < hysteria.MinSpeedBPS {
return nil, E.New("invalid down speed")
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}
inbound := &Hysteria{
myInboundAdapter: myInboundAdapter{
@ -113,224 +47,108 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
tag: tag,
listenOptions: options.ListenOptions,
},
quicConfig: quicConfig,
authKey: authKey,
authUser: authUser,
xplusKey: xplus,
sendBPS: up,
recvBPS: down,
udpSessions: make(map[uint32]chan *hysteria.UDPMessage),
tlsConfig: tlsConfig,
}
if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired
var sendBps, receiveBps uint64
if len(options.Up) > 0 {
sendBps, err = humanize.ParseBytes(options.Up)
if err != nil {
return nil, E.Cause(err, "invalid up speed format: ", options.Up)
}
if len(options.TLS.ALPN) == 0 {
options.TLS.ALPN = []string{hysteria.DefaultALPN}
} else {
sendBps = uint64(options.UpMbps) * hysteria.MbpsToBps
}
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
if len(options.Down) > 0 {
receiveBps, err = humanize.ParseBytes(options.Down)
if receiveBps == 0 {
return nil, E.New("invalid down speed format: ", options.Down)
}
} else {
receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps
}
service, err := hysteria.NewService[int](hysteria.ServiceOptions{
Context: ctx,
Logger: logger,
SendBPS: sendBps,
ReceiveBPS: receiveBps,
XPlusPassword: options.Obfs,
TLSConfig: tlsConfig,
Handler: adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, nil),
// Legacy options
ConnReceiveWindow: options.ReceiveWindowConn,
StreamReceiveWindow: options.ReceiveWindowClient,
MaxIncomingStreams: int64(options.MaxConnClient),
DisableMTUDiscovery: options.DisableMTUDiscovery,
})
if err != nil {
return nil, err
}
inbound.tlsConfig = tlsConfig
userList := make([]int, 0, len(options.Users))
userNameList := make([]string, 0, len(options.Users))
userPasswordList := make([]string, 0, len(options.Users))
for index, user := range options.Users {
userList = append(userList, index)
userNameList = append(userNameList, user.Name)
var password string
if user.AuthString != "" {
password = user.AuthString
} else {
password = string(user.Auth)
}
userPasswordList = append(userPasswordList, password)
}
service.UpdateUsers(userList, userPasswordList)
inbound.service = service
inbound.userNameList = userNameList
return inbound, nil
}
func (h *Hysteria) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
ctx = log.ContextWithNewID(ctx)
metadata = h.createMetadata(conn, metadata)
userID, _ := auth.UserFromContext[int](ctx)
if userName := h.userNameList[userID]; userName != "" {
metadata.User = userName
h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination)
} else {
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
}
return h.router.RouteConnection(ctx, conn, metadata)
}
func (h *Hysteria) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
ctx = log.ContextWithNewID(ctx)
metadata = h.createPacketMetadata(conn, metadata)
userID, _ := auth.UserFromContext[int](ctx)
if userName := h.userNameList[userID]; userName != "" {
metadata.User = userName
h.logger.InfoContext(ctx, "[", userName, "] inbound packet connection to ", metadata.Destination)
} else {
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
}
return h.router.RoutePacketConnection(ctx, conn, metadata)
}
func (h *Hysteria) Start() error {
if h.tlsConfig != nil {
err := h.tlsConfig.Start()
if err != nil {
return err
}
}
packetConn, err := h.myInboundAdapter.ListenUDP()
if err != nil {
return err
}
if len(h.xplusKey) > 0 {
packetConn = hysteria.NewXPlusPacketConn(packetConn, h.xplusKey)
packetConn = &hysteria.PacketConnWrapper{PacketConn: packetConn}
}
err = h.tlsConfig.Start()
if err != nil {
return err
}
listener, err := qtls.Listen(packetConn, h.tlsConfig, h.quicConfig)
if err != nil {
return err
}
h.listener = listener
h.logger.Info("udp server started at ", listener.Addr())
go h.acceptLoop()
return nil
}
func (h *Hysteria) acceptLoop() {
for {
ctx := log.ContextWithNewID(h.ctx)
conn, err := h.listener.Accept(ctx)
if err != nil {
return
}
go func() {
hErr := h.accept(ctx, conn)
if hErr != nil {
conn.CloseWithError(0, "")
NewError(h.logger, ctx, E.Cause(hErr, "process connection from ", conn.RemoteAddr()))
}
}()
}
}
func (h *Hysteria) accept(ctx context.Context, conn quic.Connection) error {
controlStream, err := conn.AcceptStream(ctx)
if err != nil {
return err
}
clientHello, err := hysteria.ReadClientHello(controlStream)
if err != nil {
return err
}
if len(h.authKey) > 0 {
userIndex := slices.Index(h.authKey, string(clientHello.Auth))
if userIndex == -1 {
err = hysteria.WriteServerHello(controlStream, hysteria.ServerHello{
Message: "wrong password",
})
return E.Errors(E.New("wrong password: ", string(clientHello.Auth)), err)
}
user := h.authUser[userIndex]
if user == "" {
user = F.ToString(userIndex)
} else {
ctx = auth.ContextWithUser(ctx, user)
}
h.logger.InfoContext(ctx, "[", user, "] inbound connection from ", conn.RemoteAddr())
} else {
h.logger.InfoContext(ctx, "inbound connection from ", conn.RemoteAddr())
}
h.logger.DebugContext(ctx, "peer send speed: ", clientHello.SendBPS/1024/1024, " MBps, peer recv speed: ", clientHello.RecvBPS/1024/1024, " MBps")
if clientHello.SendBPS == 0 || clientHello.RecvBPS == 0 {
return E.New("invalid rate from client")
}
serverSendBPS, serverRecvBPS := clientHello.RecvBPS, clientHello.SendBPS
if h.sendBPS > 0 && serverSendBPS > h.sendBPS {
serverSendBPS = h.sendBPS
}
if h.recvBPS > 0 && serverRecvBPS > h.recvBPS {
serverRecvBPS = h.recvBPS
}
err = hysteria.WriteServerHello(controlStream, hysteria.ServerHello{
OK: true,
SendBPS: serverSendBPS,
RecvBPS: serverRecvBPS,
})
if err != nil {
return err
}
conn.SetCongestionControl(hyCC.NewBrutalSender(serverSendBPS))
go h.udpRecvLoop(conn)
for {
var stream quic.Stream
stream, err = conn.AcceptStream(ctx)
if err != nil {
return err
}
go func() {
hErr := h.acceptStream(ctx, conn /*&hysteria.StreamWrapper{Stream: stream}*/, stream)
if hErr != nil {
stream.Close()
NewError(h.logger, ctx, E.Cause(hErr, "process stream from ", conn.RemoteAddr()))
}
}()
}
}
func (h *Hysteria) udpRecvLoop(conn quic.Connection) {
for {
packet, err := conn.ReceiveMessage(h.ctx)
if err != nil {
return
}
message, err := hysteria.ParseUDPMessage(packet)
if err != nil {
h.logger.Error("parse udp message: ", err)
continue
}
dfMsg := h.udpDefragger.Feed(message)
if dfMsg == nil {
continue
}
h.udpAccess.RLock()
ch, ok := h.udpSessions[dfMsg.SessionID]
if ok {
select {
case ch <- dfMsg:
// OK
default:
// Silently drop the message when the channel is full
}
}
h.udpAccess.RUnlock()
}
}
func (h *Hysteria) acceptStream(ctx context.Context, conn quic.Connection, stream quic.Stream) error {
request, err := hysteria.ReadClientRequest(stream)
if err != nil {
return err
}
var metadata adapter.InboundContext
metadata.Inbound = h.tag
metadata.InboundType = C.TypeHysteria
metadata.InboundOptions = h.listenOptions.InboundOptions
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
metadata.Destination = M.ParseSocksaddrHostPort(request.Host, request.Port).Unwrap()
metadata.User, _ = auth.UserFromContext[string](ctx)
if !request.UDP {
err = hysteria.WriteServerResponse(stream, hysteria.ServerResponse{
OK: true,
})
if err != nil {
return err
}
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
return h.router.RouteConnection(ctx, hysteria.NewConn(stream, metadata.Destination, false), metadata)
} else {
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
var id uint32
h.udpAccess.Lock()
id = h.udpSessionId
nCh := make(chan *hysteria.UDPMessage, 1024)
h.udpSessions[id] = nCh
h.udpSessionId += 1
h.udpAccess.Unlock()
err = hysteria.WriteServerResponse(stream, hysteria.ServerResponse{
OK: true,
UDPSessionID: id,
})
if err != nil {
return err
}
packetConn := hysteria.NewPacketConn(conn, stream, id, metadata.Destination, nCh, common.Closer(func() error {
h.udpAccess.Lock()
if ch, ok := h.udpSessions[id]; ok {
close(ch)
delete(h.udpSessions, id)
}
h.udpAccess.Unlock()
return nil
}))
go packetConn.Hold()
return h.router.RoutePacketConnection(ctx, packetConn, metadata)
}
return h.service.Start(packetConn)
}
func (h *Hysteria) Close() error {
h.udpAccess.Lock()
for _, session := range h.udpSessions {
close(session)
}
h.udpSessions = make(map[uint32]chan *hysteria.UDPMessage)
h.udpAccess.Unlock()
return common.Close(
&h.myInboundAdapter,
h.listener,
h.tlsConfig,
common.PtrOrNil(h.service),
)
}

View file

@ -14,7 +14,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/hysteria"
"github.com/sagernet/sing-quic/hysteria"
"github.com/sagernet/sing-quic/hysteria2"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
@ -32,6 +32,7 @@ type Hysteria2 struct {
}
func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (*Hysteria2, error) {
options.UDPFragmentDefault = true
if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired
}

View file

@ -5,18 +5,16 @@ package outbound
import (
"context"
"net"
"sync"
"os"
"github.com/sagernet/quic-go"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/humanize"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/hysteria"
"github.com/sagernet/sing-quic"
hyCC "github.com/sagernet/sing-quic/hysteria2/congestion"
"github.com/sagernet/sing-quic/hysteria"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
@ -25,27 +23,13 @@ import (
)
var (
_ adapter.Outbound = (*Hysteria)(nil)
_ adapter.InterfaceUpdateListener = (*Hysteria)(nil)
_ adapter.Outbound = (*TUIC)(nil)
_ adapter.InterfaceUpdateListener = (*TUIC)(nil)
)
type Hysteria struct {
myOutboundAdapter
ctx context.Context
dialer N.Dialer
serverAddr M.Socksaddr
tlsConfig tls.Config
quicConfig *quic.Config
authKey []byte
xplusKey []byte
sendBPS uint64
recvBPS uint64
connAccess sync.Mutex
conn quic.Connection
rawConn net.Conn
udpAccess sync.RWMutex
udpSessions map[uint32]chan *hysteria.UDPMessage
udpDefragger hysteria.Defragger
client *hysteria.Client
}
func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (*Hysteria, error) {
@ -57,252 +41,77 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
if err != nil {
return nil, err
}
if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{hysteria.DefaultALPN})
outboundDialer, err := dialer.New(router, options.DialerOptions)
if err != nil {
return nil, err
}
quicConfig := &quic.Config{
InitialStreamReceiveWindow: options.ReceiveWindowConn,
MaxStreamReceiveWindow: options.ReceiveWindowConn,
InitialConnectionReceiveWindow: options.ReceiveWindow,
MaxConnectionReceiveWindow: options.ReceiveWindow,
KeepAlivePeriod: hysteria.KeepAlivePeriod,
DisablePathMTUDiscovery: options.DisableMTUDiscovery,
EnableDatagrams: true,
}
if options.ReceiveWindowConn == 0 {
quicConfig.InitialStreamReceiveWindow = hysteria.DefaultStreamReceiveWindow
quicConfig.MaxStreamReceiveWindow = hysteria.DefaultStreamReceiveWindow
}
if options.ReceiveWindow == 0 {
quicConfig.InitialConnectionReceiveWindow = hysteria.DefaultConnectionReceiveWindow
quicConfig.MaxConnectionReceiveWindow = hysteria.DefaultConnectionReceiveWindow
}
if quicConfig.MaxIncomingStreams == 0 {
quicConfig.MaxIncomingStreams = hysteria.DefaultMaxIncomingStreams
}
var auth []byte
if len(options.Auth) > 0 {
auth = options.Auth
networkList := options.Network.Build()
var password string
if options.AuthString != "" {
password = options.AuthString
} else {
auth = []byte(options.AuthString)
password = string(options.Auth)
}
var xplus []byte
if options.Obfs != "" {
xplus = []byte(options.Obfs)
}
var up, down uint64
var sendBps, receiveBps uint64
if len(options.Up) > 0 {
up = hysteria.StringToBps(options.Up)
if up == 0 {
return nil, E.New("invalid up speed format: ", options.Up)
sendBps, err = humanize.ParseBytes(options.Up)
if err != nil {
return nil, E.Cause(err, "invalid up speed format: ", options.Up)
}
} else {
up = uint64(options.UpMbps) * hysteria.MbpsToBps
sendBps = uint64(options.UpMbps) * hysteria.MbpsToBps
}
if len(options.Down) > 0 {
down = hysteria.StringToBps(options.Down)
if down == 0 {
receiveBps, err = humanize.ParseBytes(options.Down)
if receiveBps == 0 {
return nil, E.New("invalid down speed format: ", options.Down)
}
} else {
down = uint64(options.DownMbps) * hysteria.MbpsToBps
receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps
}
if up < hysteria.MinSpeedBPS {
return nil, E.New("invalid up speed")
}
if down < hysteria.MinSpeedBPS {
return nil, E.New("invalid down speed")
}
outboundDialer, err := dialer.New(router, options.DialerOptions)
client, err := hysteria.NewClient(hysteria.ClientOptions{
Context: ctx,
Dialer: outboundDialer,
Logger: logger,
ServerAddress: options.ServerOptions.Build(),
SendBPS: sendBps,
ReceiveBPS: receiveBps,
XPlusPassword: options.Obfs,
Password: password,
TLSConfig: tlsConfig,
UDPDisabled: !common.Contains(networkList, N.NetworkUDP),
ConnReceiveWindow: options.ReceiveWindowConn,
StreamReceiveWindow: options.ReceiveWindow,
DisableMTUDiscovery: options.DisableMTUDiscovery,
})
if err != nil {
return nil, err
}
return &Hysteria{
myOutboundAdapter: myOutboundAdapter{
protocol: C.TypeHysteria,
network: options.Network.Build(),
network: networkList,
router: router,
logger: logger,
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
ctx: ctx,
dialer: outboundDialer,
serverAddr: options.ServerOptions.Build(),
tlsConfig: tlsConfig,
quicConfig: quicConfig,
authKey: auth,
xplusKey: xplus,
sendBPS: up,
recvBPS: down,
client: client,
}, nil
}
func (h *Hysteria) offer(ctx context.Context) (quic.Connection, error) {
conn := h.conn
if conn != nil && !common.Done(conn.Context()) {
return conn, nil
}
h.connAccess.Lock()
defer h.connAccess.Unlock()
h.udpAccess.Lock()
defer h.udpAccess.Unlock()
conn = h.conn
if conn != nil && !common.Done(conn.Context()) {
return conn, nil
}
common.Close(h.rawConn)
conn, err := h.offerNew(ctx)
if err != nil {
return nil, err
}
if common.Contains(h.network, N.NetworkUDP) {
for _, session := range h.udpSessions {
close(session)
}
h.udpSessions = make(map[uint32]chan *hysteria.UDPMessage)
h.udpDefragger = hysteria.Defragger{}
go h.udpRecvLoop(conn)
}
return conn, nil
}
func (h *Hysteria) offerNew(ctx context.Context) (quic.Connection, error) {
udpConn, err := h.dialer.DialContext(h.ctx, "udp", h.serverAddr)
if err != nil {
return nil, err
}
var packetConn net.PacketConn
packetConn = bufio.NewUnbindPacketConn(udpConn)
if h.xplusKey != nil {
packetConn = hysteria.NewXPlusPacketConn(packetConn, h.xplusKey)
}
packetConn = &hysteria.PacketConnWrapper{PacketConn: packetConn}
quicConn, err := qtls.Dial(h.ctx, packetConn, udpConn.RemoteAddr(), h.tlsConfig, h.quicConfig)
if err != nil {
packetConn.Close()
return nil, err
}
controlStream, err := quicConn.OpenStreamSync(ctx)
if err != nil {
packetConn.Close()
return nil, err
}
err = hysteria.WriteClientHello(controlStream, hysteria.ClientHello{
SendBPS: h.sendBPS,
RecvBPS: h.recvBPS,
Auth: h.authKey,
})
if err != nil {
packetConn.Close()
return nil, err
}
serverHello, err := hysteria.ReadServerHello(controlStream)
if err != nil {
packetConn.Close()
return nil, err
}
if !serverHello.OK {
packetConn.Close()
return nil, E.New("remote error: ", serverHello.Message)
}
quicConn.SetCongestionControl(hyCC.NewBrutalSender(serverHello.RecvBPS))
h.conn = quicConn
h.rawConn = udpConn
return quicConn, nil
}
func (h *Hysteria) udpRecvLoop(conn quic.Connection) {
for {
packet, err := conn.ReceiveMessage(h.ctx)
if err != nil {
return
}
message, err := hysteria.ParseUDPMessage(packet)
if err != nil {
h.logger.Error("parse udp message: ", err)
continue
}
dfMsg := h.udpDefragger.Feed(message)
if dfMsg == nil {
continue
}
h.udpAccess.RLock()
ch, ok := h.udpSessions[dfMsg.SessionID]
if ok {
select {
case ch <- dfMsg:
// OK
default:
// Silently drop the message when the channel is full
}
}
h.udpAccess.RUnlock()
}
}
func (h *Hysteria) InterfaceUpdated() {
h.Close()
return
}
func (h *Hysteria) Close() error {
h.connAccess.Lock()
defer h.connAccess.Unlock()
h.udpAccess.Lock()
defer h.udpAccess.Unlock()
if h.conn != nil {
h.conn.CloseWithError(0, "")
h.rawConn.Close()
}
for _, session := range h.udpSessions {
close(session)
}
h.udpSessions = make(map[uint32]chan *hysteria.UDPMessage)
return nil
}
func (h *Hysteria) open(ctx context.Context, reconnect bool) (quic.Connection, quic.Stream, error) {
conn, err := h.offer(ctx)
if err != nil {
if nErr, ok := err.(net.Error); ok && !nErr.Temporary() && reconnect {
return h.open(ctx, false)
}
return nil, nil, err
}
stream, err := conn.OpenStream()
if err != nil {
if nErr, ok := err.(net.Error); ok && !nErr.Temporary() && reconnect {
return h.open(ctx, false)
}
return nil, nil, err
}
return conn, &hysteria.StreamWrapper{Stream: stream}, nil
}
func (h *Hysteria) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
switch N.NetworkName(network) {
case N.NetworkTCP:
h.logger.InfoContext(ctx, "outbound connection to ", destination)
_, stream, err := h.open(ctx, true)
if err != nil {
return nil, err
}
err = hysteria.WriteClientRequest(stream, hysteria.ClientRequest{
Host: destination.AddrString(),
Port: destination.Port,
})
if err != nil {
stream.Close()
return nil, err
}
return hysteria.NewConn(stream, destination, true), nil
return h.client.DialConn(ctx, destination)
case N.NetworkUDP:
conn, err := h.ListenPacket(ctx, destination)
if err != nil {
return nil, err
}
return conn.(*hysteria.PacketConn), nil
return bufio.NewBindPacketConn(conn, destination), nil
default:
return nil, E.New("unsupported network: ", network)
}
@ -310,44 +119,7 @@ func (h *Hysteria) DialContext(ctx context.Context, network string, destination
func (h *Hysteria) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
conn, stream, err := h.open(ctx, true)
if err != nil {
return nil, err
}
err = hysteria.WriteClientRequest(stream, hysteria.ClientRequest{
UDP: true,
Host: destination.AddrString(),
Port: destination.Port,
})
if err != nil {
stream.Close()
return nil, err
}
var response *hysteria.ServerResponse
response, err = hysteria.ReadServerResponse(stream)
if err != nil {
stream.Close()
return nil, err
}
if !response.OK {
stream.Close()
return nil, E.New("remote error: ", response.Message)
}
h.udpAccess.Lock()
nCh := make(chan *hysteria.UDPMessage, 1024)
h.udpSessions[response.UDPSessionID] = nCh
h.udpAccess.Unlock()
packetConn := hysteria.NewPacketConn(conn, stream, response.UDPSessionID, destination, nCh, common.Closer(func() error {
h.udpAccess.Lock()
if ch, ok := h.udpSessions[response.UDPSessionID]; ok {
close(ch)
delete(h.udpSessions, response.UDPSessionID)
}
h.udpAccess.Unlock()
return nil
}))
go packetConn.Hold()
return packetConn, nil
return h.client.ListenPacket(ctx, destination)
}
func (h *Hysteria) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
@ -357,3 +129,11 @@ func (h *Hysteria) NewConnection(ctx context.Context, conn net.Conn, metadata ad
func (h *Hysteria) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return NewPacketConnection(ctx, h, conn, metadata)
}
func (h *Hysteria) InterfaceUpdated() error {
return h.client.CloseWithError(E.New("network changed"))
}
func (h *Hysteria) Close() error {
return h.client.CloseWithError(os.ErrClosed)
}

View file

@ -13,7 +13,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/hysteria"
"github.com/sagernet/sing-quic/hysteria"
"github.com/sagernet/sing-quic/hysteria2"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"

View file

@ -10,10 +10,10 @@ require (
github.com/docker/docker v24.0.6+incompatible
github.com/docker/go-connections v0.4.0
github.com/gofrs/uuid/v5 v5.0.0
github.com/sagernet/quic-go v0.0.0-20230919101909-0cc6c5dcecee
github.com/sagernet/sing v0.2.15
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028
github.com/sagernet/sing-dns v0.1.10
github.com/sagernet/sing-quic v0.1.2
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6
github.com/sagernet/sing-shadowsocks v0.2.5
github.com/sagernet/sing-shadowsocks2 v0.1.4
github.com/spyzhov/ajson v0.9.0
@ -70,12 +70,12 @@ require (
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a // indirect
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 // indirect
github.com/sagernet/gvisor v0.0.0-20230930141345-5fef6f2e17ab // indirect
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
github.com/sagernet/sing-mux v0.1.3 // indirect
github.com/sagernet/sing-shadowtls v0.1.4 // indirect
github.com/sagernet/sing-tun v0.1.16 // indirect
github.com/sagernet/sing-tun v0.1.17-0.20231026060825-efd9884154a6 // indirect
github.com/sagernet/sing-vmess v0.1.8 // indirect
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 // indirect

View file

@ -117,32 +117,32 @@ github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a h1:wZHruBx
github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a/go.mod h1:dNV1ZP9y3qx5ltULeKaQZTZWTLHflgW5DES+Ses7cMI=
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 h1:dnkKrzapqtAwjTSWt6hdPrARORfoYvuUczynvRLrueo=
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2/go.mod h1:1JUiV7nGuf++YFm9eWZ8q2lrwHmhcUGzptMl/vL1+LA=
github.com/sagernet/gvisor v0.0.0-20230930141345-5fef6f2e17ab h1:u+xQoi/Yc6bNUvTfrDD6HhGRybn2lzrhf5vmS+wb4Ho=
github.com/sagernet/gvisor v0.0.0-20230930141345-5fef6f2e17ab/go.mod h1:3akUhSHSVtLuJaYcW5JPepUraBOW06Ibz2HKwaK5rOk=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/quic-go v0.0.0-20230919101909-0cc6c5dcecee h1:ykuhl9jCS638N+jw1vC9AvT9bbQn6xRNScP2FWPV9dM=
github.com/sagernet/quic-go v0.0.0-20230919101909-0cc6c5dcecee/go.mod h1:0CfhWwZAeXGYM9+Nkkw1zcQtFHQC8KWjbpeDv7pu8iw=
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460 h1:dAe4OIJAtE0nHOzTHhAReQteh3+sa63rvXbuIpbeOTY=
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460/go.mod h1:uJGpmJCOcMQqMlHKc3P1Vz6uygmpz4bPeVIoOhdVQnM=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
github.com/sagernet/sing v0.2.15 h1:PFwyiMzkyJkq+YGOVznJUsRVOT6EoVxRGIsllLuvHXA=
github.com/sagernet/sing v0.2.15/go.mod h1:AhNEHu0GXrpqkuzvTwvC8+j2cQUU/dh+zLEmq4C99pg=
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028 h1:6GbQt7SC9y5Imrq5jDMbXDSaNiMhJ8KBjhjtQRuqQvE=
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028/go.mod h1:AhNEHu0GXrpqkuzvTwvC8+j2cQUU/dh+zLEmq4C99pg=
github.com/sagernet/sing-dns v0.1.10 h1:iIU7nRBlUYj+fF2TaktGIvRiTFFrHwSMedLQsvlTZCI=
github.com/sagernet/sing-dns v0.1.10/go.mod h1:vtUimtf7Nq9EdvD5WTpfCr69KL1M7bcgOVKiYBiAY/c=
github.com/sagernet/sing-mux v0.1.3 h1:fAf7PZa2A55mCeh0KKM02f1k2Y4vEmxuZZ/51ahkkLA=
github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0=
github.com/sagernet/sing-quic v0.1.2 h1:+u9CRf0KHi5HgXmJ3eB0CtqpWXtF0lx2QlWq+ZFZ+XY=
github.com/sagernet/sing-quic v0.1.2/go.mod h1:H1TX0/y9UUM43wyaLQ+qjg2+o901ibYtwWX2rWG+a3o=
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6 h1:w+TUbIZKZFSdf/AUa/y33kY9xaLeNGz/tBNcNhqpqfg=
github.com/sagernet/sing-quic v0.1.3-0.20231026034240-fa3d997246b6/go.mod h1:1M7xP4802K9Kz6BQ7LlA7UeCapWvWlH1Htmk2bAqkWc=
github.com/sagernet/sing-shadowsocks v0.2.5 h1:qxIttos4xu6ii7MTVJYA8EFQR7Q3KG6xMqmLJIFtBaY=
github.com/sagernet/sing-shadowsocks v0.2.5/go.mod h1:MGWGkcU2xW2G2mfArT9/QqpVLOGU+dBaahZCtPHdt7A=
github.com/sagernet/sing-shadowsocks2 v0.1.4 h1:vht2M8t3m5DTgXR2j24KbYOygG5aOp+MUhpQnAux728=
github.com/sagernet/sing-shadowsocks2 v0.1.4/go.mod h1:Mgdee99NxxNd5Zld3ixIs18yVs4x2dI2VTDDE1N14Wc=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/sing-tun v0.1.16 h1:RHXYIVg6uacvdfbYMiPEz9VX5uu6mNrvP7u9yAH3oNc=
github.com/sagernet/sing-tun v0.1.16/go.mod h1:S3q8GCjeyRniK+KLmo4XqKY0bS3x2UdKkKbqxT/Agl8=
github.com/sagernet/sing-tun v0.1.17-0.20231026060825-efd9884154a6 h1:4yEXBqQoUgXj7qPSLD6lr+z9/KfsvixO9JUA2i5xnM8=
github.com/sagernet/sing-tun v0.1.17-0.20231026060825-efd9884154a6/go.mod h1:w2+S+uWE94E/pQWSDdDdMIjwAEb645kuGPunr6ZllUg=
github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc=
github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=

View file

@ -79,7 +79,7 @@ func TestHysteriaSelf(t *testing.T) {
},
},
})
testSuitSimple1(t, clientPort, testPort)
testSuit(t, clientPort, testPort)
}
func TestHysteriaInbound(t *testing.T) {
@ -118,7 +118,7 @@ func TestHysteriaInbound(t *testing.T) {
caPem: "/etc/hysteria/ca.pem",
},
})
testSuitSimple1(t, clientPort, testPort)
testSuit(t, clientPort, testPort)
}
func TestHysteriaOutbound(t *testing.T) {

View file

@ -1,65 +0,0 @@
package hysteria
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.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.Data = data
m.FragID = 0
m.FragCount = 1
return &m
}
}
return nil
}

View file

@ -1,539 +0,0 @@
package hysteria
import (
"bytes"
"encoding/binary"
"io"
"math/rand"
"net"
"os"
"time"
"github.com/sagernet/quic-go"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
)
const (
MbpsToBps = 125000
MinSpeedBPS = 16384
DefaultStreamReceiveWindow = 15728640 // 15 MB/s
DefaultConnectionReceiveWindow = 67108864 // 64 MB/s
DefaultMaxIncomingStreams = 1024
DefaultALPN = "hysteria"
KeepAlivePeriod = 10 * time.Second
)
const Version = 3
type ClientHello struct {
SendBPS uint64
RecvBPS uint64
Auth []byte
}
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(Version),
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.Write(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 != Version {
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
}
var authLen uint16
err = binary.Read(reader, binary.BigEndian, &authLen)
if err != nil {
return nil, err
}
clientHello.Auth = make([]byte, authLen)
_, err = io.ReadFull(reader, clientHello.Auth)
if err != nil {
return nil, err
}
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(stream io.Writer, request ClientRequest) error {
var requestLen int
requestLen += 1 // udp
requestLen += 2 // host len
requestLen += len(request.Host)
requestLen += 2 // port
buffer := buf.NewSize(requestLen)
defer buffer.Release()
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),
)
return common.Error(stream.Write(buffer.Bytes()))
}
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 io.Writer, 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()))
}
type UDPMessage struct {
SessionID uint32
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
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)
}
func ParseUDPMessage(packet []byte) (message UDPMessage, err error) {
reader := bytes.NewReader(packet)
err = binary.Read(reader, binary.BigEndian, &message.SessionID)
if err != nil {
return
}
var hostLen uint16
err = binary.Read(reader, binary.BigEndian, &hostLen)
if err != nil {
return
}
_, err = reader.Seek(int64(hostLen), io.SeekCurrent)
if err != nil {
return
}
if 6+int(hostLen) > len(packet) {
err = E.New("invalid host length")
return
}
message.Host = string(packet[6 : 6+hostLen])
err = binary.Read(reader, binary.BigEndian, &message.Port)
if err != nil {
return
}
err = binary.Read(reader, binary.BigEndian, &message.MsgID)
if err != nil {
return
}
err = binary.Read(reader, binary.BigEndian, &message.FragID)
if err != nil {
return
}
err = binary.Read(reader, binary.BigEndian, &message.FragCount)
if err != nil {
return
}
var dataLen uint16
err = binary.Read(reader, binary.BigEndian, &dataLen)
if err != nil {
return
}
if reader.Len() != int(dataLen) {
err = E.New("invalid data length")
}
dataOffset := int(reader.Size()) - reader.Len()
message.Data = packet[dataOffset:]
return
}
func WriteUDPMessage(conn quic.Connection, message UDPMessage) error {
var messageLen int
messageLen += 4 // session id
messageLen += 2 // host len
messageLen += len(message.Host)
messageLen += 2 // port
messageLen += 2 // msg id
messageLen += 1 // frag id
messageLen += 1 // frag count
messageLen += 2 // data len
messageLen += len(message.Data)
buffer := buf.NewSize(messageLen)
defer buffer.Release()
err := writeUDPMessage(conn, message, buffer)
if errSize, ok := err.(quic.ErrMessageTooLarge); ok {
// need to frag
message.MsgID = uint16(rand.Intn(0xFFFF)) + 1 // msgID must be > 0 when fragCount > 1
fragMsgs := FragUDPMessage(message, int(errSize))
for _, fragMsg := range fragMsgs {
buffer.FullReset()
err = writeUDPMessage(conn, fragMsg, buffer)
if err != nil {
return err
}
}
return nil
}
return err
}
func writeUDPMessage(conn quic.Connection, message UDPMessage, buffer *buf.Buffer) error {
common.Must(
binary.Write(buffer, binary.BigEndian, message.SessionID),
binary.Write(buffer, binary.BigEndian, uint16(len(message.Host))),
common.Error(buffer.WriteString(message.Host)),
binary.Write(buffer, binary.BigEndian, message.Port),
binary.Write(buffer, binary.BigEndian, message.MsgID),
binary.Write(buffer, binary.BigEndian, message.FragID),
binary.Write(buffer, binary.BigEndian, message.FragCount),
binary.Write(buffer, binary.BigEndian, uint16(len(message.Data))),
common.Error(buffer.Write(message.Data)),
)
return conn.SendMessage(buffer.Bytes())
}
var _ net.Conn = (*Conn)(nil)
type Conn struct {
quic.Stream
destination M.Socksaddr
needReadResponse bool
}
func NewConn(stream quic.Stream, destination M.Socksaddr, isClient bool) *Conn {
return &Conn{
Stream: stream,
destination: destination,
needReadResponse: isClient,
}
}
func (c *Conn) Read(p []byte) (n int, err error) {
if c.needReadResponse {
var response *ServerResponse
response, err = ReadServerResponse(c.Stream)
if err != nil {
c.Close()
return
}
if !response.OK {
c.Close()
return 0, E.New("remote error: ", response.Message)
}
c.needReadResponse = false
}
return c.Stream.Read(p)
}
func (c *Conn) LocalAddr() net.Addr {
return M.Socksaddr{}
}
func (c *Conn) RemoteAddr() net.Addr {
return c.destination.TCPAddr()
}
func (c *Conn) ReaderReplaceable() bool {
return !c.needReadResponse
}
func (c *Conn) WriterReplaceable() bool {
return true
}
func (c *Conn) Upstream() any {
return c.Stream
}
type PacketConn struct {
session quic.Connection
stream quic.Stream
sessionId uint32
destination M.Socksaddr
msgCh <-chan *UDPMessage
closer io.Closer
}
func NewPacketConn(session quic.Connection, stream quic.Stream, sessionId uint32, destination M.Socksaddr, msgCh <-chan *UDPMessage, closer io.Closer) *PacketConn {
return &PacketConn{
session: session,
stream: stream,
sessionId: sessionId,
destination: destination,
msgCh: msgCh,
closer: closer,
}
}
func (c *PacketConn) 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 *PacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
msg := <-c.msgCh
if msg == nil {
err = net.ErrClosed
return
}
err = common.Error(buffer.Write(msg.Data))
destination = M.ParseSocksaddrHostPort(msg.Host, msg.Port).Unwrap()
return
}
func (c *PacketConn) ReadPacketThreadSafe() (buffer *buf.Buffer, destination M.Socksaddr, err error) {
msg := <-c.msgCh
if msg == nil {
err = net.ErrClosed
return
}
buffer = buf.As(msg.Data)
destination = M.ParseSocksaddrHostPort(msg.Host, msg.Port).Unwrap()
return
}
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
return WriteUDPMessage(c.session, UDPMessage{
SessionID: c.sessionId,
Host: destination.AddrString(),
Port: destination.Port,
FragCount: 1,
Data: buffer.Bytes(),
})
}
func (c *PacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
msg := <-c.msgCh
if msg == nil {
err = net.ErrClosed
return
}
n = copy(p, msg.Data)
destination := M.ParseSocksaddrHostPort(msg.Host, msg.Port)
if destination.IsFqdn() {
addr = destination
} else {
addr = destination.UDPAddr()
}
return
}
func (c *PacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
err = c.WritePacket(buf.As(p), M.SocksaddrFromNet(addr))
if err == nil {
n = len(p)
}
return
}
func (c *PacketConn) LocalAddr() net.Addr {
return M.Socksaddr{}
}
func (c *PacketConn) RemoteAddr() net.Addr {
return c.destination.UDPAddr()
}
func (c *PacketConn) SetDeadline(t time.Time) error {
return os.ErrInvalid
}
func (c *PacketConn) SetReadDeadline(t time.Time) error {
return os.ErrInvalid
}
func (c *PacketConn) SetWriteDeadline(t time.Time) error {
return os.ErrInvalid
}
func (c *PacketConn) NeedAdditionalReadDeadline() bool {
return true
}
func (c *PacketConn) Read(b []byte) (n int, err error) {
n, _, err = c.ReadFrom(b)
return
}
func (c *PacketConn) Write(b []byte) (n int, err error) {
return c.WriteTo(b, c.destination)
}
func (c *PacketConn) Close() error {
return common.Close(c.stream, c.closer)
}

View file

@ -1,36 +0,0 @@
package hysteria
import (
"regexp"
"strconv"
)
func StringToBps(s string) uint64 {
if s == "" {
return 0
}
m := regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`).FindStringSubmatch(s)
if m == nil {
return 0
}
var n uint64
switch m[2] {
case "K":
n = 1 << 10
case "M":
n = 1 << 20
case "G":
n = 1 << 30
case "T":
n = 1 << 40
default:
n = 1
}
v, _ := strconv.ParseUint(m[1], 10, 64)
n = v * n
if m[3] == "b" {
// Bits, need to convert to bytes
n = n >> 3
}
return n
}

View file

@ -1,68 +0,0 @@
package hysteria
import (
"net"
"os"
"syscall"
"github.com/sagernet/quic-go"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/baderror"
)
type PacketConnWrapper struct {
net.PacketConn
}
func (c *PacketConnWrapper) SetReadBuffer(bytes int) error {
return common.MustCast[*net.UDPConn](c.PacketConn).SetReadBuffer(bytes)
}
func (c *PacketConnWrapper) SetWriteBuffer(bytes int) error {
return common.MustCast[*net.UDPConn](c.PacketConn).SetWriteBuffer(bytes)
}
func (c *PacketConnWrapper) SyscallConn() (syscall.RawConn, error) {
return common.MustCast[*net.UDPConn](c.PacketConn).SyscallConn()
}
func (c *PacketConnWrapper) File() (f *os.File, err error) {
return common.MustCast[*net.UDPConn](c.PacketConn).File()
}
func (c *PacketConnWrapper) Upstream() any {
return c.PacketConn
}
type StreamWrapper struct {
Conn quic.Connection
quic.Stream
}
func (s *StreamWrapper) Read(p []byte) (n int, err error) {
n, err = s.Stream.Read(p)
return n, baderror.WrapQUIC(err)
}
func (s *StreamWrapper) Write(p []byte) (n int, err error) {
n, err = s.Stream.Write(p)
return n, baderror.WrapQUIC(err)
}
func (s *StreamWrapper) LocalAddr() net.Addr {
return s.Conn.LocalAddr()
}
func (s *StreamWrapper) RemoteAddr() net.Addr {
return s.Conn.RemoteAddr()
}
func (s *StreamWrapper) Upstream() any {
return s.Stream
}
func (s *StreamWrapper) Close() error {
s.CancelRead(0)
s.Stream.Close()
return nil
}

View file

@ -1,118 +0,0 @@
package hysteria
import (
"crypto/sha256"
"math/rand"
"net"
"sync"
"time"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
const xplusSaltLen = 16
func NewXPlusPacketConn(conn net.PacketConn, key []byte) net.PacketConn {
vectorisedWriter, isVectorised := bufio.CreateVectorisedPacketWriter(conn)
if isVectorised {
return &VectorisedXPlusConn{
XPlusPacketConn: XPlusPacketConn{
PacketConn: conn,
key: key,
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
},
writer: vectorisedWriter,
}
} else {
return &XPlusPacketConn{
PacketConn: conn,
key: key,
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
}
}
}
type XPlusPacketConn struct {
net.PacketConn
key []byte
randAccess sync.Mutex
rand *rand.Rand
}
func (c *XPlusPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
n, addr, err = c.PacketConn.ReadFrom(p)
if err != nil {
return
} else if n < xplusSaltLen {
n = 0
return
}
key := sha256.Sum256(append(c.key, p[:xplusSaltLen]...))
for i := range p[xplusSaltLen:] {
p[i] = p[xplusSaltLen+i] ^ key[i%sha256.Size]
}
n -= xplusSaltLen
return
}
func (c *XPlusPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
// can't use unsafe buffer on WriteTo
buffer := buf.NewSize(len(p) + xplusSaltLen)
defer buffer.Release()
salt := buffer.Extend(xplusSaltLen)
c.randAccess.Lock()
_, _ = c.rand.Read(salt)
c.randAccess.Unlock()
key := sha256.Sum256(append(c.key, salt...))
for i := range p {
common.Must(buffer.WriteByte(p[i] ^ key[i%sha256.Size]))
}
return c.PacketConn.WriteTo(buffer.Bytes(), addr)
}
func (c *XPlusPacketConn) Upstream() any {
return c.PacketConn
}
type VectorisedXPlusConn struct {
XPlusPacketConn
writer N.VectorisedPacketWriter
}
func (c *VectorisedXPlusConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
header := buf.NewSize(xplusSaltLen)
defer header.Release()
salt := header.Extend(xplusSaltLen)
c.randAccess.Lock()
_, _ = c.rand.Read(salt)
c.randAccess.Unlock()
key := sha256.Sum256(append(c.key, salt...))
for i := range p {
p[i] ^= key[i%sha256.Size]
}
return bufio.WriteVectorisedPacket(c.writer, [][]byte{header.Bytes(), p}, M.SocksaddrFromNet(addr))
}
func (c *VectorisedXPlusConn) WriteVectorisedPacket(buffers []*buf.Buffer, destination M.Socksaddr) error {
header := buf.NewSize(xplusSaltLen)
defer header.Release()
salt := header.Extend(xplusSaltLen)
c.randAccess.Lock()
_, _ = c.rand.Read(salt)
c.randAccess.Unlock()
key := sha256.Sum256(append(c.key, salt...))
var index int
for _, buffer := range buffers {
data := buffer.Bytes()
for i := range data {
data[i] ^= key[index%sha256.Size]
index++
}
}
buffers = append([]*buf.Buffer{header}, buffers...)
return c.writer.WriteVectorisedPacket(buffers, destination)
}

View file

@ -12,7 +12,6 @@ import (
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/hysteria"
"github.com/sagernet/sing-quic"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
@ -93,7 +92,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
if err != nil {
return nil, err
}
return &hysteria.StreamWrapper{Conn: conn, Stream: stream}, nil
return &StreamWrapper{Conn: conn, Stream: stream}, nil
}
func (c *Client) Close() error {

View file

@ -12,7 +12,6 @@ import (
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/hysteria"
"github.com/sagernet/sing-quic"
"github.com/sagernet/sing/common"
M "github.com/sagernet/sing/common/metadata"
@ -86,7 +85,7 @@ func (s *Server) streamAcceptLoop(conn quic.Connection) error {
if err != nil {
return err
}
go s.handler.NewConnection(conn.Context(), &hysteria.StreamWrapper{Conn: conn, Stream: stream}, M.Metadata{})
go s.handler.NewConnection(conn.Context(), &StreamWrapper{Conn: conn, Stream: stream}, M.Metadata{})
}
}

View file

@ -0,0 +1,41 @@
package v2rayquic
import (
"net"
"github.com/sagernet/quic-go"
"github.com/sagernet/sing/common/baderror"
)
type StreamWrapper struct {
Conn quic.Connection
quic.Stream
}
func (s *StreamWrapper) Read(p []byte) (n int, err error) {
n, err = s.Stream.Read(p)
return n, baderror.WrapQUIC(err)
}
func (s *StreamWrapper) Write(p []byte) (n int, err error) {
n, err = s.Stream.Write(p)
return n, baderror.WrapQUIC(err)
}
func (s *StreamWrapper) LocalAddr() net.Addr {
return s.Conn.LocalAddr()
}
func (s *StreamWrapper) RemoteAddr() net.Addr {
return s.Conn.RemoteAddr()
}
func (s *StreamWrapper) Upstream() any {
return s.Stream
}
func (s *StreamWrapper) Close() error {
s.CancelRead(0)
s.Stream.Close()
return nil
}