Add Snell protocol. Refactor MASQUE HTTP/2, Fair Queue. Update XHTTP, OpenVPN, Sudoku, Fallback. Fixes

This commit is contained in:
Shtorm
2026-06-26 01:25:57 +03:00
parent d174962a04
commit edf38d33d6
107 changed files with 5346 additions and 708 deletions

View File

@@ -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":

View File

@@ -39,7 +39,7 @@ func (c *splitConn) Close() error {
}
if err2 != nil {
return err
return err2
}
return nil

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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():
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
}
}