LjhAUMEM
2026-05-02 20:27:27 +08:00
committed by GitHub
parent 52cf9ef5d6
commit 1d62941bd2
20 changed files with 845 additions and 1171 deletions

View File

@@ -1,45 +1,82 @@
package hysteria
import (
"context"
"math/rand"
"time"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/proxy/hysteria/account"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/hysteria/padding"
)
const (
closeErrCodeOK = 0x100 // HTTP3 ErrCodeNoError
closeErrCodeProtocolError = 0x101 // HTTP3 ErrCodeGeneralProtocolError
MaxDatagramFrameSize = 1200
URLHost = "hysteria"
URLPath = "/auth"
RequestHeaderAuth = "Hysteria-Auth"
ResponseHeaderUDPEnabled = "Hysteria-UDP"
CommonHeaderCCRX = "Hysteria-CC-RX"
CommonHeaderPadding = "Hysteria-Padding"
StatusAuthOK = 233
udpMessageChanSize = 1024
FrameTypeTCPRequest = 0x401
idleCleanupInterval = 1 * time.Second
URLHost = "hysteria"
URLPath = "/auth"
RequestHeaderAuth = "Hysteria-Auth"
ResponseHeaderUDPEnabled = "Hysteria-UDP"
CommonHeaderCCRX = "Hysteria-CC-RX"
CommonHeaderPadding = "Hysteria-Padding"
StatusAuthOK = 233
FrameTypeTCPRequest = 0x401
MaxDatagramFrameSize = 1200
udpMessageChanSize = 1024
idleCleanupInterval = 1 * time.Second
)
var (
authRequestPadding = padding.Padding{Min: 256, Max: 2048}
authResponsePadding = padding.Padding{Min: 256, Max: 2048}
)
type Status int
const (
StatusUnknown Status = iota
paddingChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
)
type padding struct {
Min int
Max int
}
func (p padding) String() string {
n := p.Min + rand.Intn(p.Max-p.Min)
bs := make([]byte, n)
for i := range bs {
bs[i] = paddingChars[rand.Intn(len(paddingChars))]
}
return string(bs)
}
var (
AuthRequestPadding = padding{Min: 256, Max: 2048}
AuthResponsePadding = padding{Min: 256, Max: 2048}
TcpRequestPadding = padding{Min: 64, Max: 512}
TcpResponsePadding = padding{Min: 128, Max: 1024}
)
type datagramKey struct{}
func ContextWithDatagram(ctx context.Context, v bool) context.Context {
return context.WithValue(ctx, datagramKey{}, v)
}
func DatagramFromContext(ctx context.Context) bool {
v, _ := ctx.Value(datagramKey{}).(bool)
return v
}
type validatorKey struct{}
func ContextWithValidator(ctx context.Context, v *account.Validator) context.Context {
return context.WithValue(ctx, validatorKey{}, v)
}
func ValidatorFromContext(ctx context.Context) *account.Validator {
v, _ := ctx.Value(validatorKey{}).(*account.Validator)
return v
}
type status int
const (
StatusNull status = iota
StatusActive
StatusInactive
)

View File

