2024-06-18 07:36:36 +02:00
package splithttp
import (
2025-01-06 14:06:11 +00:00
"bytes"
2024-06-18 07:36:36 +02:00
"context"
2025-02-18 10:50:50 +00:00
gotls "crypto/tls"
2026-01-31 16:34:13 +03:00
"encoding/base64"
"fmt"
2024-06-18 07:36:36 +02:00
"io"
"net/http"
2026-03-07 12:34:41 +00:00
"slices"
2024-06-18 07:36:36 +02:00
"strconv"
2024-06-21 01:30:51 +02:00
"strings"
2024-06-18 07:36:36 +02:00
"sync"
"time"
2026-01-13 21:31:51 +08:00
"github.com/apernet/quic-go"
"github.com/apernet/quic-go/http3"
2024-10-30 03:31:05 +01:00
goreality "github.com/xtls/reality"
2024-06-18 07:36:36 +02:00
"github.com/xtls/xray-core/common"
2024-06-29 14:32:57 -04:00
"github.com/xtls/xray-core/common/errors"
2024-06-18 07:36:36 +02:00
"github.com/xtls/xray-core/common/net"
http_proto "github.com/xtls/xray-core/common/protocol/http"
"github.com/xtls/xray-core/common/signal/done"
"github.com/xtls/xray-core/transport/internet"
2026-03-09 20:17:32 +08:00
"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
2024-10-30 03:31:05 +01:00
"github.com/xtls/xray-core/transport/internet/reality"
2024-06-18 07:36:36 +02:00
"github.com/xtls/xray-core/transport/internet/stat"
2025-02-18 10:50:50 +00:00
"github.com/xtls/xray-core/transport/internet/tls"
2024-06-18 07:36:36 +02:00
)
type requestHandler struct {
2025-11-23 01:09:49 +00:00
config * Config
host string
path string
ln * Listener
sessionMu * sync . Mutex
sessions sync . Map
localAddr net . Addr
socketSettings * internet . SocketConfig
2024-06-18 07:36:36 +02:00
}
2024-06-21 01:30:51 +02:00
type httpSession struct {
2024-07-11 09:56:20 +02:00
uploadQueue * uploadQueue
2024-06-21 01:30:51 +02:00
// for as long as the GET request is not opened by the client, this will be
// open ("undone"), and the session may be expired within a certain TTL.
// after the client connects, this becomes "done" and the session lives as
// long as the GET request.
isFullyConnected * done . Instance
}
func ( h * requestHandler ) upsertSession ( sessionId string ) * httpSession {
2024-07-17 07:41:17 -04:00
// fast path
2024-06-21 01:30:51 +02:00
currentSessionAny , ok := h . sessions . Load ( sessionId )
if ok {
return currentSessionAny . ( * httpSession )
}
2024-07-17 07:41:17 -04:00
// slow path
h . sessionMu . Lock ( )
defer h . sessionMu . Unlock ( )
currentSessionAny , ok = h . sessions . Load ( sessionId )
if ok {
return currentSessionAny . ( * httpSession )
}
2024-06-21 01:30:51 +02:00
s := & httpSession {
2024-12-11 14:05:39 +00:00
uploadQueue : NewUploadQueue ( h . ln . config . GetNormalizedScMaxBufferedPosts ( ) ) ,
2024-06-21 01:30:51 +02:00
isFullyConnected : done . New ( ) ,
}
h . sessions . Store ( sessionId , s )
2025-02-20 16:28:06 +00:00
shouldReap := done . New ( )
go func ( ) {
time . Sleep ( 30 * time . Second )
shouldReap . Close ( )
} ( )
go func ( ) {
select {
case <- shouldReap . Wait ( ) :
h . sessions . Delete ( sessionId )
s . uploadQueue . Close ( )
case <- s . isFullyConnected . Wait ( ) :
}
} ( )
2024-06-21 01:30:51 +02:00
return s
}
2024-06-18 07:36:36 +02:00
func ( h * requestHandler ) ServeHTTP ( writer http . ResponseWriter , request * http . Request ) {
2024-07-06 17:12:49 -04:00
if len ( h . host ) > 0 && ! internet . IsValidHTTPHost ( request . Host , h . host ) {
2024-06-29 14:32:57 -04:00
errors . LogInfo ( context . Background ( ) , "failed to validate host, request:" , request . Host , ", config:" , h . host )
2024-06-18 07:36:36 +02:00
writer . WriteHeader ( http . StatusNotFound )
return
}
2024-06-21 01:30:51 +02:00
if ! strings . HasPrefix ( request . URL . Path , h . path ) {
2024-06-29 14:32:57 -04:00
errors . LogInfo ( context . Background ( ) , "failed to validate path, request:" , request . URL . Path , ", config:" , h . path )
2024-06-18 07:36:36 +02:00
writer . WriteHeader ( http . StatusNotFound )
return
}
2026-03-07 12:34:41 +00:00
h . config . WriteResponseHeader ( writer , request . Method , request . Header )
2026-01-31 16:34:13 +03:00
length := int ( h . config . GetNormalizedXPaddingBytes ( ) . rand ( ) )
config := XPaddingConfig { Length : length }
if h . config . XPaddingObfsMode {
config . Placement = XPaddingPlacement {
Placement : h . config . XPaddingPlacement ,
Key : h . config . XPaddingKey ,
Header : h . config . XPaddingHeader ,
}
config . Method = PaddingMethod ( h . config . XPaddingMethod )
} else {
config . Placement = XPaddingPlacement {
Placement : PlacementHeader ,
Header : "X-Padding" ,
}
}
2026-03-07 12:34:41 +00:00
h . config . ApplyXPaddingToResponse ( writer , config )
if request . Method == "OPTIONS" {
writer . WriteHeader ( http . StatusOK )
return
}
2024-11-09 11:05:41 +00:00
2025-01-19 13:32:07 +00:00
/*
clientVer := []int{0, 0, 0}
x_version := strings.Split(request.URL.Query().Get("x_version"), ".")
for j := 0; j < 3 && len(x_version) > j; j++ {
clientVer[j], _ = strconv.Atoi(x_version[j])
}
*/
2025-01-06 14:06:11 +00:00
2024-11-27 20:19:18 +00:00
validRange := h . config . GetNormalizedXPaddingBytes ( )
2026-01-31 16:34:13 +03:00
paddingValue , paddingPlacement := h . config . ExtractXPaddingFromRequest ( request , h . config . XPaddingObfsMode )
2025-01-18 20:05:19 +08:00
2026-01-31 16:34:13 +03:00
if ! h . config . IsPaddingValid ( paddingValue , validRange . From , validRange . To , PaddingMethod ( h . config . XPaddingMethod ) ) {
errors . LogInfo ( context . Background ( ) , "invalid padding (" + paddingPlacement + ") length:" , int32 ( len ( paddingValue ) ) )
2024-11-27 20:19:18 +00:00
writer . WriteHeader ( http . StatusBadRequest )
return
}
2026-01-31 16:34:13 +03:00
sessionId , seqStr := h . config . ExtractMetaFromRequest ( request , h . path )
2024-06-21 01:30:51 +02:00
2024-11-27 20:19:18 +00:00
if sessionId == "" && h . config . Mode != "" && h . config . Mode != "auto" && h . config . Mode != "stream-one" && h . config . Mode != "stream-up" {
errors . LogInfo ( context . Background ( ) , "stream-one mode is not allowed" )
2024-06-18 07:36:36 +02:00
writer . WriteHeader ( http . StatusBadRequest )
return
}
2025-11-23 01:09:49 +00:00
var forwardedAddrs [ ] net . Address
if h . socketSettings != nil && len ( h . socketSettings . TrustedXForwardedFor ) > 0 {
for _ , key := range h . socketSettings . TrustedXForwardedFor {
if len ( request . Header . Values ( key ) ) > 0 {
forwardedAddrs = http_proto . ParseXForwardedFor ( request . Header )
break
}
}
} else {
forwardedAddrs = http_proto . ParseXForwardedFor ( request . Header )
}
2025-02-18 10:50:50 +00:00
var remoteAddr net . Addr
var err error
remoteAddr , err = net . ResolveTCPAddr ( "tcp" , request . RemoteAddr )
2024-06-18 07:36:36 +02:00
if err != nil {
2025-02-18 10:50:50 +00:00
remoteAddr = & net . TCPAddr {
IP : [ ] byte { 0 , 0 , 0 , 0 } ,
Port : 0 ,
}
}
if request . ProtoMajor == 3 {
remoteAddr = & net . UDPAddr {
IP : remoteAddr . ( * net . TCPAddr ) . IP ,
Port : remoteAddr . ( * net . TCPAddr ) . Port ,
}
2024-06-18 07:36:36 +02:00
}
if len ( forwardedAddrs ) > 0 && forwardedAddrs [ 0 ] . Family ( ) . IsIP ( ) {
remoteAddr = & net . TCPAddr {
IP : forwardedAddrs [ 0 ] . IP ( ) ,
2025-02-18 10:50:50 +00:00
Port : 0 ,
2024-06-18 07:36:36 +02:00
}
}
2024-11-27 20:19:18 +00:00
var currentSession * httpSession
if sessionId != "" {
currentSession = h . upsertSession ( sessionId )
}
2024-07-29 12:10:29 +02:00
scMaxEachPostBytes := int ( h . ln . config . GetNormalizedScMaxEachPostBytes ( ) . To )
2026-01-31 16:34:13 +03:00
isUplinkRequest := false
2026-03-07 12:34:41 +00:00
switch request . Method {
case "GET" :
isUplinkRequest = seqStr != ""
default :
2026-01-31 16:34:13 +03:00
isUplinkRequest = true
}
2024-06-21 01:30:51 +02:00
2026-01-31 16:34:13 +03:00
uplinkDataKey := h . config . UplinkDataKey
if isUplinkRequest && sessionId != "" { // stream-up, packet-up
if seqStr == "" {
2024-11-27 20:19:18 +00:00
if h . config . Mode != "" && h . config . Mode != "auto" && h . config . Mode != "stream-up" {
2024-11-09 11:05:41 +00:00
errors . LogInfo ( context . Background ( ) , "stream-up mode is not allowed" )
writer . WriteHeader ( http . StatusBadRequest )
return
}
2025-02-20 16:28:06 +00:00
httpSC := & httpServerConn {
Instance : done . New ( ) ,
Reader : request . Body ,
ResponseWriter : writer ,
}
2024-11-09 11:05:41 +00:00
err = currentSession . uploadQueue . Push ( Packet {
2025-02-20 16:28:06 +00:00
Reader : httpSC ,
2024-11-09 11:05:41 +00:00
} )
if err != nil {
errors . LogInfoInner ( context . Background ( ) , err , "failed to upload (PushReader)" )
writer . WriteHeader ( http . StatusConflict )
} else {
2025-01-19 13:32:07 +00:00
writer . Header ( ) . Set ( "X-Accel-Buffering" , "no" )
writer . Header ( ) . Set ( "Cache-Control" , "no-store" )
2024-11-09 11:05:41 +00:00
writer . WriteHeader ( http . StatusOK )
2025-01-19 13:32:07 +00:00
scStreamUpServerSecs := h . config . GetNormalizedScStreamUpServerSecs ( )
2026-01-31 16:34:13 +03:00
referrer := request . Header . Get ( "Referer" )
2025-01-19 13:32:07 +00:00
if referrer != "" && scStreamUpServerSecs . To > 0 {
go func ( ) {
for {
2025-02-20 16:28:06 +00:00
_ , err := httpSC . Write ( bytes . Repeat ( [ ] byte { 'X' } , int ( h . config . GetNormalizedXPaddingBytes ( ) . rand ( ) ) ) )
2025-01-19 13:32:07 +00:00
if err != nil {
break
}
time . Sleep ( time . Duration ( scStreamUpServerSecs . rand ( ) ) * time . Second )
}
} ( )
2025-01-06 14:06:11 +00:00
}
2025-02-10 13:56:13 +00:00
select {
case <- request . Context ( ) . Done ( ) :
2025-02-20 16:28:06 +00:00
case <- httpSC . Wait ( ) :
2025-02-10 13:56:13 +00:00
}
2024-11-09 11:05:41 +00:00
}
2025-02-20 16:28:06 +00:00
httpSC . Close ( )
2024-11-09 11:05:41 +00:00
return
}
2024-11-27 20:19:18 +00:00
if h . config . Mode != "" && h . config . Mode != "auto" && h . config . Mode != "packet-up" {
2024-11-09 11:05:41 +00:00
errors . LogInfo ( context . Background ( ) , "packet-up mode is not allowed" )
2024-06-18 07:36:36 +02:00
writer . WriteHeader ( http . StatusBadRequest )
return
}
2026-03-07 12:34:41 +00:00
dataPlacement := h . config . GetNormalizedUplinkDataPlacement ( )
var headerPayload [ ] byte
if dataPlacement == PlacementAuto || dataPlacement == PlacementHeader {
2026-03-09 20:17:32 +08:00
var headerPayloadChunks [ ] string
2026-03-07 12:34:41 +00:00
for i := 0 ; true ; i ++ {
chunk := request . Header . Get ( fmt . Sprintf ( "%s-%d" , uplinkDataKey , i ) )
if chunk == "" {
break
2026-01-31 16:34:13 +03:00
}
2026-03-07 12:34:41 +00:00
headerPayloadChunks = append ( headerPayloadChunks , chunk )
}
headerPayloadEncoded := strings . Join ( headerPayloadChunks , "" )
headerPayload , err = base64 . RawURLEncoding . DecodeString ( headerPayloadEncoded )
if err != nil {
errors . LogInfo ( context . Background ( ) , "Invalid base64 in header's payload: " , err . Error ( ) )
writer . WriteHeader ( http . StatusBadRequest )
return
}
}
2026-01-31 16:34:13 +03:00
2026-03-07 12:34:41 +00:00
var cookiePayload [ ] byte
if dataPlacement == PlacementAuto || dataPlacement == PlacementCookie {
var cookiePayloadChunks [ ] string
for i := 0 ; true ; i ++ {
cookieName := fmt . Sprintf ( "%s_%d" , uplinkDataKey , i )
if c , _ := request . Cookie ( cookieName ) ; c != nil {
cookiePayloadChunks = append ( cookiePayloadChunks , c . Value )
} else {
break
2026-01-31 16:34:13 +03:00
}
}
2026-03-07 12:34:41 +00:00
cookiePayloadEncoded := strings . Join ( cookiePayloadChunks , "" )
cookiePayload , err = base64 . RawURLEncoding . DecodeString ( cookiePayloadEncoded )
if err != nil {
errors . LogInfo ( context . Background ( ) , "Invalid base64 in cookies' payload: " , err . Error ( ) )
writer . WriteHeader ( http . StatusBadRequest )
return
}
}
2026-01-31 16:34:13 +03:00
2026-03-07 12:34:41 +00:00
var bodyPayload [ ] byte
if dataPlacement == PlacementAuto || dataPlacement == PlacementBody {
bodyPayload , err = io . ReadAll ( io . LimitReader ( request . Body , int64 ( scMaxEachPostBytes ) + 1 ) )
if err != nil {
errors . LogInfoInner ( context . Background ( ) , err , "failed to upload (ReadAll)" )
2026-01-31 16:34:13 +03:00
writer . WriteHeader ( http . StatusInternalServerError )
return
}
}
2024-07-29 06:35:17 +02:00
2026-03-07 12:34:41 +00:00
payload := slices . Concat ( headerPayload , cookiePayload , bodyPayload )
2024-07-29 12:10:29 +02:00
if len ( payload ) > scMaxEachPostBytes {
2025-01-18 20:05:19 +08:00
errors . LogInfo ( context . Background ( ) , "Too large upload. scMaxEachPostBytes is set to " , scMaxEachPostBytes , "but request size exceed it. Adjust scMaxEachPostBytes on the server to be at least as large as client." )
2024-07-29 06:35:17 +02:00
writer . WriteHeader ( http . StatusRequestEntityTooLarge )
return
}
2026-01-31 16:34:13 +03:00
seq , err := strconv . ParseUint ( seqStr , 10 , 64 )
2024-06-18 07:36:36 +02:00
if err != nil {
2024-11-09 11:05:41 +00:00
errors . LogInfoInner ( context . Background ( ) , err , "failed to upload (ParseUint)" )
2024-06-18 07:36:36 +02:00
writer . WriteHeader ( http . StatusInternalServerError )
return
}
2024-06-21 01:30:51 +02:00
err = currentSession . uploadQueue . Push ( Packet {
2024-06-18 07:36:36 +02:00
Payload : payload ,
2026-01-31 16:34:13 +03:00
Seq : seq ,
2024-06-18 07:36:36 +02:00
} )
if err != nil {
2024-11-09 11:05:41 +00:00
errors . LogInfoInner ( context . Background ( ) , err , "failed to upload (PushPayload)" )
2024-06-18 07:36:36 +02:00
writer . WriteHeader ( http . StatusInternalServerError )
return
}
2026-03-07 12:34:41 +00:00
if len ( bodyPayload ) == 0 {
// Methods without a body are usually cached by default.
writer . Header ( ) . Set ( "Cache-Control" , "no-store" )
}
2024-06-18 07:36:36 +02:00
writer . WriteHeader ( http . StatusOK )
2025-02-10 13:56:13 +00:00
} else if request . Method == "GET" || sessionId == "" { // stream-down, stream-one
2024-11-27 20:19:18 +00:00
if sessionId != "" {
// after GET is done, the connection is finished. disable automatic
// session reaping, and handle it in defer
currentSession . isFullyConnected . Close ( )
defer h . sessions . Delete ( sessionId )
}
2024-06-18 07:36:36 +02:00
// magic header instructs nginx + apache to not buffer response body
writer . Header ( ) . Set ( "X-Accel-Buffering" , "no" )
2024-08-11 00:59:42 +01:00
// A web-compliant header telling all middleboxes to disable caching.
// Should be able to prevent overloading the cache, or stop CDNs from
// teeing the response stream into their cache, causing slowdowns.
writer . Header ( ) . Set ( "Cache-Control" , "no-store" )
2024-11-27 20:19:18 +00:00
2024-11-30 04:16:35 +00:00
if ! h . config . NoSSEHeader {
2024-07-29 06:32:04 +00:00
// magic header to make the HTTP middle box consider this as SSE to disable buffer
writer . Header ( ) . Set ( "Content-Type" , "text/event-stream" )
}
2024-07-11 09:56:20 +02:00
2024-06-18 07:36:36 +02:00
writer . WriteHeader ( http . StatusOK )
2025-02-20 16:28:06 +00:00
writer . ( http . Flusher ) . Flush ( )
2024-08-11 01:56:25 +02:00
2025-02-20 16:28:06 +00:00
httpSC := & httpServerConn {
Instance : done . New ( ) ,
Reader : request . Body ,
ResponseWriter : writer ,
}
2024-06-18 07:36:36 +02:00
conn := splitConn {
2025-02-20 16:28:06 +00:00
writer : httpSC ,
reader : httpSC ,
2024-06-18 07:36:36 +02:00
remoteAddr : remoteAddr ,
2025-02-20 16:28:06 +00:00
localAddr : h . localAddr ,
2024-06-18 07:36:36 +02:00
}
2025-02-10 13:56:13 +00:00
if sessionId != "" { // if not stream-one
2024-11-27 20:19:18 +00:00
conn . reader = currentSession . uploadQueue
}
2024-06-18 07:36:36 +02:00
h . ln . addConn ( stat . Connection ( & conn ) )
// "A ResponseWriter may not be used after [Handler.ServeHTTP] has returned."
2024-08-22 17:07:57 +02:00
select {
case <- request . Context ( ) . Done ( ) :
2025-02-20 16:28:06 +00:00
case <- httpSC . Wait ( ) :
2024-08-22 17:07:57 +02:00
}
2024-06-18 07:36:36 +02:00
2024-08-22 17:07:57 +02:00
conn . Close ( )
2024-06-18 07:36:36 +02:00
} else {
2024-11-09 11:05:41 +00:00
errors . LogInfo ( context . Background ( ) , "unsupported method: " , request . Method )
2024-06-18 07:36:36 +02:00
writer . WriteHeader ( http . StatusMethodNotAllowed )
}
}
2025-02-20 16:28:06 +00:00
type httpServerConn struct {
2024-06-18 07:36:36 +02:00
sync . Mutex
2025-02-20 16:28:06 +00:00
* done . Instance
io . Reader // no need to Close request.Body
http . ResponseWriter
2024-06-18 07:36:36 +02:00
}
2025-02-20 16:28:06 +00:00
func ( c * httpServerConn ) Write ( b [ ] byte ) ( int , error ) {
2024-06-18 07:36:36 +02:00
c . Lock ( )
defer c . Unlock ( )
2025-02-20 16:28:06 +00:00
if c . Done ( ) {
2024-06-18 07:36:36 +02:00
return 0 , io . ErrClosedPipe
}
2025-02-20 16:28:06 +00:00
n , err := c . ResponseWriter . Write ( b )
2024-06-18 07:36:36 +02:00
if err == nil {
2025-02-20 16:28:06 +00:00
c . ResponseWriter . ( http . Flusher ) . Flush ( )
2024-06-18 07:36:36 +02:00
}
return n , err
}
2025-02-20 16:28:06 +00:00
func ( c * httpServerConn ) Close ( ) error {
2024-06-18 07:36:36 +02:00
c . Lock ( )
defer c . Unlock ( )
2025-02-20 16:28:06 +00:00
return c . Instance . Close ( )
2024-06-18 07:36:36 +02:00
}
type Listener struct {
sync . Mutex
2024-07-19 17:53:47 +00:00
server http . Server
h3server * http3 . Server
listener net . Listener
h3listener * quic . EarlyListener
config * Config
addConn internet . ConnHandler
isH3 bool
2024-06-18 07:36:36 +02:00
}
2025-02-18 10:50:50 +00:00
func ListenXH ( ctx context . Context , address net . Address , port net . Port , streamSettings * internet . MemoryStreamConfig , addConn internet . ConnHandler ) ( internet . Listener , error ) {
2024-06-18 07:36:36 +02:00
l := & Listener {
addConn : addConn ,
}
2025-02-18 10:50:50 +00:00
l . config = streamSettings . ProtocolSettings . ( * Config )
2024-06-18 07:36:36 +02:00
if l . config != nil {
if streamSettings . SocketSettings == nil {
streamSettings . SocketSettings = & internet . SocketConfig { }
}
}
2024-07-19 17:53:47 +00:00
handler := & requestHandler {
2025-11-23 01:09:49 +00:00
config : l . config ,
host : l . config . Host ,
path : l . config . GetNormalizedPath ( ) ,
ln : l ,
sessionMu : & sync . Mutex { } ,
sessions : sync . Map { } ,
socketSettings : streamSettings . SocketSettings ,
2024-07-19 17:53:47 +00:00
}
tlsConfig := getTLSConfig ( streamSettings )
l . isH3 = len ( tlsConfig . NextProtos ) == 1 && tlsConfig . NextProtos [ 0 ] == "h3"
2025-02-18 10:50:50 +00:00
var err error
2024-06-18 07:36:36 +02:00
if port == net . Port ( 0 ) { // unix
2025-02-18 10:50:50 +00:00
l . listener , err = internet . ListenSystem ( ctx , & net . UnixAddr {
2024-06-18 07:36:36 +02:00
Name : address . Domain ( ) ,
Net : "unix" ,
} , streamSettings . SocketSettings )
if err != nil {
2024-12-12 12:19:18 +00:00
return nil , errors . New ( "failed to listen UNIX domain socket for XHTTP on " , address ) . Base ( err )
2024-06-18 07:36:36 +02:00
}
2024-12-12 12:19:18 +00:00
errors . LogInfo ( ctx , "listening UNIX domain socket for XHTTP on " , address )
2024-07-19 17:53:47 +00:00
} else if l . isH3 { // quic
Conn , err := internet . ListenSystemPacket ( context . Background ( ) , & net . UDPAddr {
IP : address . IP ( ) ,
Port : int ( port ) ,
} , streamSettings . SocketSettings )
if err != nil {
2024-12-12 12:19:18 +00:00
return nil , errors . New ( "failed to listen UDP for XHTTP/3 on " , address , ":" , port ) . Base ( err )
2024-07-19 17:53:47 +00:00
}
2026-03-07 23:42:18 +08:00
if streamSettings . UdpmaskManager != nil {
pktConn , err := streamSettings . UdpmaskManager . WrapPacketConnServer ( Conn )
if err != nil {
Conn . Close ( )
return nil , errors . New ( "mask err" ) . Base ( err )
}
Conn = pktConn
}
2026-03-09 20:17:32 +08:00
quicParams := streamSettings . QuicParams
if quicParams == nil {
quicParams = & internet . QuicParams { }
}
quicConfig := & quic . Config {
InitialStreamReceiveWindow : quicParams . InitStreamReceiveWindow ,
MaxStreamReceiveWindow : quicParams . MaxStreamReceiveWindow ,
InitialConnectionReceiveWindow : quicParams . InitConnReceiveWindow ,
MaxConnectionReceiveWindow : quicParams . MaxConnReceiveWindow ,
MaxIdleTimeout : time . Duration ( quicParams . MaxIdleTimeout ) * time . Second ,
MaxIncomingStreams : quicParams . MaxIncomingStreams ,
DisablePathMTUDiscovery : quicParams . DisablePathMtuDiscovery ,
}
l . h3listener , err = quic . ListenEarly ( Conn , tlsConfig , quicConfig )
2024-07-19 17:53:47 +00:00
if err != nil {
2024-12-12 12:19:18 +00:00
return nil , errors . New ( "failed to listen QUIC for XHTTP/3 on " , address , ":" , port ) . Base ( err )
2024-07-19 17:53:47 +00:00
}
2024-12-12 12:19:18 +00:00
errors . LogInfo ( ctx , "listening QUIC for XHTTP/3 on " , address , ":" , port )
2024-07-19 17:53:47 +00:00
2025-02-18 10:50:50 +00:00
handler . localAddr = l . h3listener . Addr ( )
2024-07-19 17:53:47 +00:00
l . h3server = & http3 . Server {
Handler : handler ,
}
go func ( ) {
2026-03-07 16:46:40 +04:00
for {
conn , err := l . h3listener . Accept ( context . Background ( ) )
if err != nil {
errors . LogInfoInner ( ctx , err , "XHTTP/3 listener closed" )
return
}
2026-03-09 20:17:32 +08:00
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" )
2026-03-07 16:46:40 +04:00
congestion . UseBBR ( conn )
}
2026-03-09 20:17:32 +08:00
2026-03-07 16:46:40 +04:00
go func ( ) {
if err := l . h3server . ServeQUICConn ( conn ) ; err != nil {
errors . LogDebugInner ( ctx , err , "XHTTP/3 connection ended" )
}
2026-03-09 20:17:32 +08:00
_ = conn . CloseWithError ( 0 , "" )
2026-03-07 16:46:40 +04:00
} ( )
2024-07-19 17:53:47 +00:00
}
} ( )
2024-06-18 07:36:36 +02:00
} else { // tcp
2025-02-18 10:50:50 +00:00
l . listener , err = internet . ListenSystem ( ctx , & net . TCPAddr {
2024-06-18 07:36:36 +02:00
IP : address . IP ( ) ,
Port : int ( port ) ,
} , streamSettings . SocketSettings )
if err != nil {
2024-12-12 12:19:18 +00:00
return nil , errors . New ( "failed to listen TCP for XHTTP on " , address , ":" , port ) . Base ( err )
2024-06-18 07:36:36 +02:00
}
2024-12-12 12:19:18 +00:00
errors . LogInfo ( ctx , "listening TCP for XHTTP on " , address , ":" , port )
2024-07-21 02:29:50 +02:00
}
2026-03-07 23:42:18 +08:00
if ! l . isH3 && streamSettings . TcpmaskManager != nil {
l . listener , _ = streamSettings . TcpmaskManager . WrapListener ( l . listener )
}
2024-07-21 02:29:50 +02:00
// tcp/unix (h1/h2)
2025-02-18 10:50:50 +00:00
if l . listener != nil {
if config := tls . ConfigFromStreamSettings ( streamSettings ) ; config != nil {
2024-07-21 02:29:50 +02:00
if tlsConfig := config . GetTLSConfig ( ) ; tlsConfig != nil {
2025-02-18 10:50:50 +00:00
l . listener = gotls . NewListener ( l . listener , tlsConfig )
2024-07-21 02:29:50 +02:00
}
}
2024-10-30 03:31:05 +01:00
if config := reality . ConfigFromStreamSettings ( streamSettings ) ; config != nil {
2025-02-18 10:50:50 +00:00
l . listener = goreality . NewListener ( l . listener , config . GetREALITYConfig ( ) )
2024-10-30 03:31:05 +01:00
}
2025-02-18 10:50:50 +00:00
handler . localAddr = l . listener . Addr ( )
2025-03-01 11:20:54 +08:00
// server can handle both plaintext HTTP/1.1 and h2c
protocols := new ( http . Protocols )
protocols . SetHTTP1 ( true )
protocols . SetUnencryptedHTTP2 ( true )
2024-07-23 04:19:31 +08:00
l . server = http . Server {
2025-03-01 11:20:54 +08:00
Handler : handler ,
2024-07-23 04:19:31 +08:00
ReadHeaderTimeout : time . Second * 4 ,
2026-03-07 12:34:41 +00:00
MaxHeaderBytes : l . config . GetNormalizedServerMaxHeaderBytes ( ) ,
2025-03-01 11:20:54 +08:00
Protocols : protocols ,
2024-07-23 04:19:31 +08:00
}
2024-07-19 17:53:47 +00:00
go func ( ) {
if err := l . server . Serve ( l . listener ) ; err != nil {
2025-02-18 11:55:07 +00:00
errors . LogErrorInner ( ctx , err , "failed to serve HTTP for XHTTP" )
2024-07-19 17:53:47 +00:00
}
} ( )
}
2024-06-18 07:36:36 +02:00
return l , err
}
// Addr implements net.Listener.Addr().
func ( ln * Listener ) Addr ( ) net . Addr {
2024-09-25 21:29:41 -04:00
if ln . h3listener != nil {
return ln . h3listener . Addr ( )
}
if ln . listener != nil {
return ln . listener . Addr ( )
}
return nil
2024-06-18 07:36:36 +02:00
}
// Close implements net.Listener.Close().
func ( ln * Listener ) Close ( ) error {
2024-07-19 17:53:47 +00:00
if ln . h3server != nil {
if err := ln . h3server . Close ( ) ; err != nil {
2026-03-09 20:17:32 +08:00
_ = ln . h3listener . Close ( )
2024-07-19 17:53:47 +00:00
return err
}
2026-03-07 16:46:40 +04:00
return ln . h3listener . Close ( )
2024-07-19 17:53:47 +00:00
} else if ln . listener != nil {
return ln . listener . Close ( )
}
return errors . New ( "listener does not have an HTTP/3 server or a net.listener" )
}
2025-02-18 10:50:50 +00:00
func getTLSConfig ( streamSettings * internet . MemoryStreamConfig ) * gotls . Config {
config := tls . ConfigFromStreamSettings ( streamSettings )
2024-07-19 17:53:47 +00:00
if config == nil {
2025-02-18 10:50:50 +00:00
return & gotls . Config { }
2024-07-19 17:53:47 +00:00
}
return config . GetTLSConfig ( )
2024-06-18 07:36:36 +02:00
}
func init ( ) {
2025-02-18 10:50:50 +00:00
common . Must ( internet . RegisterTransportListener ( protocolName , ListenXH ) )
2024-06-18 07:36:36 +02:00
}