mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-05-08 14:13:22 +00:00
Proxy: Add Hysteria 2 inbound & transport (supports listening port range, Salamander finalmask) (#5679)
https://github.com/XTLS/Xray-core/pull/5679#issuecomment-3888548778 Closes https://github.com/XTLS/Xray-core/issues/5605
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package hysteria
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/hysteria/padding"
|
||||
@@ -23,11 +25,15 @@ const (
|
||||
StatusAuthOK = 233
|
||||
|
||||
udpMessageChanSize = 1024
|
||||
|
||||
FrameTypeTCPRequest = 0x401
|
||||
|
||||
idleCleanupInterval = 1 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
authRequestPadding = padding.Padding{Min: 256, Max: 2048}
|
||||
// authResponsePadding = padding.Padding{Min: 256, Max: 2048}
|
||||
authRequestPadding = padding.Padding{Min: 256, Max: 2048}
|
||||
authResponsePadding = padding.Padding{Min: 256, Max: 2048}
|
||||
)
|
||||
|
||||
type Status int
|
||||
|
||||
@@ -38,6 +38,16 @@ type Config struct {
|
||||
MaxIdleTimeout int64 `protobuf:"varint,13,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"`
|
||||
KeepAlivePeriod int64 `protobuf:"varint,14,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,omitempty"`
|
||||
DisablePathMtuDiscovery bool `protobuf:"varint,15,opt,name=disable_path_mtu_discovery,json=disablePathMtuDiscovery,proto3" json:"disable_path_mtu_discovery,omitempty"`
|
||||
MaxIncomingStreams int64 `protobuf:"varint,16,opt,name=max_incoming_streams,json=maxIncomingStreams,proto3" json:"max_incoming_streams,omitempty"`
|
||||
UdpIdleTimeout int64 `protobuf:"varint,17,opt,name=udp_idle_timeout,json=udpIdleTimeout,proto3" json:"udp_idle_timeout,omitempty"`
|
||||
MasqType string `protobuf:"bytes,18,opt,name=masq_type,json=masqType,proto3" json:"masq_type,omitempty"`
|
||||
MasqFile string `protobuf:"bytes,19,opt,name=masq_file,json=masqFile,proto3" json:"masq_file,omitempty"`
|
||||
MasqUrl string `protobuf:"bytes,20,opt,name=masq_url,json=masqUrl,proto3" json:"masq_url,omitempty"`
|
||||
MasqUrlRewriteHost bool `protobuf:"varint,21,opt,name=masq_url_rewrite_host,json=masqUrlRewriteHost,proto3" json:"masq_url_rewrite_host,omitempty"`
|
||||
MasqUrlInsecure bool `protobuf:"varint,22,opt,name=masq_url_insecure,json=masqUrlInsecure,proto3" json:"masq_url_insecure,omitempty"`
|
||||
MasqString string `protobuf:"bytes,23,opt,name=masq_string,json=masqString,proto3" json:"masq_string,omitempty"`
|
||||
MasqStringHeaders map[string]string `protobuf:"bytes,24,rep,name=masq_string_headers,json=masqStringHeaders,proto3" json:"masq_string_headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
MasqStringStatusCode int32 `protobuf:"varint,25,opt,name=masq_string_status_code,json=masqStringStatusCode,proto3" json:"masq_string_status_code,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -177,11 +187,81 @@ func (x *Config) GetDisablePathMtuDiscovery() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Config) GetMaxIncomingStreams() int64 {
|
||||
if x != nil {
|
||||
return x.MaxIncomingStreams
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetUdpIdleTimeout() int64 {
|
||||
if x != nil {
|
||||
return x.UdpIdleTimeout
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Config) GetMasqType() string {
|
||||
if x != nil {
|
||||
return x.MasqType
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetMasqFile() string {
|
||||
if x != nil {
|
||||
return x.MasqFile
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetMasqUrl() string {
|
||||
if x != nil {
|
||||
return x.MasqUrl
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetMasqUrlRewriteHost() bool {
|
||||
if x != nil {
|
||||
return x.MasqUrlRewriteHost
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Config) GetMasqUrlInsecure() bool {
|
||||
if x != nil {
|
||||
return x.MasqUrlInsecure
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Config) GetMasqString() string {
|
||||
if x != nil {
|
||||
return x.MasqString
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetMasqStringHeaders() map[string]string {
|
||||
if x != nil {
|
||||
return x.MasqStringHeaders
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetMasqStringStatusCode() int32 {
|
||||
if x != nil {
|
||||
return x.MasqStringStatusCode
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_transport_internet_hysteria_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_transport_internet_hysteria_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"(transport/internet/hysteria/config.proto\x12 xray.transport.internet.hysteria\"\xd1\x04\n" +
|
||||
"(transport/internet/hysteria/config.proto\x12 xray.transport.internet.hysteria\"\xf0\b\n" +
|
||||
"\x06Config\x12\x18\n" +
|
||||
"\aversion\x18\x01 \x01(\x05R\aversion\x12\x12\n" +
|
||||
"\x04auth\x18\x02 \x01(\tR\x04auth\x12\x1e\n" +
|
||||
@@ -200,7 +280,21 @@ const file_transport_internet_hysteria_config_proto_rawDesc = "" +
|
||||
"\x17max_conn_receive_window\x18\f \x01(\x04R\x14maxConnReceiveWindow\x12(\n" +
|
||||
"\x10max_idle_timeout\x18\r \x01(\x03R\x0emaxIdleTimeout\x12*\n" +
|
||||
"\x11keep_alive_period\x18\x0e \x01(\x03R\x0fkeepAlivePeriod\x12;\n" +
|
||||
"\x1adisable_path_mtu_discovery\x18\x0f \x01(\bR\x17disablePathMtuDiscoveryB\x82\x01\n" +
|
||||
"\x1adisable_path_mtu_discovery\x18\x0f \x01(\bR\x17disablePathMtuDiscovery\x120\n" +
|
||||
"\x14max_incoming_streams\x18\x10 \x01(\x03R\x12maxIncomingStreams\x12(\n" +
|
||||
"\x10udp_idle_timeout\x18\x11 \x01(\x03R\x0eudpIdleTimeout\x12\x1b\n" +
|
||||
"\tmasq_type\x18\x12 \x01(\tR\bmasqType\x12\x1b\n" +
|
||||
"\tmasq_file\x18\x13 \x01(\tR\bmasqFile\x12\x19\n" +
|
||||
"\bmasq_url\x18\x14 \x01(\tR\amasqUrl\x121\n" +
|
||||
"\x15masq_url_rewrite_host\x18\x15 \x01(\bR\x12masqUrlRewriteHost\x12*\n" +
|
||||
"\x11masq_url_insecure\x18\x16 \x01(\bR\x0fmasqUrlInsecure\x12\x1f\n" +
|
||||
"\vmasq_string\x18\x17 \x01(\tR\n" +
|
||||
"masqString\x12o\n" +
|
||||
"\x13masq_string_headers\x18\x18 \x03(\v2?.xray.transport.internet.hysteria.Config.MasqStringHeadersEntryR\x11masqStringHeaders\x125\n" +
|
||||
"\x17masq_string_status_code\x18\x19 \x01(\x05R\x14masqStringStatusCode\x1aD\n" +
|
||||
"\x16MasqStringHeadersEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x82\x01\n" +
|
||||
"$com.xray.transport.internet.hysteriaP\x01Z5github.com/xtls/xray-core/transport/internet/hysteria\xaa\x02 Xray.Transport.Internet.Hysteriab\x06proto3"
|
||||
|
||||
var (
|
||||
@@ -215,16 +309,18 @@ func file_transport_internet_hysteria_config_proto_rawDescGZIP() []byte {
|
||||
return file_transport_internet_hysteria_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_transport_internet_hysteria_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_transport_internet_hysteria_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_transport_internet_hysteria_config_proto_goTypes = []any{
|
||||
(*Config)(nil), // 0: xray.transport.internet.hysteria.Config
|
||||
nil, // 1: xray.transport.internet.hysteria.Config.MasqStringHeadersEntry
|
||||
}
|
||||
var file_transport_internet_hysteria_config_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
1, // 0: xray.transport.internet.hysteria.Config.masq_string_headers:type_name -> xray.transport.internet.hysteria.Config.MasqStringHeadersEntry
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_transport_internet_hysteria_config_proto_init() }
|
||||
@@ -238,7 +334,7 @@ func file_transport_internet_hysteria_config_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_transport_internet_hysteria_config_proto_rawDesc), len(file_transport_internet_hysteria_config_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -23,5 +23,15 @@ message Config {
|
||||
int64 max_idle_timeout = 13;
|
||||
int64 keep_alive_period = 14;
|
||||
bool disable_path_mtu_discovery = 15;
|
||||
}
|
||||
int64 max_incoming_streams = 16;
|
||||
|
||||
int64 udp_idle_timeout = 17;
|
||||
string masq_type = 18;
|
||||
string masq_file = 19;
|
||||
string masq_url = 20;
|
||||
bool masq_url_rewrite_host = 21;
|
||||
bool masq_url_insecure = 22;
|
||||
string masq_string = 23;
|
||||
map<string, string> masq_string_headers = 24;
|
||||
int32 masq_string_status_code = 25;
|
||||
}
|
||||
@@ -3,16 +3,28 @@ package hysteria
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/quic-go"
|
||||
"github.com/apernet/quic-go/quicvarint"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
)
|
||||
|
||||
type interConn struct {
|
||||
stream *quic.Stream
|
||||
local net.Addr
|
||||
remote net.Addr
|
||||
|
||||
client bool
|
||||
mutex sync.Mutex
|
||||
|
||||
user *protocol.MemoryUser
|
||||
}
|
||||
|
||||
func (i *interConn) User() *protocol.MemoryUser {
|
||||
return i.user
|
||||
}
|
||||
|
||||
func (i *interConn) Read(b []byte) (int, error) {
|
||||
@@ -20,6 +32,22 @@ func (i *interConn) Read(b []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (i *interConn) Write(b []byte) (int, error) {
|
||||
if i.client {
|
||||
i.mutex.Lock()
|
||||
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
|
||||
}
|
||||
i.mutex.Unlock()
|
||||
}
|
||||
|
||||
return i.stream.Write(b)
|
||||
}
|
||||
|
||||
@@ -53,10 +81,34 @@ type InterUdpConn struct {
|
||||
local net.Addr
|
||||
remote net.Addr
|
||||
|
||||
id uint32
|
||||
ch chan []byte
|
||||
id uint32
|
||||
ch chan []byte
|
||||
|
||||
closed bool
|
||||
closeFunc func()
|
||||
|
||||
last time.Time
|
||||
mutex sync.Mutex
|
||||
|
||||
user *protocol.MemoryUser
|
||||
}
|
||||
|
||||
func (i *InterUdpConn) User() *protocol.MemoryUser {
|
||||
return i.user
|
||||
}
|
||||
|
||||
func (i *InterUdpConn) SetLast() {
|
||||
i.mutex.Lock()
|
||||
defer i.mutex.Unlock()
|
||||
|
||||
i.last = time.Now()
|
||||
}
|
||||
|
||||
func (i *InterUdpConn) GetLast() time.Time {
|
||||
i.mutex.Lock()
|
||||
defer i.mutex.Unlock()
|
||||
|
||||
return i.last
|
||||
}
|
||||
|
||||
func (i *InterUdpConn) Read(p []byte) (int, error) {
|
||||
@@ -68,10 +120,14 @@ func (i *InterUdpConn) Read(p []byte) (int, error) {
|
||||
if n != len(b) {
|
||||
return 0, io.ErrShortBuffer
|
||||
}
|
||||
|
||||
i.SetLast()
|
||||
return n, 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 {
|
||||
return 0, err
|
||||
|
||||
@@ -26,15 +26,23 @@ import (
|
||||
"github.com/xtls/xray-core/transport/internet/tls"
|
||||
)
|
||||
|
||||
type udpSessionManager struct {
|
||||
type udpSessionManagerClient struct {
|
||||
conn *quic.Conn
|
||||
m map[uint32]*InterUdpConn
|
||||
nextId uint32
|
||||
next uint32
|
||||
closed bool
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (m *udpSessionManager) run() {
|
||||
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 {
|
||||
@@ -44,29 +52,22 @@ func (m *udpSessionManager) run() {
|
||||
if len(d) < 4 {
|
||||
continue
|
||||
}
|
||||
sessionId := binary.BigEndian.Uint32(d[:4])
|
||||
id := binary.BigEndian.Uint32(d[:4])
|
||||
|
||||
m.feed(sessionId, d)
|
||||
m.feed(id, d)
|
||||
}
|
||||
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
m.closed = true
|
||||
|
||||
for _, udpConn := range m.m {
|
||||
m.close(udpConn)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *udpSessionManager) close(udpConn *InterUdpConn) {
|
||||
if !udpConn.closed {
|
||||
udpConn.closed = true
|
||||
close(udpConn.ch)
|
||||
delete(m.m, udpConn.id)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *udpSessionManager) udp() (*InterUdpConn, error) {
|
||||
func (m *udpSessionManagerClient) udp() (*InterUdpConn, error) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
@@ -79,7 +80,7 @@ func (m *udpSessionManager) udp() (*InterUdpConn, error) {
|
||||
local: m.conn.LocalAddr(),
|
||||
remote: m.conn.RemoteAddr(),
|
||||
|
||||
id: m.nextId,
|
||||
id: m.next,
|
||||
ch: make(chan []byte, udpMessageChanSize),
|
||||
}
|
||||
udpConn.closeFunc = func() {
|
||||
@@ -87,17 +88,17 @@ func (m *udpSessionManager) udp() (*InterUdpConn, error) {
|
||||
defer m.mutex.Unlock()
|
||||
m.close(udpConn)
|
||||
}
|
||||
m.m[m.nextId] = udpConn
|
||||
m.nextId++
|
||||
m.m[m.next] = udpConn
|
||||
m.next++
|
||||
|
||||
return udpConn, nil
|
||||
}
|
||||
|
||||
func (m *udpSessionManager) feed(sessionId uint32, d []byte) {
|
||||
func (m *udpSessionManagerClient) feed(id uint32, d []byte) {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
udpConn, ok := m.m[sessionId]
|
||||
udpConn, ok := m.m[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -117,7 +118,7 @@ type client struct {
|
||||
tlsConfig *go_tls.Config
|
||||
socketConfig *internet.SocketConfig
|
||||
udpmaskManager *finalmask.UdpmaskManager
|
||||
udpSM *udpSessionManager
|
||||
udpSM *udpSessionManagerClient
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
@@ -269,10 +270,10 @@ func (c *client) dial() error {
|
||||
c.pktConn = pktConn
|
||||
c.conn = quicConn
|
||||
if serverUdp {
|
||||
c.udpSM = &udpSessionManager{
|
||||
conn: quicConn,
|
||||
m: make(map[uint32]*InterUdpConn),
|
||||
nextId: 1,
|
||||
c.udpSM = &udpSessionManagerClient{
|
||||
conn: quicConn,
|
||||
m: make(map[uint32]*InterUdpConn),
|
||||
next: 1,
|
||||
}
|
||||
go c.udpSM.run()
|
||||
}
|
||||
@@ -307,6 +308,8 @@ func (c *client) tcp() (stat.Connection, error) {
|
||||
stream: stream,
|
||||
local: c.conn.LocalAddr(),
|
||||
remote: c.conn.RemoteAddr(),
|
||||
|
||||
client: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
412
transport/internet/hysteria/hub.go
Normal file
412
transport/internet/hysteria/hub.go
Normal file
@@ -0,0 +1,412 @@
|
||||
package hysteria
|
||||
|
||||
import (
|
||||
"context"
|
||||
gotls "crypto/tls"
|
||||
"encoding/binary"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/apernet/quic-go"
|
||||
"github.com/apernet/quic-go/http3"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"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/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/tls"
|
||||
)
|
||||
|
||||
type udpSessionManagerServer struct {
|
||||
conn *quic.Conn
|
||||
m map[uint32]*InterUdpConn
|
||||
addConn internet.ConnHandler
|
||||
stopCh chan struct{}
|
||||
udpIdleTimeout time.Duration
|
||||
mutex sync.RWMutex
|
||||
|
||||
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]
|
||||
m.mutex.RUnlock()
|
||||
|
||||
if !ok {
|
||||
m.mutex.Lock()
|
||||
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()
|
||||
defer m.mutex.Unlock()
|
||||
m.close(udpConn)
|
||||
}
|
||||
m.m[id] = udpConn
|
||||
m.addConn(udpConn)
|
||||
}
|
||||
m.mutex.Unlock()
|
||||
}
|
||||
|
||||
select {
|
||||
case udpConn.ch <- d:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
type httpHandler struct {
|
||||
ctx context.Context
|
||||
conn *quic.Conn
|
||||
addConn internet.ConnHandler
|
||||
|
||||
config *Config
|
||||
validator *account.Validator
|
||||
masqHandler http.Handler
|
||||
|
||||
auth bool
|
||||
mutex sync.Mutex
|
||||
user *protocol.MemoryUser
|
||||
}
|
||||
|
||||
func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodPost && r.Host == URLHost && r.URL.Path == URLPath {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
if h.auth {
|
||||
w.Header().Set(ResponseHeaderUDPEnabled, strconv.FormatBool(hyCtx.RequireDatagramFromContext(h.ctx)))
|
||||
w.Header().Set(CommonHeaderCCRX, strconv.FormatUint(h.config.Down, 10))
|
||||
w.Header().Set(CommonHeaderPadding, authResponsePadding.String())
|
||||
w.WriteHeader(StatusAuthOK)
|
||||
return
|
||||
}
|
||||
|
||||
auth := r.Header.Get(RequestHeaderAuth)
|
||||
clientDown, _ := strconv.ParseUint(r.Header.Get(CommonHeaderCCRX), 10, 64)
|
||||
|
||||
var user *protocol.MemoryUser
|
||||
var ok bool
|
||||
if h.validator != nil {
|
||||
user = h.validator.Get(auth)
|
||||
} else if auth == h.config.Auth {
|
||||
ok = true
|
||||
}
|
||||
|
||||
if user != nil || ok {
|
||||
h.auth = true
|
||||
h.user = user
|
||||
|
||||
switch h.config.Congestion {
|
||||
case "reno":
|
||||
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion reno")
|
||||
case "bbr":
|
||||
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion bbr")
|
||||
congestion.UseBBR(h.conn)
|
||||
case "brutal", "":
|
||||
if h.config.Up == 0 || clientDown == 0 {
|
||||
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion bbr")
|
||||
congestion.UseBBR(h.conn)
|
||||
} else {
|
||||
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion brutal bytes per second ", min(h.config.Up, clientDown))
|
||||
congestion.UseBrutal(h.conn, min(h.config.Up, clientDown))
|
||||
}
|
||||
case "force-brutal":
|
||||
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion brutal bytes per second ", h.config.Up)
|
||||
congestion.UseBrutal(h.conn, h.config.Up)
|
||||
default:
|
||||
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion reno")
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
user: h.user,
|
||||
}
|
||||
go udpSM.clean()
|
||||
go udpSM.run()
|
||||
}
|
||||
|
||||
w.Header().Set(ResponseHeaderUDPEnabled, strconv.FormatBool(hyCtx.RequireDatagramFromContext(h.ctx)))
|
||||
w.Header().Set(CommonHeaderCCRX, strconv.FormatUint(h.config.Down, 10))
|
||||
w.Header().Set(CommonHeaderPadding, authResponsePadding.String())
|
||||
w.WriteHeader(StatusAuthOK)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
h.masqHandler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *httpHandler) ProxyStreamHijacker(ft http3.FrameType, id quic.ConnectionTracingID, stream *quic.Stream, err error) (bool, error) {
|
||||
if err != nil || !h.auth {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
switch ft {
|
||||
case FrameTypeTCPRequest:
|
||||
h.addConn(&interConn{
|
||||
stream: stream,
|
||||
local: h.conn.LocalAddr(),
|
||||
remote: h.conn.RemoteAddr(),
|
||||
|
||||
user: h.user,
|
||||
})
|
||||
return true, nil
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
type Listener struct {
|
||||
ctx context.Context
|
||||
pktConn net.PacketConn
|
||||
listener *quic.Listener
|
||||
addConn internet.ConnHandler
|
||||
|
||||
config *Config
|
||||
validator *account.Validator
|
||||
masqHandler http.Handler
|
||||
}
|
||||
|
||||
func (l *Listener) handleClient(conn *quic.Conn) {
|
||||
handler := &httpHandler{
|
||||
ctx: l.ctx,
|
||||
conn: conn,
|
||||
addConn: l.addConn,
|
||||
|
||||
config: l.config,
|
||||
validator: l.validator,
|
||||
masqHandler: l.masqHandler,
|
||||
}
|
||||
h3 := http3.Server{
|
||||
Handler: handler,
|
||||
StreamHijacker: handler.ProxyStreamHijacker,
|
||||
}
|
||||
err := h3.ServeQUICConn(conn)
|
||||
errors.LogDebug(context.Background(), conn.RemoteAddr(), " disconnected with err ", err)
|
||||
_ = conn.CloseWithError(closeErrCodeOK, "")
|
||||
}
|
||||
|
||||
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")
|
||||
break
|
||||
}
|
||||
go l.handleClient(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Listener) Addr() net.Addr {
|
||||
return l.listener.Addr()
|
||||
}
|
||||
|
||||
func (l *Listener) Close() error {
|
||||
err := l.listener.Close()
|
||||
_ = l.pktConn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func Listen(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) {
|
||||
if address.Family().IsDomain() {
|
||||
return nil, errors.New("address is domain")
|
||||
}
|
||||
|
||||
tlsConfig := tls.ConfigFromStreamSettings(streamSettings)
|
||||
if tlsConfig == nil {
|
||||
return nil, errors.New("tls config is nil")
|
||||
}
|
||||
|
||||
config := streamSettings.ProtocolSettings.(*Config)
|
||||
|
||||
validator := hyCtx.ValidatorFromContext(ctx)
|
||||
|
||||
if config.Auth == "" && validator == nil {
|
||||
return nil, errors.New("validator is nil")
|
||||
}
|
||||
|
||||
var masqHandler http.Handler
|
||||
switch strings.ToLower(config.MasqType) {
|
||||
case "", "404":
|
||||
masqHandler = http.NotFoundHandler()
|
||||
case "file":
|
||||
masqHandler = http.FileServer(http.Dir(config.MasqFile))
|
||||
case "proxy":
|
||||
u, err := url.Parse(config.MasqUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transport := http.DefaultTransport.(*http.Transport)
|
||||
if config.MasqUrlInsecure {
|
||||
transport = transport.Clone()
|
||||
transport.TLSClientConfig = &gotls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
}
|
||||
masqHandler = &httputil.ReverseProxy{
|
||||
Rewrite: func(pr *httputil.ProxyRequest) {
|
||||
pr.SetURL(u)
|
||||
if !config.MasqUrlRewriteHost {
|
||||
pr.Out.Host = pr.In.Host
|
||||
}
|
||||
},
|
||||
Transport: transport,
|
||||
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
},
|
||||
}
|
||||
case "string":
|
||||
masqHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
for k, v := range config.MasqStringHeaders {
|
||||
w.Header().Set(k, v)
|
||||
}
|
||||
if config.MasqStringStatusCode != 0 {
|
||||
w.WriteHeader(int(config.MasqStringStatusCode))
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
_, _ = w.Write([]byte(config.MasqString))
|
||||
})
|
||||
default:
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
quicConfig := &quic.Config{
|
||||
InitialStreamReceiveWindow: config.InitStreamReceiveWindow,
|
||||
MaxStreamReceiveWindow: config.MaxStreamReceiveWindow,
|
||||
InitialConnectionReceiveWindow: config.InitConnReceiveWindow,
|
||||
MaxConnectionReceiveWindow: config.MaxConnReceiveWindow,
|
||||
MaxIdleTimeout: time.Duration(config.MaxIdleTimeout) * time.Second,
|
||||
MaxIncomingStreams: config.MaxIncomingStreams,
|
||||
DisablePathMTUDiscovery: config.DisablePathMtuDiscovery,
|
||||
EnableDatagrams: true,
|
||||
MaxDatagramFrameSize: MaxDatagramFrameSize,
|
||||
DisablePathManager: true,
|
||||
}
|
||||
|
||||
qListener, err := quic.Listen(pktConn, tlsConfig.GetTLSConfig(), quicConfig)
|
||||
if err != nil {
|
||||
_ = pktConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
listener := &Listener{
|
||||
ctx: ctx,
|
||||
pktConn: pktConn,
|
||||
listener: qListener,
|
||||
addConn: handler,
|
||||
|
||||
config: config,
|
||||
validator: validator,
|
||||
masqHandler: masqHandler,
|
||||
}
|
||||
|
||||
go listener.keepAccepting()
|
||||
|
||||
return listener, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.Must(internet.RegisterTransportListener(protocolName, Listen))
|
||||
}
|
||||
Reference in New Issue
Block a user