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:
@@ -16,12 +16,12 @@ 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/tls"
|
||||
"github.com/sagernet/sing-box/common/xray/buf"
|
||||
"github.com/sagernet/sing-box/common/xray/net"
|
||||
"github.com/sagernet/sing-box/common/xray/pipe"
|
||||
"github.com/sagernet/sing-box/common/xray/signal/done"
|
||||
"github.com/sagernet/sing-box/common/xray/uuid"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
qtls "github.com/sagernet/sing-quic"
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
sHTTP "github.com/sagernet/sing/protocol/http"
|
||||
"github.com/sagernet/sing/service"
|
||||
"golang.org/x/net/http2"
|
||||
@@ -42,15 +43,22 @@ type Client struct {
|
||||
baseRequestURL2 url.URL
|
||||
getHTTPClient func() (DialerClient, *XmuxClient)
|
||||
getHTTPClient2 func() (DialerClient, *XmuxClient)
|
||||
xmuxManager *XmuxManager
|
||||
xmuxManager2 *XmuxManager
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, logger log.ContextLogger, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayXHTTPOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {
|
||||
if options.Mode == "" {
|
||||
return nil, E.New("mode is not set")
|
||||
}
|
||||
if tlsConfig != nil && len(tlsConfig.NextProtos()) == 0 {
|
||||
tlsConfig.SetNextProtos([]string{"h2"})
|
||||
}
|
||||
if _, err := congestion.NewCongestionControl(options.CongestionController, options.CWND, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options.Download != nil {
|
||||
if _, err := congestion.NewCongestionControl(options.Download.CongestionController, options.Download.CWND, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
dest := serverAddr
|
||||
baseRequestURL, err := getBaseRequestURL(&options.V2RayXHTTPBaseOptions, dest, tlsConfig)
|
||||
if err != nil {
|
||||
@@ -61,7 +69,7 @@ func NewClient(ctx context.Context, logger log.ContextLogger, dialer N.Dialer, s
|
||||
xmuxOptions = *options.Xmux
|
||||
}
|
||||
xmuxManager := NewXmuxManager(xmuxOptions, func() XmuxConn {
|
||||
return createHTTPClient(dest, dialer, &options.V2RayXHTTPBaseOptions, tlsConfig)
|
||||
return createHTTPClient(ctx, dest, dialer, &options.V2RayXHTTPBaseOptions, tlsConfig)
|
||||
})
|
||||
getHTTPClient := func() (DialerClient, *XmuxClient) {
|
||||
xmuxClient := xmuxManager.GetXmuxClient(ctx)
|
||||
@@ -69,6 +77,7 @@ func NewClient(ctx context.Context, logger log.ContextLogger, dialer N.Dialer, s
|
||||
}
|
||||
baseRequestURL2 := baseRequestURL
|
||||
getHTTPClient2 := getHTTPClient
|
||||
var xmuxManager2 *XmuxManager
|
||||
if options.Download != nil {
|
||||
options2 := options.Download
|
||||
dialer2 := dialer
|
||||
@@ -98,8 +107,8 @@ func NewClient(ctx context.Context, logger log.ContextLogger, dialer N.Dialer, s
|
||||
if options2.Xmux != nil {
|
||||
xmuxOptions2 = *options2.Xmux
|
||||
}
|
||||
xmuxManager2 := NewXmuxManager(xmuxOptions2, func() XmuxConn {
|
||||
return createHTTPClient(dest2, dialer2, &options2.V2RayXHTTPBaseOptions, tlsConfig2)
|
||||
xmuxManager2 = NewXmuxManager(xmuxOptions2, func() XmuxConn {
|
||||
return createHTTPClient(ctx, dest2, dialer2, &options2.V2RayXHTTPBaseOptions, tlsConfig2)
|
||||
})
|
||||
getHTTPClient2 = func() (DialerClient, *XmuxClient) {
|
||||
xmuxClient2 := xmuxManager2.GetXmuxClient(ctx)
|
||||
@@ -113,6 +122,8 @@ func NewClient(ctx context.Context, logger log.ContextLogger, dialer N.Dialer, s
|
||||
getHTTPClient2: getHTTPClient2,
|
||||
baseRequestURL: baseRequestURL,
|
||||
baseRequestURL2: baseRequestURL2,
|
||||
xmuxManager: xmuxManager,
|
||||
xmuxManager2: xmuxManager2,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -121,8 +132,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
||||
mode := c.options.Mode
|
||||
sessionId := ""
|
||||
if c.options.Mode != "stream-one" {
|
||||
sessionIdUuid := uuid.New()
|
||||
sessionId = sessionIdUuid.String()
|
||||
sessionId = GenerateSessionID(&c.options.V2RayXHTTPBaseOptions)
|
||||
}
|
||||
requestURL := c.baseRequestURL
|
||||
requestURL2 := c.baseRequestURL2
|
||||
@@ -182,10 +192,7 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
||||
}
|
||||
scMaxEachPostBytes := options.GetNormalizedScMaxEachPostBytes()
|
||||
scMinPostsIntervalMs := options.GetNormalizedScMinPostsIntervalMs()
|
||||
if scMaxEachPostBytes.From <= 0 {
|
||||
panic("`scMaxEachPostBytes` should be bigger than 0")
|
||||
}
|
||||
maxUploadSize := scMaxEachPostBytes.Rand()
|
||||
maxUploadSize := int32(scMaxEachPostBytes.Rand())
|
||||
// WithSizeLimit(0) will still allow single bytes to pass, and a lot of
|
||||
// code relies on this behavior. Subtract 1 so that together with
|
||||
// uploadWriter wrapper, exact size limits can be enforced
|
||||
@@ -255,6 +262,10 @@ func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
c.xmuxManager.Close()
|
||||
if c.xmuxManager2 != nil {
|
||||
c.xmuxManager2.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -294,7 +305,7 @@ func getBaseRequestURL(options *option.V2RayXHTTPBaseOptions, dest M.Socksaddr,
|
||||
return requestURL, nil
|
||||
}
|
||||
|
||||
func createHTTPClient(dest M.Socksaddr, dialer N.Dialer, options *option.V2RayXHTTPBaseOptions, tlsConfig tls.Config) DialerClient {
|
||||
func createHTTPClient(ctx context.Context, dest M.Socksaddr, dialer N.Dialer, options *option.V2RayXHTTPBaseOptions, tlsConfig tls.Config) DialerClient {
|
||||
httpVersion := decideHTTPVersion(tlsConfig)
|
||||
dialContext := func(ctxInner context.Context) (net.Conn, error) {
|
||||
conn, err := dialer.DialContext(ctxInner, "tcp", dest)
|
||||
@@ -319,6 +330,7 @@ func createHTTPClient(dest M.Socksaddr, dialer N.Dialer, options *option.V2RayXH
|
||||
if keepAlivePeriod < 0 {
|
||||
keepAlivePeriod = 0
|
||||
}
|
||||
congestionControlFactory, _ := congestion.NewCongestionControl(options.CongestionController, options.CWND, ntp.TimeFuncFromContext(ctx))
|
||||
quicConfig := &quic.Config{
|
||||
MaxIdleTimeout: net.ConnIdleTimeout,
|
||||
// these two are defaults of quic-go/http3. the default of quic-go (no
|
||||
@@ -334,7 +346,14 @@ func createHTTPClient(dest M.Socksaddr, dialer N.Dialer, options *option.V2RayXH
|
||||
if dErr != nil {
|
||||
return nil, dErr
|
||||
}
|
||||
return qtls.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsConfig, cfg)
|
||||
conn, dErr := qtls.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsConfig, cfg)
|
||||
if dErr != nil {
|
||||
return nil, dErr
|
||||
}
|
||||
if congestionControlFactory != nil {
|
||||
conn.SetCongestionControl(congestionControlFactory(conn))
|
||||
}
|
||||
return conn, nil
|
||||
},
|
||||
}
|
||||
case "2":
|
||||
|
||||
@@ -39,7 +39,7 @@ func (c *splitConn) Close() error {
|
||||
}
|
||||
|
||||
if err2 != nil {
|
||||
return err
|
||||
return err2
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -147,7 +147,7 @@ func (c *DefaultDialerClient) PostPacket(ctx context.Context, url string, sessio
|
||||
if c.httpVersion != "1.1" {
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
c.closed = true
|
||||
c.Close()
|
||||
return err
|
||||
}
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
@@ -225,10 +225,9 @@ func (w *WaitReadCloser) Set(rc io.ReadCloser) {
|
||||
}
|
||||
|
||||
func (w *WaitReadCloser) Read(b []byte) (int, error) {
|
||||
<-w.Wait
|
||||
if w.ReadCloser == nil {
|
||||
if <-w.Wait; w.ReadCloser == nil {
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
return 0, io.ErrClosedPipe
|
||||
}
|
||||
return w.ReadCloser.Read(b)
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ type XmuxConn interface {
|
||||
|
||||
type XmuxClient struct {
|
||||
XmuxConn XmuxConn
|
||||
openUsage int32
|
||||
leftUsage int32
|
||||
openUsage int
|
||||
leftUsage int
|
||||
LeftRequests atomic.Int32
|
||||
UnreusableAt time.Time
|
||||
|
||||
@@ -37,7 +37,7 @@ func (c *XmuxClient) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *XmuxClient) AddOpenUsage(delta int32) {
|
||||
func (c *XmuxClient) AddOpenUsage(delta int) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
c.openUsage += delta
|
||||
@@ -46,7 +46,7 @@ func (c *XmuxClient) AddOpenUsage(delta int32) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *XmuxClient) GetOpenUsage() int32 {
|
||||
func (c *XmuxClient) GetOpenUsage() int {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
return c.openUsage
|
||||
@@ -54,8 +54,8 @@ func (c *XmuxClient) GetOpenUsage() int32 {
|
||||
|
||||
type XmuxManager struct {
|
||||
options option.V2RayXHTTPXmuxOptions
|
||||
concurrency int32
|
||||
connections int32
|
||||
concurrency int
|
||||
connections int
|
||||
newConnFunc func() XmuxConn
|
||||
xmuxClients []*XmuxClient
|
||||
mtx sync.Mutex
|
||||
@@ -71,6 +71,15 @@ func NewXmuxManager(options option.V2RayXHTTPXmuxOptions, newConnFunc func() Xmu
|
||||
}
|
||||
}
|
||||
|
||||
func (m *XmuxManager) Close() {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
for _, xmuxClient := range m.xmuxClients {
|
||||
xmuxClient.Close()
|
||||
}
|
||||
m.xmuxClients = m.xmuxClients[:0]
|
||||
}
|
||||
|
||||
func (m *XmuxManager) newXmuxClient() *XmuxClient {
|
||||
xmuxClient := &XmuxClient{
|
||||
XmuxConn: m.newConnFunc(),
|
||||
@@ -81,7 +90,7 @@ func (m *XmuxManager) newXmuxClient() *XmuxClient {
|
||||
}
|
||||
xmuxClient.LeftRequests.Store(math.MaxInt32)
|
||||
if x := m.options.GetNormalizedHMaxRequestTimes().Rand(); x > 0 {
|
||||
xmuxClient.LeftRequests.Store(x)
|
||||
xmuxClient.LeftRequests.Store(int32(x))
|
||||
}
|
||||
if x := m.options.GetNormalizedHMaxReusableSecs().Rand(); x > 0 {
|
||||
xmuxClient.UnreusableAt = time.Now().Add(time.Duration(x) * time.Second)
|
||||
@@ -112,7 +121,7 @@ func (m *XmuxManager) GetXmuxClient(ctx context.Context) *XmuxClient {
|
||||
if len(m.xmuxClients) == 0 {
|
||||
return m.newXmuxClient()
|
||||
}
|
||||
if m.connections > 0 && len(m.xmuxClients) < int(m.connections) {
|
||||
if m.connections > 0 && len(m.xmuxClients) < m.connections {
|
||||
return m.newXmuxClient()
|
||||
}
|
||||
xmuxClients := make([]*XmuxClient, 0)
|
||||
|
||||
@@ -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():
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ package xhttp
|
||||
import (
|
||||
"container/heap"
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -19,19 +18,22 @@ type Packet struct {
|
||||
}
|
||||
|
||||
type uploadQueue struct {
|
||||
reader io.ReadCloser
|
||||
nomore bool
|
||||
pushedPackets chan Packet
|
||||
writeCloseMutex sync.Mutex
|
||||
heap uploadHeap
|
||||
nextSeq uint64
|
||||
closed bool
|
||||
maxPackets int
|
||||
reader io.ReadCloser
|
||||
nomore bool
|
||||
pushedPackets chan Packet
|
||||
done chan struct{}
|
||||
heap uploadHeap
|
||||
nextSeq uint64
|
||||
closed bool
|
||||
maxPackets int
|
||||
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func NewUploadQueue(maxPackets int) *uploadQueue {
|
||||
return &uploadQueue{
|
||||
pushedPackets: make(chan Packet, maxPackets),
|
||||
done: make(chan struct{}),
|
||||
heap: uploadHeap{},
|
||||
nextSeq: 0,
|
||||
closed: false,
|
||||
@@ -40,63 +42,83 @@ func NewUploadQueue(maxPackets int) *uploadQueue {
|
||||
}
|
||||
|
||||
func (h *uploadQueue) Push(p Packet) error {
|
||||
h.writeCloseMutex.Lock()
|
||||
defer h.writeCloseMutex.Unlock()
|
||||
h.mtx.Lock()
|
||||
if h.closed {
|
||||
h.mtx.Unlock()
|
||||
return E.New("packet queue closed")
|
||||
}
|
||||
if h.nomore {
|
||||
h.mtx.Unlock()
|
||||
return E.New("h.reader already exists")
|
||||
}
|
||||
if p.Reader != nil {
|
||||
h.nomore = true
|
||||
}
|
||||
h.pushedPackets <- p
|
||||
return nil
|
||||
h.mtx.Unlock()
|
||||
select {
|
||||
case h.pushedPackets <- p:
|
||||
return nil
|
||||
case <-h.done:
|
||||
return E.New("packet queue closed")
|
||||
}
|
||||
}
|
||||
|
||||
func (h *uploadQueue) Close() error {
|
||||
h.writeCloseMutex.Lock()
|
||||
defer h.writeCloseMutex.Unlock()
|
||||
if !h.closed {
|
||||
h.closed = true
|
||||
runtime.Gosched() // hope Read() gets the packet
|
||||
f:
|
||||
for {
|
||||
select {
|
||||
case p := <-h.pushedPackets:
|
||||
if p.Reader != nil {
|
||||
h.reader = p.Reader
|
||||
}
|
||||
default:
|
||||
break f
|
||||
h.mtx.Lock()
|
||||
if h.closed {
|
||||
h.mtx.Unlock()
|
||||
return nil
|
||||
}
|
||||
h.closed = true
|
||||
close(h.done)
|
||||
h.mtx.Unlock()
|
||||
|
||||
for {
|
||||
select {
|
||||
case p := <-h.pushedPackets:
|
||||
if p.Reader != nil {
|
||||
p.Reader.Close()
|
||||
}
|
||||
default:
|
||||
if h.reader != nil {
|
||||
return h.reader.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
close(h.pushedPackets)
|
||||
}
|
||||
if h.reader != nil {
|
||||
return h.reader.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *uploadQueue) Read(b []byte) (int, error) {
|
||||
h.mtx.Lock()
|
||||
if h.closed {
|
||||
h.mtx.Unlock()
|
||||
return 0, io.EOF
|
||||
}
|
||||
h.mtx.Unlock()
|
||||
if h.reader != nil {
|
||||
return h.reader.Read(b)
|
||||
}
|
||||
if h.closed {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if len(h.heap) == 0 {
|
||||
packet, more := <-h.pushedPackets
|
||||
if !more {
|
||||
select {
|
||||
case packet, more := <-h.pushedPackets:
|
||||
if !more {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if packet.Reader != nil {
|
||||
h.mtx.Lock()
|
||||
if h.closed {
|
||||
packet.Reader.Close()
|
||||
h.mtx.Unlock()
|
||||
return 0, io.EOF
|
||||
}
|
||||
h.reader = packet.Reader
|
||||
h.mtx.Unlock()
|
||||
return h.reader.Read(b)
|
||||
}
|
||||
heap.Push(&h.heap, packet)
|
||||
case <-h.done:
|
||||
return 0, io.EOF
|
||||
}
|
||||
if packet.Reader != nil {
|
||||
h.reader = packet.Reader
|
||||
return h.reader.Read(b)
|
||||
}
|
||||
heap.Push(&h.heap, packet)
|
||||
}
|
||||
for len(h.heap) > 0 {
|
||||
packet := heap.Pop(&h.heap).(Packet)
|
||||
@@ -125,11 +147,15 @@ func (h *uploadQueue) Read(b []byte) (int, error) {
|
||||
return 0, E.New("packet queue is too large")
|
||||
}
|
||||
heap.Push(&h.heap, packet)
|
||||
packet2, more := <-h.pushedPackets
|
||||
if !more {
|
||||
select {
|
||||
case packet2, more := <-h.pushedPackets:
|
||||
if !more {
|
||||
return 0, io.EOF
|
||||
}
|
||||
heap.Push(&h.heap, packet2)
|
||||
case <-h.done:
|
||||
return 0, io.EOF
|
||||
}
|
||||
heap.Push(&h.heap, packet2)
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
|
||||
@@ -4,16 +4,49 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand/v2"
|
||||
"net/http"
|
||||
|
||||
"github.com/sagernet/sing-box/common/xray/buf"
|
||||
"github.com/sagernet/sing-box/common/xray/utils"
|
||||
"github.com/sagernet/sing-box/common/xray/uuid"
|
||||
"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 {
|
||||
table = predefined
|
||||
}
|
||||
if table != "" && length > 0 {
|
||||
id := make([]byte, length)
|
||||
for i := range id {
|
||||
id[i] = table[rand.N(len(table))]
|
||||
}
|
||||
return string(id)
|
||||
}
|
||||
newUUID := uuid.New()
|
||||
return newUUID.String()
|
||||
}
|
||||
|
||||
func FillStreamRequest(request *http.Request, sessionId string, seqStr string, options *option.V2RayXHTTPBaseOptions) {
|
||||
request.Header = options.GetRequestHeader()
|
||||
length := int(options.GetNormalizedXPaddingBytes().Rand())
|
||||
length := options.GetNormalizedXPaddingBytes().Rand()
|
||||
config := XPaddingConfig{Length: length}
|
||||
if options.XPaddingObfsMode {
|
||||
config.Placement = XPaddingPlacement{
|
||||
@@ -58,7 +91,7 @@ func FillPacketRequest(request *http.Request, sessionId string, seqStr string, p
|
||||
}
|
||||
}
|
||||
}
|
||||
length := int(options.GetNormalizedXPaddingBytes().Rand())
|
||||
length := options.GetNormalizedXPaddingBytes().Rand()
|
||||
config := XPaddingConfig{Length: length}
|
||||
if options.XPaddingObfsMode {
|
||||
config.Placement = XPaddingPlacement{
|
||||
@@ -125,7 +158,7 @@ func GetRequestHeaderWithPayload(payload []byte, options *option.V2RayXHTTPBaseO
|
||||
key := options.UplinkDataKey
|
||||
encodedData := base64.RawURLEncoding.EncodeToString(payload)
|
||||
for i := 0; len(encodedData) > 0; i++ {
|
||||
chunkSize := min(int(options.GetNormalizedUplinkChunkSize().Rand()), len(encodedData))
|
||||
chunkSize := min(options.GetNormalizedUplinkChunkSize().Rand(), len(encodedData))
|
||||
chunk := encodedData[:chunkSize]
|
||||
encodedData = encodedData[chunkSize:]
|
||||
headerKey := fmt.Sprintf("%s-%d", key, i)
|
||||
@@ -140,7 +173,7 @@ func GetRequestCookiesWithPayload(payload []byte, options *option.V2RayXHTTPBase
|
||||
key := options.UplinkDataKey
|
||||
encodedData := base64.RawURLEncoding.EncodeToString(payload)
|
||||
for i := 0; len(encodedData) > 0; i++ {
|
||||
chunkSize := min(int(options.GetNormalizedUplinkChunkSize().Rand()), len(encodedData))
|
||||
chunkSize := min(options.GetNormalizedUplinkChunkSize().Rand(), len(encodedData))
|
||||
chunk := encodedData[:chunkSize]
|
||||
encodedData = encodedData[chunkSize:]
|
||||
cookieName := fmt.Sprintf("%s_%d", key, i)
|
||||
|
||||
@@ -31,11 +31,12 @@ func (w uploadWriter) Write(b []byte) (int, error) {
|
||||
|
||||
var writed int
|
||||
for _, buff := range buffer.MultiBuffer {
|
||||
n := int(buff.Len())
|
||||
err := w.WriteMultiBuffer(buf.MultiBuffer{buff})
|
||||
if err != nil {
|
||||
return writed, err
|
||||
}
|
||||
writed += int(buff.Len())
|
||||
writed += n
|
||||
}
|
||||
return writed, nil
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ func ExtractXPaddingFromRequest(options *option.V2RayXHTTPBaseOptions, req *http
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func IsPaddingValid(options *option.V2RayXHTTPBaseOptions, paddingValue string, from, to int32, method PaddingMethod) bool {
|
||||
func IsPaddingValid(options *option.V2RayXHTTPBaseOptions, paddingValue string, from, to int, method PaddingMethod) bool {
|
||||
if paddingValue == "" {
|
||||
return false
|
||||
}
|
||||
@@ -274,11 +274,11 @@ func IsPaddingValid(options *option.V2RayXHTTPBaseOptions, paddingValue string,
|
||||
}
|
||||
switch method {
|
||||
case PaddingMethodRepeatX:
|
||||
n := int32(len(paddingValue))
|
||||
n := len(paddingValue)
|
||||
return n >= from && n <= to
|
||||
case PaddingMethodTokenish:
|
||||
const tolerance = int32(validationTolerance)
|
||||
n := int32(hpack.HuffmanEncodeLength(paddingValue))
|
||||
const tolerance = validationTolerance
|
||||
n := int(hpack.HuffmanEncodeLength(paddingValue))
|
||||
f := from - tolerance
|
||||
t := to + tolerance
|
||||
if f < 0 {
|
||||
@@ -286,7 +286,7 @@ func IsPaddingValid(options *option.V2RayXHTTPBaseOptions, paddingValue string,
|
||||
}
|
||||
return n >= f && n <= t
|
||||
default:
|
||||
n := int32(len(paddingValue))
|
||||
n := len(paddingValue)
|
||||
return n >= from && n <= to
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user