mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-06-27 04:39:02 +03:00
Add Snell protocol. Refactor MASQUE HTTP/2, Fair Queue. Update XHTTP, OpenVPN, Sudoku, Fallback. Fixes
This commit is contained in:
@@ -18,6 +18,8 @@ import (
|
||||
"github.com/sagernet/quic-go"
|
||||
"github.com/sagernet/quic-go/http3"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/congestion"
|
||||
"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"
|
||||
@@ -31,6 +33,7 @@ import (
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
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"
|
||||
)
|
||||
@@ -49,7 +52,7 @@ type Server struct {
|
||||
options *option.V2RayXHTTPOptions
|
||||
host string
|
||||
path string
|
||||
sessionMu sync.Mutex
|
||||
sessionMu *kmutex.Kmutex[string]
|
||||
sessions sync.Map
|
||||
}
|
||||
|
||||
@@ -62,6 +65,7 @@ func NewServer(ctx context.Context, logger logger.ContextLogger, options option.
|
||||
options: &options,
|
||||
host: options.Host,
|
||||
path: options.GetNormalizedPath(),
|
||||
sessionMu: kmutex.New[string](),
|
||||
}
|
||||
if server.network() == N.NetworkTCP {
|
||||
protocols := new(http.Protocols)
|
||||
@@ -80,11 +84,21 @@ func NewServer(ctx context.Context, logger logger.ContextLogger, options option.
|
||||
},
|
||||
}
|
||||
} else {
|
||||
congestionControlFactory, err := congestion.NewCongestionControl(options.CongestionController, options.CWND, ntp.TimeFuncFromContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
server.quicConfig = &quic.Config{
|
||||
DisablePathMTUDiscovery: !C.IsLinux && !C.IsWindows,
|
||||
}
|
||||
server.http3Server = &http3.Server{
|
||||
Handler: server,
|
||||
ConnContext: func(ctx context.Context, conn *quic.Conn) context.Context {
|
||||
if congestionControlFactory != nil {
|
||||
conn.SetCongestionControl(congestionControlFactory(conn))
|
||||
}
|
||||
return log.ContextWithNewID(ctx)
|
||||
},
|
||||
}
|
||||
}
|
||||
return server, nil
|
||||
@@ -102,7 +116,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
WriteResponseHeader(writer, request.Method, request.Header, s.options)
|
||||
length := int(s.options.GetNormalizedXPaddingBytes().Rand())
|
||||
length := s.options.GetNormalizedXPaddingBytes().Rand()
|
||||
config := XPaddingConfig{Length: length}
|
||||
if s.options.XPaddingObfsMode {
|
||||
config.Placement = XPaddingPlacement{
|
||||
@@ -125,15 +139,25 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
validRange := s.options.GetNormalizedXPaddingBytes()
|
||||
paddingValue, paddingPlacement := ExtractXPaddingFromRequest(&s.options.V2RayXHTTPBaseOptions, request, s.options.XPaddingObfsMode)
|
||||
if !IsPaddingValid(&s.options.V2RayXHTTPBaseOptions, paddingValue, validRange.From, validRange.To, PaddingMethod(s.options.XPaddingMethod)) {
|
||||
s.logger.ErrorContext(request.Context(), "invalid padding ("+paddingPlacement+") length:", int32(len(paddingValue)))
|
||||
s.logger.ErrorContext(request.Context(), "invalid padding ("+paddingPlacement+") length:", len(paddingValue))
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
sessionId, seqStr := ExtractMetaFromRequest(s.options, request, s.path)
|
||||
if sessionId == "" && s.options.Mode != "" && s.options.Mode != "auto" && s.options.Mode != "stream-one" && s.options.Mode != "stream-up" {
|
||||
s.logger.ErrorContext(request.Context(), "stream-one mode is not allowed")
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
if s.options.Mode != "" && s.options.Mode != "auto" {
|
||||
if sessionId == "" {
|
||||
if s.options.Mode != "stream-one" && s.options.Mode != "stream-up" {
|
||||
s.logger.ErrorContext(request.Context(), "stream-one mode is not allowed")
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if s.options.Mode == "stream-one" {
|
||||
s.logger.ErrorContext(request.Context(), "session is not allowed in stream-one mode")
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
var forwardedAddrs []xnet.Address
|
||||
if len(s.options.TrustedXForwardedFor) > 0 {
|
||||
@@ -171,7 +195,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
if sessionId != "" {
|
||||
currentSession = s.upsertSession(sessionId)
|
||||
}
|
||||
scMaxEachPostBytes := int(s.options.GetNormalizedScMaxEachPostBytes().To)
|
||||
scMaxEachPostBytes := s.options.GetNormalizedScMaxEachPostBytes().To
|
||||
uplinkDataPlacement := s.options.GetNormalizedUplinkDataPlacement()
|
||||
uplinkDataKey := s.options.UplinkDataKey
|
||||
isUplinkRequest := false
|
||||
@@ -207,12 +231,22 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
referrer := request.Header.Get("Referer")
|
||||
if referrer != "" && scStreamUpServerSecs.To > 0 {
|
||||
go func() {
|
||||
timer := time.NewTimer(0)
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
defer timer.Stop()
|
||||
for {
|
||||
_, err := httpSC.Write(bytes.Repeat([]byte{'X'}, int(s.options.GetNormalizedXPaddingBytes().Rand())))
|
||||
_, err := httpSC.Write(bytes.Repeat([]byte{'X'}, s.options.GetNormalizedXPaddingBytes().Rand()))
|
||||
if err != nil {
|
||||
break
|
||||
return
|
||||
}
|
||||
timer.Reset(time.Duration(scStreamUpServerSecs.Rand()) * time.Second)
|
||||
select {
|
||||
case <-timer.C:
|
||||
case <-httpSC.Wait():
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Duration(scStreamUpServerSecs.Rand()) * time.Second)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -327,7 +361,11 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
// after GET is done, the connection is finished. disable automatic
|
||||
// session reaping, and handle it in defer
|
||||
currentSession.isFullyConnected.Close()
|
||||
defer s.sessions.Delete(sessionId)
|
||||
defer func() {
|
||||
s.sessionMu.Lock(sessionId)
|
||||
defer s.sessionMu.Unlock(sessionId)
|
||||
s.sessions.Delete(sessionId)
|
||||
}()
|
||||
}
|
||||
// magic header instructs nginx + apache to not buffer response body
|
||||
writer.Header().Set("X-Accel-Buffering", "no")
|
||||
@@ -410,32 +448,27 @@ func (s *Server) network() string {
|
||||
}
|
||||
|
||||
func (s *Server) upsertSession(sessionId string) *httpSession {
|
||||
// fast path
|
||||
s.sessionMu.Lock(sessionId)
|
||||
defer s.sessionMu.Unlock(sessionId)
|
||||
currentSessionAny, ok := s.sessions.Load(sessionId)
|
||||
if ok {
|
||||
return currentSessionAny.(*httpSession)
|
||||
}
|
||||
// slow path
|
||||
s.sessionMu.Lock()
|
||||
defer s.sessionMu.Unlock()
|
||||
currentSessionAny, ok = s.sessions.Load(sessionId)
|
||||
if ok {
|
||||
return currentSessionAny.(*httpSession)
|
||||
}
|
||||
session := &httpSession{
|
||||
uploadQueue: NewUploadQueue(s.options.GetNormalizedScMaxBufferedPosts()),
|
||||
isFullyConnected: done.New(),
|
||||
}
|
||||
s.sessions.Store(sessionId, session)
|
||||
shouldReap := done.New()
|
||||
go func() {
|
||||
time.Sleep(30 * time.Second)
|
||||
shouldReap.Close()
|
||||
}()
|
||||
go func() {
|
||||
reapTimer := time.NewTimer(30 * time.Second)
|
||||
defer reapTimer.Stop()
|
||||
select {
|
||||
case <-shouldReap.Wait():
|
||||
s.sessions.Delete(sessionId)
|
||||
case <-reapTimer.C:
|
||||
s.sessionMu.Lock(sessionId)
|
||||
if current, ok := s.sessions.Load(sessionId); ok && current.(*httpSession) == session {
|
||||
s.sessions.Delete(sessionId)
|
||||
}
|
||||
s.sessionMu.Unlock(sessionId)
|
||||
session.uploadQueue.Close()
|
||||
case <-session.isFullyConnected.Wait():
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user