mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-22 08:31:30 +00:00
Update BBR and Hysteria congestion control & Migrate legacy Hysteria protocol to library
This commit is contained in:
parent
3b161ab30c
commit
31c294d998
6
go.mod
6
go.mod
|
@ -24,12 +24,12 @@ require (
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a
|
github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a
|
||||||
github.com/sagernet/gomobile v0.0.0-20230915142329-c6740b6d2950
|
github.com/sagernet/gomobile v0.0.0-20230915142329-c6740b6d2950
|
||||||
github.com/sagernet/gvisor v0.0.0-20230930141345-5fef6f2e17ab
|
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/reality v0.0.0-20230406110435-ee17307e7691
|
||||||
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028
|
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028
|
||||||
github.com/sagernet/sing-dns v0.1.10
|
github.com/sagernet/sing-dns v0.1.10
|
||||||
github.com/sagernet/sing-mux v0.1.3
|
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-shadowsocks v0.2.5
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.1.4
|
github.com/sagernet/sing-shadowsocks2 v0.1.4
|
||||||
github.com/sagernet/sing-shadowtls v0.1.4
|
github.com/sagernet/sing-shadowtls v0.1.4
|
||||||
|
@ -45,7 +45,6 @@ require (
|
||||||
go.uber.org/zap v1.26.0
|
go.uber.org/zap v1.26.0
|
||||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925
|
go4.org/netipx v0.0.0-20230824141953-6213f710f925
|
||||||
golang.org/x/crypto v0.14.0
|
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/net v0.17.0
|
||||||
golang.org/x/sys v0.13.0
|
golang.org/x/sys v0.13.0
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
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/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // 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/mod v0.13.0 // indirect
|
||||||
golang.org/x/text v0.13.0 // indirect
|
golang.org/x/text v0.13.0 // indirect
|
||||||
golang.org/x/time v0.3.0 // indirect
|
golang.org/x/time v0.3.0 // indirect
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -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/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 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
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-20231008035953-32727fef9460 h1:dAe4OIJAtE0nHOzTHhAReQteh3+sa63rvXbuIpbeOTY=
|
||||||
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/go.mod h1:uJGpmJCOcMQqMlHKc3P1Vz6uygmpz4bPeVIoOhdVQnM=
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
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/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.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-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 h1:fAf7PZa2A55mCeh0KKM02f1k2Y4vEmxuZZ/51ahkkLA=
|
||||||
github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0=
|
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.3-0.20231026034240-fa3d997246b6 h1:w+TUbIZKZFSdf/AUa/y33kY9xaLeNGz/tBNcNhqpqfg=
|
||||||
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/go.mod h1:1M7xP4802K9Kz6BQ7LlA7UeCapWvWlH1Htmk2bAqkWc=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.5 h1:qxIttos4xu6ii7MTVJYA8EFQR7Q3KG6xMqmLJIFtBaY=
|
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-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 h1:vht2M8t3m5DTgXR2j24KbYOygG5aOp+MUhpQnAux728=
|
||||||
|
|
|
@ -4,104 +4,38 @@ package inbound
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sync"
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/quic-go"
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/humanize"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/transport/hysteria"
|
"github.com/sagernet/sing-quic/hysteria"
|
||||||
"github.com/sagernet/sing-quic"
|
|
||||||
hyCC "github.com/sagernet/sing-quic/hysteria2/congestion"
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/auth"
|
"github.com/sagernet/sing/common/auth"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
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"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.Inbound = (*Hysteria)(nil)
|
var _ adapter.Inbound = (*Hysteria)(nil)
|
||||||
|
|
||||||
type Hysteria struct {
|
type Hysteria struct {
|
||||||
myInboundAdapter
|
myInboundAdapter
|
||||||
quicConfig *quic.Config
|
|
||||||
tlsConfig tls.ServerConfig
|
tlsConfig tls.ServerConfig
|
||||||
authKey []string
|
service *hysteria.Service[int]
|
||||||
authUser []string
|
userNameList []string
|
||||||
xplusKey []byte
|
|
||||||
sendBPS uint64
|
|
||||||
recvBPS uint64
|
|
||||||
listener qtls.Listener
|
|
||||||
udpAccess sync.RWMutex
|
|
||||||
udpSessionId uint32
|
|
||||||
udpSessions map[uint32]chan *hysteria.UDPMessage
|
|
||||||
udpDefragger hysteria.Defragger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (*Hysteria, error) {
|
func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (*Hysteria, error) {
|
||||||
options.UDPFragmentDefault = true
|
options.UDPFragmentDefault = true
|
||||||
quicConfig := &quic.Config{
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
InitialStreamReceiveWindow: options.ReceiveWindowConn,
|
return nil, C.ErrTLSRequired
|
||||||
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.ReceiveWindowConn == 0 {
|
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
quicConfig.InitialStreamReceiveWindow = hysteria.DefaultStreamReceiveWindow
|
if err != nil {
|
||||||
quicConfig.MaxStreamReceiveWindow = hysteria.DefaultStreamReceiveWindow
|
return nil, err
|
||||||
}
|
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
inbound := &Hysteria{
|
inbound := &Hysteria{
|
||||||
myInboundAdapter: myInboundAdapter{
|
myInboundAdapter: myInboundAdapter{
|
||||||
|
@ -113,224 +47,108 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
|
||||||
tag: tag,
|
tag: tag,
|
||||||
listenOptions: options.ListenOptions,
|
listenOptions: options.ListenOptions,
|
||||||
},
|
},
|
||||||
quicConfig: quicConfig,
|
tlsConfig: tlsConfig,
|
||||||
authKey: authKey,
|
|
||||||
authUser: authUser,
|
|
||||||
xplusKey: xplus,
|
|
||||||
sendBPS: up,
|
|
||||||
recvBPS: down,
|
|
||||||
udpSessions: make(map[uint32]chan *hysteria.UDPMessage),
|
|
||||||
}
|
}
|
||||||
if options.TLS == nil || !options.TLS.Enabled {
|
var sendBps, receiveBps uint64
|
||||||
return nil, C.ErrTLSRequired
|
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)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendBps = uint64(options.UpMbps) * hysteria.MbpsToBps
|
||||||
}
|
}
|
||||||
if len(options.TLS.ALPN) == 0 {
|
if len(options.Down) > 0 {
|
||||||
options.TLS.ALPN = []string{hysteria.DefaultALPN}
|
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
|
||||||
}
|
}
|
||||||
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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
|
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 {
|
func (h *Hysteria) Start() error {
|
||||||
|
if h.tlsConfig != nil {
|
||||||
|
err := h.tlsConfig.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
packetConn, err := h.myInboundAdapter.ListenUDP()
|
packetConn, err := h.myInboundAdapter.ListenUDP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(h.xplusKey) > 0 {
|
return h.service.Start(packetConn)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hysteria) Close() error {
|
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(
|
return common.Close(
|
||||||
&h.myInboundAdapter,
|
&h.myInboundAdapter,
|
||||||
h.listener,
|
|
||||||
h.tlsConfig,
|
h.tlsConfig,
|
||||||
|
common.PtrOrNil(h.service),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"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-quic/hysteria2"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/auth"
|
"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) {
|
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 {
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
return nil, C.ErrTLSRequired
|
return nil, C.ErrTLSRequired
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,16 @@ package outbound
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/quic-go"
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
|
"github.com/sagernet/sing-box/common/humanize"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/transport/hysteria"
|
"github.com/sagernet/sing-quic/hysteria"
|
||||||
"github.com/sagernet/sing-quic"
|
|
||||||
hyCC "github.com/sagernet/sing-quic/hysteria2/congestion"
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
@ -25,27 +23,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ adapter.Outbound = (*Hysteria)(nil)
|
_ adapter.Outbound = (*TUIC)(nil)
|
||||||
_ adapter.InterfaceUpdateListener = (*Hysteria)(nil)
|
_ adapter.InterfaceUpdateListener = (*TUIC)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Hysteria struct {
|
type Hysteria struct {
|
||||||
myOutboundAdapter
|
myOutboundAdapter
|
||||||
ctx context.Context
|
client *hysteria.Client
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (*Hysteria, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(tlsConfig.NextProtos()) == 0 {
|
outboundDialer, err := dialer.New(router, options.DialerOptions)
|
||||||
tlsConfig.SetNextProtos([]string{hysteria.DefaultALPN})
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
quicConfig := &quic.Config{
|
networkList := options.Network.Build()
|
||||||
InitialStreamReceiveWindow: options.ReceiveWindowConn,
|
var password string
|
||||||
MaxStreamReceiveWindow: options.ReceiveWindowConn,
|
if options.AuthString != "" {
|
||||||
InitialConnectionReceiveWindow: options.ReceiveWindow,
|
password = options.AuthString
|
||||||
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
|
|
||||||
} else {
|
} else {
|
||||||
auth = []byte(options.AuthString)
|
password = string(options.Auth)
|
||||||
}
|
}
|
||||||
var xplus []byte
|
var sendBps, receiveBps uint64
|
||||||
if options.Obfs != "" {
|
|
||||||
xplus = []byte(options.Obfs)
|
|
||||||
}
|
|
||||||
var up, down uint64
|
|
||||||
if len(options.Up) > 0 {
|
if len(options.Up) > 0 {
|
||||||
up = hysteria.StringToBps(options.Up)
|
sendBps, err = humanize.ParseBytes(options.Up)
|
||||||
if up == 0 {
|
if err != nil {
|
||||||
return nil, E.New("invalid up speed format: ", options.Up)
|
return nil, E.Cause(err, "invalid up speed format: ", options.Up)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
up = uint64(options.UpMbps) * hysteria.MbpsToBps
|
sendBps = uint64(options.UpMbps) * hysteria.MbpsToBps
|
||||||
}
|
}
|
||||||
if len(options.Down) > 0 {
|
if len(options.Down) > 0 {
|
||||||
down = hysteria.StringToBps(options.Down)
|
receiveBps, err = humanize.ParseBytes(options.Down)
|
||||||
if down == 0 {
|
if receiveBps == 0 {
|
||||||
return nil, E.New("invalid down speed format: ", options.Down)
|
return nil, E.New("invalid down speed format: ", options.Down)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
down = uint64(options.DownMbps) * hysteria.MbpsToBps
|
receiveBps = uint64(options.DownMbps) * hysteria.MbpsToBps
|
||||||
}
|
}
|
||||||
if up < hysteria.MinSpeedBPS {
|
client, err := hysteria.NewClient(hysteria.ClientOptions{
|
||||||
return nil, E.New("invalid up speed")
|
Context: ctx,
|
||||||
}
|
Dialer: outboundDialer,
|
||||||
if down < hysteria.MinSpeedBPS {
|
Logger: logger,
|
||||||
return nil, E.New("invalid down speed")
|
ServerAddress: options.ServerOptions.Build(),
|
||||||
}
|
SendBPS: sendBps,
|
||||||
outboundDialer, err := dialer.New(router, options.DialerOptions)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Hysteria{
|
return &Hysteria{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeHysteria,
|
protocol: C.TypeHysteria,
|
||||||
network: options.Network.Build(),
|
network: networkList,
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dependencies: withDialerDependency(options.DialerOptions),
|
dependencies: withDialerDependency(options.DialerOptions),
|
||||||
},
|
},
|
||||||
ctx: ctx,
|
client: client,
|
||||||
dialer: outboundDialer,
|
|
||||||
serverAddr: options.ServerOptions.Build(),
|
|
||||||
tlsConfig: tlsConfig,
|
|
||||||
quicConfig: quicConfig,
|
|
||||||
authKey: auth,
|
|
||||||
xplusKey: xplus,
|
|
||||||
sendBPS: up,
|
|
||||||
recvBPS: down,
|
|
||||||
}, nil
|
}, 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) {
|
func (h *Hysteria) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
switch N.NetworkName(network) {
|
switch N.NetworkName(network) {
|
||||||
case N.NetworkTCP:
|
case N.NetworkTCP:
|
||||||
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
||||||
_, stream, err := h.open(ctx, true)
|
return h.client.DialConn(ctx, destination)
|
||||||
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
|
|
||||||
case N.NetworkUDP:
|
case N.NetworkUDP:
|
||||||
conn, err := h.ListenPacket(ctx, destination)
|
conn, err := h.ListenPacket(ctx, destination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return conn.(*hysteria.PacketConn), nil
|
return bufio.NewBindPacketConn(conn, destination), nil
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unsupported network: ", network)
|
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) {
|
func (h *Hysteria) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||||
conn, stream, err := h.open(ctx, true)
|
return h.client.ListenPacket(ctx, destination)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hysteria) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
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 {
|
func (h *Hysteria) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
return NewPacketConnection(ctx, h, conn, metadata)
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"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-quic/hysteria2"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
|
10
test/go.mod
10
test/go.mod
|
@ -10,10 +10,10 @@ require (
|
||||||
github.com/docker/docker v24.0.6+incompatible
|
github.com/docker/docker v24.0.6+incompatible
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/gofrs/uuid/v5 v5.0.0
|
github.com/gofrs/uuid/v5 v5.0.0
|
||||||
github.com/sagernet/quic-go v0.0.0-20230919101909-0cc6c5dcecee
|
github.com/sagernet/quic-go v0.0.0-20231008035953-32727fef9460
|
||||||
github.com/sagernet/sing v0.2.15
|
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028
|
||||||
github.com/sagernet/sing-dns v0.1.10
|
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-shadowsocks v0.2.5
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.1.4
|
github.com/sagernet/sing-shadowsocks2 v0.1.4
|
||||||
github.com/spyzhov/ajson v0.9.0
|
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/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20230829051644-4a68352d0c4a // 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/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/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
|
||||||
github.com/sagernet/sing-mux v0.1.3 // indirect
|
github.com/sagernet/sing-mux v0.1.3 // indirect
|
||||||
github.com/sagernet/sing-shadowtls v0.1.4 // 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/sing-vmess v0.1.8 // indirect
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 // indirect
|
github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 // indirect
|
||||||
|
|
20
test/go.sum
20
test/go.sum
|
@ -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/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 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
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-20230930141345-5fef6f2e17ab h1:u+xQoi/Yc6bNUvTfrDD6HhGRybn2lzrhf5vmS+wb4Ho=
|
||||||
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/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 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
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-20231008035953-32727fef9460 h1:dAe4OIJAtE0nHOzTHhAReQteh3+sa63rvXbuIpbeOTY=
|
||||||
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/go.mod h1:uJGpmJCOcMQqMlHKc3P1Vz6uygmpz4bPeVIoOhdVQnM=
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
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/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.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.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
||||||
github.com/sagernet/sing v0.2.15 h1:PFwyiMzkyJkq+YGOVznJUsRVOT6EoVxRGIsllLuvHXA=
|
github.com/sagernet/sing v0.2.16-0.20231021090846-8002db54c028 h1:6GbQt7SC9y5Imrq5jDMbXDSaNiMhJ8KBjhjtQRuqQvE=
|
||||||
github.com/sagernet/sing v0.2.15/go.mod h1:AhNEHu0GXrpqkuzvTwvC8+j2cQUU/dh+zLEmq4C99pg=
|
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 h1:iIU7nRBlUYj+fF2TaktGIvRiTFFrHwSMedLQsvlTZCI=
|
||||||
github.com/sagernet/sing-dns v0.1.10/go.mod h1:vtUimtf7Nq9EdvD5WTpfCr69KL1M7bcgOVKiYBiAY/c=
|
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 h1:fAf7PZa2A55mCeh0KKM02f1k2Y4vEmxuZZ/51ahkkLA=
|
||||||
github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0=
|
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.3-0.20231026034240-fa3d997246b6 h1:w+TUbIZKZFSdf/AUa/y33kY9xaLeNGz/tBNcNhqpqfg=
|
||||||
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/go.mod h1:1M7xP4802K9Kz6BQ7LlA7UeCapWvWlH1Htmk2bAqkWc=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.5 h1:qxIttos4xu6ii7MTVJYA8EFQR7Q3KG6xMqmLJIFtBaY=
|
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-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 h1:vht2M8t3m5DTgXR2j24KbYOygG5aOp+MUhpQnAux728=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.1.4/go.mod h1:Mgdee99NxxNd5Zld3ixIs18yVs4x2dI2VTDDE1N14Wc=
|
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 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
|
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.17-0.20231026060825-efd9884154a6 h1:4yEXBqQoUgXj7qPSLD6lr+z9/KfsvixO9JUA2i5xnM8=
|
||||||
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/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 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc=
|
||||||
github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA=
|
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=
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
||||||
|
|
|
@ -79,7 +79,7 @@ func TestHysteriaSelf(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
testSuitSimple1(t, clientPort, testPort)
|
testSuit(t, clientPort, testPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHysteriaInbound(t *testing.T) {
|
func TestHysteriaInbound(t *testing.T) {
|
||||||
|
@ -118,7 +118,7 @@ func TestHysteriaInbound(t *testing.T) {
|
||||||
caPem: "/etc/hysteria/ca.pem",
|
caPem: "/etc/hysteria/ca.pem",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
testSuitSimple1(t, clientPort, testPort)
|
testSuit(t, clientPort, testPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHysteriaOutbound(t *testing.T) {
|
func TestHysteriaOutbound(t *testing.T) {
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/transport/hysteria"
|
|
||||||
"github.com/sagernet/sing-quic"
|
"github.com/sagernet/sing-quic"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
@ -93,7 +92,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &hysteria.StreamWrapper{Conn: conn, Stream: stream}, nil
|
return &StreamWrapper{Conn: conn, Stream: stream}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Close() error {
|
func (c *Client) Close() error {
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/transport/hysteria"
|
|
||||||
"github.com/sagernet/sing-quic"
|
"github.com/sagernet/sing-quic"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
@ -86,7 +85,7 @@ func (s *Server) streamAcceptLoop(conn quic.Connection) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
41
transport/v2rayquic/stream.go
Normal file
41
transport/v2rayquic/stream.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue