mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-05-08 14:13:22 +00:00
XHTTP transport: New options for bypassing CDN's detection (#5414)
Usage: https://github.com/XTLS/Xray-core/pull/5414#issuecomment-3770071786 Closes https://github.com/XTLS/Xray-core/issues/4346 --------- Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
This commit is contained in:
@@ -4,9 +4,10 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
gotls "crypto/tls"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -100,6 +101,24 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
||||
}
|
||||
|
||||
h.config.WriteResponseHeader(writer)
|
||||
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",
|
||||
}
|
||||
}
|
||||
|
||||
h.config.ApplyXPaddingToHeader(writer.Header(), config)
|
||||
|
||||
/*
|
||||
clientVer := []int{0, 0, 0}
|
||||
@@ -110,29 +129,15 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
||||
*/
|
||||
|
||||
validRange := h.config.GetNormalizedXPaddingBytes()
|
||||
paddingLength := 0
|
||||
paddingValue, paddingPlacement := h.config.ExtractXPaddingFromRequest(request, h.config.XPaddingObfsMode)
|
||||
|
||||
referrer := request.Header.Get("Referer")
|
||||
if referrer != "" {
|
||||
if referrerURL, err := url.Parse(referrer); err == nil {
|
||||
// Browser dialer cannot control the host part of referrer header, so only check the query
|
||||
paddingLength = len(referrerURL.Query().Get("x_padding"))
|
||||
}
|
||||
} else {
|
||||
paddingLength = len(request.URL.Query().Get("x_padding"))
|
||||
}
|
||||
|
||||
if int32(paddingLength) < validRange.From || int32(paddingLength) > validRange.To {
|
||||
errors.LogInfo(context.Background(), "invalid x_padding length:", int32(paddingLength))
|
||||
if !h.config.IsPaddingValid(paddingValue, validRange.From, validRange.To, PaddingMethod(h.config.XPaddingMethod)) {
|
||||
errors.LogInfo(context.Background(), "invalid padding ("+paddingPlacement+") length:", int32(len(paddingValue)))
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
sessionId := ""
|
||||
subpath := strings.Split(request.URL.Path[len(h.path):], "/")
|
||||
if len(subpath) > 0 {
|
||||
sessionId = subpath[0]
|
||||
}
|
||||
sessionId, seqStr := h.config.ExtractMetaFromRequest(request, h.path)
|
||||
|
||||
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")
|
||||
@@ -178,14 +183,29 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
||||
currentSession = h.upsertSession(sessionId)
|
||||
}
|
||||
scMaxEachPostBytes := int(h.ln.config.GetNormalizedScMaxEachPostBytes().To)
|
||||
uplinkHTTPMethod := h.config.GetNormalizedUplinkHTTPMethod()
|
||||
isUplinkRequest := false
|
||||
|
||||
if request.Method == "POST" && sessionId != "" { // stream-up, packet-up
|
||||
seq := ""
|
||||
if len(subpath) > 1 {
|
||||
seq = subpath[1]
|
||||
if uplinkHTTPMethod != "GET" && request.Method == uplinkHTTPMethod {
|
||||
isUplinkRequest = true
|
||||
}
|
||||
|
||||
uplinkDataPlacement := h.config.GetNormalizedUplinkDataPlacement()
|
||||
uplinkDataKey := h.config.UplinkDataKey
|
||||
|
||||
switch uplinkDataPlacement {
|
||||
case PlacementHeader:
|
||||
if request.Header.Get(uplinkDataKey+"-Upstream") == "1" {
|
||||
isUplinkRequest = true
|
||||
}
|
||||
case PlacementCookie:
|
||||
if c, _ := request.Cookie(uplinkDataKey + "_upstream"); c != nil && c.Value == "1" {
|
||||
isUplinkRequest = true
|
||||
}
|
||||
}
|
||||
|
||||
if seq == "" {
|
||||
if isUplinkRequest && sessionId != "" { // stream-up, packet-up
|
||||
if seqStr == "" {
|
||||
if h.config.Mode != "" && h.config.Mode != "auto" && h.config.Mode != "stream-up" {
|
||||
errors.LogInfo(context.Background(), "stream-up mode is not allowed")
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
@@ -207,6 +227,7 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
||||
writer.Header().Set("Cache-Control", "no-store")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
scStreamUpServerSecs := h.config.GetNormalizedScStreamUpServerSecs()
|
||||
referrer := request.Header.Get("Referer")
|
||||
if referrer != "" && scStreamUpServerSecs.To > 0 {
|
||||
go func() {
|
||||
for {
|
||||
@@ -233,7 +254,62 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
payload, err := io.ReadAll(io.LimitReader(request.Body, int64(scMaxEachPostBytes)+1))
|
||||
var payload []byte
|
||||
|
||||
if uplinkDataPlacement != PlacementBody {
|
||||
var encodedStr string
|
||||
switch uplinkDataPlacement {
|
||||
case PlacementHeader:
|
||||
dataLenStr := request.Header.Get(uplinkDataKey + "-Length")
|
||||
|
||||
if dataLenStr != "" {
|
||||
dataLen, _ := strconv.Atoi(dataLenStr)
|
||||
var chunks []string
|
||||
i := 0
|
||||
|
||||
for {
|
||||
chunk := request.Header.Get(fmt.Sprintf("%s-%d", uplinkDataKey, i))
|
||||
if chunk == "" {
|
||||
break
|
||||
}
|
||||
chunks = append(chunks, chunk)
|
||||
i++
|
||||
}
|
||||
|
||||
encodedStr = strings.Join(chunks, "")
|
||||
if len(encodedStr) != dataLen {
|
||||
encodedStr = ""
|
||||
}
|
||||
}
|
||||
case PlacementCookie:
|
||||
var chunks []string
|
||||
i := 0
|
||||
|
||||
for {
|
||||
cookieName := fmt.Sprintf("%s_%d", uplinkDataKey, i)
|
||||
if c, _ := request.Cookie(cookieName); c != nil {
|
||||
chunks = append(chunks, c.Value)
|
||||
i++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(chunks) > 0 {
|
||||
encodedStr = strings.Join(chunks, "")
|
||||
}
|
||||
}
|
||||
|
||||
if encodedStr != "" {
|
||||
payload, err = base64.RawURLEncoding.DecodeString(encodedStr)
|
||||
} else {
|
||||
errors.LogInfoInner(context.Background(), err, "failed to extract data from key "+uplinkDataKey+" placed in "+uplinkDataPlacement)
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
payload, err = io.ReadAll(io.LimitReader(request.Body, int64(scMaxEachPostBytes)+1))
|
||||
}
|
||||
|
||||
if len(payload) > scMaxEachPostBytes {
|
||||
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.")
|
||||
@@ -247,7 +323,7 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
seqInt, err := strconv.ParseUint(seq, 10, 64)
|
||||
seq, err := strconv.ParseUint(seqStr, 10, 64)
|
||||
if err != nil {
|
||||
errors.LogInfoInner(context.Background(), err, "failed to upload (ParseUint)")
|
||||
writer.WriteHeader(http.StatusInternalServerError)
|
||||
@@ -256,7 +332,7 @@ func (h *requestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Req
|
||||
|
||||
err = currentSession.uploadQueue.Push(Packet{
|
||||
Payload: payload,
|
||||
Seq: seqInt,
|
||||
Seq: seq,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user