mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-10-20 08:14:24 +00:00
xhttp: Port upstream Xray fixes and config checks
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -186,6 +187,18 @@ const (
|
||||
PlacementAuto = "auto"
|
||||
)
|
||||
|
||||
var V2RayXHTTPPredefinedSessionIDTable = map[string]string{
|
||||
"ALPHABET": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
"Alphabet": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
||||
"BASE36": "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
"Base62": "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
||||
"HEX": "0123456789ABCDEF",
|
||||
"alphabet": "abcdefghijklmnopqrstuvwxyz",
|
||||
"base36": "0123456789abcdefghijklmnopqrstuvwxyz",
|
||||
"hex": "0123456789abcdef",
|
||||
"number": "0123456789",
|
||||
}
|
||||
|
||||
func (c V2RayXHTTPOptions) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal((*_V2RayXHTTPOptions)(&c))
|
||||
}
|
||||
@@ -202,6 +215,9 @@ func (c *V2RayXHTTPOptions) UnmarshalJSON(bytes []byte) error {
|
||||
default:
|
||||
return E.New("unsupported mode: " + c.Mode)
|
||||
}
|
||||
if c.Download != nil && c.Mode == "stream-one" {
|
||||
return E.New(`can not use "download" in "stream-one" mode`)
|
||||
}
|
||||
err = checkV2RayXHTTPBaseOptions(c.Mode, &c.V2RayXHTTPBaseOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -297,6 +313,27 @@ func checkV2RayXHTTPBaseOptions(mode string, options *V2RayXHTTPBaseOptions) err
|
||||
}
|
||||
}
|
||||
|
||||
if options.SessionIDTable != "" {
|
||||
if predefined, ok := V2RayXHTTPPredefinedSessionIDTable[options.SessionIDTable]; ok {
|
||||
options.SessionIDTable = predefined
|
||||
}
|
||||
if options.SessionIDLength.From <= 0 {
|
||||
return E.New("session_id_length.from must be greater than 0")
|
||||
}
|
||||
room := new(big.Int)
|
||||
for length := options.SessionIDLength.From; length <= options.SessionIDLength.To; length++ {
|
||||
room.Add(room, new(big.Int).Exp(big.NewInt(int64(len(options.SessionIDTable))), big.NewInt(int64(length)), nil))
|
||||
}
|
||||
if room.Cmp(big.NewInt(2<<30)) < 0 {
|
||||
return E.New("session_id_table or session_id_length is too small")
|
||||
}
|
||||
for i := 0; i < len(options.SessionIDTable); i++ {
|
||||
if options.SessionIDTable[i] >= 0x80 {
|
||||
return E.New("session_id_table must contain only ASCII characters")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if options.SeqPlacement != "path" && options.SeqKey == "" {
|
||||
switch options.SeqPlacement {
|
||||
case "cookie", "query":
|
||||
@@ -325,14 +362,17 @@ func checkV2RayXHTTPBaseOptions(mode string, options *V2RayXHTTPBaseOptions) err
|
||||
|
||||
if options.Xmux == nil {
|
||||
options.Xmux = &V2RayXHTTPXmuxOptions{}
|
||||
options.Xmux.MaxConcurrency.From = 1
|
||||
options.Xmux.MaxConcurrency.To = 1
|
||||
}
|
||||
if options.Xmux.MaxConnections.To > 0 && options.Xmux.MaxConcurrency.To > 0 {
|
||||
return E.New("max_connections cannot be specified together with max_concurrency")
|
||||
}
|
||||
if *options.Xmux == (V2RayXHTTPXmuxOptions{}) {
|
||||
options.Xmux.MaxConnections.From = 3
|
||||
options.Xmux.MaxConnections.To = 3
|
||||
options.Xmux.HMaxRequestTimes.From = 600
|
||||
options.Xmux.HMaxRequestTimes.To = 900
|
||||
options.Xmux.HMaxReusableSecs.From = 1800
|
||||
options.Xmux.HMaxReusableSecs.To = 3000
|
||||
} else if options.Xmux.MaxConnections.To > 0 && options.Xmux.MaxConcurrency.To > 0 {
|
||||
return E.New("max_connections cannot be specified together with max_concurrency")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -343,8 +383,10 @@ func (c *V2RayXHTTPBaseOptions) GetNormalizedPath() string {
|
||||
if path == "" || path[0] != '/' {
|
||||
path = "/" + path
|
||||
}
|
||||
if path[len(path)-1] != '/' {
|
||||
path = path + "/"
|
||||
if c.GetNormalizedSessionPlacement() == PlacementPath || c.GetNormalizedSeqPlacement() == PlacementPath {
|
||||
if path[len(path)-1] != '/' {
|
||||
path = path + "/"
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
@@ -385,7 +427,7 @@ func (c *V2RayXHTTPBaseOptions) GetNormalizedUplinkHTTPMethod() string {
|
||||
}
|
||||
|
||||
func (c *V2RayXHTTPBaseOptions) GetNormalizedScMaxEachPostBytes() badoption.Range[int] {
|
||||
if c.ScMaxEachPostBytes == nil {
|
||||
if c.ScMaxEachPostBytes == nil || c.ScMaxEachPostBytes.To == 0 {
|
||||
return badoption.Range[int]{
|
||||
From: 1000000,
|
||||
To: 1000000,
|
||||
@@ -395,7 +437,7 @@ func (c *V2RayXHTTPBaseOptions) GetNormalizedScMaxEachPostBytes() badoption.Rang
|
||||
}
|
||||
|
||||
func (c *V2RayXHTTPBaseOptions) GetNormalizedScMinPostsIntervalMs() badoption.Range[int] {
|
||||
if c.ScMinPostsIntervalMs == nil {
|
||||
if c.ScMinPostsIntervalMs == nil || c.ScMinPostsIntervalMs.To == 0 {
|
||||
return badoption.Range[int]{
|
||||
From: 30,
|
||||
To: 30,
|
||||
@@ -413,7 +455,7 @@ func (c *V2RayXHTTPBaseOptions) GetNormalizedScMaxBufferedPosts() int {
|
||||
}
|
||||
|
||||
func (c *V2RayXHTTPBaseOptions) GetNormalizedScStreamUpServerSecs() badoption.Range[int] {
|
||||
if c.ScStreamUpServerSecs == nil {
|
||||
if c.ScStreamUpServerSecs == nil || c.ScStreamUpServerSecs.To == 0 {
|
||||
return badoption.Range[int]{
|
||||
From: 20,
|
||||
To: 80,
|
||||
|
||||
@@ -79,7 +79,11 @@ func (c *DefaultDialerClient) OpenStream(ctx context.Context, url string, sessio
|
||||
method = c.options.GetNormalizedUplinkHTTPMethod() // stream-up/one
|
||||
}
|
||||
reqCtx, cancel := context.WithCancel(context.WithoutCancel(ctx))
|
||||
req, _ := http.NewRequestWithContext(reqCtx, method, url, body)
|
||||
req, err := http.NewRequestWithContext(reqCtx, method, url, body)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
FillStreamRequest(req, sessionId, "", c.options)
|
||||
wrc = &WaitReadCloser{wait: done.New(), Cancel: cancel}
|
||||
go func() {
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"github.com/sagernet/sing-box/common/kmutex"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
"github.com/sagernet/sing-box/common/xray/buf"
|
||||
xnet "github.com/sagernet/sing-box/common/xray/net"
|
||||
"github.com/sagernet/sing-box/common/xray/signal/done"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
@@ -35,7 +34,6 @@ import (
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
aTLS "github.com/sagernet/sing/common/tls"
|
||||
sHttp "github.com/sagernet/sing/protocol/http"
|
||||
)
|
||||
|
||||
var _ adapter.V2RayServerTransport = (*Server)(nil)
|
||||
@@ -143,6 +141,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
obfsPaddingAccepted := s.options.XPaddingObfsMode && paddingValue != ""
|
||||
sessionId, seqStr := ExtractMetaFromRequest(s.options, request, s.path)
|
||||
if s.options.Mode != "" && s.options.Mode != "auto" {
|
||||
if sessionId == "" {
|
||||
@@ -159,17 +158,6 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
}
|
||||
}
|
||||
}
|
||||
var forwardedAddrs []xnet.Address
|
||||
if len(s.options.TrustedXForwardedFor) > 0 {
|
||||
for _, key := range s.options.TrustedXForwardedFor {
|
||||
if len(request.Header.Values(key)) > 0 {
|
||||
forwardedAddrs = parseXForwardedFor(request.Header)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
forwardedAddrs = parseXForwardedFor(request.Header)
|
||||
}
|
||||
var remoteAddr net.Addr
|
||||
var err error
|
||||
remoteAddr, err = net.ResolveTCPAddr("tcp", request.RemoteAddr)
|
||||
@@ -185,10 +173,20 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
Port: remoteAddr.(*net.TCPAddr).Port,
|
||||
}
|
||||
}
|
||||
if len(forwardedAddrs) > 0 && forwardedAddrs[0].Family().IsIP() {
|
||||
remoteAddr = &net.TCPAddr{
|
||||
IP: forwardedAddrs[0].IP(),
|
||||
Port: 0,
|
||||
if forwardedFor := request.Header.Get("X-Forwarded-For"); forwardedFor != "" {
|
||||
if slices.ContainsFunc(s.options.TrustedXForwardedFor, func(key string) bool {
|
||||
return len(request.Header.Values(key)) > 0
|
||||
}) {
|
||||
if forwardedAddress := parseXForwardedFor(request.Header)[0]; forwardedAddress.Family().IsIP() {
|
||||
remoteAddr = &net.TCPAddr{
|
||||
IP: forwardedAddress.IP(),
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
} else if len(s.options.TrustedXForwardedFor) == 0 {
|
||||
s.logger.WarnContext(request.Context(), "received \"X-Forwarded-For\" from ", remoteAddr, " but \"trusted_x_forwarded_for\" is not configured; ignoring it and using the real remote address")
|
||||
} else {
|
||||
s.logger.ErrorContext(request.Context(), "ignored potentially forged \"X-Forwarded-For\" from ", remoteAddr, ": ", forwardedFor)
|
||||
}
|
||||
}
|
||||
var currentSession *httpSession
|
||||
@@ -228,8 +226,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.Header().Set("Cache-Control", "no-store")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
scStreamUpServerSecs := s.options.GetNormalizedScStreamUpServerSecs()
|
||||
referrer := request.Header.Get("Referer")
|
||||
if referrer != "" && scStreamUpServerSecs.To > 0 {
|
||||
if (request.Header.Get("Referer") != "" || obfsPaddingAccepted) && scStreamUpServerSecs.To > 0 {
|
||||
go func() {
|
||||
timer := time.NewTimer(0)
|
||||
if !timer.Stop() {
|
||||
@@ -384,16 +381,20 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
Reader: request.Body,
|
||||
ResponseWriter: writer,
|
||||
}
|
||||
localAddress := s.localAddr
|
||||
if requestLocalAddress, ok := request.Context().Value(http.LocalAddrContextKey).(net.Addr); ok && requestLocalAddress != nil {
|
||||
localAddress = requestLocalAddress
|
||||
}
|
||||
conn := splitConn{
|
||||
writer: httpSC,
|
||||
reader: httpSC,
|
||||
remoteAddr: remoteAddr,
|
||||
localAddr: s.localAddr,
|
||||
localAddr: localAddress,
|
||||
}
|
||||
if sessionId != "" { // if not stream-one
|
||||
conn.reader = currentSession.uploadQueue
|
||||
}
|
||||
s.handler.NewConnectionEx(v2rayhttp.HWIDContext(request.Context(), request.Header), &conn, sHttp.SourceAddress(request), M.Socksaddr{}, func(it error) {})
|
||||
s.handler.NewConnectionEx(v2rayhttp.HWIDContext(request.Context(), request.Header), &conn, M.SocksaddrFromNet(remoteAddr).Unwrap(), M.Socksaddr{}, func(it error) {})
|
||||
// "A ResponseWriter may not be used after [Handler.ServeHTTP] has returned."
|
||||
select {
|
||||
case <-request.Context().Done():
|
||||
|
||||
@@ -14,24 +14,10 @@ import (
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
// PredefinedTable maps named charsets to their alphabets for session ID generation.
|
||||
|
||||
var PredefinedTable = map[string]string{
|
||||
"ALPHABET": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
"Alphabet": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
||||
"BASE36": "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
"Base62": "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
||||
"HEX": "0123456789ABCDEF",
|
||||
"alphabet": "abcdefghijklmnopqrstuvwxyz",
|
||||
"base36": "0123456789abcdefghijklmnopqrstuvwxyz",
|
||||
"hex": "0123456789abcdef",
|
||||
"number": "0123456789",
|
||||
}
|
||||
|
||||
func GenerateSessionID(options *option.V2RayXHTTPBaseOptions) string {
|
||||
length := options.SessionIDLength.Rand()
|
||||
table := options.SessionIDTable
|
||||
if predefined, ok := PredefinedTable[table]; ok {
|
||||
if predefined, ok := option.V2RayXHTTPPredefinedSessionIDTable[table]; ok {
|
||||
table = predefined
|
||||
}
|
||||
if table != "" && length > 0 {
|
||||
|
||||
Reference in New Issue
Block a user