@@ -1,6 +1,7 @@
package hysteria
import (
"context"
"encoding/binary"
"io"
"sync"
@@ -8,8 +9,10 @@ import (
"github.com/apernet/quic-go"
"github.com/apernet/quic-go/quicvarint"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/transport/internet"
)
type interConn struct {
@@ -18,144 +21,278 @@ type interConn struct {
remote net.Addr
client bool
mutex sync.Mutex
user *protocol.MemoryUser
user *protocol.MemoryUser
}
func (i *interConn) User() *protocol.MemoryUser {
return i.user
func (c *interConn) User() *protocol.MemoryUser {
return c.user
}
func (i *interConn) Read(b []byte) (int, error) {
return i.stream.Read(b)
func (c *interConn) Read(b []byte) (int, error) {
return c.stream.Read(b)
}
func (i *interConn) Write(b []byte) (int, error) {
if i.client {
i.mutex.Lock()
defer i.mutex.Unlock()
if i.client {
buf := make([]byte, 0, quicvarint.Len(FrameTypeTCPRequest)+len(b))
buf = quicvarint.Append(buf, FrameTypeTCPRequest)
buf = append(buf, b...)
_, err := i.stream.Write(buf)
if err != nil {
return 0, err
}
i.client = false
return len(b), nil
func (c *interConn) Write(b []byte) (int, error) {
if c.client {
c.client = false
if _, err := c.stream.Write(append(quicvarint.Append(nil, FrameTypeTCPRequest), b...)); err != nil {
return 0, err
}
return len(b), nil
}
return i.stream.Write(b)
return c.stream.Write(b)
}
func (i *interConn) Close() error {
i.stream.CancelRead(0)
return i.stream.Close()
func (c *interConn) Close() error {
c.stream.CancelRead(0)
return c.stream.Close()
}
func (i *interConn) LocalAddr() net.Addr {
return i.local
func (c *interConn) LocalAddr() net.Addr {
return c.local
}
func (i *interConn) RemoteAddr() net.Addr {
return i.remote
func (c *interConn) RemoteAddr() net.Addr {
return c.remote
}
func (i *interConn) SetDeadline(t time.Time) error {
return i.stream.SetDeadline(t)
func (c *interConn) SetDeadline(t time.Time) error {
return c.stream.SetDeadline(t)
}
func (i *interConn) SetReadDeadline(t time.Time) error {
return i.stream.SetReadDeadline(t)
func (c *interConn) SetReadDeadline(t time.Time) error {
return c.stream.SetReadDeadline(t)
}
func (i *interConn) SetWriteDeadline(t time.Time) error {
return i.stream.SetWriteDeadline(t)
func (c *interConn) SetWriteDeadline(t time.Time) error {
return c.stream.SetWriteDeadline(t)
}
type InterUdpConn struct {
conn *quic.Conn
type InterConn struct {
local net.Addr
remote net.Addr
id uint32
ch chan []byte
id uint32
ch chan []byte
time time.Time
mutex sync.Mutex
closed bool
closed bool
closeFunc func()
last time.Time
mutex sync.Mutex
user *protocol.MemoryUser
write func(p []byte) error
close func()
user *protocol.MemoryUser
}
func (i *InterUdpConn) User() *protocol.MemoryUser {
func (i *InterConn) User() *protocol.MemoryUser {
return i.user
}
func (i *InterUdpConn) SetLast() {
i.mutex.Lock()
defer i.mutex.Unlock()
i.last = time.Now()
func (c *InterConn) Time() time.Time {
c.mutex.Lock()
v := c.time
c.mutex.Unlock()
return v
}
func (i *InterUdpConn) GetLast() time.Time {
i.mutex.Lock()
defer i.mutex.Unlock()
return i.last
func (c *InterConn) Update() {
c.mutex.Lock()
c.time = time.Now()
c.mutex.Unlock()
}
func (i *InterUdpConn) Read(p []byte) (int, error) {
b, ok := <-i.ch
func (c *InterConn) Read(p []byte) (int, error) {
b, ok := <-c.ch
if !ok {
return 0, io.EOF
}
n := copy(p, b)
if n != len(b) {
if len(p) < len(b) {
return 0, io.ErrShortBuffer
}
i.SetLast()
return n, nil
c.Update()
return copy(p, b), nil
}
func (i *InterUdpConn) Write(p []byte) (int, error) {
i.SetLast()
binary.BigEndian.PutUint32(p, i.id)
if err := i.conn.SendDatagram(p); err != nil {
func (c *InterConn) Write(p []byte) (int, error) {
if c.closed {
return 0, io.ErrClosedPipe
}
binary.BigEndian.PutUint32(p, c.id)
if err := c.write(p); err != nil {
return 0, err
}
c.Update()
return len(p), nil
}
func (i *InterUdpConn) Close() error {
i.closeFunc()
func (c *InterConn) Close() error {
c.close()
return nil
}
func (i *InterUdpConn) LocalAddr() net.Addr {
return i.local
func (c *InterConn) LocalAddr() net.Addr {
return c.local
}
func (i *InterUdpConn) RemoteAddr() net.Addr {
return i.remote
func (c *InterConn) RemoteAddr() net.Addr {
return c.remote
}
func (i *InterUdpConn) SetDeadline(t time.Time) error {
func (c *InterConn) SetDeadline(t time.Time) error {
return nil
}
func (i *InterUdpConn) SetReadDeadline(t time.Time) error {
func (c *InterConn) SetReadDeadline(t time.Time) error {
return nil
}
func (i *InterUdpConn) SetWriteDeadline(t time.Time) error {
func (c *InterConn) SetWriteDeadline(t time.Time) error {
return nil
}
type udpSessionManager struct {
sync.RWMutex
conn *quic.Conn
m map[uint32]*InterConn
next uint32
closed bool
addConn internet.ConnHandler
udpIdleTimeout time.Duration
user *protocol.MemoryUser
}
func (m *udpSessionManager) close(udpConn *InterConn) {
if !udpConn.closed {
udpConn.closed = true
close(udpConn.ch)
delete(m.m, udpConn.id)
}
}
func (m *udpSessionManager) clean() {
ticker := time.NewTicker(idleCleanupInterval)
defer ticker.Stop()
for range ticker.C {
if m.closed {
return
}
m.RLock()
now := time.Now()
timeoutConn := make([]*InterConn, 0, len(m.m))
for _, udpConn := range m.m {
if now.Sub(udpConn.Time()) > m.udpIdleTimeout {
timeoutConn = append(timeoutConn, udpConn)
}
}
m.RUnlock()
for _, udpConn := range timeoutConn {
m.Lock()
m.close(udpConn)
m.Unlock()
}
}
}
func (m *udpSessionManager) run() {
for {
d, err := m.conn.ReceiveDatagram(context.Background())
if err != nil {
break
}
if len(d) < 4 {
continue
}
id := binary.BigEndian.Uint32(d[:4])
m.feed(id, d)
}
m.Lock()
defer m.Unlock()
m.closed = true
for _, udpConn := range m.m {
m.close(udpConn)
}
}
func (m *udpSessionManager) udp() (*InterConn, error) {
m.Lock()
defer m.Unlock()
if m.closed {
return nil, errors.New("closed")
}
udpConn := &InterConn{
local: m.conn.LocalAddr(),
remote: m.conn.RemoteAddr(),
id: m.next,
ch: make(chan []byte, udpMessageChanSize),
}
udpConn.write = m.conn.SendDatagram
udpConn.close = func() {
m.Lock()
m.close(udpConn)
m.Unlock()
}
m.m[m.next] = udpConn
m.next++
return udpConn, nil
}
func (m *udpSessionManager) feed(id uint32, d []byte) {
m.RLock()
udpConn, ok := m.m[id]
if ok {
select {
case udpConn.ch <- d:
default:
}
m.RUnlock()
return
}
m.RUnlock()
if m.addConn == nil {
return
}
m.Lock()
defer m.Unlock()
udpConn, ok = m.m[id]
if !ok {
udpConn = &InterConn{
local: m.conn.LocalAddr(),
remote: m.conn.RemoteAddr(),
id: id,
ch: make(chan []byte, udpMessageChanSize),
time: time.Now(),
}
udpConn.write = m.conn.SendDatagram
udpConn.close = func() {
m.Lock()
m.close(udpConn)
m.Unlock()
}
udpConn.user = m.user
m.m[id] = udpConn
m.addConn(udpConn)
}
select {
case udpConn.ch <- d:
default:
}
}

View File

@@ -3,11 +3,10 @@ package hysteria
import (
"context"
go_tls "crypto/tls"
"encoding/binary"
"math/rand"
"net/http"
"net/url"
"reflect"
"runtime"
"strconv"
"sync"
"time"
@@ -18,8 +17,6 @@ import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/common/task"
hyCtx "github.com/xtls/xray-core/proxy/hysteria/ctx"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
@@ -29,107 +26,25 @@ import (
"github.com/xtls/xray-core/transport/internet/tls"
)
type udpSessionManagerClient struct {
conn *quic.Conn
m map[uint32]*InterUdpConn
next uint32
closed bool
mutex sync.RWMutex
}
func (m *udpSessionManagerClient) close(udpConn *InterUdpConn) {
if !udpConn.closed {
udpConn.closed = true
close(udpConn.ch)
delete(m.m, udpConn.id)
}
}
func (m *udpSessionManagerClient) run() {
for {
d, err := m.conn.ReceiveDatagram(context.Background())
if err != nil {
break
}
if len(d) < 4 {
continue
}
id := binary.BigEndian.Uint32(d[:4])
m.feed(id, d)
}
m.mutex.Lock()
defer m.mutex.Unlock()
m.closed = true
for _, udpConn := range m.m {
m.close(udpConn)
}
}
func (m *udpSessionManagerClient) udp() (*InterUdpConn, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
if m.closed {
return nil, errors.New("closed")
}
udpConn := &InterUdpConn{
conn: m.conn,
local: m.conn.LocalAddr(),
remote: m.conn.RemoteAddr(),
id: m.next,
ch: make(chan []byte, udpMessageChanSize),
}
udpConn.closeFunc = func() {
m.mutex.Lock()
defer m.mutex.Unlock()
m.close(udpConn)
}
m.m[m.next] = udpConn
m.next++
return udpConn, nil
}
func (m *udpSessionManagerClient) feed(id uint32, d []byte) {
m.mutex.RLock()
defer m.mutex.RUnlock()
udpConn, ok := m.m[id]
if !ok {
return
}
select {
case udpConn.ch <- d:
default:
}
}
type client struct {
ctx context.Context
sync.Mutex
dest net.Destination
pktConn net.PacketConn
conn *quic.Conn
config *Config
tlsConfig *go_tls.Config
socketConfig *internet.SocketConfig
udpmaskManager *finalmask.UdpmaskManager
quicParams *internet.QuicParams
udpSM *udpSessionManagerClient
mutex sync.Mutex
conn *quic.Conn
tr *quic.Transport
pktConn net.PacketConn
udpSM *udpSessionManager
}
func (c *client) status() Status {
func (c *client) status() status {
if c.conn == nil {
return StatusUnknown
return StatusNull
}
select {
case <-c.conn.Context().Done():
@@ -140,10 +55,12 @@ func (c *client) status() Status {
}
func (c *client) close() {
_ = c.conn.CloseWithError(closeErrCodeOK, "")
_ = c.pktConn.Close()
c.pktConn = nil
c.conn.CloseWithError(closeErrCodeOK, "")
c.tr.Close()
c.pktConn.Close()
c.conn = nil
c.tr = nil
c.pktConn = nil
c.udpSM = nil
}
@@ -164,61 +81,6 @@ func (c *client) dial() error {
}
}
var index int
if len(quicParams.UdpHop.Ports) > 0 {
index = rand.Intn(len(quicParams.UdpHop.Ports))
c.dest.Port = net.Port(quicParams.UdpHop.Ports[index])
}
raw, err := internet.DialSystem(c.ctx, c.dest, c.socketConfig)
if err != nil {
return errors.New("failed to dial to dest").Base(err)
}
var pktConn net.PacketConn
var remote *net.UDPAddr
switch conn := raw.(type) {
case *internet.PacketConnWrapper:
pktConn = conn.PacketConn
remote = conn.RemoteAddr().(*net.UDPAddr)
case *net.UDPConn:
pktConn = conn
remote = conn.RemoteAddr().(*net.UDPAddr)
case *cnc.Connection:
fakeConn := &internet.FakePacketConn{Conn: conn}
pktConn = fakeConn
remote = fakeConn.RemoteAddr().(*net.UDPAddr)
if len(quicParams.UdpHop.Ports) > 0 {
raw.Close()
return errors.New("udphop requires being at the outermost level")
}
default:
raw.Close()
return errors.New("unknown conn ", reflect.TypeOf(conn))
}
if len(quicParams.UdpHop.Ports) > 0 {
addr := &udphop.UDPHopAddr{
IP: remote.IP,
Ports: quicParams.UdpHop.Ports,
}
pktConn, err = udphop.NewUDPHopPacketConn(addr, index, quicParams.UdpHop.IntervalMin, quicParams.UdpHop.IntervalMax, c.udphopDialer, pktConn)
if err != nil {
raw.Close()
return errors.New("udphop err").Base(err)
}
}
if c.udpmaskManager != nil {
pktConn, err = c.udpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
raw.Close()
return errors.New("mask err").Base(err)
}
}
quicConfig := &quic.Config{
InitialStreamReceiveWindow: quicParams.InitStreamReceiveWindow,
MaxStreamReceiveWindow: quicParams.MaxStreamReceiveWindow,
@@ -226,9 +88,10 @@ func (c *client) dial() error {
MaxConnectionReceiveWindow: quicParams.MaxConnReceiveWindow,
MaxIdleTimeout: time.Duration(quicParams.MaxIdleTimeout) * time.Second,
KeepAlivePeriod: time.Duration(quicParams.KeepAlivePeriod) * time.Second,
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery,
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery || (runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin"),
EnableDatagrams: true,
MaxDatagramFrameSize: MaxDatagramFrameSize,
OmitMaxDatagramFrameSize: time.Now().After(time.Date(2026, 9, 1, 0, 0, 0, 0, time.UTC)),
DisablePathManager: true,
}
if quicParams.InitStreamReceiveWindow == 0 {
@@ -250,16 +113,56 @@ func (c *client) dial() error {
// quicConfig.KeepAlivePeriod = 10 * time.Second
// }
var quicConn *quic.Conn
var pktConn net.PacketConn
var udpAddr *net.UDPAddr
var err error
udpAddr, err = net.ResolveUDPAddr("udp", c.dest.NetAddr())
if err != nil {
return err
}
if len(quicParams.UdpHop.Ports) > 0 {
pktConn, err = udphop.NewUDPHopPacketConn(udphop.ToAddrs(udpAddr.IP, quicParams.UdpHop.Ports), time.Duration(quicParams.UdpHop.IntervalMin)*time.Second, time.Duration(quicParams.UdpHop.IntervalMax)*time.Second, c.udpHopDialer)
if err != nil {
return err
}
} else {
conn, err := internet.DialSystem(context.Background(), c.dest, c.socketConfig)
if err != nil {
return err
}
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
case *net.UDPConn:
pktConn = c
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
default:
panic(reflect.TypeOf(c))
}
}
if c.udpmaskManager != nil {
newConn, err := c.udpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
pktConn.Close()
return errors.New("mask err").Base(err)
}
pktConn = newConn
}
tr := &quic.Transport{Conn: pktConn}
var conn *quic.Conn
rt := &http3.Transport{
TLSClientConfig: c.tlsConfig,
QUICConfig: quicConfig,
Dial: func(ctx context.Context, _ string, tlsCfg *go_tls.Config, cfg *quic.Config) (*quic.Conn, error) {
qc, err := quic.DialEarly(ctx, pktConn, remote, tlsCfg, cfg)
qc, err := tr.DialEarly(ctx, udpAddr, tlsCfg, cfg)
if err != nil {
return nil, err
}
quicConn = qc
conn = qc
return qc, nil
},
}
@@ -273,75 +176,61 @@ func (c *client) dial() error {
Header: http.Header{
RequestHeaderAuth: []string{c.config.Auth},
CommonHeaderCCRX: []string{strconv.FormatUint(quicParams.BrutalDown, 10)},
CommonHeaderPadding: []string{authRequestPadding.String()},
CommonHeaderPadding: []string{AuthRequestPadding.String()},
},
}
resp, err := rt.RoundTrip(req)
if err != nil {
if quicConn != nil {
_ = quicConn.CloseWithError(closeErrCodeProtocolError, "")
if conn != nil {
_ = conn.CloseWithError(closeErrCodeProtocolError, "")
}
_ = tr.Close()
_ = pktConn.Close()
return errors.New("RoundTrip err").Base(err)
return err
}
if resp.StatusCode != StatusAuthOK {
_ = quicConn.CloseWithError(closeErrCodeProtocolError, "")
_ = conn.CloseWithError(closeErrCodeProtocolError, "")
_ = tr.Close()
_ = pktConn.Close()
return errors.New("auth failed")
return errors.New("auth failed code ", resp.StatusCode)
}
_ = resp.Body.Close()
serverUdp, _ := strconv.ParseBool(resp.Header.Get(ResponseHeaderUDPEnabled))
serverAuto := resp.Header.Get(CommonHeaderCCRX)
serverDown, _ := strconv.ParseUint(serverAuto, 10, 64)
// udp, _ := strconv.ParseBool(resp.Header.Get(ResponseHeaderUDPEnabled))
down, _ := strconv.ParseUint(resp.Header.Get(CommonHeaderCCRX), 10, 64)
switch quicParams.Congestion {
case "reno":
errors.LogDebug(c.ctx, "congestion reno")
case "bbr":
errors.LogDebug(c.ctx, "congestion bbr ", quicParams.BbrProfile)
congestion.UseBBR(quicConn, bbr.Profile(quicParams.BbrProfile))
case "brutal", "":
if serverAuto == "auto" || quicParams.BrutalUp == 0 || serverDown == 0 {
errors.LogDebug(c.ctx, "congestion bbr ", quicParams.BbrProfile)
congestion.UseBBR(quicConn, bbr.Profile(quicParams.BbrProfile))
congestion.UseBBR(conn, bbr.Profile(quicParams.BbrProfile))
case "", "brutal":
if quicParams.BrutalUp == 0 || down == 0 {
congestion.UseBBR(conn, bbr.Profile(quicParams.BbrProfile))
} else {
errors.LogDebug(c.ctx, "congestion brutal bytes per second ", min(quicParams.BrutalUp, serverDown))
congestion.UseBrutal(quicConn, min(quicParams.BrutalUp, serverDown))
congestion.UseBrutal(conn, min(quicParams.BrutalUp, down))
}
case "force-brutal":
errors.LogDebug(c.ctx, "congestion brutal bytes per second ", quicParams.BrutalUp)
congestion.UseBrutal(quicConn, quicParams.BrutalUp)
congestion.UseBrutal(conn, quicParams.BrutalUp)
default:
errors.LogDebug(c.ctx, "congestion reno")
panic(quicParams.Congestion)
}
c.pktConn = pktConn
c.conn = quicConn
if serverUdp {
c.udpSM = &udpSessionManagerClient{
conn: quicConn,
m: make(map[uint32]*InterUdpConn),
next: 1,
}
go c.udpSM.run()
c.tr = tr
c.conn = conn
c.udpSM = &udpSessionManager{
conn: conn,
m: make(map[uint32]*InterConn),
next: 1,
}
go c.udpSM.run()
return nil
}
func (c *client) clean() {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.status() == StatusInactive {
c.close()
}
}
func (c *client) tcp() (stat.Connection, error) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.Lock()
defer c.Unlock()
err := c.dial()
if err != nil {
@@ -363,59 +252,43 @@ func (c *client) tcp() (stat.Connection, error) {
}
func (c *client) udp() (stat.Connection, error) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.Lock()
defer c.Unlock()
err := c.dial()
if err != nil {
return nil, err
}
if c.udpSM == nil {
return nil, errors.New("server does not support udp")
}
return c.udpSM.udp()
}
func (c *client) setCtx(ctx context.Context) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.ctx = ctx
func (c *client) clean() {
c.Lock()
if c.status() == StatusInactive {
c.close()
}
c.Unlock()
}
func (c *client) udphopDialer(addr *net.UDPAddr) (net.PacketConn, error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.status() != StatusActive {
errors.LogDebug(context.Background(), "skip hop: disconnected QUIC")
return nil, errors.New()
}
raw, err := internet.DialSystem(c.ctx, net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), c.socketConfig)
func (c *client) udpHopDialer(addr *net.UDPAddr) (net.PacketConn, error) {
conn, err := internet.DialSystem(context.Background(), net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), c.socketConfig)
if err != nil {
errors.LogDebug(context.Background(), "skip hop: failed to dial to dest")
raw.Close()
return nil, errors.New()
errors.LogInfoInner(context.Background(), err, "skip hop: failed to dial to dest")
return nil, errors.New("failed to dial to dest").Base(err)
}
var pktConn net.PacketConn
switch conn := raw.(type) {
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = conn.PacketConn
pktConn = c.PacketConn
case *net.UDPConn:
pktConn = conn
case *cnc.Connection:
errors.LogDebug(context.Background(), "skip hop: udphop requires being at the outermost level")
raw.Close()
return nil, errors.New()
pktConn = c
default:
errors.LogDebug(context.Background(), "skip hop: unknown conn ", reflect.TypeOf(conn))
raw.Close()
return nil, errors.New()
errors.LogInfo(context.Background(), "skip hop: invalid conn ", reflect.TypeOf(c))
conn.Close()
return nil, errors.New("invalid conn ", reflect.TypeOf(c))
}
return pktConn, nil
@@ -427,16 +300,18 @@ type dialerConf struct {
}
type clientManager struct {
m map[dialerConf]*client
mutex sync.Mutex
sync.RWMutex
m map[dialerConf]*client
}
func (m *clientManager) clean() {
m.mutex.Lock()
defer m.mutex.Unlock()
for _, c := range m.m {
c.clean()
ticker := time.NewTicker(idleCleanupInterval)
for range ticker.C {
m.RLock()
for _, c := range m.m {
c.clean()
}
m.RUnlock()
}
}
@@ -449,41 +324,38 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
return nil, errors.New("tls config is nil")
}
requireDatagram := hyCtx.RequireDatagramFromContext(ctx)
datagram := DatagramFromContext(ctx)
dest.Network = net.Network_UDP
config := streamSettings.ProtocolSettings.(*Config)
initmanager.Do(func() {
manager = &clientManager{
m: make(map[dialerConf]*client),
}
(&task.Periodic{
Interval: 30 * time.Second,
Execute: func() error {
manager.clean()
return nil
},
}).Start()
go manager.clean()
})
manager.mutex.Lock()
c, ok := manager.m[dialerConf{Destination: dest, MemoryStreamConfig: streamSettings}]
if !ok {
c = &client{
ctx: ctx,
dest: dest,
config: config,
tlsConfig: tlsConfig.GetTLSConfig(),
socketConfig: streamSettings.SocketSettings,
udpmaskManager: streamSettings.UdpmaskManager,
quicParams: streamSettings.QuicParams,
}
manager.m[dialerConf{Destination: dest, MemoryStreamConfig: streamSettings}] = c
}
c.setCtx(ctx)
manager.mutex.Unlock()
manager.RLock()
c := manager.m[dialerConf{dest, streamSettings}]
manager.RUnlock()
if requireDatagram {
if c == nil {
manager.Lock()
c = manager.m[dialerConf{dest, streamSettings}]
if c == nil {
c = &client{
dest: dest,
config: streamSettings.ProtocolSettings.(*Config),
tlsConfig: tlsConfig.GetTLSConfig(),
socketConfig: streamSettings.SocketSettings,
udpmaskManager: streamSettings.UdpmaskManager,
quicParams: streamSettings.QuicParams,
}
manager.m[dialerConf{dest, streamSettings}] = c
}
manager.Unlock()
}
if datagram {
return c.udp()
}
return c.tcp()

View File

@@ -3,10 +3,10 @@ package hysteria
import (
"context"
gotls "crypto/tls"
"encoding/binary"
"net/http"
"net/http/httputil"
"net/url"
"runtime"
"strconv"
"strings"
"sync"
@@ -20,158 +20,41 @@ import (
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/proxy/hysteria/account"
hyCtx "github.com/xtls/xray-core/proxy/hysteria/ctx"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr"
"github.com/xtls/xray-core/transport/internet/tls"
)
type udpSessionManagerServer struct {
conn *quic.Conn
m map[uint32]*InterUdpConn
addConn internet.ConnHandler
stopCh chan struct{}
udpIdleTimeout time.Duration
mutex sync.RWMutex
type httpHandler struct {
sync.Mutex
validator *account.Validator
config *Config
masqHandler http.Handler
quicParams *internet.QuicParams
addConn internet.ConnHandler
conn *quic.Conn
auth bool
user *protocol.MemoryUser
}
func (m *udpSessionManagerServer) close(udpConn *InterUdpConn) {
if !udpConn.closed {
udpConn.closed = true
close(udpConn.ch)
delete(m.m, udpConn.id)
}
}
func (m *udpSessionManagerServer) clean() {
ticker := time.NewTicker(idleCleanupInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
m.mutex.RLock()
now := time.Now()
timeoutConn := make([]*InterUdpConn, 0, len(m.m))
for _, udpConn := range m.m {
if now.Sub(udpConn.GetLast()) > m.udpIdleTimeout {
timeoutConn = append(timeoutConn, udpConn)
}
}
m.mutex.RUnlock()
for _, udpConn := range timeoutConn {
m.mutex.Lock()
m.close(udpConn)
m.mutex.Unlock()
}
case <-m.stopCh:
return
}
}
}
func (m *udpSessionManagerServer) run() {
for {
d, err := m.conn.ReceiveDatagram(context.Background())
if err != nil {
break
}
if len(d) < 4 {
continue
}
id := binary.BigEndian.Uint32(d[:4])
m.feed(id, d)
}
m.mutex.Lock()
defer m.mutex.Unlock()
close(m.stopCh)
for _, udpConn := range m.m {
m.close(udpConn)
}
}
func (m *udpSessionManagerServer) feed(id uint32, d []byte) {
m.mutex.RLock()
udpConn, ok := m.m[id]
if ok {
select {
case udpConn.ch <- d:
default:
}
m.mutex.RUnlock()
return
}
m.mutex.RUnlock()
m.mutex.Lock()
defer m.mutex.Unlock()
udpConn, ok = m.m[id]
if !ok {
udpConn = &InterUdpConn{
conn: m.conn,
local: m.conn.LocalAddr(),
remote: m.conn.RemoteAddr(),
id: id,
ch: make(chan []byte, udpMessageChanSize),
last: time.Now(),
user: m.user,
}
udpConn.closeFunc = func() {
m.mutex.Lock()
m.close(udpConn)
m.mutex.Unlock()
}
m.m[id] = udpConn
m.addConn(udpConn)
}
select {
case udpConn.ch <- d:
default:
}
}
type httpHandler struct {
ctx context.Context
conn *quic.Conn
addConn internet.ConnHandler
config *Config
quicParams *internet.QuicParams
validator *account.Validator
masqHandler http.Handler
auth bool
mutex sync.Mutex
user *protocol.MemoryUser
}
func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (h *httpHandler) AuthHTTP(w http.ResponseWriter, r *http.Request) bool {
if r.Method == http.MethodPost && r.Host == URLHost && r.URL.Path == URLPath {
h.mutex.Lock()
defer h.mutex.Unlock()
h.Lock()
defer h.Unlock()
if h.auth {
w.Header().Set(ResponseHeaderUDPEnabled, strconv.FormatBool(hyCtx.RequireDatagramFromContext(h.ctx)))
w.Header().Set(ResponseHeaderUDPEnabled, strconv.FormatBool(h.validator != nil))
w.Header().Set(CommonHeaderCCRX, strconv.FormatUint(h.quicParams.BrutalDown, 10))
w.Header().Set(CommonHeaderPadding, authResponsePadding.String())
w.Header().Set(CommonHeaderPadding, AuthResponsePadding.String())
w.WriteHeader(StatusAuthOK)
return
return true
}
auth := r.Header.Get(RequestHeaderAuth)
clientDown, _ := strconv.ParseUint(r.Header.Get(CommonHeaderCCRX), 10, 64)
down, _ := strconv.ParseUint(r.Header.Get(CommonHeaderCCRX), 10, 64)
var user *protocol.MemoryUser
var ok bool
@@ -185,49 +68,51 @@ func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.auth = true
h.user = user
switch h.quicParams.Congestion {
conn := h.conn
quicParams := h.quicParams
switch quicParams.Congestion {
case "reno":
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion reno")
case "bbr":
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion bbr ", h.quicParams.BbrProfile)
congestion.UseBBR(h.conn, bbr.Profile(h.quicParams.BbrProfile))
case "brutal", "":
if h.quicParams.BrutalUp == 0 || clientDown == 0 {
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion bbr ", h.quicParams.BbrProfile)
congestion.UseBBR(h.conn, bbr.Profile(h.quicParams.BbrProfile))
congestion.UseBBR(conn, bbr.Profile(quicParams.BbrProfile))
case "", "brutal":
if quicParams.BrutalUp == 0 || down == 0 {
congestion.UseBBR(conn, bbr.Profile(quicParams.BbrProfile))
} else {
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion brutal bytes per second ", min(h.quicParams.BrutalUp, clientDown))
congestion.UseBrutal(h.conn, min(h.quicParams.BrutalUp, clientDown))
congestion.UseBrutal(conn, min(quicParams.BrutalUp, down))
}
case "force-brutal":
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion brutal bytes per second ", h.quicParams.BrutalUp)
congestion.UseBrutal(h.conn, h.quicParams.BrutalUp)
congestion.UseBrutal(conn, quicParams.BrutalUp)
default:
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion reno")
panic(quicParams.Congestion)
}
if hyCtx.RequireDatagramFromContext(h.ctx) {
udpSM := &udpSessionManagerServer{
conn: h.conn,
m: make(map[uint32]*InterUdpConn),
addConn: h.addConn,
stopCh: make(chan struct{}),
udpIdleTimeout: time.Duration(h.config.UdpIdleTimeout) * time.Second,
if h.validator != nil {
udpSM := &udpSessionManager{
conn: h.conn,
m: make(map[uint32]*InterConn),
user: h.user,
addConn: h.addConn,
udpIdleTimeout: time.Duration(h.config.UdpIdleTimeout) * time.Second,
user: h.user,
}
go udpSM.clean()
go udpSM.run()
}
w.Header().Set(ResponseHeaderUDPEnabled, strconv.FormatBool(hyCtx.RequireDatagramFromContext(h.ctx)))
w.Header().Set(ResponseHeaderUDPEnabled, strconv.FormatBool(h.validator != nil))
w.Header().Set(CommonHeaderCCRX, strconv.FormatUint(h.quicParams.BrutalDown, 10))
w.Header().Set(CommonHeaderPadding, authResponsePadding.String())
w.Header().Set(CommonHeaderPadding, AuthResponsePadding.String())
w.WriteHeader(StatusAuthOK)
return
return true
}
}
return false
}
func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if h.AuthHTTP(w, r) {
return
}
h.masqHandler.ServeHTTP(w, r)
}
@@ -256,42 +141,41 @@ func (h *httpHandler) StreamDispatcher(ft http3.FrameType, stream *quic.Stream,
}
type Listener struct {
ctx context.Context
pktConn net.PacketConn
listener *quic.Listener
addConn internet.ConnHandler
config *Config
quicParams *internet.QuicParams
validator *account.Validator
config *Config
masqHandler http.Handler
quicParams *internet.QuicParams
addConn internet.ConnHandler
pktConn net.PacketConn
tr *quic.Transport
listener *quic.Listener
}
func (l *Listener) handleClient(conn *quic.Conn) {
handler := &httpHandler{
ctx: l.ctx,
conn: conn,
addConn: l.addConn,
config: l.config,
quicParams: l.quicParams,
validator: l.validator,
config: l.config,
masqHandler: l.masqHandler,
quicParams: l.quicParams,
addConn: l.addConn,
conn: conn,
}
h3 := http3.Server{
h3s := http3.Server{
Handler: handler,
StreamDispatcher: handler.StreamDispatcher,
}
err := h3.ServeQUICConn(conn)
_ = h3s.ServeQUICConn(conn)
_ = conn.CloseWithError(closeErrCodeOK, "")
errors.LogDebug(context.Background(), conn.RemoteAddr(), " disconnected with err ", err)
}
func (l *Listener) keepAccepting() {
for {
conn, err := l.listener.Accept(context.Background())
if err != nil {
errors.LogInfoInner(context.Background(), err, "failed to accept QUIC connection")
if err != quic.ErrServerClosed {
errors.LogErrorInner(context.Background(), err, "failed to serve hysteria")
}
break
}
go l.handleClient(conn)
@@ -303,9 +187,7 @@ func (l *Listener) Addr() net.Addr {
}
func (l *Listener) Close() error {
err := l.listener.Close()
_ = l.pktConn.Close()
return err
return errors.Combine(l.listener.Close(), l.tr.Close(), l.pktConn.Close())
}
func Listen(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) {
@@ -318,11 +200,10 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
return nil, errors.New("tls config is nil")
}
validator := ValidatorFromContext(ctx)
config := streamSettings.ProtocolSettings.(*Config)
validator := hyCtx.ValidatorFromContext(ctx)
if config.Auth == "" && validator == nil {
if validator == nil && config.Auth == "" {
return nil, errors.New("validator is nil")
}
@@ -372,22 +253,6 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
return nil, errors.New("unknown masq type")
}
raw, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: address.IP(), Port: int(port)}, streamSettings.SocketSettings)
if err != nil {
return nil, err
}
var pktConn net.PacketConn
pktConn = raw
if streamSettings.UdpmaskManager != nil {
pktConn, err = streamSettings.UdpmaskManager.WrapPacketConnServer(raw)
if err != nil {
raw.Close()
return nil, errors.New("mask err").Base(err)
}
}
quicParams := streamSettings.QuicParams
if quicParams == nil {
quicParams = &internet.QuicParams{
@@ -403,9 +268,10 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
MaxConnectionReceiveWindow: quicParams.MaxConnReceiveWindow,
MaxIdleTimeout: time.Duration(quicParams.MaxIdleTimeout) * time.Second,
MaxIncomingStreams: quicParams.MaxIncomingStreams,
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery,
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery || (runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin"),
EnableDatagrams: true,
MaxDatagramFrameSize: MaxDatagramFrameSize,
AssumePeerMaxDatagramFrameSize: MaxDatagramFrameSize,
DisablePathManager: true,
}
if quicParams.InitStreamReceiveWindow == 0 {
@@ -427,27 +293,44 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
quicConfig.MaxIncomingStreams = 1024
}
qListener, err := quic.Listen(pktConn, tlsConfig.GetTLSConfig(), quicConfig)
pktConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: address.IP(), Port: int(port)}, streamSettings.SocketSettings)
if err != nil {
return nil, err
}
if streamSettings.UdpmaskManager != nil {
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnServer(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
}
tr := &quic.Transport{Conn: pktConn}
listener, err := tr.Listen(tlsConfig.GetTLSConfig(), quicConfig)
if err != nil {
_ = tr.Close()
_ = pktConn.Close()
return nil, err
}
listener := &Listener{
ctx: ctx,
pktConn: pktConn,
listener: qListener,
addConn: handler,
config: config,
quicParams: quicParams,
l := &Listener{
validator: validator,
config: config,
masqHandler: masqHandler,
quicParams: quicParams,
addConn: handler,
pktConn: pktConn,
tr: tr,
listener: listener,
}
go listener.keepAccepting()
go l.keepAccepting()
return listener, nil
return l, nil
}
func init() {

View File

@@ -1,24 +0,0 @@
package padding
import (
"math/rand"
)
const (
paddingChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
)
// padding specifies a half-open range [Min, Max).
type Padding struct {
Min int
Max int
}
func (p Padding) String() string {
n := p.Min + rand.Intn(p.Max-p.Min)
bs := make([]byte, n)
for i := range bs {
bs[i] = paddingChars[rand.Intn(len(paddingChars))]
}
return string(bs)
}

View File

@@ -1,65 +0,0 @@
package udphop
import (
"fmt"
"net"
)
type InvalidPortError struct {
PortStr string
}
func (e InvalidPortError) Error() string {
return fmt.Sprintf("%s is not a valid port number or range", e.PortStr)
}
// UDPHopAddr contains an IP address and a list of ports.
type UDPHopAddr struct {
IP net.IP
Ports []uint32
PortStr string
}
func (a *UDPHopAddr) Network() string {
return "udphop"
}
func (a *UDPHopAddr) String() string {
return net.JoinHostPort(a.IP.String(), a.PortStr)
}
// addrs returns a list of net.Addr's, one for each port.
func (a *UDPHopAddr) addrs() ([]net.Addr, error) {
var addrs []net.Addr
for _, port := range a.Ports {
addr := &net.UDPAddr{
IP: a.IP,
Port: int(port),
}
addrs = append(addrs, addr)
}
return addrs, nil
}
// func ResolveUDPHopAddr(addr string) (*UDPHopAddr, error) {
// host, portStr, err := net.SplitHostPort(addr)
// if err != nil {
// return nil, err
// }
// ip, err := net.ResolveIPAddr("ip", host)
// if err != nil {
// return nil, err
// }
// result := &UDPHopAddr{
// IP: ip.IP,
// PortStr: portStr,
// }
// pu := utils.ParsePortUnion(portStr)
// if pu == nil {
// return nil, InvalidPortError{portStr}
// }
// result.Ports = pu.Ports()
// return result, nil
// }

View File

@@ -8,7 +8,6 @@ import (
"syscall"
"time"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/transport/internet/finalmask"
)
@@ -20,19 +19,19 @@ const (
)
type UdpHopPacketConn struct {
Addr net.Addr
Addrs []net.Addr
HopIntervalMin int64
HopIntervalMax int64
ListenUDPFunc ListenUDPFunc
HopIntervalMin time.Duration
HopIntervalMax time.Duration
ListenUDPFunc func(addr *net.UDPAddr) (net.PacketConn, error)
connMutex sync.RWMutex
prevConn net.PacketConn
currentConn net.PacketConn
addrIndex int
readBufferSize int
writeBufferSize int
deadline time.Time
readDeadline time.Time
writeDeadline time.Time
recvQueue chan *udpPacket
closeChan chan struct{}
@@ -48,41 +47,36 @@ type udpPacket struct {
Err error
}
type ListenUDPFunc = func(*net.UDPAddr) (net.PacketConn, error)
func NewUDPHopPacketConn(addr *UDPHopAddr, index int, intervalMin int64, intervalMax int64, listenUDPFunc ListenUDPFunc, pktConn net.PacketConn) (net.PacketConn, error) {
if intervalMin == 0 || intervalMax == 0 {
intervalMin = int64(defaultHopInterval)
intervalMax = int64(defaultHopInterval)
func NewUDPHopPacketConn(addrs []net.Addr, hopIntervalMin time.Duration, hopIntervalMax time.Duration, listenUDPFunc func(addr *net.UDPAddr) (net.PacketConn, error)) (net.PacketConn, error) {
if len(addrs) == 0 {
panic("len(addrs) == 0")
}
if intervalMin < 5 || intervalMax < 5 {
return nil, errors.New("hop interval must be at least 5 seconds")
if hopIntervalMin == 0 {
hopIntervalMin = defaultHopInterval
}
if hopIntervalMax == 0 {
hopIntervalMax = defaultHopInterval
}
if hopIntervalMin < 5*time.Second {
panic("hopIntervalMin < 5*time.Second")
}
if hopIntervalMax < 5*time.Second {
panic("hopIntervalMax < 5*time.Second")
}
if hopIntervalMax < hopIntervalMin {
panic("hopIntervalMax < hopIntervalMin")
}
// if listenUDPFunc == nil {
// listenUDPFunc = func() (net.PacketConn, error) {
// return net.ListenUDP("udp", nil)
// }
// }
if listenUDPFunc == nil {
return nil, errors.New("nil listenUDPFunc")
panic("listenUDPFunc is nil")
}
addrs, err := addr.addrs()
if err != nil {
return nil, err
}
// curConn, err := listenUDPFunc()
// if err != nil {
// return nil, err
// }
hConn := &UdpHopPacketConn{
Addr: addr,
Addrs: addrs,
HopIntervalMin: intervalMin,
HopIntervalMax: intervalMax,
HopIntervalMin: hopIntervalMin,
HopIntervalMax: hopIntervalMax,
ListenUDPFunc: listenUDPFunc,
prevConn: nil,
currentConn: pktConn,
addrIndex: index,
currentConn: nil,
addrIndex: rand.Intn(len(addrs)),
recvQueue: make(chan *udpPacket, packetQueueSize),
closeChan: make(chan struct{}),
bufPool: sync.Pool{
@@ -91,7 +85,12 @@ func NewUDPHopPacketConn(addr *UDPHopAddr, index int, intervalMin int64, interva
},
},
}
go hConn.recvLoop(pktConn)
var err error
hConn.currentConn, err = listenUDPFunc(hConn.Addrs[hConn.addrIndex].(*net.UDPAddr))
if err != nil {
return nil, err
}
go hConn.recvLoop(hConn.currentConn)
go hConn.hopLoop()
return hConn, nil
}
@@ -104,69 +103,64 @@ func (u *UdpHopPacketConn) recvLoop(conn net.PacketConn) {
u.bufPool.Put(buf)
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
// Only pass through timeout errors here, not permanent errors
// like connection closed. Connection close is normal as we close
// the old connection to exit this loop every time we hop.
u.recvQueue <- &udpPacket{nil, 0, nil, netErr}
continue
}
return
}
select {
case u.recvQueue <- &udpPacket{buf, n, addr, nil}:
// Packet successfully queued
default:
// Queue is full, drop the packet
u.bufPool.Put(buf)
}
}
}
func (u *UdpHopPacketConn) hopLoop() {
ticker := time.NewTicker(time.Duration(crypto.RandBetween(u.HopIntervalMin, u.HopIntervalMax)) * time.Second)
defer ticker.Stop()
timer := time.NewTimer(u.nextHopInterval())
defer timer.Stop()
for {
select {
case <-ticker.C:
case <-timer.C:
u.hop()
ticker.Reset(time.Duration(crypto.RandBetween(u.HopIntervalMin, u.HopIntervalMax)) * time.Second)
timer.Reset(u.nextHopInterval())
case <-u.closeChan:
return
}
}
}
func (u *UdpHopPacketConn) nextHopInterval() time.Duration {
if u.HopIntervalMin == u.HopIntervalMax {
return u.HopIntervalMin
}
return u.HopIntervalMin + time.Duration(rand.Int63n(int64(u.HopIntervalMax-u.HopIntervalMin)+1))
}
func (u *UdpHopPacketConn) hop() {
u.connMutex.Lock()
defer u.connMutex.Unlock()
if u.closed {
return
}
// Update addrIndex to a new random value
u.addrIndex = rand.Intn(len(u.Addrs))
newConn, err := u.ListenUDPFunc(u.Addrs[u.addrIndex].(*net.UDPAddr))
if err != nil {
// Could be temporary, just skip this hop
return
}
// We need to keep receiving packets from the previous connection,
// because otherwise there will be packet loss due to the time gap
// between we hop to a new port and the server acknowledges this change.
// So we do the following:
// Close prevConn,
// move currentConn to prevConn,
// set newConn as currentConn,
// start recvLoop on newConn.
if u.prevConn != nil {
_ = u.prevConn.Close() // recvLoop for this conn will exit
_ = u.prevConn.Close()
}
u.prevConn = u.currentConn
u.currentConn = newConn
// Set buffer sizes if previously set
if u.readBufferSize > 0 {
_ = trySetReadBuffer(u.currentConn, u.readBufferSize)
if !u.deadline.IsZero() {
_ = u.currentConn.SetDeadline(u.deadline)
}
if u.writeBufferSize > 0 {
_ = trySetWriteBuffer(u.currentConn, u.writeBufferSize)
if !u.readDeadline.IsZero() {
_ = u.currentConn.SetReadDeadline(u.readDeadline)
}
if !u.writeDeadline.IsZero() {
_ = u.currentConn.SetWriteDeadline(u.writeDeadline)
}
go u.recvLoop(newConn)
}
@@ -178,11 +172,9 @@ func (u *UdpHopPacketConn) ReadFrom(b []byte) (n int, addr net.Addr, err error)
if p.Err != nil {
return 0, nil, p.Err
}
// Currently we do not check whether the packet is from
// the server or not due to performance reasons.
n := copy(b, p.Buf[:p.N])
u.bufPool.Put(p.Buf)
return n, u.Addr, nil
return n, p.Addr, nil
case <-u.closeChan:
return 0, nil, net.ErrClosed
}
@@ -195,8 +187,6 @@ func (u *UdpHopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
if u.closed {
return 0, net.ErrClosed
}
// Skip the check for now, always write to the server,
// for the same reason as in ReadFrom.
return u.currentConn.WriteTo(b, u.Addrs[u.addrIndex])
}
@@ -206,16 +196,13 @@ func (u *UdpHopPacketConn) Close() error {
if u.closed {
return nil
}
// Close prevConn and currentConn
// Close closeChan to unblock ReadFrom & hopLoop
// Set closed flag to true to prevent double close
if u.prevConn != nil {
_ = u.prevConn.Close()
}
err := u.currentConn.Close()
close(u.closeChan)
u.closed = true
u.Addrs = nil // For GC
u.Addrs = nil
return err
}
@@ -226,8 +213,11 @@ func (u *UdpHopPacketConn) LocalAddr() net.Addr {
}
func (u *UdpHopPacketConn) SetDeadline(t time.Time) error {
u.connMutex.RLock()
defer u.connMutex.RUnlock()
u.connMutex.Lock()
defer u.connMutex.Unlock()
u.deadline = t
u.readDeadline = t
u.writeDeadline = t
if u.prevConn != nil {
_ = u.prevConn.SetDeadline(t)
}
@@ -235,8 +225,10 @@ func (u *UdpHopPacketConn) SetDeadline(t time.Time) error {
}
func (u *UdpHopPacketConn) SetReadDeadline(t time.Time) error {
u.connMutex.RLock()
defer u.connMutex.RUnlock()
u.connMutex.Lock()
defer u.connMutex.Unlock()
u.deadline = time.Time{}
u.readDeadline = t
if u.prevConn != nil {
_ = u.prevConn.SetReadDeadline(t)
}
@@ -244,36 +236,16 @@ func (u *UdpHopPacketConn) SetReadDeadline(t time.Time) error {
}
func (u *UdpHopPacketConn) SetWriteDeadline(t time.Time) error {
u.connMutex.RLock()
defer u.connMutex.RUnlock()
u.connMutex.Lock()
defer u.connMutex.Unlock()
u.deadline = time.Time{}
u.writeDeadline = t
if u.prevConn != nil {
_ = u.prevConn.SetWriteDeadline(t)
}
return u.currentConn.SetWriteDeadline(t)
}
// UDP-specific methods below
func (u *UdpHopPacketConn) SetReadBuffer(bytes int) error {
u.connMutex.Lock()
defer u.connMutex.Unlock()
u.readBufferSize = bytes
if u.prevConn != nil {
_ = trySetReadBuffer(u.prevConn, bytes)
}
return trySetReadBuffer(u.currentConn, bytes)
}
func (u *UdpHopPacketConn) SetWriteBuffer(bytes int) error {
u.connMutex.Lock()
defer u.connMutex.Unlock()
u.writeBufferSize = bytes
if u.prevConn != nil {
_ = trySetWriteBuffer(u.prevConn, bytes)
}
return trySetWriteBuffer(u.currentConn, bytes)
}
func (u *UdpHopPacketConn) SyscallConn() (syscall.RawConn, error) {
u.connMutex.RLock()
defer u.connMutex.RUnlock()
@@ -284,22 +256,14 @@ func (u *UdpHopPacketConn) SyscallConn() (syscall.RawConn, error) {
return sc.SyscallConn()
}
func trySetReadBuffer(pc net.PacketConn, bytes int) error {
sc, ok := pc.(interface {
SetReadBuffer(bytes int) error
})
if ok {
return sc.SetReadBuffer(bytes)
func ToAddrs(ip net.IP, ports []uint32) []net.Addr {
var addrs []net.Addr
for _, port := range ports {
addr := &net.UDPAddr{
IP: ip,
Port: int(port),
}
addrs = append(addrs, addr)
}
return nil
}
func trySetWriteBuffer(pc net.PacketConn, bytes int) error {
sc, ok := pc.(interface {
SetWriteBuffer(bytes int) error
})
if ok {
return sc.SetWriteBuffer(bytes)
}
return nil
return addrs
}

View File

@@ -57,41 +57,27 @@ func DialKCP(ctx context.Context, dest net.Destination, streamSettings *internet
}
if streamSettings.UdpmaskManager != nil {
var pktConn net.PacketConn
var udpAddr = conn.RemoteAddr().(*net.UDPAddr)
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(c.PacketConn)
if err != nil {
conn.Close()
return nil, errors.New("mask err").Base(err)
}
c.PacketConn = pktConn
pktConn = c.PacketConn
case *net.UDPConn:
pktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(c)
if err != nil {
conn.Close()
return nil, errors.New("mask err").Base(err)
}
conn = &internet.PacketConnWrapper{
PacketConn: pktConn,
Dest: c.RemoteAddr().(*net.UDPAddr),
}
pktConn = c
case *cnc.Connection:
fakeConn := &internet.FakePacketConn{Conn: c}
pktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(fakeConn)
if err != nil {
conn.Close()
return nil, errors.New("mask err").Base(err)
}
conn = &internet.PacketConnWrapper{
PacketConn: pktConn,
Dest: &net.UDPAddr{
IP: []byte{0, 0, 0, 0},
Port: 0,
},
}
pktConn = &internet.FakePacketConn{Conn: c}
default:
conn.Close()
return nil, errors.New("unknown conn ", reflect.TypeOf(c))
panic(reflect.TypeOf(c))
}
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
conn = &internet.PacketConnWrapper{
PacketConn: pktConn,
Dest: udpAddr,
}
}

View File

@@ -5,11 +5,11 @@ import (
gotls "crypto/tls"
"fmt"
"io"
"math/rand"
"net/http"
"net/http/httptrace"
"net/url"
reflect "reflect"
"runtime"
"strconv"
"sync"
"sync/atomic"
@@ -21,6 +21,7 @@ import (
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/net/cnc"
"github.com/xtls/xray-core/common/signal/done"
"github.com/xtls/xray-core/common/uuid"
"github.com/xtls/xray-core/transport/internet"
@@ -173,7 +174,7 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
MaxIdleTimeout: time.Duration(quicParams.MaxIdleTimeout) * time.Second,
KeepAlivePeriod: time.Duration(quicParams.KeepAlivePeriod) * time.Second,
MaxIncomingStreams: quicParams.MaxIncomingStreams,
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery,
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery || (runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin"),
}
if quicParams.MaxIdleTimeout == 0 {
quicConfig.MaxIdleTimeout = net.ConnIdleTimeout
@@ -194,110 +195,83 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
QUICConfig: quicConfig,
TLSClientConfig: gotlsConfig,
Dial: func(ctx context.Context, addr string, tlsCfg *gotls.Config, cfg *quic.Config) (*quic.Conn, error) {
udphopDialer := func(addr *net.UDPAddr) (net.PacketConn, error) {
udpHopDialer := func(addr *net.UDPAddr) (net.PacketConn, error) {
conn, err := internet.DialSystem(ctx, net.UDPDestination(net.IPAddress(addr.IP), net.Port(addr.Port)), streamSettings.SocketSettings)
if err != nil {
errors.LogDebug(context.Background(), "skip hop: failed to dial to dest")
conn.Close()
return nil, errors.New()
errors.LogInfoInner(context.Background(), err, "skip hop: failed to dial to dest")
return nil, errors.New("failed to dial to dest").Base(err)
}
var udpConn net.PacketConn
var pktConn net.PacketConn
switch c := conn.(type) {
case *internet.PacketConnWrapper:
udpConn = c.PacketConn
pktConn = c.PacketConn
case *net.UDPConn:
udpConn = c
pktConn = c
default:
errors.LogDebug(context.Background(), "skip hop: udphop requires being at the outermost level ", reflect.TypeOf(c))
errors.LogInfo(context.Background(), "skip hop: invalid conn ", reflect.TypeOf(c))
conn.Close()
return nil, errors.New()
return nil, errors.New("invalid conn ", reflect.TypeOf(c))
}
return udpConn, nil
return pktConn, nil
}
var index int
if len(quicParams.UdpHop.Ports) > 0 {
index = rand.Intn(len(quicParams.UdpHop.Ports))
dest.Port = net.Port(quicParams.UdpHop.Ports[index])
}
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
var pktConn net.PacketConn
var udpAddr *net.UDPAddr
var err error
udpAddr, err = net.ResolveUDPAddr("udp", dest.NetAddr())
if err != nil {
return nil, err
}
var udpConn net.PacketConn
var udpAddr *net.UDPAddr
switch c := conn.(type) {
case *internet.PacketConnWrapper:
udpConn = c.PacketConn
udpAddr, err = net.ResolveUDPAddr("udp", c.Dest.String())
if err != nil {
conn.Close()
return nil, err
}
case *net.UDPConn:
udpConn = c
udpAddr, err = net.ResolveUDPAddr("udp", c.RemoteAddr().String())
if err != nil {
conn.Close()
return nil, err
}
default:
udpConn = &internet.FakePacketConn{Conn: c}
udpAddr, err = net.ResolveUDPAddr("udp", c.RemoteAddr().String())
if err != nil {
conn.Close()
return nil, err
}
if len(quicParams.UdpHop.Ports) > 0 {
conn.Close()
return nil, errors.New("udphop requires being at the outermost level ", reflect.TypeOf(c))
}
}
if len(quicParams.UdpHop.Ports) > 0 {
addr := &udphop.UDPHopAddr{
IP: udpAddr.IP,
Ports: quicParams.UdpHop.Ports,
}
udpConn, err = udphop.NewUDPHopPacketConn(addr, index, quicParams.UdpHop.IntervalMin, quicParams.UdpHop.IntervalMax, udphopDialer, udpConn)
pktConn, err = udphop.NewUDPHopPacketConn(udphop.ToAddrs(udpAddr.IP, quicParams.UdpHop.Ports), time.Duration(quicParams.UdpHop.IntervalMin)*time.Second, time.Duration(quicParams.UdpHop.IntervalMax)*time.Second, udpHopDialer)
if err != nil {
conn.Close()
return nil, errors.New("udphop err").Base(err)
return nil, err
}
} else {
conn, err := internet.DialSystem(ctx, dest, streamSettings.SocketSettings)
if err != nil {
return nil, err
}
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn = c.PacketConn
case *net.UDPConn:
pktConn = c
case *cnc.Connection:
pktConn = &internet.FakePacketConn{Conn: c}
default:
panic(reflect.TypeOf(c))
}
}
if streamSettings.UdpmaskManager != nil {
udpConn, err = streamSettings.UdpmaskManager.WrapPacketConnClient(udpConn)
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
conn.Close()
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
}
quicConn, err := quic.DialEarly(ctx, udpConn, udpAddr, tlsCfg, cfg)
conn, err := quic.DialEarly(ctx, pktConn, udpAddr, tlsCfg, cfg)
if err != nil {
return nil, err
}
switch quicParams.Congestion {
case "force-brutal":
errors.LogDebug(context.Background(), quicConn.RemoteAddr(), " ", "congestion brutal bytes per second ", quicParams.BrutalUp)
congestion.UseBrutal(quicConn, quicParams.BrutalUp)
case "reno":
errors.LogDebug(context.Background(), quicConn.RemoteAddr(), " ", "congestion reno")
case "", "bbr":
congestion.UseBBR(conn, bbr.Profile(quicParams.BbrProfile))
case "force-brutal":
congestion.UseBrutal(conn, quicParams.BrutalUp)
default:
errors.LogDebug(context.Background(), quicConn.RemoteAddr(), " ", "congestion bbr ", quicParams.BbrProfile)
congestion.UseBBR(quicConn, bbr.Profile(quicParams.BbrProfile))
panic(quicParams.Congestion)
}
return quicConn, nil
return conn, nil
},
}
} else if httpVersion == "2" {

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"net/http"
"runtime"
"slices"
"strconv"
"strings"
@@ -440,7 +441,7 @@ type Listener struct {
server http.Server
h3server *http3.Server
listener net.Listener
h3listener *quic.EarlyListener
h3listener Qface
config *Config
addConn internet.ConnHandler
isH3 bool
@@ -487,12 +488,12 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
return nil, errors.New("failed to listen UDP for XHTTP/3 on ", address, ":", port).Base(err)
}
if streamSettings.UdpmaskManager != nil {
pktConn, err := streamSettings.UdpmaskManager.WrapPacketConnServer(Conn)
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnServer(Conn)
if err != nil {
Conn.Close()
return nil, errors.New("mask err").Base(err)
}
Conn = pktConn
Conn = newConn
}
quicParams := streamSettings.QuicParams
@@ -510,13 +511,17 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
MaxConnectionReceiveWindow: quicParams.MaxConnReceiveWindow,
MaxIdleTimeout: time.Duration(quicParams.MaxIdleTimeout) * time.Second,
MaxIncomingStreams: quicParams.MaxIncomingStreams,
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery,
DisablePathMTUDiscovery: quicParams.DisablePathMtuDiscovery || (runtime.GOOS != "linux" && runtime.GOOS != "windows" && runtime.GOOS != "darwin"),
}
l.h3listener, err = quic.ListenEarly(Conn, tlsConfig, quicConfig)
if err != nil {
return nil, errors.New("failed to listen QUIC for XHTTP/3 on ", address, ":", port).Base(err)
}
l.h3listener = &QListener{
Qface: l.h3listener,
quicParams: quicParams,
}
errors.LogInfo(ctx, "listening QUIC for XHTTP/3 on ", address, ":", port)
handler.localAddr = l.h3listener.Addr()
@@ -525,30 +530,8 @@ func ListenXH(ctx context.Context, address net.Address, port net.Port, streamSet
Handler: handler,
}
go func() {
for {
conn, err := l.h3listener.Accept(context.Background())
if err != nil {
errors.LogInfoInner(ctx, err, "XHTTP/3 listener closed")
return
}
switch quicParams.Congestion {
case "force-brutal":
errors.LogDebug(context.Background(), conn.RemoteAddr(), " ", "congestion brutal bytes per second ", quicParams.BrutalUp)
congestion.UseBrutal(conn, quicParams.BrutalUp)
case "reno":
errors.LogDebug(context.Background(), conn.RemoteAddr(), " ", "congestion reno")
default:
errors.LogDebug(context.Background(), conn.RemoteAddr(), " ", "congestion bbr ", quicParams.BbrProfile)
congestion.UseBBR(conn, bbr.Profile(quicParams.BbrProfile))
}
go func() {
if err := l.h3server.ServeQUICConn(conn); err != nil {
errors.LogDebugInner(ctx, err, "XHTTP/3 connection ended")
}
_ = conn.CloseWithError(0, "")
}()
if err := l.h3server.ServeListener(l.h3listener); err != nil {
errors.LogErrorInner(ctx, err, "failed to serve HTTP/3 for XHTTP/3")
}
}()
} else { // tcp
@@ -614,10 +597,8 @@ func (ln *Listener) Addr() net.Addr {
func (ln *Listener) Close() error {
if ln.h3server != nil {
if err := ln.h3server.Close(); err != nil {
_ = ln.h3listener.Close()
return err
}
return ln.h3listener.Close()
} else if ln.listener != nil {
return ln.listener.Close()
}
@@ -633,3 +614,33 @@ func getTLSConfig(streamSettings *internet.MemoryStreamConfig) *gotls.Config {
func init() {
common.Must(internet.RegisterTransportListener(protocolName, ListenXH))
}
type Qface interface {
Accept(ctx context.Context) (*quic.Conn, error)
Addr() net.Addr
Close() error
}
var _ Qface = (*quic.EarlyListener)(nil)
type QListener struct {
Qface
quicParams *internet.QuicParams
}
func (l *QListener) Accept(ctx context.Context) (*quic.Conn, error) {
conn, err := l.Qface.Accept(ctx)
if err != nil {
return nil, err
}
switch l.quicParams.Congestion {
case "reno":
case "", "bbr":
congestion.UseBBR(conn, bbr.Profile(l.quicParams.BbrProfile))
case "force-brutal":
congestion.UseBrutal(conn, l.quicParams.BrutalUp)
default:
panic(l.quicParams.Congestion)
}
return conn, nil
}

View File

@@ -25,48 +25,30 @@ func init() {
}
if streamSettings != nil && streamSettings.UdpmaskManager != nil {
var pktConn net.PacketConn
var udpAddr = conn.RemoteAddr().(*net.UDPAddr)
switch c := conn.(type) {
case *internet.PacketConnWrapper:
pktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(c.PacketConn)
if err != nil {
conn.Close()
return nil, errors.New("mask err").Base(err)
}
c.PacketConn = pktConn
errors.LogInfo(ctx, "finalmask udp dialer: wrapped existing PacketConnWrapper with ", reflect.TypeOf(pktConn))
pktConn = c.PacketConn
case *net.UDPConn:
pktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(c)
if err != nil {
conn.Close()
return nil, errors.New("mask err").Base(err)
}
conn = &internet.PacketConnWrapper{
PacketConn: pktConn,
Dest: c.RemoteAddr().(*net.UDPAddr),
}
errors.LogInfo(ctx, "finalmask udp dialer: wrapped UDPConn with ", reflect.TypeOf(pktConn))
pktConn = c
case *cnc.Connection:
fakeConn := &internet.FakePacketConn{Conn: c}
pktConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(fakeConn)
if err != nil {
conn.Close()
return nil, errors.New("mask err").Base(err)
}
conn = &internet.PacketConnWrapper{
PacketConn: pktConn,
Dest: &net.UDPAddr{
IP: []byte{0, 0, 0, 0},
Port: 0,
},
}
errors.LogInfo(ctx, "finalmask udp dialer: wrapped cnc.Connection with ", reflect.TypeOf(pktConn))
pktConn = &internet.FakePacketConn{Conn: c}
default:
conn.Close()
return nil, errors.New("unknown conn ", reflect.TypeOf(c))
panic(reflect.TypeOf(c))
}
newConn, err := streamSettings.UdpmaskManager.WrapPacketConnClient(pktConn)
if err != nil {
pktConn.Close()
return nil, errors.New("mask err").Base(err)
}
pktConn = newConn
conn = &internet.PacketConnWrapper{
PacketConn: pktConn,
Dest: udpAddr,
}
}
// TODO: handle dialer options
return conn, nil
}))
}