Repository: v2fly/v2ray-core
Pull request #2457 (2023-04-09 02:47:41)

outbounds: remove unused nil check for GetDefaultHandler


Patch:
diff --git a/.github/linters/.golangci.yml b/.github/linters/.golangci.yml
index cb5d188d..07df100d 100644
--- a/.github/linters/.golangci.yml
+++ b/.github/linters/.golangci.yml
@@ -52,3 +52,9 @@ linters-settings:
       - name: blank-imports
         severity: warning
         disabled: true
+  depguard:
+    rules:
+      Main:
+        deny:
+          - pkg: "github.com/pkg/errors"
+            desc: Should be replaced by standard lib errors package
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
index 75760cbe..8381f195 100644
--- a/.github/workflows/linter.yml
+++ b/.github/workflows/linter.yml
@@ -20,7 +20,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Set up Go 1.x
-        uses: actions/setup-go@v3
+        uses: actions/setup-go@v4
         with:
           go-version: ^1.19
 
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9713479e..c57361e9 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -74,6 +74,8 @@ jobs:
             goarch: arm64
           - goos: linux
             goarch: riscv64
+          - goos: linux
+            goarch: loong64
           - goos: windows
             goarch: arm64
           - goos: android
@@ -117,9 +119,9 @@ jobs:
           echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
 
       - name: Set up Go
-        uses: actions/setup-go@v3
+        uses: actions/setup-go@v4
         with:
-          go-version: ^1.20
+          go-version: ^1.21
 
       - name: Get project dependencies
         run: go mod download
@@ -185,9 +187,9 @@ jobs:
           fetch-depth: 0
 
       - name: Set up Go
-        uses: actions/setup-go@v3
+        uses: actions/setup-go@v4
         with:
-          go-version: ^1.20
+          go-version: ^1.21
 
       - uses: actions/download-artifact@v3
         with:
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index c25b8aec..40dbd63c 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -8,7 +8,7 @@ jobs:
   stale:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/stale@v7
+      - uses: actions/stale@v8
         with:
           repo-token: ${{ secrets.GITHUB_TOKEN }}
           stale-issue-message: "This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days"
diff --git a/app/dns/nameserver_doh.go b/app/dns/nameserver_doh.go
index 5e2a63d0..6e1430e6 100644
--- a/app/dns/nameserver_doh.go
+++ b/app/dns/nameserver_doh.go
@@ -231,7 +231,7 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP n
 			}
 
 			dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{
-				Protocol:       "https",
+				Protocol:       "tls",
 				SkipDNSResolve: true,
 			})
 
diff --git a/app/dns/nameserver_quic.go b/app/dns/nameserver_quic.go
index 1b82e27b..28e69225 100644
--- a/app/dns/nameserver_quic.go
+++ b/app/dns/nameserver_quic.go
@@ -11,7 +11,6 @@ import (
 
 	"github.com/quic-go/quic-go"
 	"golang.org/x/net/dns/dnsmessage"
-	"golang.org/x/net/http2"
 
 	"github.com/v2fly/v2ray-core/v5/common"
 	"github.com/v2fly/v2ray-core/v5/common/buf"
@@ -25,7 +24,7 @@ import (
 )
 
 // NextProtoDQ - During connection establishment, DNS/QUIC support is indicated
-// by selecting the ALPN token "dq" in the crypto handshake.
+// by selecting the ALPN token "doq" in the crypto handshake.
 const NextProtoDQ = "doq"
 
 const handshakeIdleTimeout = time.Second * 8
@@ -378,12 +377,23 @@ func (s *QUICNameServer) getConnection(ctx context.Context) (quic.Connection, er
 }
 
 func (s *QUICNameServer) openConnection(ctx context.Context) (quic.Connection, error) {
-	tlsConfig := tls.Config{}
+	tlsConfig := tls.Config{
+		ServerName: func() string {
+			switch s.destination.Address.Family() {
+			case net.AddressFamilyIPv4, net.AddressFamilyIPv6:
+				return s.destination.Address.IP().String()
+			case net.AddressFamilyDomain:
+				return s.destination.Address.Domain()
+			default:
+				panic("unknown address family")
+			}
+		}(),
+	}
 	quicConfig := &quic.Config{
 		HandshakeIdleTimeout: handshakeIdleTimeout,
 	}
 
-	conn, err := quic.DialAddrContext(ctx, s.destination.NetAddr(), tlsConfig.GetTLSConfig(tls.WithNextProto("http/1.1", http2.NextProtoTLS, NextProtoDQ)), quicConfig)
+	conn, err := quic.DialAddr(ctx, s.destination.NetAddr(), tlsConfig.GetTLSConfig(tls.WithNextProto(NextProtoDQ)), quicConfig)
 	if err != nil {
 		return nil, err
 	}
diff --git a/app/proxyman/inbound/inbound.go b/app/proxyman/inbound/inbound.go
index 3fe5597c..6a0f6759 100644
--- a/app/proxyman/inbound/inbound.go
+++ b/app/proxyman/inbound/inbound.go
@@ -16,6 +16,7 @@ import (
 
 // Manager is to manage all inbound handlers.
 type Manager struct {
+	ctx             context.Context
 	access          sync.RWMutex
 	untaggedHandler []inbound.Handler
 	taggedHandlers  map[string]inbound.Handler
@@ -25,6 +26,7 @@ type Manager struct {
 // New returns a new Manager for inbound handlers.
 func New(ctx context.Context, config *proxyman.InboundConfig) (*Manager, error) {
 	m := &Manager{
+		ctx:            ctx,
 		taggedHandlers: make(map[string]inbound.Handler),
 	}
 	return m, nil
diff --git a/app/proxyman/inbound/worker.go b/app/proxyman/inbound/worker.go
index b2d74c89..1434af07 100644
--- a/app/proxyman/inbound/worker.go
+++ b/app/proxyman/inbound/worker.go
@@ -9,6 +9,8 @@ import (
 	"github.com/v2fly/v2ray-core/v5/app/proxyman"
 	"github.com/v2fly/v2ray-core/v5/common"
 	"github.com/v2fly/v2ray-core/v5/common/buf"
+	"github.com/v2fly/v2ray-core/v5/common/environment"
+	"github.com/v2fly/v2ray-core/v5/common/environment/envctx"
 	"github.com/v2fly/v2ray-core/v5/common/net"
 	"github.com/v2fly/v2ray-core/v5/common/serial"
 	"github.com/v2fly/v2ray-core/v5/common/session"
@@ -112,6 +114,12 @@ func (w *tcpWorker) Proxy() proxy.Inbound {
 
 func (w *tcpWorker) Start() error {
 	ctx := context.Background()
+	proxyEnvironment := envctx.EnvironmentFromContext(w.ctx).(environment.ProxyEnvironment)
+	transportEnvironment, err := proxyEnvironment.NarrowScopeToTransport("transport")
+	if err != nil {
+		return newError("unable to narrow environment to transport").Base(err)
+	}
+	ctx = envctx.ContextWithEnvironment(ctx, transportEnvironment)
 	hub, err := internet.ListenTCP(ctx, w.address, w.port, w.stream, func(conn internet.Connection) {
 		go w.callback(conn)
 	})
@@ -371,6 +379,12 @@ func (w *udpWorker) clean() error {
 func (w *udpWorker) Start() error {
 	w.activeConn = make(map[connID]*udpConn, 16)
 	ctx := context.Background()
+	proxyEnvironment := envctx.EnvironmentFromContext(w.ctx).(environment.ProxyEnvironment)
+	transportEnvironment, err := proxyEnvironment.NarrowScopeToTransport("transport")
+	if err != nil {
+		return newError("unable to narrow environment to transport").Base(err)
+	}
+	ctx = envctx.ContextWithEnvironment(ctx, transportEnvironment)
 	h, err := udp.ListenUDP(ctx, w.address, w.port, w.stream, udp.HubCapacity(256))
 	if err != nil {
 		return err
@@ -480,6 +494,12 @@ func (w *dsWorker) Port() net.Port {
 
 func (w *dsWorker) Start() error {
 	ctx := context.Background()
+	proxyEnvironment := envctx.EnvironmentFromContext(w.ctx).(environment.ProxyEnvironment)
+	transportEnvironment, err := proxyEnvironment.NarrowScopeToTransport("transport")
+	if err != nil {
+		return newError("unable to narrow environment to transport").Base(err)
+	}
+	ctx = envctx.ContextWithEnvironment(ctx, transportEnvironment)
 	hub, err := internet.ListenUnix(ctx, w.address, w.stream, func(conn internet.Connection) {
 		go w.callback(conn)
 	})
diff --git a/app/proxyman/outbound/handler.go b/app/proxyman/outbound/handler.go
index 1ba81413..5c6d0dd3 100644
--- a/app/proxyman/outbound/handler.go
+++ b/app/proxyman/outbound/handler.go
@@ -7,6 +7,8 @@ import (
 	"github.com/v2fly/v2ray-core/v5/app/proxyman"
 	"github.com/v2fly/v2ray-core/v5/common"
 	"github.com/v2fly/v2ray-core/v5/common/dice"
+	"github.com/v2fly/v2ray-core/v5/common/environment"
+	"github.com/v2fly/v2ray-core/v5/common/environment/envctx"
 	"github.com/v2fly/v2ray-core/v5/common/mux"
 	"github.com/v2fly/v2ray-core/v5/common/net"
 	"github.com/v2fly/v2ray-core/v5/common/net/packetaddr"
@@ -50,6 +52,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
 
 // Handler is an implements of outbound.Handler.
 type Handler struct {
+	ctx             context.Context
 	tag             string
 	senderSettings  *proxyman.SenderConfig
 	streamSettings  *internet.MemoryStreamConfig
@@ -66,6 +69,7 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
 	v := core.MustFromContext(ctx)
 	uplinkCounter, downlinkCounter := getStatCounter(v, config.Tag)
 	h := &Handler{
+		ctx:             ctx,
 		tag:             config.Tag,
 		outboundManager: v.GetFeature(outbound.ManagerType()).(outbound.Manager),
 		uplinkCounter:   uplinkCounter,
@@ -251,6 +255,12 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (internet.Conn
 		return h.getStatCouterConnection(conn), nil
 	}
 
+	proxyEnvironment := envctx.EnvironmentFromContext(h.ctx).(environment.ProxyEnvironment)
+	transportEnvironment, err := proxyEnvironment.NarrowScopeToTransport("transport")
+	if err != nil {
+		return nil, newError("unable to narrow environment to transport").Base(err)
+	}
+	ctx = envctx.ContextWithEnvironment(ctx, transportEnvironment)
 	conn, err := internet.Dial(ctx, dest, h.streamSettings)
 	return h.getStatCouterConnection(conn), err
 }
diff --git a/app/proxyman/outbound/outbound.go b/app/proxyman/outbound/outbound.go
index f44691a8..300a535f 100644
--- a/app/proxyman/outbound/outbound.go
+++ b/app/proxyman/outbound/outbound.go
@@ -82,9 +82,6 @@ func (m *Manager) GetDefaultHandler() outbound.Handler {
 	m.access.RLock()
 	defer m.access.RUnlock()
 
-	if m.defaultHandler == nil {
-		return nil
-	}
 	return m.defaultHandler
 }
 
diff --git a/app/router/weight.go b/app/router/weight.go
index ec7711b9..dc6e90b5 100644
--- a/app/router/weight.go
+++ b/app/router/weight.go
@@ -4,6 +4,7 @@ import (
 	"regexp"
 	"strconv"
 	"strings"
+	"sync"
 )
 
 type weightScaler func(value, weight float64) float64
@@ -26,10 +27,13 @@ type WeightManager struct {
 	cache         map[string]float64
 	scaler        weightScaler
 	defaultWeight float64
+	mu            sync.Mutex
 }
 
 // Get gets the weight of specified tag
 func (s *WeightManager) Get(tag string) float64 {
+	s.mu.Lock()
+	defer s.mu.Unlock()
 	weight, ok := s.cache[tag]
 	if ok {
 		return weight
diff --git a/common/cmdarg/arg.go b/common/cmdarg/arg.go
index 8a0958d4..52ccb7d6 100644
--- a/common/cmdarg/arg.go
+++ b/common/cmdarg/arg.go
@@ -3,7 +3,7 @@ package cmdarg
 import (
 	"bytes"
 	"io"
-	"io/ioutil"
+	"os"
 )
 
 // LoadArg loads one arg, maybe an remote url, or local file path
@@ -18,7 +18,7 @@ func LoadArg(arg string) (out io.Reader, err error) {
 
 // LoadArgToBytes loads one arg to []byte, maybe an remote url, or local file path
 func LoadArgToBytes(arg string) (out []byte, err error) {
-	out, err = ioutil.ReadFile(arg)
+	out, err = os.ReadFile(arg)
 	if err != nil {
 		return
 	}
diff --git a/common/environment/rootcap_impl.go b/common/environment/rootcap_impl.go
index acea6a51..2bbf862e 100644
--- a/common/environment/rootcap_impl.go
+++ b/common/environment/rootcap_impl.go
@@ -9,12 +9,21 @@ import (
 	"github.com/v2fly/v2ray-core/v5/transport/internet/tagged"
 )
 
-func NewRootEnvImpl(ctx context.Context, transientStorage storage.ScopedTransientStorage) RootEnvironment {
-	return &rootEnvImpl{transientStorage: transientStorage, ctx: ctx}
+func NewRootEnvImpl(ctx context.Context, transientStorage storage.ScopedTransientStorage,
+	systemDialer internet.SystemDialer, systemListener internet.SystemListener,
+) RootEnvironment {
+	return &rootEnvImpl{
+		transientStorage: transientStorage,
+		systemListener:   systemListener,
+		systemDialer:     systemDialer,
+		ctx:              ctx,
+	}
 }
 
 type rootEnvImpl struct {
 	transientStorage storage.ScopedTransientStorage
+	systemDialer     internet.SystemDialer
+	systemListener   internet.SystemListener
 
 	ctx context.Context
 }
@@ -30,6 +39,8 @@ func (r *rootEnvImpl) AppEnvironment(tag string) AppEnvironment {
 	}
 	return &appEnvImpl{
 		transientStorage: transientStorage,
+		systemListener:   r.systemListener,
+		systemDialer:     r.systemDialer,
 		ctx:              r.ctx,
 	}
 }
@@ -41,12 +52,16 @@ func (r *rootEnvImpl) ProxyEnvironment(tag string) ProxyEnvironment {
 	}
 	return &proxyEnvImpl{
 		transientStorage: transientStorage,
+		systemListener:   r.systemListener,
+		systemDialer:     r.systemDialer,
 		ctx:              r.ctx,
 	}
 }
 
 type appEnvImpl struct {
 	transientStorage storage.ScopedTransientStorage
+	systemDialer     internet.SystemDialer
+	systemListener   internet.SystemListener
 
 	ctx context.Context
 }
@@ -98,6 +113,8 @@ func (a *appEnvImpl) NarrowScope(key string) (AppEnvironment, error) {
 	}
 	return &appEnvImpl{
 		transientStorage: transientStorage,
+		systemDialer:     a.systemDialer,
+		systemListener:   a.systemListener,
 		ctx:              a.ctx,
 	}, nil
 }
@@ -108,6 +125,8 @@ func (a *appEnvImpl) doNotImpl() {
 
 type proxyEnvImpl struct {
 	transientStorage storage.ScopedTransientStorage
+	systemDialer     internet.SystemDialer
+	systemListener   internet.SystemListener
 
 	ctx context.Context
 }
@@ -147,6 +166,8 @@ func (p *proxyEnvImpl) NarrowScopeToTransport(key string) (TransportEnvironment,
 	return &transportEnvImpl{
 		ctx:              p.ctx,
 		transientStorage: transientStorage,
+		systemDialer:     p.systemDialer,
+		systemListener:   p.systemListener,
 	}, nil
 }
 
@@ -156,6 +177,8 @@ func (p *proxyEnvImpl) doNotImpl() {
 
 type transportEnvImpl struct {
 	transientStorage storage.ScopedTransientStorage
+	systemDialer     internet.SystemDialer
+	systemListener   internet.SystemListener
 
 	ctx context.Context
 }
@@ -169,11 +192,11 @@ func (t *transportEnvImpl) RecordLog() interface{} {
 }
 
 func (t *transportEnvImpl) Dialer() internet.SystemDialer {
-	panic("implement me")
+	return t.systemDialer
 }
 
 func (t *transportEnvImpl) Listener() internet.SystemListener {
-	panic("implement me")
+	return t.systemListener
 }
 
 func (t *transportEnvImpl) OutboundDialer() tagged.DialFunc {
diff --git a/common/environment/systemnetworkimpl/systemnetwork.go b/common/environment/systemnetworkimpl/systemnetwork.go
new file mode 100644
index 00000000..e6e770cd
--- /dev/null
+++ b/common/environment/systemnetworkimpl/systemnetwork.go
@@ -0,0 +1,84 @@
+package systemnetworkimpl
+
+import (
+	"context"
+
+	"github.com/v2fly/v2ray-core/v5/common/environment"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/transport/internet"
+)
+
+func NewSystemNetworkImpl(listener internet.SystemListener, dialer internet.SystemDialer) environment.SystemNetworkCapabilitySet {
+	return &systemNetworkImpl{dialer: dialer, listener: listener}
+}
+
+type systemDefaultDialer struct{}
+
+func (s systemDefaultDialer) Listen(ctx context.Context, addr net.Addr, sockopt *internet.SocketConfig) (net.Listener, error) {
+	return internet.ListenSystem(ctx, addr, sockopt)
+}
+
+func (s systemDefaultDialer) ListenPacket(ctx context.Context, addr net.Addr, sockopt *internet.SocketConfig) (net.PacketConn, error) {
+	return internet.ListenSystemPacket(ctx, addr, sockopt)
+}
+
+func (s systemDefaultDialer) Dial(ctx context.Context, source net.Address, destination net.Destination, sockopt *internet.SocketConfig) (net.Conn, error) {
+	return internet.DialSystem(ctx, destination, sockopt)
+}
+
+func NewSystemNetworkDefault() environment.SystemNetworkCapabilitySet {
+	systemDefault := systemDefaultDialer{}
+	return &systemNetworkImpl{dialer: systemDefault, listener: systemDefault}
+}
+
+type systemNetworkImpl struct {
+	listener internet.SystemListener
+	dialer   internet.SystemDialer
+}
+
+func (s systemNetworkImpl) Dialer() internet.SystemDialer {
+	return s.dialer
+}
+
+func (s systemNetworkImpl) Listener() internet.SystemListener {
+	return s.listener
+}
+
+func NewSystemListenerWithDefaultOpt(listener internet.SystemListener, opt *internet.SocketConfig) internet.SystemListener {
+	return systemListenerWithDefaultOpt{SystemListener: listener, opt: opt}
+}
+
+type systemListenerWithDefaultOpt struct {
+	internet.SystemListener
+	opt *internet.SocketConfig
+}
+
+func (s systemListenerWithDefaultOpt) Listen(ctx context.Context, addr net.Addr, sockopt *internet.SocketConfig) (net.Listener, error) {
+	if sockopt == nil {
+		return s.Listen(ctx, addr, s.opt)
+	}
+	return s.Listen(ctx, addr, sockopt)
+}
+
+func (s systemListenerWithDefaultOpt) ListenPacket(ctx context.Context, addr net.Addr, sockopt *internet.SocketConfig) (net.PacketConn, error) {
+	if sockopt == nil {
+		return s.ListenPacket(ctx, addr, s.opt)
+	}
+	return s.ListenPacket(ctx, addr, sockopt)
+}
+
+func NewSystemDialerWithDefaultOpt(listener internet.SystemDialer, opt *internet.SocketConfig) internet.SystemDialer {
+	return systemDialerWithDefaultOpt{SystemDialer: listener, opt: opt}
+}
+
+type systemDialerWithDefaultOpt struct {
+	internet.SystemDialer
+	opt *internet.SocketConfig
+}
+
+func (s systemDialerWithDefaultOpt) Dial(ctx context.Context, source net.Address, destination net.Destination, sockopt *internet.SocketConfig) (net.Conn, error) {
+	if sockopt == nil {
+		return s.Dial(ctx, source, destination, s.opt)
+	}
+	return s.Dial(ctx, source, destination, sockopt)
+}
diff --git a/common/protocol/address.go b/common/protocol/address.go
index bbf923f2..11d22f4b 100644
--- a/common/protocol/address.go
+++ b/common/protocol/address.go
@@ -239,7 +239,7 @@ func (p *addressParser) writeAddress(writer io.Writer, address net.Address) erro
 		}
 	case net.AddressFamilyDomain:
 		domain := address.Domain()
-		if isDomainTooLong(domain) {
+		if IsDomainTooLong(domain) {
 			return newError("Super long domain is not supported: ", domain)
 		}
 
diff --git a/common/protocol/headers.go b/common/protocol/headers.go
index 6b65fd52..528c9e8b 100644
--- a/common/protocol/headers.go
+++ b/common/protocol/headers.go
@@ -103,6 +103,6 @@ func (sc *SecurityConfig) GetSecurityType() SecurityType {
 	return sc.Type
 }
 
-func isDomainTooLong(domain string) bool {
-	return len(domain) > 256
+func IsDomainTooLong(domain string) bool {
+	return len(domain) > 255
 }
diff --git a/common/protocol/http/sniff.go b/common/protocol/http/sniff.go
index 98e4e908..fb60eea3 100644
--- a/common/protocol/http/sniff.go
+++ b/common/protocol/http/sniff.go
@@ -11,25 +11,12 @@ import (
 
 type version byte
 
-const (
-	HTTP1 version = iota
-	HTTP2
-)
-
 type SniffHeader struct {
-	version version
-	host    string
+	host string
 }
 
 func (h *SniffHeader) Protocol() string {
-	switch h.version {
-	case HTTP1:
-		return "http1"
-	case HTTP2:
-		return "http2"
-	default:
-		return "unknown"
-	}
+	return "http1"
 }
 
 func (h *SniffHeader) Domain() string {
@@ -62,9 +49,7 @@ func SniffHTTP(b []byte) (*SniffHeader, error) {
 		return nil, err
 	}
 
-	sh := &SniffHeader{
-		version: HTTP1,
-	}
+	sh := &SniffHeader{}
 
 	headers := bytes.Split(b, []byte{'\n'})
 	for i := 1; i < len(headers); i++ {
diff --git a/common/protocol/quic/cipher_suite.go b/common/protocol/quic/cipher_suite.go
new file mode 100644
index 00000000..798a03af
--- /dev/null
+++ b/common/protocol/quic/cipher_suite.go
@@ -0,0 +1,23 @@
+package quic
+
+import (
+	"crypto"
+	"crypto/cipher"
+	_ "crypto/tls"
+	_ "unsafe"
+)
+
+// copied from github.com/quic-go/quic-go/internal/qtls/cipher_suite_go121.go
+
+type cipherSuiteTLS13 struct {
+	ID     uint16
+	KeyLen int
+	AEAD   func(key, fixedNonce []byte) cipher.AEAD
+	Hash   crypto.Hash
+}
+
+// github.com/quic-go/quic-go/internal/handshake/cipher_suite.go describes these cipher suite implementations are copied from the standard library crypto/tls package.
+// So we can user go:linkname to implement the same feature.
+
+//go:linkname aeadAESGCMTLS13 crypto/tls.aeadAESGCMTLS13
+func aeadAESGCMTLS13(key, nonceMask []byte) cipher.AEAD
diff --git a/common/protocol/quic/qtls_go119.go b/common/protocol/quic/qtls_go119.go
deleted file mode 100644
index 4ab25af8..00000000
--- a/common/protocol/quic/qtls_go119.go
+++ /dev/null
@@ -1,18 +0,0 @@
-//go:build go1.19 && !go1.20
-
-package quic
-
-import (
-	"crypto/cipher"
-
-	"github.com/quic-go/qtls-go1-19"
-)
-
-type (
-	// A CipherSuiteTLS13 is a cipher suite for TLS 1.3
-	CipherSuiteTLS13 = qtls.CipherSuiteTLS13
-)
-
-func AEADAESGCMTLS13(key, fixedNonce []byte) cipher.AEAD {
-	return qtls.AEADAESGCMTLS13(key, fixedNonce)
-}
diff --git a/common/protocol/quic/qtls_go120.go b/common/protocol/quic/qtls_go120.go
deleted file mode 100644
index e182c75b..00000000
--- a/common/protocol/quic/qtls_go120.go
+++ /dev/null
@@ -1,18 +0,0 @@
-//go:build go1.20
-
-package quic
-
-import (
-	"crypto/cipher"
-
-	"github.com/quic-go/qtls-go1-20"
-)
-
-type (
-	// A CipherSuiteTLS13 is a cipher suite for TLS 1.3
-	CipherSuiteTLS13 = qtls.CipherSuiteTLS13
-)
-
-func AEADAESGCMTLS13(key, fixedNonce []byte) cipher.AEAD {
-	return qtls.AEADAESGCMTLS13(key, fixedNonce)
-}
diff --git a/common/protocol/quic/sniff.go b/common/protocol/quic/sniff.go
index d88c2945..f8fcd0ba 100644
--- a/common/protocol/quic/sniff.go
+++ b/common/protocol/quic/sniff.go
@@ -37,10 +37,10 @@ const (
 var (
 	quicSaltOld  = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
 	quicSalt     = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
-	initialSuite = &CipherSuiteTLS13{
+	initialSuite = &cipherSuiteTLS13{
 		ID:     tls.TLS_AES_128_GCM_SHA256,
 		KeyLen: 16,
-		AEAD:   AEADAESGCMTLS13,
+		AEAD:   aeadAESGCMTLS13,
 		Hash:   crypto.SHA256,
 	}
 	errNotQuic        = errors.New("not quic")
@@ -153,7 +153,7 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
 
 	key := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic key", 16)
 	iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12)
-	cipher := AEADAESGCMTLS13(key, iv)
+	cipher := aeadAESGCMTLS13(key, iv)
 	nonce := cache.Extend(int32(cipher.NonceSize()))
 	binary.BigEndian.PutUint64(nonce[len(nonce)-8:], uint64(packetNumber))
 	decrypted, err := cipher.Open(b[extHdrLen:extHdrLen], nonce, data, b[:extHdrLen])
diff --git a/core.go b/core.go
index ad15fc7b..c9ab606c 100644
--- a/core.go
+++ b/core.go
@@ -18,7 +18,7 @@ import (
 )
 
 var (
-	version  = "5.4.1"
+	version  = "5.8.0"
 	build    = "Custom"
 	codename = "V2Fly, a community-driven edition of V2Ray."
 	intro    = "A unified platform for anti-censorship."
diff --git a/go.mod b/go.mod
index 8a2a3619..e820051d 100644
--- a/go.mod
+++ b/go.mod
@@ -1,39 +1,37 @@
 module github.com/v2fly/v2ray-core/v5
 
-go 1.19
+go 1.20
 
 require (
 	github.com/adrg/xdg v0.4.0
 	github.com/go-chi/chi/v5 v5.0.8
 	github.com/go-chi/render v1.0.2
-	github.com/go-playground/validator/v10 v10.11.2
+	github.com/go-playground/validator/v10 v10.14.1
 	github.com/golang/mock v1.6.0
-	github.com/golang/protobuf v1.5.2
+	github.com/golang/protobuf v1.5.3
 	github.com/google/go-cmp v0.5.9
 	github.com/gorilla/websocket v1.5.0
-	github.com/jhump/protoreflect v1.15.0
-	github.com/miekg/dns v1.1.51
+	github.com/jhump/protoreflect v1.15.1
+	github.com/miekg/dns v1.1.54
 	github.com/mustafaturan/bus v1.0.2
 	github.com/pelletier/go-toml v1.9.5
-	github.com/pires/go-proxyproto v0.6.2
-	github.com/quic-go/qtls-go1-19 v0.3.2
-	github.com/quic-go/qtls-go1-20 v0.1.1
-	github.com/quic-go/quic-go v0.33.0
-	github.com/refraction-networking/utls v1.3.1
+	github.com/pires/go-proxyproto v0.7.0
+	github.com/quic-go/quic-go v0.39.0
+	github.com/refraction-networking/utls v1.5.4
 	github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb
-	github.com/stretchr/testify v1.8.2
+	github.com/stretchr/testify v1.8.4
 	github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08
 	github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848
 	github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
 	github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432
-	go.starlark.net v0.0.0-20220817180228-f738f5508c12
-	go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d
-	golang.org/x/crypto v0.6.0
-	golang.org/x/net v0.7.0
-	golang.org/x/sync v0.1.0
-	golang.org/x/sys v0.5.0
-	google.golang.org/grpc v1.53.0
-	google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8
+	go.starlark.net v0.0.0-20230612165344-9532f5667272
+	go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35
+	golang.org/x/crypto v0.12.0
+	golang.org/x/net v0.14.0
+	golang.org/x/sync v0.2.0
+	golang.org/x/sys v0.11.0
+	google.golang.org/grpc v1.57.0
+	google.golang.org/protobuf v1.31.0
 	gopkg.in/yaml.v3 v3.0.1
 	h12.io/socks v1.0.3
 )
@@ -41,37 +39,41 @@ require (
 require (
 	github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 // indirect
 	github.com/ajg/form v1.5.1 // indirect
-	github.com/andybalholm/brotli v1.0.4 // indirect
+	github.com/andybalholm/brotli v1.0.5 // indirect
 	github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d // indirect
-	github.com/bufbuild/protocompile v0.2.1-0.20230123224550-da57cd758c2f // indirect
+	github.com/bufbuild/protocompile v0.5.1 // indirect
+	github.com/cloudflare/circl v1.3.3 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 // indirect
+	github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
 	github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a // indirect
+	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
+	github.com/gaukas/godicttls v0.0.4 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
-	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
-	github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
-	github.com/klauspost/compress v1.15.15 // indirect
-	github.com/klauspost/cpuid v1.2.3 // indirect
-	github.com/klauspost/reedsolomon v1.9.3 // indirect
-	github.com/leodido/go-urn v1.2.1 // indirect
+	github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
+	github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
+	github.com/klauspost/compress v1.16.7 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.5 // indirect
+	github.com/klauspost/reedsolomon v1.11.7 // indirect
+	github.com/leodido/go-urn v1.2.4 // indirect
 	github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
 	github.com/mustafaturan/monoton v1.0.0 // indirect
-	github.com/onsi/ginkgo/v2 v2.2.0 // indirect
+	github.com/onsi/ginkgo/v2 v2.10.0 // indirect
 	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
-	github.com/pion/dtls/v2 v2.2.4 // indirect
+	github.com/pion/dtls/v2 v2.2.7 // indirect
 	github.com/pion/logging v0.2.2 // indirect
-	github.com/pion/sctp v1.7.6 // indirect
-	github.com/pion/transport/v2 v2.0.0 // indirect
-	github.com/pion/udp v0.1.4 // indirect
-	github.com/pkg/errors v0.9.1 // indirect
+	github.com/pion/randutil v0.1.0 // indirect
+	github.com/pion/sctp v1.8.7 // indirect
+	github.com/pion/transport/v2 v2.2.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
 	github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
 	github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4 // indirect
-	github.com/xtaci/smux v1.5.15 // indirect
-	golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
-	golang.org/x/mod v0.7.0 // indirect
-	golang.org/x/text v0.7.0 // indirect
-	golang.org/x/tools v0.3.0 // indirect
-	google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
+	github.com/xtaci/smux v1.5.24 // indirect
+	go.uber.org/mock v0.3.0 // indirect
+	golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
+	golang.org/x/mod v0.11.0 // indirect
+	golang.org/x/text v0.12.0 // indirect
+	golang.org/x/tools v0.9.3 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
 )
diff --git a/go.sum b/go.sum
index f27c39ce..09c86151 100644
--- a/go.sum
+++ b/go.sum
@@ -23,8 +23,8 @@ github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
 github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
-github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
+github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
@@ -34,14 +34,16 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
 github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
 github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d h1:zsO4lp+bjv5XvPTF58Vq+qgmZEYZttJK+CWtSZhKenI=
 github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d/go.mod h1:f1iKL6ZhUWvbk7PdWVmOaak10o86cqMUYEmn1CZNGEI=
-github.com/bufbuild/protocompile v0.2.1-0.20230123224550-da57cd758c2f h1:IXSA5gow10s7zIOJfPOpXDtNBWCTA0715BDAhoJBXEs=
-github.com/bufbuild/protocompile v0.2.1-0.20230123224550-da57cd758c2f/go.mod h1:tleDrpPTlLUVmgnEoN6qBliKWqJaZFJXqZdFjTd+ocU=
+github.com/bufbuild/protocompile v0.5.1 h1:mixz5lJX4Hiz4FpqFREJHIXLfaLBntfaJv1h+/jS+Qg=
+github.com/bufbuild/protocompile v0.5.1/go.mod h1:G5iLmavmF4NsYtpZFvE3B/zFch2GIY8+wjsYLR/lc40=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
+github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
@@ -53,8 +55,9 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
-github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 h1:BS21ZUJ/B5X2UVUbczfmdWH7GapPWAhxcMsDnjJTU1E=
 github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
+github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
+github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a h1:YtdtTUN1iH97s+6PUjLnaiKSQj4oG1/EZ3N9bx6g4kU=
 github.com/ebfe/bcrypt_pbkdf v0.0.0-20140212075826-3c8d2dcb253a/go.mod h1:/CZpbhAusDOobpcb9yubw46kdYjq0zRC0Wpg9a9zFQM=
@@ -63,6 +66,10 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
+github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
+github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
+github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
 github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
@@ -72,16 +79,17 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
-github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
+github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
+github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
-github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259/go.mod h1:9Qcha0gTWLw//0VNka1Cbnjvg3pNKGFdAm7E9sBabxE=
@@ -102,8 +110,8 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
-github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -119,8 +127,8 @@ github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8v
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
-github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
+github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
@@ -155,10 +163,9 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
 github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
 github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
-github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/jhump/protoreflect v1.15.0 h1:U5T5/2LF0AZQFP9T4W5GfBjBaTruomrKobiR4E+oA/Q=
-github.com/jhump/protoreflect v1.15.0/go.mod h1:qww51KYjD2hoCl/ohxw5cK2LSssFczrbO1t8Ld2TENs=
+github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
+github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@@ -166,21 +173,23 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
-github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
-github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=
+github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
+github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
 github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
-github.com/klauspost/reedsolomon v1.9.3 h1:N/VzgeMfHmLc+KHMD1UL/tNkfXAt8FnUqlgXGIduwAY=
+github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
+github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
 github.com/klauspost/reedsolomon v1.9.3/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
+github.com/klauspost/reedsolomon v1.11.7 h1:9uaHU0slncktTEEg4+7Vl7q7XUNMBUOK4R9gnKhMjAU=
+github.com/klauspost/reedsolomon v1.11.7/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
-github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
+github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
+github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
 github.com/lunixbochs/struc v0.0.0-20190916212049-a5c72983bc42/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
@@ -189,8 +198,8 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
-github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo=
-github.com/miekg/dns v1.1.51/go.mod h1:2Z9d3CP1LQWihRZUf29mQ19yDThaI4DAYzte2CaQW5c=
+github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
+github.com/miekg/dns v1.1.54/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
 github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -210,9 +219,9 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
 github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
 github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
-github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
-github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
+github.com/onsi/ginkgo/v2 v2.10.0 h1:sfUl4qgLdvkChZrWCYndY2EAu9BRIw1YphNAzy1VNWs=
+github.com/onsi/ginkgo/v2 v2.10.0/go.mod h1:UDQOh5wbQUlMnkLfVaIUMtQ1Vus92oM+P2JX1aulgcE=
+github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
 github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
@@ -222,23 +231,23 @@ github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko
 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
 github.com/pion/dtls/v2 v2.0.0-rc.7/go.mod h1:U199DvHpRBN0muE9+tVN4TMy1jvEhZIZ63lk4xkvVSk=
-github.com/pion/dtls/v2 v2.2.4 h1:YSfYwDQgrxMYXLBc/m7PFY5BVtWlNm/DN4qoU2CbcWg=
-github.com/pion/dtls/v2 v2.2.4/go.mod h1:WGKfxqhrddne4Kg3p11FUMJrynkOY4lb25zHNO49wuw=
+github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
+github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
 github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
 github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
-github.com/pion/sctp v1.7.6 h1:8qZTdJtbKfAns/Hv5L0PAj8FyXcsKhMH1pKUCGisQg4=
+github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
+github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
 github.com/pion/sctp v1.7.6/go.mod h1:ichkYQ5tlgCQwEwvgfdcAolqx1nHbYCxo4D7zK/K0X8=
-github.com/pion/transport v0.8.10 h1:lTiobMEw2PG6BH/mgIVqTV2mBp/mPT+IJLaN8ZxgdHk=
+github.com/pion/sctp v1.8.7 h1:JnABvFakZueGAn4KU/4PSKg+GWbF6QWbKTWZOSGJjXw=
+github.com/pion/sctp v1.8.7/go.mod h1:g1Ul+ARqZq5JEmoFy87Q/4CePtKnTJ1QCL9dBBdN6AU=
 github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8=
-github.com/pion/transport/v2 v2.0.0 h1:bsMYyqHCbkvHwj+eNCFBuxtlKndKfyGI2vaQmM3fIE4=
-github.com/pion/transport/v2 v2.0.0/go.mod h1:HS2MEBJTwD+1ZI2eSXSvHJx/HnzQqRy2/LXxt6eVMHc=
-github.com/pion/udp v0.1.4 h1:OowsTmu1Od3sD6i3fQUJxJn2fEvJO6L1TidgadtbTI8=
-github.com/pion/udp v0.1.4/go.mod h1:G8LDo56HsFwC24LIcnT4YIDU5qcB6NepqqjP0keL2us=
-github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
-github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
+github.com/pion/transport/v2 v2.1.0/go.mod h1:AdSw4YBZVDkZm8fpoz+fclXyQwANWmZAlDuQdctTThQ=
+github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c=
+github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
+github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
+github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -253,19 +262,16 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
-github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
-github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
-github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk=
-github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
-github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
-github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA=
-github.com/refraction-networking/utls v1.3.1 h1:3zVomUqx7nCmyGuU/6kYA/jp5NcqX8KQSGko8pY5Ch4=
-github.com/refraction-networking/utls v1.3.1/go.mod h1:kHXvVB66a4BzVRYC4Em7e1HAfp7uwOCCw0+2CZ3sMY8=
+github.com/quic-go/qtls-go1-20 v0.3.4 h1:MfFAPULvst4yoMgY9QmtpYmfij/em7O8UUi+bNVm7Cg=
+github.com/quic-go/qtls-go1-20 v0.3.4/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
+github.com/quic-go/quic-go v0.39.0 h1:AgP40iThFMY0bj8jGxROhw3S0FMGa8ryqsmi9tBH3So=
+github.com/quic-go/quic-go v0.39.0/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q=
+github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o=
+github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw=
 github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
 github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
@@ -302,9 +308,10 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
 github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/txthinking/runnergroup v0.0.0-20200327135940-540a793bb997/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
@@ -320,21 +327,24 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
 github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432 h1:I/ATawgO2RerCq9ACwL0wBB8xNXZdE3J+93MCEHReRs=
 github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432/go.mod h1:QN7Go2ftTVfx0aCTh9RXHV8pkpi0FtmbwQw40dy61wQ=
 github.com/xtaci/smux v1.5.12/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
-github.com/xtaci/smux v1.5.15 h1:6hMiXswcleXj5oNfcJc+DXS8Vj36XX2LaX98udog6Kc=
 github.com/xtaci/smux v1.5.15/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
+github.com/xtaci/smux v1.5.24 h1:77emW9dtnOxxOQ5ltR+8BbsX1kzcOxQ5gB+aaV9hXOY=
+github.com/xtaci/smux v1.5.24/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.starlark.net v0.0.0-20220817180228-f738f5508c12 h1:xOBJXWGEDwU5xSDxH6macxO11Us0AH2fTa9rmsbbF7g=
-go.starlark.net v0.0.0-20220817180228-f738f5508c12/go.mod h1:VZcBMdr3cT3PnBoWunTabuSEXwVAH+ZJ5zxfs3AdASk=
+go.starlark.net v0.0.0-20230612165344-9532f5667272 h1:2/wtqS591wZyD2OsClsVBKRPEvBsQt/Js+fsCiYhwu8=
+go.starlark.net v0.0.0-20230612165344-9532f5667272/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
+go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d h1:ggxwEf5eu0l8v+87VhX1czFh8zJul3hK16Gmruxn7hw=
-go4.org/netipx v0.0.0-20220812043211-3cc044ffd68d/go.mod h1:tgPU4N2u9RByaTN3NC2p9xOzyFpte4jYwsIIRF7XlSc=
+go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 h1:nJAwRlGWZZDOD+6wni9KVUNHMpHko/OnRwsrCYeAzPo=
+go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -346,16 +356,16 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
-golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
-golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
+golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
+golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
+golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
 golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
 golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
-golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
+golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
+golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -372,8 +382,9 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
-golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
+golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -393,11 +404,11 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
-golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
-golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
-golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
-golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
+golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
+golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -409,8 +420,9 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
+golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -427,7 +439,6 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -438,27 +449,29 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
+golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
-golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
+golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
+golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
+golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -483,8 +496,9 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM=
-golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
+golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -508,15 +522,15 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
 google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
 google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
-google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
-google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
+google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
+google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -527,13 +541,13 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8 h1:KR8+MyP7/qOlV+8Af01LtjL04bu7on42eVsxT4EyBQk=
-google.golang.org/protobuf v1.28.2-0.20220831092852-f930b1dc76e8/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
diff --git a/infra/conf/geodata/memconservative/cache.go b/infra/conf/geodata/memconservative/cache.go
index 386086cb..307db9d6 100644
--- a/infra/conf/geodata/memconservative/cache.go
+++ b/infra/conf/geodata/memconservative/cache.go
@@ -1,7 +1,7 @@
 package memconservative
 
 import (
-	"io/ioutil"
+	"os"
 	"strings"
 
 	"google.golang.org/protobuf/proto"
@@ -53,7 +53,7 @@ func (g GeoIPCache) Unmarshal(filename, code string) (*routercommon.GeoIP, error
 	case errFailedToReadBytes, errFailedToReadExpectedLenBytes,
 		errInvalidGeodataFile, errInvalidGeodataVarintLength:
 		newError("failed to decode geoip file: ", filename, ", fallback to the original ReadFile method")
-		geoipBytes, err = ioutil.ReadFile(asset)
+		geoipBytes, err = os.ReadFile(asset)
 		if err != nil {
 			return nil, err
 		}
@@ -118,7 +118,7 @@ func (g GeoSiteCache) Unmarshal(filename, code string) (*routercommon.GeoSite, e
 	case errFailedToReadBytes, errFailedToReadExpectedLenBytes,
 		errInvalidGeodataFile, errInvalidGeodataVarintLength:
 		newError("failed to decode geoip file: ", filename, ", fallback to the original ReadFile method")
-		geositeBytes, err = ioutil.ReadFile(asset)
+		geositeBytes, err = os.ReadFile(asset)
 		if err != nil {
 			return nil, err
 		}
diff --git a/infra/vprotogen/main.go b/infra/vprotogen/main.go
index 2551808c..df78fa65 100644
--- a/infra/vprotogen/main.go
+++ b/infra/vprotogen/main.go
@@ -6,7 +6,6 @@ import (
 	"fmt"
 	"go/build"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"os"
 	"os/exec"
@@ -48,7 +47,7 @@ func GetRuntimeEnv(key string) (string, error) {
 	}
 	var data []byte
 	var runtimeEnv string
-	data, readErr := ioutil.ReadFile(file)
+	data, readErr := os.ReadFile(file)
 	if readErr != nil {
 		return "", readErr
 	}
diff --git a/main/commands/helpers/fs.go b/main/commands/helpers/fs.go
index 855ee19b..542a52ed 100644
--- a/main/commands/helpers/fs.go
+++ b/main/commands/helpers/fs.go
@@ -2,7 +2,6 @@ package helpers
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -10,7 +9,7 @@ import (
 
 // ReadDir finds files according to extensions in the dir
 func ReadDir(dir string, extensions []string) ([]string, error) {
-	confs, err := ioutil.ReadDir(dir)
+	confs, err := os.ReadDir(dir)
 	if err != nil {
 		return nil, err
 	}
diff --git a/main/commands/run.go b/main/commands/run.go
index 1e88b84b..066bfae1 100644
--- a/main/commands/run.go
+++ b/main/commands/run.go
@@ -2,7 +2,6 @@ package commands
 
 import (
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"os/signal"
@@ -113,7 +112,7 @@ func dirExists(file string) bool {
 }
 
 func readConfDir(dirPath string, extension []string) cmdarg.Arg {
-	confs, err := ioutil.ReadDir(dirPath)
+	confs, err := os.ReadDir(dirPath)
 	if err != nil {
 		base.Fatalf("failed to read dir %s: %s", dirPath, err)
 	}
diff --git a/main/distro/all/all.go b/main/distro/all/all.go
index efe8042c..9bcbc14c 100644
--- a/main/distro/all/all.go
+++ b/main/distro/all/all.go
@@ -65,6 +65,14 @@ import (
 	_ "github.com/v2fly/v2ray-core/v5/transport/internet/udp"
 	_ "github.com/v2fly/v2ray-core/v5/transport/internet/websocket"
 
+	// Developer preview transports
+	_ "github.com/v2fly/v2ray-core/v5/transport/internet/request/assembly"
+
+	_ "github.com/v2fly/v2ray-core/v5/transport/internet/request/assembler/simple"
+	_ "github.com/v2fly/v2ray-core/v5/transport/internet/request/roundtripper/httprt"
+
+	_ "github.com/v2fly/v2ray-core/v5/transport/internet/request/stereotype/meek"
+
 	// Transport headers
 	_ "github.com/v2fly/v2ray-core/v5/transport/internet/headers/http"
 	_ "github.com/v2fly/v2ray-core/v5/transport/internet/headers/noop"
diff --git a/proxy/http/client.go b/proxy/http/client.go
index 0178940c..316ed74a 100644
--- a/proxy/http/client.go
+++ b/proxy/http/client.go
@@ -2,12 +2,14 @@ package http
 
 import (
 	"bufio"
+	"bytes"
 	"context"
 	"encoding/base64"
 	"io"
 	"net/http"
 	"net/url"
 	"sync"
+	"time"
 
 	"golang.org/x/net/http2"
 
@@ -29,8 +31,9 @@ import (
 )
 
 type Client struct {
-	serverPicker  protocol.ServerPicker
-	policyManager policy.Manager
+	serverPicker       protocol.ServerPicker
+	policyManager      policy.Manager
+	h1SkipWaitForReply bool
 }
 
 type h2Conn struct {
@@ -59,8 +62,9 @@ func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
 
 	v := core.MustFromContext(ctx)
 	return &Client{
-		serverPicker:  protocol.NewRoundRobinServerPicker(serverList),
-		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
+		serverPicker:       protocol.NewRoundRobinServerPicker(serverList),
+		policyManager:      v.GetFeature(policy.ManagerType()).(policy.Manager),
+		h1SkipWaitForReply: config.H1SkipWaitForReply,
 	}, nil
 }
 
@@ -87,7 +91,13 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
 		// transmitted together. Note we should not get stuck here, as the payload may
 		// not exist (considering to access MySQL database via a HTTP proxy, where the
 		// server sends hello to the client first).
-		if mbuf, _ := reader.ReadMultiBufferTimeout(proxy.FirstPayloadTimeout); mbuf != nil {
+		waitTime := proxy.FirstPayloadTimeout
+		if c.h1SkipWaitForReply {
+			// Some server require first write to be present in client hello.
+			// Increase timeout to if the client have explicitly requested to skip waiting for reply.
+			waitTime = time.Second
+		}
+		if mbuf, _ := reader.ReadMultiBufferTimeout(waitTime); mbuf != nil {
 			mlen := mbuf.Len()
 			firstPayload = bytespool.Alloc(mlen)
 			mbuf, _ = buf.SplitBytes(mbuf, firstPayload)
@@ -103,14 +113,19 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
 		dest := server.Destination()
 		user = server.PickUser()
 
-		netConn, err := setUpHTTPTunnel(ctx, dest, targetAddr, user, dialer, firstPayload)
+		netConn, firstResp, err := setUpHTTPTunnel(ctx, dest, targetAddr, user, dialer, firstPayload, c.h1SkipWaitForReply)
 		if netConn != nil {
-			if _, ok := netConn.(*http2Conn); !ok {
+			if _, ok := netConn.(*http2Conn); !ok && !c.h1SkipWaitForReply {
 				if _, err := netConn.Write(firstPayload); err != nil {
 					netConn.Close()
 					return err
 				}
 			}
+			if firstResp != nil {
+				if err := link.Writer.WriteMultiBuffer(firstResp); err != nil {
+					return err
+				}
+			}
 			conn = internet.Connection(netConn)
 		}
 		return err
@@ -150,7 +165,8 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
 }
 
 // setUpHTTPTunnel will create a socket tunnel via HTTP CONNECT method
-func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, user *protocol.MemoryUser, dialer internet.Dialer, firstPayload []byte) (net.Conn, error) {
+func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, user *protocol.MemoryUser, dialer internet.Dialer, firstPayload []byte, writeFirstPayloadInH1 bool,
+) (net.Conn, buf.MultiBuffer, error) {
 	req := &http.Request{
 		Method: http.MethodConnect,
 		URL:    &url.URL{Host: target},
@@ -164,27 +180,53 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u
 		req.Header.Set("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
 	}
 
-	connectHTTP1 := func(rawConn net.Conn) (net.Conn, error) {
+	connectHTTP1 := func(rawConn net.Conn) (net.Conn, buf.MultiBuffer, error) {
 		req.Header.Set("Proxy-Connection", "Keep-Alive")
 
-		err := req.Write(rawConn)
-		if err != nil {
-			rawConn.Close()
-			return nil, err
+		if !writeFirstPayloadInH1 {
+			err := req.Write(rawConn)
+			if err != nil {
+				rawConn.Close()
+				return nil, nil, err
+			}
+		} else {
+			buffer := bytes.NewBuffer(nil)
+			err := req.Write(buffer)
+			if err != nil {
+				rawConn.Close()
+				return nil, nil, err
+			}
+			_, err = io.Copy(buffer, bytes.NewReader(firstPayload))
+			if err != nil {
+				rawConn.Close()
+				return nil, nil, err
+			}
+			_, err = rawConn.Write(buffer.Bytes())
+			if err != nil {
+				rawConn.Close()
+				return nil, nil, err
+			}
 		}
-
-		resp, err := http.ReadResponse(bufio.NewReader(rawConn), req)
+		bufferedReader := bufio.NewReader(rawConn)
+		resp, err := http.ReadResponse(bufferedReader, req)
 		if err != nil {
 			rawConn.Close()
-			return nil, err
+			return nil, nil, err
 		}
 		defer resp.Body.Close()
 
 		if resp.StatusCode != http.StatusOK {
 			rawConn.Close()
-			return nil, newError("Proxy responded with non 200 code: " + resp.Status)
+			return nil, nil, newError("Proxy responded with non 200 code: " + resp.Status)
 		}
-		return rawConn, nil
+		if bufferedReader.Buffered() > 0 {
+			payload, err := buf.ReadFrom(io.LimitReader(bufferedReader, int64(bufferedReader.Buffered())))
+			if err != nil {
+				return nil, nil, newError("unable to drain buffer: ").Base(err)
+			}
+			return rawConn, payload, nil
+		}
+		return rawConn, nil, nil
 	}
 
 	connectHTTP2 := func(rawConn net.Conn, h2clientConn *http2.ClientConn) (net.Conn, error) {
@@ -228,16 +270,16 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u
 		if cc.CanTakeNewRequest() {
 			proxyConn, err := connectHTTP2(rc, cc)
 			if err != nil {
-				return nil, err
+				return nil, nil, err
 			}
 
-			return proxyConn, nil
+			return proxyConn, nil, nil
 		}
 	}
 
 	rawConn, err := dialer.Dial(ctx, dest)
 	if err != nil {
-		return nil, err
+		return nil, nil, err
 	}
 
 	iConn := rawConn
@@ -249,7 +291,7 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u
 	if tlsConn, ok := iConn.(*tls.Conn); ok {
 		if err := tlsConn.Handshake(); err != nil {
 			rawConn.Close()
-			return nil, err
+			return nil, nil, err
 		}
 		nextProto = tlsConn.ConnectionState().NegotiatedProtocol
 	}
@@ -262,13 +304,13 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u
 		h2clientConn, err := t.NewClientConn(rawConn)
 		if err != nil {
 			rawConn.Close()
-			return nil, err
+			return nil, nil, err
 		}
 
 		proxyConn, err := connectHTTP2(rawConn, h2clientConn)
 		if err != nil {
 			rawConn.Close()
-			return nil, err
+			return nil, nil, err
 		}
 
 		cachedH2Mutex.Lock()
@@ -282,9 +324,9 @@ func setUpHTTPTunnel(ctx context.Context, dest net.Destination, target string, u
 		}
 		cachedH2Mutex.Unlock()
 
-		return proxyConn, err
+		return proxyConn, nil, err
 	default:
-		return nil, newError("negotiated unsupported application layer protocol: " + nextProto)
+		return nil, nil, newError("negotiated unsupported application layer protocol: " + nextProto)
 	}
 }
 
diff --git a/proxy/http/config.pb.go b/proxy/http/config.pb.go
index 8195b5ff..8c26d08f 100644
--- a/proxy/http/config.pb.go
+++ b/proxy/http/config.pb.go
@@ -76,7 +76,7 @@ type ServerConfig struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	// Deprecated: Do not use.
+	// Deprecated: Marked as deprecated in proxy/http/config.proto.
 	Timeout          uint32            `protobuf:"varint,1,opt,name=timeout,proto3" json:"timeout,omitempty"`
 	Accounts         map[string]string `protobuf:"bytes,2,rep,name=accounts,proto3" json:"accounts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
 	AllowTransparent bool              `protobuf:"varint,3,opt,name=allow_transparent,json=allowTransparent,proto3" json:"allow_transparent,omitempty"`
@@ -115,7 +115,7 @@ func (*ServerConfig) Descriptor() ([]byte, []int) {
 	return file_proxy_http_config_proto_rawDescGZIP(), []int{1}
 }
 
-// Deprecated: Do not use.
+// Deprecated: Marked as deprecated in proxy/http/config.proto.
 func (x *ServerConfig) GetTimeout() uint32 {
 	if x != nil {
 		return x.Timeout
@@ -151,7 +151,8 @@ type ClientConfig struct {
 	unknownFields protoimpl.UnknownFields
 
 	// Sever is a list of HTTP server addresses.
-	Server []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=server,proto3" json:"server,omitempty"`
+	Server             []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=server,proto3" json:"server,omitempty"`
+	H1SkipWaitForReply bool                       `protobuf:"varint,2,opt,name=h1_skip_wait_for_reply,json=h1SkipWaitForReply,proto3" json:"h1_skip_wait_for_reply,omitempty"`
 }
 
 func (x *ClientConfig) Reset() {
@@ -193,6 +194,13 @@ func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint {
 	return nil
 }
 
+func (x *ClientConfig) GetH1SkipWaitForReply() bool {
+	if x != nil {
+		return x.H1SkipWaitForReply
+	}
+	return false
+}
+
 var File_proxy_http_config_proto protoreflect.FileDescriptor
 
 var file_proxy_http_config_proto_rawDesc = []byte{
@@ -221,19 +229,22 @@ var file_proxy_http_config_proto_rawDesc = []byte{
 	0x1a, 0x3b, 0x0a, 0x0d, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72,
 	0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
 	0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x52, 0x0a,
-	0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42, 0x0a,
-	0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e,
-	0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
-	0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65,
-	0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65,
-	0x72, 0x42, 0x60, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63,
-	0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x50, 0x01,
-	0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66,
-	0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x35,
-	0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x68, 0x74, 0x74, 0x70, 0xaa, 0x02, 0x15, 0x56, 0x32,
-	0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x48,
-	0x74, 0x74, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x86, 0x01,
+	0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42,
+	0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a,
+	0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
+	0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76,
+	0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76,
+	0x65, 0x72, 0x12, 0x32, 0x0a, 0x16, 0x68, 0x31, 0x5f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x77, 0x61,
+	0x69, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x08, 0x52, 0x12, 0x68, 0x31, 0x53, 0x6b, 0x69, 0x70, 0x57, 0x61, 0x69, 0x74, 0x46, 0x6f,
+	0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x42, 0x60, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32,
+	0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x68,
+	0x74, 0x74, 0x70, 0x50, 0x01, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
+	0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f,
+	0x72, 0x65, 0x2f, 0x76, 0x35, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x68, 0x74, 0x74, 0x70,
+	0xaa, 0x02, 0x15, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72,
+	0x6f, 0x78, 0x79, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/proxy/http/config.proto b/proxy/http/config.proto
index 8c1773a1..ee8ae562 100644
--- a/proxy/http/config.proto
+++ b/proxy/http/config.proto
@@ -25,4 +25,5 @@ message ServerConfig {
 message ClientConfig {
   // Sever is a list of HTTP server addresses.
   repeated v2ray.core.common.protocol.ServerEndpoint server = 1;
+  bool h1_skip_wait_for_reply = 2;
 }
diff --git a/proxy/http/server.go b/proxy/http/server.go
index 26dc66b8..d698f3fa 100644
--- a/proxy/http/server.go
+++ b/proxy/http/server.go
@@ -242,9 +242,7 @@ func (s *Server) handlePlainHTTP(ctx context.Context, request *http.Request, wri
 		request.Header.Set("User-Agent", "")
 	}
 
-	content := &session.Content{
-		Protocol: "http/1.1",
-	}
+	content := &session.Content{}
 
 	content.SetAttribute(":method", strings.ToUpper(request.Method))
 	content.SetAttribute(":path", request.URL.Path)
diff --git a/proxy/http/simplified/config.go b/proxy/http/simplified/config.go
index ef80de16..fd407fe4 100644
--- a/proxy/http/simplified/config.go
+++ b/proxy/http/simplified/config.go
@@ -25,6 +25,7 @@ func init() {
 					Port:    simplifiedClient.Port,
 				},
 			},
+			H1SkipWaitForReply: simplifiedClient.H1SkipWaitForReply,
 		}
 		return common.CreateObject(ctx, fullClient)
 	}))
diff --git a/proxy/http/simplified/config.pb.go b/proxy/http/simplified/config.pb.go
index 13375dcd..9089ce8a 100644
--- a/proxy/http/simplified/config.pb.go
+++ b/proxy/http/simplified/config.pb.go
@@ -59,8 +59,9 @@ type ClientConfig struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Address *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
-	Port    uint32          `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
+	Address            *net.IPOrDomain `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
+	Port               uint32          `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
+	H1SkipWaitForReply bool            `protobuf:"varint,3,opt,name=h1_skip_wait_for_reply,json=h1SkipWaitForReply,proto3" json:"h1_skip_wait_for_reply,omitempty"`
 }
 
 func (x *ClientConfig) Reset() {
@@ -109,6 +110,13 @@ func (x *ClientConfig) GetPort() uint32 {
 	return 0
 }
 
+func (x *ClientConfig) GetH1SkipWaitForReply() bool {
+	if x != nil {
+		return x.H1SkipWaitForReply
+	}
+	return false
+}
+
 var File_proxy_http_simplified_config_proto protoreflect.FileDescriptor
 
 var file_proxy_http_simplified_config_proto_rawDesc = []byte{
@@ -120,26 +128,28 @@ var file_proxy_http_simplified_config_proto_rawDesc = []byte{
 	0x72, 0x6f, 0x74, 0x6f, 0x65, 0x78, 0x74, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f,
 	0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
 	0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f,
-	0x74, 0x6f, 0x22, 0x27, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66,
-	0x69, 0x67, 0x3a, 0x17, 0x82, 0xb5, 0x18, 0x09, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e,
-	0x64, 0x82, 0xb5, 0x18, 0x06, 0x12, 0x04, 0x68, 0x74, 0x74, 0x70, 0x22, 0x79, 0x0a, 0x0c, 0x43,
-	0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3b, 0x0a, 0x07, 0x61,
-	0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76,
-	0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
-	0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52,
-	0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x3a, 0x18, 0x82, 0xb5,
-	0x18, 0x0a, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x82, 0xb5, 0x18, 0x06,
-	0x12, 0x04, 0x68, 0x74, 0x74, 0x70, 0x42, 0x81, 0x01, 0x0a, 0x24, 0x63, 0x6f, 0x6d, 0x2e, 0x76,
-	0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e,
-	0x68, 0x74, 0x74, 0x70, 0x2e, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x50,
-	0x01, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32,
-	0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76,
-	0x35, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x73, 0x69, 0x6d,
-	0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0xaa, 0x02, 0x20, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e,
-	0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x2e,
-	0x53, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x33,
+	0x74, 0x6f, 0x22, 0x23, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x3a, 0x13, 0x82, 0xb5, 0x18, 0x0f, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e,
+	0x64, 0x12, 0x04, 0x68, 0x74, 0x74, 0x70, 0x22, 0xa9, 0x01, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65,
+	0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3b, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72,
+	0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61,
+	0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65,
+	0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x07, 0x61, 0x64,
+	0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x32, 0x0a, 0x16, 0x68, 0x31, 0x5f,
+	0x73, 0x6b, 0x69, 0x70, 0x5f, 0x77, 0x61, 0x69, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x72, 0x65,
+	0x70, 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x68, 0x31, 0x53, 0x6b, 0x69,
+	0x70, 0x57, 0x61, 0x69, 0x74, 0x46, 0x6f, 0x72, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x3a, 0x14, 0x82,
+	0xb5, 0x18, 0x10, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x04, 0x68,
+	0x74, 0x74, 0x70, 0x42, 0x81, 0x01, 0x0a, 0x24, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61,
+	0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x68, 0x74, 0x74,
+	0x70, 0x2e, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x50, 0x01, 0x5a, 0x34,
+	0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79,
+	0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x35, 0x2f, 0x70,
+	0x72, 0x6f, 0x78, 0x79, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x69,
+	0x66, 0x69, 0x65, 0x64, 0xaa, 0x02, 0x20, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72,
+	0x65, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x48, 0x74, 0x74, 0x70, 0x2e, 0x53, 0x69, 0x6d,
+	0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/proxy/http/simplified/config.proto b/proxy/http/simplified/config.proto
index e097ff36..220275c4 100644
--- a/proxy/http/simplified/config.proto
+++ b/proxy/http/simplified/config.proto
@@ -20,4 +20,5 @@ message ClientConfig {
 
   v2ray.core.common.net.IPOrDomain address = 1;
   uint32 port = 2;
+  bool h1_skip_wait_for_reply = 3;
 }
\ No newline at end of file
diff --git a/proxy/shadowsocks/config.go b/proxy/shadowsocks/config.go
index 1a0cb7a2..e7cf8a88 100644
--- a/proxy/shadowsocks/config.go
+++ b/proxy/shadowsocks/config.go
@@ -6,11 +6,9 @@ import (
 	"crypto/cipher"
 	"crypto/md5"
 	"crypto/sha1"
-	"encoding/json"
 	"io"
 	"strings"
 
-	"github.com/golang/protobuf/jsonpb"
 	"golang.org/x/crypto/chacha20poly1305"
 	"golang.org/x/crypto/hkdf"
 
@@ -212,27 +210,13 @@ func (NoneCipher) DecodePacket(key []byte, b *buf.Buffer) error {
 	return nil
 }
 
-func (c *CipherType) UnmarshalJSONPB(unmarshaler *jsonpb.Unmarshaler, bytes []byte) error {
-	var method string
-
-	if err := json.Unmarshal(bytes, &method); err != nil {
-		return err
-	}
-
-	if *c = CipherFromString(method); *c == CipherType_UNKNOWN {
-		return newError("unknown cipher method: ", method)
-	}
-
-	return nil
-}
-
 func CipherFromString(c string) CipherType {
 	switch strings.ToLower(c) {
-	case "aes-128-gcm", "aead_aes_128_gcm":
+	case "aes-128-gcm", "aes_128_gcm", "aead_aes_128_gcm":
 		return CipherType_AES_128_GCM
-	case "aes-256-gcm", "aead_aes_256_gcm":
+	case "aes-256-gcm", "aes_256_gcm", "aead_aes_256_gcm":
 		return CipherType_AES_256_GCM
-	case "chacha20-poly1305", "aead_chacha20_poly1305", "chacha20-ietf-poly1305":
+	case "chacha20-poly1305", "chacha20_poly1305", "aead_chacha20_poly1305", "chacha20-ietf-poly1305":
 		return CipherType_CHACHA20_POLY1305
 	case "none", "plain":
 		return CipherType_NONE
diff --git a/proxy/shadowsocks/simplified/config.go b/proxy/shadowsocks/simplified/config.go
index 3e9ee1ba..0bbdd18a 100644
--- a/proxy/shadowsocks/simplified/config.go
+++ b/proxy/shadowsocks/simplified/config.go
@@ -2,6 +2,9 @@ package simplified
 
 import (
 	"context"
+	"encoding/json"
+
+	"github.com/golang/protobuf/jsonpb"
 
 	"github.com/v2fly/v2ray-core/v5/common"
 	"github.com/v2fly/v2ray-core/v5/common/protocol"
@@ -9,6 +12,20 @@ import (
 	"github.com/v2fly/v2ray-core/v5/proxy/shadowsocks"
 )
 
+func (c *CipherTypeWrapper) UnmarshalJSONPB(unmarshaler *jsonpb.Unmarshaler, bytes []byte) error {
+	var method string
+
+	if err := json.Unmarshal(bytes, &method); err != nil {
+		return err
+	}
+
+	if c.Value = shadowsocks.CipherFromString(method); c.Value == shadowsocks.CipherType_UNKNOWN {
+		return newError("unknown cipher method: ", method)
+	}
+
+	return nil
+}
+
 func init() {
 	common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
 		simplifiedServer := config.(*ServerConfig)
@@ -16,7 +33,7 @@ func init() {
 			User: &protocol.User{
 				Account: serial.ToTypedMessage(&shadowsocks.Account{
 					Password:   simplifiedServer.Password,
-					CipherType: simplifiedServer.Method,
+					CipherType: simplifiedServer.Method.Value,
 				}),
 			},
 			Network:        simplifiedServer.Networks.GetNetwork(),
@@ -37,7 +54,7 @@ func init() {
 						{
 							Account: serial.ToTypedMessage(&shadowsocks.Account{
 								Password:                       simplifiedClient.Password,
-								CipherType:                     simplifiedClient.Method,
+								CipherType:                     simplifiedClient.Method.Value,
 								ExperimentReducedIvHeadEntropy: simplifiedClient.ExperimentReducedIvHeadEntropy,
 							}),
 						},
diff --git a/proxy/shadowsocks/simplified/config.pb.go b/proxy/shadowsocks/simplified/config.pb.go
index aa317c1f..ec01869b 100644
--- a/proxy/shadowsocks/simplified/config.pb.go
+++ b/proxy/shadowsocks/simplified/config.pb.go
@@ -1,3 +1,9 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.30.0
+// 	protoc        v3.21.12
+// source: proxy/shadowsocks/simplified/config.proto
+
 package simplified
 
 import (
@@ -23,7 +29,7 @@ type ServerConfig struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Method         shadowsocks.CipherType    `protobuf:"varint,1,opt,name=method,proto3,enum=v2ray.core.proxy.shadowsocks.CipherType" json:"method,omitempty"`
+	Method         *CipherTypeWrapper        `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"`
 	Password       string                    `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
 	Networks       *net.NetworkList          `protobuf:"bytes,3,opt,name=networks,proto3" json:"networks,omitempty"`
 	PacketEncoding packetaddr.PacketAddrType `protobuf:"varint,4,opt,name=packet_encoding,json=packetEncoding,proto3,enum=v2ray.core.net.packetaddr.PacketAddrType" json:"packet_encoding,omitempty"`
@@ -61,11 +67,11 @@ func (*ServerConfig) Descriptor() ([]byte, []int) {
 	return file_proxy_shadowsocks_simplified_config_proto_rawDescGZIP(), []int{0}
 }
 
-func (x *ServerConfig) GetMethod() shadowsocks.CipherType {
+func (x *ServerConfig) GetMethod() *CipherTypeWrapper {
 	if x != nil {
 		return x.Method
 	}
-	return shadowsocks.CipherType(0)
+	return nil
 }
 
 func (x *ServerConfig) GetPassword() string {
@@ -94,11 +100,11 @@ type ClientConfig struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Address                        *net.IPOrDomain        `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
-	Port                           uint32                 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
-	Method                         shadowsocks.CipherType `protobuf:"varint,3,opt,name=method,proto3,enum=v2ray.core.proxy.shadowsocks.CipherType" json:"method,omitempty"`
-	Password                       string                 `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"`
-	ExperimentReducedIvHeadEntropy bool                   `protobuf:"varint,90001,opt,name=experiment_reduced_iv_head_entropy,json=experimentReducedIvHeadEntropy,proto3" json:"experiment_reduced_iv_head_entropy,omitempty"`
+	Address                        *net.IPOrDomain    `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
+	Port                           uint32             `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"`
+	Method                         *CipherTypeWrapper `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"`
+	Password                       string             `protobuf:"bytes,4,opt,name=password,proto3" json:"password,omitempty"`
+	ExperimentReducedIvHeadEntropy bool               `protobuf:"varint,90001,opt,name=experiment_reduced_iv_head_entropy,json=experimentReducedIvHeadEntropy,proto3" json:"experiment_reduced_iv_head_entropy,omitempty"`
 }
 
 func (x *ClientConfig) Reset() {
@@ -147,11 +153,11 @@ func (x *ClientConfig) GetPort() uint32 {
 	return 0
 }
 
-func (x *ClientConfig) GetMethod() shadowsocks.CipherType {
+func (x *ClientConfig) GetMethod() *CipherTypeWrapper {
 	if x != nil {
 		return x.Method
 	}
-	return shadowsocks.CipherType(0)
+	return nil
 }
 
 func (x *ClientConfig) GetPassword() string {
@@ -168,6 +174,53 @@ func (x *ClientConfig) GetExperimentReducedIvHeadEntropy() bool {
 	return false
 }
 
+type CipherTypeWrapper struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Value shadowsocks.CipherType `protobuf:"varint,1,opt,name=value,proto3,enum=v2ray.core.proxy.shadowsocks.CipherType" json:"value,omitempty"`
+}
+
+func (x *CipherTypeWrapper) Reset() {
+	*x = CipherTypeWrapper{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proxy_shadowsocks_simplified_config_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CipherTypeWrapper) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CipherTypeWrapper) ProtoMessage() {}
+
+func (x *CipherTypeWrapper) ProtoReflect() protoreflect.Message {
+	mi := &file_proxy_shadowsocks_simplified_config_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CipherTypeWrapper.ProtoReflect.Descriptor instead.
+func (*CipherTypeWrapper) Descriptor() ([]byte, []int) {
+	return file_proxy_shadowsocks_simplified_config_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *CipherTypeWrapper) GetValue() shadowsocks.CipherType {
+	if x != nil {
+		return x.Value
+	}
+	return shadowsocks.CipherType(0)
+}
+
 var File_proxy_shadowsocks_simplified_config_proto protoreflect.FileDescriptor
 
 var file_proxy_shadowsocks_simplified_config_proto_rawDesc = []byte{
@@ -185,35 +238,37 @@ var file_proxy_shadowsocks_simplified_config_proto_rawDesc = []byte{
 	0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x61, 0x64, 0x64,
 	0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e,
 	0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b,
-	0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa0,
+	0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xae,
 	0x02, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
-	0x40, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
-	0x28, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f,
-	0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e, 0x43,
-	0x69, 0x70, 0x68, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f,
-	0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x3e, 0x0a,
-	0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
-	0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x4c,
-	0x69, 0x73, 0x74, 0x52, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x52, 0x0a,
-	0x0f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67,
-	0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63,
-	0x6f, 0x72, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x61, 0x64,
-	0x64, 0x72, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x54, 0x79, 0x70,
-	0x65, 0x52, 0x0e, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e,
-	0x67, 0x3a, 0x1e, 0x82, 0xb5, 0x18, 0x09, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,
-	0x82, 0xb5, 0x18, 0x0d, 0x12, 0x0b, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b,
-	0x73, 0x22, 0xac, 0x02, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66,
-	0x69, 0x67, 0x12, 0x3b, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65,
-	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72,
-	0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12,
-	0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70,
-	0x6f, 0x72, 0x74, 0x12, 0x40, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20,
-	0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65,
-	0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63,
-	0x6b, 0x73, 0x2e, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x06, 0x6d,
+	0x52, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x3a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f,
+	0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e, 0x73,
+	0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72,
+	0x54, 0x79, 0x70, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x06, 0x6d, 0x65, 0x74,
+	0x68, 0x6f, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12,
+	0x3e, 0x0a, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x22, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63,
+	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+	0x6b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12,
+	0x52, 0x0a, 0x0f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69,
+	0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79,
+	0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74,
+	0x61, 0x64, 0x64, 0x72, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x54,
+	0x79, 0x70, 0x65, 0x52, 0x0e, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x45, 0x6e, 0x63, 0x6f, 0x64,
+	0x69, 0x6e, 0x67, 0x3a, 0x1a, 0x82, 0xb5, 0x18, 0x16, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75,
+	0x6e, 0x64, 0x12, 0x0b, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x22,
+	0xba, 0x02, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x12, 0x3b, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x21, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63,
+	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f,
+	0x6d, 0x61, 0x69, 0x6e, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a,
+	0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x70, 0x6f, 0x72,
+	0x74, 0x12, 0x52, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x3a, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70,
+	0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73,
+	0x2e, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x43, 0x69, 0x70, 0x68,
+	0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x06, 0x6d,
 	0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
 	0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
 	0x64, 0x12, 0x4c, 0x0a, 0x22, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x5f,
@@ -221,19 +276,24 @@ var file_proxy_shadowsocks_simplified_config_proto_rawDesc = []byte{
 	0x65, 0x6e, 0x74, 0x72, 0x6f, 0x70, 0x79, 0x18, 0x91, 0xbf, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52,
 	0x1e, 0x65, 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x64, 0x75, 0x63,
 	0x65, 0x64, 0x49, 0x76, 0x48, 0x65, 0x61, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x6f, 0x70, 0x79, 0x3a,
-	0x1f, 0x82, 0xb5, 0x18, 0x0a, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x82,
-	0xb5, 0x18, 0x0d, 0x12, 0x0b, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73,
-	0x42, 0x96, 0x01, 0x0a, 0x2b, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63,
-	0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77,
-	0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64,
-	0x50, 0x01, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76,
-	0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f,
-	0x76, 0x35, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73,
-	0x6f, 0x63, 0x6b, 0x73, 0x2f, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0xaa,
-	0x02, 0x27, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f,
-	0x78, 0x79, 0x2e, 0x53, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e, 0x53,
-	0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x33,
+	0x1b, 0x82, 0xb5, 0x18, 0x17, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12,
+	0x0b, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x22, 0x53, 0x0a, 0x11,
+	0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65,
+	0x72, 0x12, 0x3e, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e,
+	0x32, 0x28, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72,
+	0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e,
+	0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
+	0x65, 0x42, 0x96, 0x01, 0x0a, 0x2b, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e,
+	0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x73, 0x68, 0x61, 0x64, 0x6f,
+	0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65,
+	0x64, 0x50, 0x01, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+	0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65,
+	0x2f, 0x76, 0x35, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77,
+	0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2f, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64,
+	0xaa, 0x02, 0x27, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72,
+	0x6f, 0x78, 0x79, 0x2e, 0x53, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x73, 0x6f, 0x63, 0x6b, 0x73, 0x2e,
+	0x53, 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x33,
 }
 
 var (
@@ -248,26 +308,28 @@ func file_proxy_shadowsocks_simplified_config_proto_rawDescGZIP() []byte {
 	return file_proxy_shadowsocks_simplified_config_proto_rawDescData
 }
 
-var file_proxy_shadowsocks_simplified_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_proxy_shadowsocks_simplified_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
 var file_proxy_shadowsocks_simplified_config_proto_goTypes = []interface{}{
 	(*ServerConfig)(nil),           // 0: v2ray.core.proxy.shadowsocks.simplified.ServerConfig
 	(*ClientConfig)(nil),           // 1: v2ray.core.proxy.shadowsocks.simplified.ClientConfig
-	(shadowsocks.CipherType)(0),    // 2: v2ray.core.proxy.shadowsocks.CipherType
+	(*CipherTypeWrapper)(nil),      // 2: v2ray.core.proxy.shadowsocks.simplified.CipherTypeWrapper
 	(*net.NetworkList)(nil),        // 3: v2ray.core.common.net.NetworkList
 	(packetaddr.PacketAddrType)(0), // 4: v2ray.core.net.packetaddr.PacketAddrType
 	(*net.IPOrDomain)(nil),         // 5: v2ray.core.common.net.IPOrDomain
+	(shadowsocks.CipherType)(0),    // 6: v2ray.core.proxy.shadowsocks.CipherType
 }
 var file_proxy_shadowsocks_simplified_config_proto_depIdxs = []int32{
-	2, // 0: v2ray.core.proxy.shadowsocks.simplified.ServerConfig.method:type_name -> v2ray.core.proxy.shadowsocks.CipherType
+	2, // 0: v2ray.core.proxy.shadowsocks.simplified.ServerConfig.method:type_name -> v2ray.core.proxy.shadowsocks.simplified.CipherTypeWrapper
 	3, // 1: v2ray.core.proxy.shadowsocks.simplified.ServerConfig.networks:type_name -> v2ray.core.common.net.NetworkList
 	4, // 2: v2ray.core.proxy.shadowsocks.simplified.ServerConfig.packet_encoding:type_name -> v2ray.core.net.packetaddr.PacketAddrType
 	5, // 3: v2ray.core.proxy.shadowsocks.simplified.ClientConfig.address:type_name -> v2ray.core.common.net.IPOrDomain
-	2, // 4: v2ray.core.proxy.shadowsocks.simplified.ClientConfig.method:type_name -> v2ray.core.proxy.shadowsocks.CipherType
-	5, // [5:5] is the sub-list for method output_type
-	5, // [5:5] is the sub-list for method input_type
-	5, // [5:5] is the sub-list for extension type_name
-	5, // [5:5] is the sub-list for extension extendee
-	0, // [0:5] is the sub-list for field type_name
+	2, // 4: v2ray.core.proxy.shadowsocks.simplified.ClientConfig.method:type_name -> v2ray.core.proxy.shadowsocks.simplified.CipherTypeWrapper
+	6, // 5: v2ray.core.proxy.shadowsocks.simplified.CipherTypeWrapper.value:type_name -> v2ray.core.proxy.shadowsocks.CipherType
+	6, // [6:6] is the sub-list for method output_type
+	6, // [6:6] is the sub-list for method input_type
+	6, // [6:6] is the sub-list for extension type_name
+	6, // [6:6] is the sub-list for extension extendee
+	0, // [0:6] is the sub-list for field type_name
 }
 
 func init() { file_proxy_shadowsocks_simplified_config_proto_init() }
@@ -300,6 +362,18 @@ func file_proxy_shadowsocks_simplified_config_proto_init() {
 				return nil
 			}
 		}
+		file_proxy_shadowsocks_simplified_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CipherTypeWrapper); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
@@ -307,7 +381,7 @@ func file_proxy_shadowsocks_simplified_config_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_proxy_shadowsocks_simplified_config_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   2,
+			NumMessages:   3,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/proxy/shadowsocks/simplified/config.proto b/proxy/shadowsocks/simplified/config.proto
index 3677862b..936a8d61 100644
--- a/proxy/shadowsocks/simplified/config.proto
+++ b/proxy/shadowsocks/simplified/config.proto
@@ -16,7 +16,7 @@ message ServerConfig{
   option (v2ray.core.common.protoext.message_opt).type = "inbound";
   option (v2ray.core.common.protoext.message_opt).short_name = "shadowsocks";
 
-  v2ray.core.proxy.shadowsocks.CipherType method = 1;
+  CipherTypeWrapper method = 1;
   string password = 2;
   v2ray.core.common.net.NetworkList networks = 3;
   v2ray.core.net.packetaddr.PacketAddrType packet_encoding = 4;
@@ -28,7 +28,11 @@ message ClientConfig {
 
   v2ray.core.common.net.IPOrDomain address = 1;
   uint32 port = 2;
-  v2ray.core.proxy.shadowsocks.CipherType method = 3;
+  CipherTypeWrapper method = 3;
   string password = 4;
   bool experiment_reduced_iv_head_entropy = 90001;
+}
+
+message CipherTypeWrapper {
+  v2ray.core.proxy.shadowsocks.CipherType value = 1;
 }
\ No newline at end of file
diff --git a/proxy/shadowsocks/simplified/errors.generated.go b/proxy/shadowsocks/simplified/errors.generated.go
new file mode 100644
index 00000000..ec7517f2
--- /dev/null
+++ b/proxy/shadowsocks/simplified/errors.generated.go
@@ -0,0 +1,9 @@
+package simplified
+
+import "github.com/v2fly/v2ray-core/v5/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}
diff --git a/proxy/trojan/client.go b/proxy/trojan/client.go
index dd74345d..3724f0b0 100644
--- a/proxy/trojan/client.go
+++ b/proxy/trojan/client.go
@@ -146,7 +146,14 @@ func (c *Client) Process(ctx context.Context, link *transport.Link, dialer inter
 		}
 
 		// write some request payload to buffer
-		if err = buf.CopyOnceTimeout(link.Reader, bodyWriter, proxy.FirstPayloadTimeout); err != nil && err != buf.ErrNotTimeoutReader && err != buf.ErrReadTimeout {
+		err = buf.CopyOnceTimeout(link.Reader, bodyWriter, proxy.FirstPayloadTimeout)
+		switch err {
+		case buf.ErrNotTimeoutReader, buf.ErrReadTimeout:
+			if err := connWriter.WriteHeader(); err != nil {
+				return newError("failed to write request header").Base(err).AtWarning()
+			}
+		case nil:
+		default:
 			return newError("failed to write a request payload").Base(err).AtWarning()
 		}
 
diff --git a/proxy/trojan/protocol.go b/proxy/trojan/protocol.go
index 21e6a5a8..d6ef0f66 100644
--- a/proxy/trojan/protocol.go
+++ b/proxy/trojan/protocol.go
@@ -21,7 +21,6 @@ var (
 )
 
 const (
-	maxLength       = 8192
 	commandTCP byte = 1
 	commandUDP byte = 3
 )
@@ -60,6 +59,15 @@ func (c *ConnWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
 	return nil
 }
 
+func (c *ConnWriter) WriteHeader() error {
+	if !c.headerSent {
+		if err := c.writeHeader(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 func (c *ConnWriter) writeHeader() error {
 	buffer := buf.StackNew()
 	defer buffer.Release()
@@ -101,11 +109,11 @@ type PacketWriter struct {
 
 // WriteMultiBuffer implements buf.Writer
 func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
-	b := make([]byte, maxLength)
-	for !mb.IsEmpty() {
-		var length int
-		mb, length = buf.SplitBytes(mb, b)
-		if _, err := w.writePacket(b[:length], w.Target); err != nil {
+	for _, b := range mb {
+		if b.IsEmpty() {
+			continue
+		}
+		if _, err := w.writePacket(b.Bytes(), w.Target); err != nil {
 			buf.ReleaseMulti(mb)
 			return err
 		}
@@ -116,11 +124,11 @@ func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
 
 // WriteMultiBufferWithMetadata writes udp packet with destination specified
 func (w *PacketWriter) WriteMultiBufferWithMetadata(mb buf.MultiBuffer, dest net.Destination) error {
-	b := make([]byte, maxLength)
-	for !mb.IsEmpty() {
-		var length int
-		mb, length = buf.SplitBytes(mb, b)
-		if _, err := w.writePacket(b[:length], dest); err != nil {
+	for _, b := range mb {
+		if b.IsEmpty() {
+			continue
+		}
+		if _, err := w.writePacket(b.Bytes(), dest); err != nil {
 			buf.ReleaseMulti(mb)
 			return err
 		}
@@ -136,13 +144,29 @@ func (w *PacketWriter) WriteTo(payload []byte, addr gonet.Addr) (int, error) {
 }
 
 func (w *PacketWriter) writePacket(payload []byte, dest net.Destination) (int, error) { // nolint: unparam
-	buffer := buf.StackNew()
-	defer buffer.Release()
+	var addrPortLen int32
+	switch dest.Address.Family() {
+	case net.AddressFamilyDomain:
+		if protocol.IsDomainTooLong(dest.Address.Domain()) {
+			return 0, newError("Super long domain is not supported: ", dest.Address.Domain())
+		}
+		addrPortLen = 1 + 1 + int32(len(dest.Address.Domain())) + 2
+	case net.AddressFamilyIPv4:
+		addrPortLen = 1 + 4 + 2
+	case net.AddressFamilyIPv6:
+		addrPortLen = 1 + 16 + 2
+	default:
+		panic("Unknown address type.")
+	}
 
 	length := len(payload)
 	lengthBuf := [2]byte{}
 	binary.BigEndian.PutUint16(lengthBuf[:], uint16(length))
-	if err := addrParser.WriteAddressPort(&buffer, dest.Address, dest.Port); err != nil {
+
+	buffer := buf.NewWithSize(addrPortLen + 2 + 2 + int32(length))
+	defer buffer.Release()
+
+	if err := addrParser.WriteAddressPort(buffer, dest.Address, dest.Port); err != nil {
 		return 0, err
 	}
 	if _, err := buffer.Write(lengthBuf[:]); err != nil {
@@ -255,10 +279,7 @@ func (r *PacketReader) ReadMultiBufferWithMetadata() (*PacketPayload, error) {
 		return nil, newError("failed to read payload length").Base(err)
 	}
 
-	remain := int(binary.BigEndian.Uint16(lengthBuf[:]))
-	if remain > maxLength {
-		return nil, newError("oversize payload")
-	}
+	length := binary.BigEndian.Uint16(lengthBuf[:])
 
 	var crlf [2]byte
 	if _, err := io.ReadFull(r, crlf[:]); err != nil {
@@ -266,25 +287,14 @@ func (r *PacketReader) ReadMultiBufferWithMetadata() (*PacketPayload, error) {
 	}
 
 	dest := net.UDPDestination(addr, port)
-	var mb buf.MultiBuffer
-	for remain > 0 {
-		length := buf.Size
-		if remain < length {
-			length = remain
-		}
-
-		b := buf.New()
-		mb = append(mb, b)
-		n, err := b.ReadFullFrom(r, int32(length))
-		if err != nil {
-			buf.ReleaseMulti(mb)
-			return nil, newError("failed to read payload").Base(err)
-		}
 
-		remain -= int(n)
+	b := buf.NewWithSize(int32(length))
+	_, err = b.ReadFullFrom(r, int32(length))
+	if err != nil {
+		return nil, newError("failed to read payload").Base(err)
 	}
 
-	return &PacketPayload{Target: dest, Buffer: mb}, nil
+	return &PacketPayload{Target: dest, Buffer: buf.MultiBuffer{b}}, nil
 }
 
 type PacketConnectionReader struct {
diff --git a/release/container/Containerfile b/release/container/Containerfile
index 4d76a4dc..b5f1d364 100644
--- a/release/container/Containerfile
+++ b/release/container/Containerfile
@@ -16,4 +16,5 @@ ENV v2ray.location.asset=/opt/v2ray/share
 
 COPY ./ /opt/v2ray/
 
-CMD ["/opt/v2ray/bin/v2ray"]
+ENTRYPOINT [ "/opt/v2ray/bin/v2ray" ]
+CMD [ "run", "-config", "/etc/v2ray/config.json" ]
diff --git a/release/friendly-filenames.json b/release/friendly-filenames.json
index f395e8e2..63ea7a8f 100644
--- a/release/friendly-filenames.json
+++ b/release/friendly-filenames.json
@@ -18,6 +18,7 @@
   "linux-mipsle": { "friendlyName": "linux-mips32le" },
   "linux-mips": { "friendlyName": "linux-mips32" },
   "linux-riscv64": { "friendlyName": "linux-riscv64" },
+  "linux-loong64": { "friendlyName": "linux-loong64" },
   "openbsd-386": { "friendlyName": "openbsd-32" },
   "openbsd-amd64": { "friendlyName": "openbsd-64" },
   "openbsd-arm6": { "friendlyName": "openbsd-arm32-v6" },
@@ -28,4 +29,4 @@
   "windows-arm64": { "friendlyName": "windows-arm64-v8a" },
   "windows-arm7": { "friendlyName": "windows-arm32-v7a" },
   "android-arm64": { "friendlyName": "android-arm64-v8a" }
-}
\ No newline at end of file
+}
diff --git a/release/user-package.sh b/release/user-package.sh
index 8bfb0c4f..9c709bef 100755
--- a/release/user-package.sh
+++ b/release/user-package.sh
@@ -95,7 +95,7 @@ PKGSUFFIX=
 
 for arg in "$@"; do
 	case $arg in
-	386 | arm* | mips* | ppc64* | riscv64 | s390x)
+	386 | arm* | mips* | ppc64* | riscv64 | loong64 | s390x)
 		GOARCH=$arg
 		;;
 	windows)
diff --git a/transport/internet/quic/dialer.go b/transport/internet/quic/dialer.go
index f3e14294..b52dd4a5 100644
--- a/transport/internet/quic/dialer.go
+++ b/transport/internet/quic/dialer.go
@@ -150,7 +150,6 @@ func (s *clientConnections) openConnection(destAddr net.Addr, config *Config, tl
 	}
 
 	quicConfig := &quic.Config{
-		ConnectionIDLength:   12,
 		HandshakeIdleTimeout: time.Second * 8,
 		MaxIdleTimeout:       time.Second * 30,
 		KeepAlivePeriod:      time.Second * 15,
@@ -162,7 +161,12 @@ func (s *clientConnections) openConnection(destAddr net.Addr, config *Config, tl
 		return nil, err
 	}
 
-	conn, err := quic.DialContext(context.Background(), sysConn, destAddr, "", tlsConfig.GetTLSConfig(tls.WithDestination(dest)), quicConfig)
+	tr := quic.Transport{
+		Conn:               sysConn,
+		ConnectionIDLength: 12,
+	}
+
+	conn, err := tr.Dial(context.Background(), destAddr, tlsConfig.GetTLSConfig(tls.WithDestination(dest)), quicConfig)
 	if err != nil {
 		sysConn.Close()
 		return nil, err
diff --git a/transport/internet/quic/hub.go b/transport/internet/quic/hub.go
index 41bae59e..9a89ba7d 100644
--- a/transport/internet/quic/hub.go
+++ b/transport/internet/quic/hub.go
@@ -17,7 +17,7 @@ import (
 // Listener is an internet.Listener that listens for TCP connections.
 type Listener struct {
 	rawConn  *sysConn
-	listener quic.Listener
+	listener *quic.Listener
 	done     *done.Instance
 	addConn  internet.ConnHandler
 }
@@ -102,7 +102,6 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
 	}
 
 	quicConfig := &quic.Config{
-		ConnectionIDLength:    12,
 		HandshakeIdleTimeout:  time.Second * 8,
 		MaxIdleTimeout:        time.Second * 45,
 		MaxIncomingStreams:    32,
@@ -116,7 +115,12 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
 		return nil, err
 	}
 
-	qListener, err := quic.Listen(conn, tlsConfig.GetTLSConfig(), quicConfig)
+	tr := quic.Transport{
+		Conn:               conn,
+		ConnectionIDLength: 12,
+	}
+
+	qListener, err := tr.Listen(tlsConfig.GetTLSConfig(), quicConfig)
 	if err != nil {
 		conn.Close()
 		return nil, err
diff --git a/transport/internet/request/assembler.go b/transport/internet/request/assembler.go
new file mode 100644
index 00000000..58e1e01f
--- /dev/null
+++ b/transport/internet/request/assembler.go
@@ -0,0 +1,22 @@
+package request
+
+import (
+	"io"
+)
+
+type SessionAssemblerClient interface {
+	SessionCreator
+	TransportClientAssemblyReceiver
+}
+type SessionAssemblerServer interface {
+	TripperReceiver
+	TransportServerAssemblyReceiver
+}
+
+type SessionOption interface {
+	RoundTripperOption()
+}
+
+type Session interface {
+	io.ReadWriteCloser
+}
diff --git a/transport/internet/request/assembler/simple/client.go b/transport/internet/request/assembler/simple/client.go
new file mode 100644
index 00000000..b1377c09
--- /dev/null
+++ b/transport/internet/request/assembler/simple/client.go
@@ -0,0 +1,174 @@
+package simple
+
+import (
+	"bytes"
+	"context"
+	"crypto/rand"
+	"io"
+	"time"
+
+	"github.com/v2fly/v2ray-core/v5/common"
+
+	"github.com/v2fly/v2ray-core/v5/transport/internet/request"
+)
+
+func newClient(config *ClientConfig) request.SessionAssemblerClient {
+	return &simpleAssemblerClient{config: config}
+}
+
+type simpleAssemblerClient struct {
+	assembly request.TransportClientAssembly
+	config   *ClientConfig
+}
+
+func (s *simpleAssemblerClient) OnTransportClientAssemblyReady(assembly request.TransportClientAssembly) {
+	s.assembly = assembly
+}
+
+func (s *simpleAssemblerClient) NewSession(ctx context.Context, opts ...request.SessionOption) (request.Session, error) {
+	sessionID := make([]byte, 16)
+	_, err := io.ReadFull(rand.Reader, sessionID)
+	if err != nil {
+		return nil, err
+	}
+	sessionContext, finish := context.WithCancel(ctx)
+	session := &simpleAssemblerClientSession{
+		sessionID: sessionID, tripper: s.assembly.Tripper(), readBuffer: bytes.NewBuffer(nil),
+		ctx: sessionContext, finish: finish, writerChan: make(chan []byte), readerChan: make(chan []byte, 16), assembler: s,
+	}
+	go session.keepRunning()
+	return session, nil
+}
+
+type simpleAssemblerClientSession struct {
+	sessionID        []byte
+	currentWriteWait int
+
+	assembler  *simpleAssemblerClient
+	tripper    request.Tripper
+	readBuffer *bytes.Buffer
+	writerChan chan []byte
+	readerChan chan []byte
+	ctx        context.Context
+	finish     func()
+}
+
+func (s *simpleAssemblerClientSession) keepRunning() {
+	s.currentWriteWait = int(s.assembler.config.InitialPollingIntervalMs)
+	for s.ctx.Err() == nil {
+		s.runOnce()
+	}
+}
+
+func (s *simpleAssemblerClientSession) runOnce() {
+	sendBuffer := bytes.NewBuffer(nil)
+	if s.currentWriteWait != 0 {
+		waitTimer := time.NewTimer(time.Millisecond * time.Duration(s.currentWriteWait))
+		waitForFirstWrite := true
+	copyFromWriterLoop:
+		for {
+			select {
+			case <-s.ctx.Done():
+				return
+			case data := <-s.writerChan:
+				sendBuffer.Write(data)
+				if sendBuffer.Len() >= int(s.assembler.config.MaxWriteSize) {
+					break copyFromWriterLoop
+				}
+				if waitForFirstWrite {
+					waitForFirstWrite = false
+					waitTimer.Reset(time.Millisecond * time.Duration(s.assembler.config.WaitSubsequentWriteMs))
+				}
+			case <-waitTimer.C:
+				break copyFromWriterLoop
+			}
+		}
+		waitTimer.Stop()
+	}
+
+	firstRound := true
+	pollConnection := true
+	for sendBuffer.Len() != 0 || firstRound {
+		firstRound = false
+		sendAmount := sendBuffer.Len()
+		if sendAmount > int(s.assembler.config.MaxWriteSize) {
+			sendAmount = int(s.assembler.config.MaxWriteSize)
+		}
+		data := sendBuffer.Next(sendAmount)
+		if len(data) != 0 {
+			pollConnection = false
+		}
+		for {
+			resp, err := s.tripper.RoundTrip(s.ctx, request.Request{Data: data, ConnectionTag: s.sessionID})
+			if err != nil {
+				newError("failed to send data").Base(err).WriteToLog()
+				if s.ctx.Err() != nil {
+					return
+				}
+				time.Sleep(time.Millisecond * time.Duration(s.assembler.config.FailedRetryIntervalMs))
+				continue
+			}
+			if len(resp.Data) != 0 {
+				s.readerChan <- resp.Data
+			}
+			if len(resp.Data) != 0 {
+				pollConnection = false
+			}
+			break
+		}
+	}
+	if pollConnection {
+		s.currentWriteWait = int(s.assembler.config.BackoffFactor * float32(s.currentWriteWait))
+		if s.currentWriteWait > int(s.assembler.config.MaxPollingIntervalMs) {
+			s.currentWriteWait = int(s.assembler.config.MaxPollingIntervalMs)
+		}
+		if s.currentWriteWait < int(s.assembler.config.MinPollingIntervalMs) {
+			s.currentWriteWait = int(s.assembler.config.MinPollingIntervalMs)
+		}
+	} else {
+		s.currentWriteWait = int(0)
+	}
+}
+
+func (s *simpleAssemblerClientSession) Read(p []byte) (n int, err error) {
+	if s.readBuffer.Len() == 0 {
+		select {
+		case <-s.ctx.Done():
+			return 0, s.ctx.Err()
+		case data := <-s.readerChan:
+			s.readBuffer.Write(data)
+		}
+	}
+	n, err = s.readBuffer.Read(p)
+	if err == io.EOF {
+		s.readBuffer.Reset()
+		return 0, nil
+	}
+	return
+}
+
+func (s *simpleAssemblerClientSession) Write(p []byte) (n int, err error) {
+	buf := make([]byte, len(p))
+	copy(buf, p)
+	select {
+	case <-s.ctx.Done():
+		return 0, s.ctx.Err()
+	case s.writerChan <- buf:
+		return len(p), nil
+	}
+}
+
+func (s *simpleAssemblerClientSession) Close() error {
+	s.finish()
+	return nil
+}
+
+func init() {
+	common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		clientConfig, ok := config.(*ClientConfig)
+		if !ok {
+			return nil, newError("not a ClientConfig")
+		}
+		return newClient(clientConfig), nil
+	}))
+}
diff --git a/transport/internet/request/assembler/simple/config.pb.go b/transport/internet/request/assembler/simple/config.pb.go
new file mode 100644
index 00000000..cb9daf86
--- /dev/null
+++ b/transport/internet/request/assembler/simple/config.pb.go
@@ -0,0 +1,295 @@
+package simple
+
+import (
+	_ "github.com/v2fly/v2ray-core/v5/common/protoext"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type ClientConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	MaxWriteSize             int32   `protobuf:"varint,1,opt,name=max_write_size,json=maxWriteSize,proto3" json:"max_write_size,omitempty"`
+	WaitSubsequentWriteMs    int32   `protobuf:"varint,2,opt,name=wait_subsequent_write_ms,json=waitSubsequentWriteMs,proto3" json:"wait_subsequent_write_ms,omitempty"`
+	InitialPollingIntervalMs int32   `protobuf:"varint,3,opt,name=initial_polling_interval_ms,json=initialPollingIntervalMs,proto3" json:"initial_polling_interval_ms,omitempty"`
+	MaxPollingIntervalMs     int32   `protobuf:"varint,4,opt,name=max_polling_interval_ms,json=maxPollingIntervalMs,proto3" json:"max_polling_interval_ms,omitempty"`
+	MinPollingIntervalMs     int32   `protobuf:"varint,5,opt,name=min_polling_interval_ms,json=minPollingIntervalMs,proto3" json:"min_polling_interval_ms,omitempty"`
+	BackoffFactor            float32 `protobuf:"fixed32,6,opt,name=backoff_factor,json=backoffFactor,proto3" json:"backoff_factor,omitempty"`
+	FailedRetryIntervalMs    int32   `protobuf:"varint,7,opt,name=failed_retry_interval_ms,json=failedRetryIntervalMs,proto3" json:"failed_retry_interval_ms,omitempty"`
+}
+
+func (x *ClientConfig) Reset() {
+	*x = ClientConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_transport_internet_request_assembler_simple_config_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ClientConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ClientConfig) ProtoMessage() {}
+
+func (x *ClientConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_transport_internet_request_assembler_simple_config_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead.
+func (*ClientConfig) Descriptor() ([]byte, []int) {
+	return file_transport_internet_request_assembler_simple_config_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *ClientConfig) GetMaxWriteSize() int32 {
+	if x != nil {
+		return x.MaxWriteSize
+	}
+	return 0
+}
+
+func (x *ClientConfig) GetWaitSubsequentWriteMs() int32 {
+	if x != nil {
+		return x.WaitSubsequentWriteMs
+	}
+	return 0
+}
+
+func (x *ClientConfig) GetInitialPollingIntervalMs() int32 {
+	if x != nil {
+		return x.InitialPollingIntervalMs
+	}
+	return 0
+}
+
+func (x *ClientConfig) GetMaxPollingIntervalMs() int32 {
+	if x != nil {
+		return x.MaxPollingIntervalMs
+	}
+	return 0
+}
+
+func (x *ClientConfig) GetMinPollingIntervalMs() int32 {
+	if x != nil {
+		return x.MinPollingIntervalMs
+	}
+	return 0
+}
+
+func (x *ClientConfig) GetBackoffFactor() float32 {
+	if x != nil {
+		return x.BackoffFactor
+	}
+	return 0
+}
+
+func (x *ClientConfig) GetFailedRetryIntervalMs() int32 {
+	if x != nil {
+		return x.FailedRetryIntervalMs
+	}
+	return 0
+}
+
+type ServerConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	MaxWriteSize int32 `protobuf:"varint,1,opt,name=max_write_size,json=maxWriteSize,proto3" json:"max_write_size,omitempty"`
+}
+
+func (x *ServerConfig) Reset() {
+	*x = ServerConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_transport_internet_request_assembler_simple_config_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ServerConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ServerConfig) ProtoMessage() {}
+
+func (x *ServerConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_transport_internet_request_assembler_simple_config_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead.
+func (*ServerConfig) Descriptor() ([]byte, []int) {
+	return file_transport_internet_request_assembler_simple_config_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *ServerConfig) GetMaxWriteSize() int32 {
+	if x != nil {
+		return x.MaxWriteSize
+	}
+	return 0
+}
+
+var File_transport_internet_request_assembler_simple_config_proto protoreflect.FileDescriptor
+
+var file_transport_internet_request_assembler_simple_config_proto_rawDesc = []byte{
+	0x0a, 0x38, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65,
+	0x72, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2f, 0x61, 0x73, 0x73,
+	0x65, 0x6d, 0x62, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x2f, 0x63, 0x6f,
+	0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x36, 0x76, 0x32, 0x72, 0x61,
+	0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
+	0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x2e, 0x61, 0x73, 0x73, 0x65, 0x6d, 0x62, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x69, 0x6d, 0x70,
+	0x6c, 0x65, 0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x65, 0x78, 0x74, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x22, 0xac, 0x03, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69,
+	0x74, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6d,
+	0x61, 0x78, 0x57, 0x72, 0x69, 0x74, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x37, 0x0a, 0x18, 0x77,
+	0x61, 0x69, 0x74, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x5f, 0x77,
+	0x72, 0x69, 0x74, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x77,
+	0x61, 0x69, 0x74, 0x53, 0x75, 0x62, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x57, 0x72, 0x69,
+	0x74, 0x65, 0x4d, 0x73, 0x12, 0x3d, 0x0a, 0x1b, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f,
+	0x70, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c,
+	0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x69, 0x6e, 0x69, 0x74, 0x69,
+	0x61, 0x6c, 0x50, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61,
+	0x6c, 0x4d, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x6f, 0x6c, 0x6c, 0x69,
+	0x6e, 0x67, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x04,
+	0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x6d, 0x61, 0x78, 0x50, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67,
+	0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x6d, 0x69,
+	0x6e, 0x5f, 0x70, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76,
+	0x61, 0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x6d, 0x69, 0x6e,
+	0x50, 0x6f, 0x6c, 0x6c, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d,
+	0x73, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x61, 0x63, 0x6b, 0x6f, 0x66, 0x66, 0x5f, 0x66, 0x61, 0x63,
+	0x74, 0x6f, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0d, 0x62, 0x61, 0x63, 0x6b, 0x6f,
+	0x66, 0x66, 0x46, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x37, 0x0a, 0x18, 0x66, 0x61, 0x69, 0x6c,
+	0x65, 0x64, 0x5f, 0x72, 0x65, 0x74, 0x72, 0x79, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61,
+	0x6c, 0x5f, 0x6d, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x15, 0x66, 0x61, 0x69, 0x6c,
+	0x65, 0x64, 0x52, 0x65, 0x74, 0x72, 0x79, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4d,
+	0x73, 0x3a, 0x30, 0x82, 0xb5, 0x18, 0x2c, 0x0a, 0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f,
+	0x72, 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x61, 0x73, 0x73, 0x65, 0x6d,
+	0x62, 0x6c, 0x65, 0x72, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x06, 0x73, 0x69, 0x6d,
+	0x70, 0x6c, 0x65, 0x22, 0x66, 0x0a, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x65,
+	0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6d, 0x61, 0x78,
+	0x57, 0x72, 0x69, 0x74, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x3a, 0x30, 0x82, 0xb5, 0x18, 0x2c, 0x0a,
+	0x22, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x2e, 0x61, 0x73, 0x73, 0x65, 0x6d, 0x62, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72,
+	0x76, 0x65, 0x72, 0x12, 0x06, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x42, 0xc3, 0x01, 0x0a, 0x3a,
+	0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74,
+	0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65,
+	0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x61, 0x73, 0x73, 0x65, 0x6d, 0x62,
+	0x6c, 0x65, 0x72, 0x2e, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x50, 0x01, 0x5a, 0x4a, 0x67, 0x69,
+	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76,
+	0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x35, 0x2f, 0x74, 0x72, 0x61,
+	0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f,
+	0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2f, 0x61, 0x73, 0x73, 0x65, 0x6d, 0x62, 0x6c, 0x65,
+	0x72, 0x2f, 0x73, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0xaa, 0x02, 0x36, 0x56, 0x32, 0x52, 0x61, 0x79,
+	0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e,
+	0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x2e, 0x41, 0x73, 0x73, 0x65, 0x6d, 0x62, 0x6c, 0x65, 0x72, 0x2e, 0x53, 0x69, 0x6d, 0x70, 0x6c,
+	0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_transport_internet_request_assembler_simple_config_proto_rawDescOnce sync.Once
+	file_transport_internet_request_assembler_simple_config_proto_rawDescData = file_transport_internet_request_assembler_simple_config_proto_rawDesc
+)
+
+func file_transport_internet_request_assembler_simple_config_proto_rawDescGZIP() []byte {
+	file_transport_internet_request_assembler_simple_config_proto_rawDescOnce.Do(func() {
+		file_transport_internet_request_assembler_simple_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_request_assembler_simple_config_proto_rawDescData)
+	})
+	return file_transport_internet_request_assembler_simple_config_proto_rawDescData
+}
+
+var file_transport_internet_request_assembler_simple_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_transport_internet_request_assembler_simple_config_proto_goTypes = []interface{}{
+	(*ClientConfig)(nil), // 0: v2ray.core.transport.internet.request.assembler.simple.ClientConfig
+	(*ServerConfig)(nil), // 1: v2ray.core.transport.internet.request.assembler.simple.ServerConfig
+}
+var file_transport_internet_request_assembler_simple_config_proto_depIdxs = []int32{
+	0, // [0:0] is the sub-list for method output_type
+	0, // [0:0] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_transport_internet_request_assembler_simple_config_proto_init() }
+func file_transport_internet_request_assembler_simple_config_proto_init() {
+	if File_transport_internet_request_assembler_simple_config_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_transport_internet_request_assembler_simple_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ClientConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_transport_internet_request_assembler_simple_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ServerConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_transport_internet_request_assembler_simple_config_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_transport_internet_request_assembler_simple_config_proto_goTypes,
+		DependencyIndexes: file_transport_internet_request_assembler_simple_config_proto_depIdxs,
+		MessageInfos:      file_transport_internet_request_assembler_simple_config_proto_msgTypes,
+	}.Build()
+	File_transport_internet_request_assembler_simple_config_proto = out.File
+	file_transport_internet_request_assembler_simple_config_proto_rawDesc = nil
+	file_transport_internet_request_assembler_simple_config_proto_goTypes = nil
+	file_transport_internet_request_assembler_simple_config_proto_depIdxs = nil
+}
diff --git a/transport/internet/request/assembler/simple/config.proto b/transport/internet/request/assembler/simple/config.proto
new file mode 100644
index 00000000..c48b1177
--- /dev/null
+++ b/transport/internet/request/assembler/simple/config.proto
@@ -0,0 +1,29 @@
+syntax = "proto3";
+
+package v2ray.core.transport.internet.request.assembler.simple;
+option csharp_namespace = "V2Ray.Core.Transport.Internet.Request.Assembler.Simple";
+option go_package = "github.com/v2fly/v2ray-core/v5/transport/internet/request/assembler/simple";
+option java_package = "com.v2ray.core.transport.internet.request.assembler.simple";
+option java_multiple_files = true;
+
+import "common/protoext/extensions.proto";
+
+message ClientConfig {
+  option (v2ray.core.common.protoext.message_opt).type = "transport.request.assembler.client";
+  option (v2ray.core.common.protoext.message_opt).short_name = "simple";
+
+  int32 max_write_size = 1;
+  int32 wait_subsequent_write_ms = 2;
+  int32 initial_polling_interval_ms = 3;
+  int32 max_polling_interval_ms = 4;
+  int32 min_polling_interval_ms = 5;
+  float backoff_factor = 6;
+  int32 failed_retry_interval_ms = 7;
+}
+
+message ServerConfig {
+  option (v2ray.core.common.protoext.message_opt).type = "transport.request.assembler.server";
+  option (v2ray.core.common.protoext.message_opt).short_name = "simple";
+
+  int32 max_write_size = 1;
+}
\ No newline at end of file
diff --git a/transport/internet/request/assembler/simple/errors.generated.go b/transport/internet/request/assembler/simple/errors.generated.go
new file mode 100644
index 00000000..7ca71c8e
--- /dev/null
+++ b/transport/internet/request/assembler/simple/errors.generated.go
@@ -0,0 +1,9 @@
+package simple
+
+import "github.com/v2fly/v2ray-core/v5/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}
diff --git a/transport/internet/request/assembler/simple/server.go b/transport/internet/request/assembler/simple/server.go
new file mode 100644
index 00000000..696e7323
--- /dev/null
+++ b/transport/internet/request/assembler/simple/server.go
@@ -0,0 +1,137 @@
+package simple
+
+import (
+	"bytes"
+	"context"
+	"sync"
+
+	"github.com/v2fly/v2ray-core/v5/common"
+
+	"github.com/v2fly/v2ray-core/v5/transport/internet/request"
+)
+
+func newServer(config *ServerConfig) request.SessionAssemblerServer {
+	return &simpleAssemblerServer{}
+}
+
+type simpleAssemblerServer struct {
+	sessions sync.Map
+	assembly request.TransportServerAssembly
+}
+
+func (s *simpleAssemblerServer) OnTransportServerAssemblyReady(assembly request.TransportServerAssembly) {
+	s.assembly = assembly
+}
+
+func (s *simpleAssemblerServer) OnRoundTrip(ctx context.Context, req request.Request, opts ...request.RoundTripperOption,
+) (resp request.Response, err error) {
+	connectionID := req.ConnectionTag
+	session := newSimpleAssemblerServerSession(ctx)
+	loadedSession, loaded := s.sessions.LoadOrStore(string(connectionID), session)
+	if loaded {
+		session = loadedSession.(*simpleAssemblerServerSession)
+	} else {
+		if err := s.assembly.SessionReceiver().OnNewSession(ctx, session); err != nil {
+			return request.Response{}, newError("failed to create new session").Base(err)
+		}
+	}
+	return session.OnRoundTrip(ctx, req, opts...)
+}
+
+func newSimpleAssemblerServerSession(ctx context.Context) *simpleAssemblerServerSession {
+	sessionCtx, finish := context.WithCancel(ctx)
+	return &simpleAssemblerServerSession{
+		readBuffer:       bytes.NewBuffer(nil),
+		readChan:         make(chan []byte, 16),
+		requestProcessed: make(chan struct{}),
+		writeLock:        new(sync.Mutex),
+		writeBuffer:      bytes.NewBuffer(nil),
+		maxWriteSize:     4096,
+		ctx:              sessionCtx,
+		finish:           finish,
+	}
+}
+
+type simpleAssemblerServerSession struct {
+	maxWriteSize int
+
+	readBuffer       *bytes.Buffer
+	readChan         chan []byte
+	requestProcessed chan struct{}
+
+	writeLock   *sync.Mutex
+	writeBuffer *bytes.Buffer
+
+	ctx    context.Context
+	finish func()
+}
+
+func (s *simpleAssemblerServerSession) Read(p []byte) (n int, err error) {
+	if s.readBuffer.Len() == 0 {
+		select {
+		case <-s.ctx.Done():
+			return 0, s.ctx.Err()
+		case data := <-s.readChan:
+			s.readBuffer.Write(data)
+		}
+	}
+	return s.readBuffer.Read(p)
+}
+
+func (s *simpleAssemblerServerSession) Write(p []byte) (n int, err error) {
+	s.writeLock.Lock()
+
+	n, err = s.writeBuffer.Write(p)
+	length := s.writeBuffer.Len()
+	s.writeLock.Unlock()
+	if err != nil {
+		return 0, err
+	}
+	if length > s.maxWriteSize {
+		select {
+		case <-s.requestProcessed:
+		case <-s.ctx.Done():
+			return 0, s.ctx.Err()
+		}
+	}
+	return
+}
+
+func (s *simpleAssemblerServerSession) Close() error {
+	s.finish()
+	return nil
+}
+
+func (s *simpleAssemblerServerSession) OnRoundTrip(ctx context.Context, req request.Request, opts ...request.RoundTripperOption,
+) (resp request.Response, err error) {
+	if req.Data != nil && len(req.Data) > 0 {
+		select {
+		case <-s.ctx.Done():
+			return request.Response{}, s.ctx.Err()
+		case s.readChan <- req.Data:
+		}
+	}
+
+	s.writeLock.Lock()
+	nextWrite := s.writeBuffer.Next(s.maxWriteSize)
+	data := make([]byte, len(nextWrite))
+	copy(data, nextWrite)
+	s.writeLock.Unlock()
+	select {
+	case s.requestProcessed <- struct{}{}:
+	case <-s.ctx.Done():
+		return request.Response{}, s.ctx.Err()
+	default:
+	}
+	return request.Response{Data: data}, nil
+}
+
+func init() {
+	common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		serverConfig, ok := config.(*ServerConfig)
+		if !ok {
+			return nil, newError("not a SimpleServerConfig")
+		}
+		return newServer(serverConfig), nil
+	}))
+}
diff --git a/transport/internet/request/assembler/simple/simple.go b/transport/internet/request/assembler/simple/simple.go
new file mode 100644
index 00000000..2a92860f
--- /dev/null
+++ b/transport/internet/request/assembler/simple/simple.go
@@ -0,0 +1,3 @@
+package simple
+
+//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
diff --git a/transport/internet/request/assembly/assembly.go b/transport/internet/request/assembly/assembly.go
new file mode 100644
index 00000000..22d6df81
--- /dev/null
+++ b/transport/internet/request/assembly/assembly.go
@@ -0,0 +1,17 @@
+package assembly
+
+import (
+	"context"
+
+	"github.com/v2fly/v2ray-core/v5/common"
+)
+
+//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
+
+const protocolName = "request"
+
+func init() {
+	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		return nil, newError("request is a transport protocol.")
+	}))
+}
diff --git a/transport/internet/request/assembly/config.pb.go b/transport/internet/request/assembly/config.pb.go
new file mode 100644
index 00000000..036907e3
--- /dev/null
+++ b/transport/internet/request/assembly/config.pb.go
@@ -0,0 +1,174 @@
+package assembly
+
+import (
+	_ "github.com/v2fly/v2ray-core/v5/common/protoext"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	anypb "google.golang.org/protobuf/types/known/anypb"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Config struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Assembler    *anypb.Any `protobuf:"bytes,1,opt,name=assembler,proto3" json:"assembler,omitempty"`
+	Roundtripper *anypb.Any `protobuf:"bytes,2,opt,name=roundtripper,proto3" json:"roundtripper,omitempty"`
+}
+
+func (x *Config) Reset() {
+	*x = Config{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_transport_internet_request_assembly_config_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Config) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Config) ProtoMessage() {}
+
+func (x *Config) ProtoReflect() protoreflect.Message {
+	mi := &file_transport_internet_request_assembly_config_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Config.ProtoReflect.Descriptor instead.
+func (*Config) Descriptor() ([]byte, []int) {
+	return file_transport_internet_request_assembly_config_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Config) GetAssembler() *anypb.Any {
+	if x != nil {
+		return x.Assembler
+	}
+	return nil
+}
+
+func (x *Config) GetRoundtripper() *anypb.Any {
+	if x != nil {
+		return x.Roundtripper
+	}
+	return nil
+}
+
+var File_transport_internet_request_assembly_config_proto protoreflect.FileDescriptor
+
+var file_transport_internet_request_assembly_config_proto_rawDesc = []byte{
+	0x0a, 0x30, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65,
+	0x72, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2f, 0x61, 0x73, 0x73,
+	0x65, 0x6d, 0x62, 0x6c, 0x79, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x12, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74,
+	0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65,
+	0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x61, 0x73, 0x73, 0x65, 0x6d, 0x62,
+	0x6c, 0x79, 0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x65, 0x78, 0x74, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
+	0x90, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x32, 0x0a, 0x09, 0x61, 0x73,
+	0x73, 0x65, 0x6d, 0x62, 0x6c, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e,
+	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
+	0x41, 0x6e, 0x79, 0x52, 0x09, 0x61, 0x73, 0x73, 0x65, 0x6d, 0x62, 0x6c, 0x65, 0x72, 0x12, 0x38,
+	0x0a, 0x0c, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x0c, 0x72, 0x6f, 0x75, 0x6e,
+	0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x3a, 0x18, 0x82, 0xb5, 0x18, 0x14, 0x0a, 0x09,
+	0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x07, 0x72, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x42, 0xab, 0x01, 0x0a, 0x32, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79,
+	0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e,
+	0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x2e, 0x61, 0x73, 0x73, 0x65, 0x6d, 0x62, 0x6c, 0x79, 0x50, 0x01, 0x5a, 0x42, 0x67, 0x69, 0x74,
+	0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32,
+	0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x35, 0x2f, 0x74, 0x72, 0x61, 0x6e,
+	0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x72,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2f, 0x61, 0x73, 0x73, 0x65, 0x6d, 0x62, 0x6c, 0x79, 0xaa,
+	0x02, 0x2e, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61,
+	0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x73, 0x73, 0x65, 0x6d, 0x62, 0x6c, 0x79,
+	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_transport_internet_request_assembly_config_proto_rawDescOnce sync.Once
+	file_transport_internet_request_assembly_config_proto_rawDescData = file_transport_internet_request_assembly_config_proto_rawDesc
+)
+
+func file_transport_internet_request_assembly_config_proto_rawDescGZIP() []byte {
+	file_transport_internet_request_assembly_config_proto_rawDescOnce.Do(func() {
+		file_transport_internet_request_assembly_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_request_assembly_config_proto_rawDescData)
+	})
+	return file_transport_internet_request_assembly_config_proto_rawDescData
+}
+
+var file_transport_internet_request_assembly_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_transport_internet_request_assembly_config_proto_goTypes = []interface{}{
+	(*Config)(nil),    // 0: v2ray.core.transport.internet.request.assembly.Config
+	(*anypb.Any)(nil), // 1: google.protobuf.Any
+}
+var file_transport_internet_request_assembly_config_proto_depIdxs = []int32{
+	1, // 0: v2ray.core.transport.internet.request.assembly.Config.assembler:type_name -> google.protobuf.Any
+	1, // 1: v2ray.core.transport.internet.request.assembly.Config.roundtripper:type_name -> google.protobuf.Any
+	2, // [2:2] is the sub-list for method output_type
+	2, // [2:2] is the sub-list for method input_type
+	2, // [2:2] is the sub-list for extension type_name
+	2, // [2:2] is the sub-list for extension extendee
+	0, // [0:2] is the sub-list for field type_name
+}
+
+func init() { file_transport_internet_request_assembly_config_proto_init() }
+func file_transport_internet_request_assembly_config_proto_init() {
+	if File_transport_internet_request_assembly_config_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_transport_internet_request_assembly_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Config); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_transport_internet_request_assembly_config_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   1,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_transport_internet_request_assembly_config_proto_goTypes,
+		DependencyIndexes: file_transport_internet_request_assembly_config_proto_depIdxs,
+		MessageInfos:      file_transport_internet_request_assembly_config_proto_msgTypes,
+	}.Build()
+	File_transport_internet_request_assembly_config_proto = out.File
+	file_transport_internet_request_assembly_config_proto_rawDesc = nil
+	file_transport_internet_request_assembly_config_proto_goTypes = nil
+	file_transport_internet_request_assembly_config_proto_depIdxs = nil
+}
diff --git a/transport/internet/request/assembly/config.proto b/transport/internet/request/assembly/config.proto
new file mode 100644
index 00000000..4004b23c
--- /dev/null
+++ b/transport/internet/request/assembly/config.proto
@@ -0,0 +1,18 @@
+syntax = "proto3";
+
+package v2ray.core.transport.internet.request.assembly;
+option csharp_namespace = "V2Ray.Core.Transport.Internet.Request.Assembly";
+option go_package = "github.com/v2fly/v2ray-core/v5/transport/internet/request/assembly";
+option java_package = "com.v2ray.core.transport.internet.request.assembly";
+option java_multiple_files = true;
+
+import "common/protoext/extensions.proto";
+import "google/protobuf/any.proto";
+
+message Config {
+  option (v2ray.core.common.protoext.message_opt).type = "transport";
+  option (v2ray.core.common.protoext.message_opt).short_name = "request";
+
+  google.protobuf.Any assembler = 1;
+  google.protobuf.Any roundtripper = 2;
+}
\ No newline at end of file
diff --git a/transport/internet/request/assembly/dialer.go b/transport/internet/request/assembly/dialer.go
new file mode 100644
index 00000000..80d75bf1
--- /dev/null
+++ b/transport/internet/request/assembly/dialer.go
@@ -0,0 +1,125 @@
+package assembly
+
+import (
+	"context"
+	gonet "net"
+	"time"
+
+	"github.com/v2fly/v2ray-core/v5/transport/internet/transportcommon"
+
+	"github.com/v2fly/v2ray-core/v5/common"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/common/serial"
+	"github.com/v2fly/v2ray-core/v5/common/session"
+	"github.com/v2fly/v2ray-core/v5/transport/internet"
+	"github.com/v2fly/v2ray-core/v5/transport/internet/request"
+)
+
+type client struct {
+	tripper   request.RoundTripperClient
+	assembler request.SessionAssemblerClient
+
+	streamSettings *internet.MemoryStreamConfig
+	dest           net.Destination
+}
+
+func (c client) Dial(ctx context.Context) (net.Conn, error) {
+	return transportcommon.DialWithSecuritySettings(ctx, c.dest, c.streamSettings)
+}
+
+func (c client) AutoImplDialer() request.Dialer {
+	return c
+}
+
+func (c client) Tripper() request.Tripper {
+	return c.tripper
+}
+
+func (c client) dialRequestSession(ctx context.Context) (net.Conn, error) {
+	session, err := c.assembler.NewSession(ctx)
+	if err != nil {
+		return nil, newError("failed to create new session").Base(err)
+	}
+	return clientConnection{session}, nil
+}
+
+type clientConnection struct {
+	request.Session
+}
+
+func (c clientConnection) LocalAddr() gonet.Addr {
+	return &net.UnixAddr{Name: "unimplemented"}
+}
+
+func (c clientConnection) RemoteAddr() gonet.Addr {
+	return &net.UnixAddr{Name: "unimplemented"}
+}
+
+func (c clientConnection) SetDeadline(t time.Time) error {
+	// Unimplemented
+	return nil
+}
+
+func (c clientConnection) SetReadDeadline(t time.Time) error {
+	// Unimplemented
+	return nil
+}
+
+func (c clientConnection) SetWriteDeadline(t time.Time) error {
+	// Unimplemented
+	return nil
+}
+
+func dialRequest(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (net.Conn, error) {
+	clientAssembly := &client{}
+	transportConfiguration := streamSettings.ProtocolSettings.(*Config)
+
+	assemblerConfigInstance, err := serial.GetInstanceOf(transportConfiguration.Assembler)
+	if err != nil {
+		return nil, newError("failed to get config instance of assembler").Base(err)
+	}
+	assembler, err := common.CreateObject(ctx, assemblerConfigInstance)
+	if err != nil {
+		return nil, newError("failed to create assembler").Base(err)
+	}
+	if typedAssembler, ok := assembler.(request.SessionAssemblerClient); !ok {
+		return nil, newError("failed to type assert assembler to SessionAssemblerClient")
+	} else {
+		clientAssembly.assembler = typedAssembler
+	}
+
+	roundtripperConfigInstance, err := serial.GetInstanceOf(transportConfiguration.Roundtripper)
+	if err != nil {
+		return nil, newError("failed to get config instance of roundtripper").Base(err)
+	}
+	roundtripper, err := common.CreateObject(ctx, roundtripperConfigInstance)
+	if err != nil {
+		return nil, newError("failed to create roundtripper").Base(err)
+	}
+	if typedRoundtripper, ok := roundtripper.(request.RoundTripperClient); !ok {
+		return nil, newError("failed to type assert roundtripper to RoundTripperClient")
+	} else {
+		clientAssembly.tripper = typedRoundtripper
+	}
+
+	clientAssembly.streamSettings = streamSettings
+	clientAssembly.dest = dest
+
+	clientAssembly.assembler.OnTransportClientAssemblyReady(clientAssembly)
+	clientAssembly.tripper.OnTransportClientAssemblyReady(clientAssembly)
+	return clientAssembly.dialRequestSession(ctx)
+}
+
+func dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) {
+	newError("creating connection to ", dest).WriteToLog(session.ExportIDToError(ctx))
+
+	conn, err := dialRequest(ctx, dest, streamSettings)
+	if err != nil {
+		return nil, newError("failed to dial request to ", dest).Base(err)
+	}
+	return internet.Connection(conn), nil
+}
+
+func init() {
+	common.Must(internet.RegisterTransportDialer(protocolName, dial))
+}
diff --git a/transport/internet/request/assembly/errors.generated.go b/transport/internet/request/assembly/errors.generated.go
new file mode 100644
index 00000000..48ffbedc
--- /dev/null
+++ b/transport/internet/request/assembly/errors.generated.go
@@ -0,0 +1,9 @@
+package assembly
+
+import "github.com/v2fly/v2ray-core/v5/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}
diff --git a/transport/internet/request/assembly/hub.go b/transport/internet/request/assembly/hub.go
new file mode 100644
index 00000000..7a6fc404
--- /dev/null
+++ b/transport/internet/request/assembly/hub.go
@@ -0,0 +1,135 @@
+package assembly
+
+import (
+	"context"
+	gonet "net"
+	"time"
+
+	"github.com/v2fly/v2ray-core/v5/transport/internet/transportcommon"
+
+	"github.com/v2fly/v2ray-core/v5/common"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/common/serial"
+	"github.com/v2fly/v2ray-core/v5/transport/internet"
+	"github.com/v2fly/v2ray-core/v5/transport/internet/request"
+)
+
+type server struct {
+	tripper   request.RoundTripperServer
+	assembler request.SessionAssemblerServer
+	addConn   internet.ConnHandler
+
+	streamSettings *internet.MemoryStreamConfig
+	addr           net.Address
+	port           net.Port
+}
+
+func (s server) Listen(ctx context.Context) (net.Listener, error) {
+	return transportcommon.ListenWithSecuritySettings(ctx, s.addr, s.port, s.streamSettings)
+}
+
+func (s server) AutoImplListener() request.Listener {
+	return s
+}
+
+func (s server) Close() error {
+	if err := s.tripper.Close(); err != nil {
+		return newError("failed to close tripper").Base(err)
+	}
+	return nil
+}
+
+func (s server) Addr() net.Addr {
+	// Unimplemented
+	return nil
+}
+
+type serverConnection struct {
+	request.Session
+}
+
+func (s serverConnection) LocalAddr() gonet.Addr {
+	return &net.UnixAddr{Name: "unimplemented"}
+}
+
+func (s serverConnection) RemoteAddr() gonet.Addr {
+	return &net.UnixAddr{Name: "unimplemented"}
+}
+
+func (s serverConnection) SetDeadline(t time.Time) error {
+	// Unimplemented
+	return nil
+}
+
+func (s serverConnection) SetReadDeadline(t time.Time) error {
+	// Unimplemented
+	return nil
+}
+
+func (s serverConnection) SetWriteDeadline(t time.Time) error {
+	// Unimplemented
+	return nil
+}
+
+func (s server) OnNewSession(ctx context.Context, sess request.Session, opts ...request.SessionOption) error {
+	s.addConn(&serverConnection{sess})
+	return nil
+}
+
+func (s server) SessionReceiver() request.SessionReceiver {
+	return s
+}
+
+func (s server) TripperReceiver() request.TripperReceiver {
+	return s.assembler
+}
+
+func listenRequest(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, addConn internet.ConnHandler) (internet.Listener, error) {
+	transportConfiguration := streamSettings.ProtocolSettings.(*Config)
+	serverAssembly := &server{addConn: addConn}
+
+	assemblerConfigInstance, err := serial.GetInstanceOf(transportConfiguration.Assembler)
+	if err != nil {
+		return nil, newError("failed to get config instance of assembler").Base(err)
+	}
+	assembler, err := common.CreateObject(ctx, assemblerConfigInstance)
+	if err != nil {
+		return nil, newError("failed to create assembler").Base(err)
+	}
+	if typedAssembler, ok := assembler.(request.SessionAssemblerServer); !ok {
+		return nil, newError("failed to type assert assembler to SessionAssemblerServer")
+	} else {
+		serverAssembly.assembler = typedAssembler
+	}
+
+	roundtripperConfigInstance, err := serial.GetInstanceOf(transportConfiguration.Roundtripper)
+	if err != nil {
+		return nil, newError("failed to get config instance of roundtripper").Base(err)
+	}
+	roundtripper, err := common.CreateObject(ctx, roundtripperConfigInstance)
+	if err != nil {
+		return nil, newError("failed to create roundtripper").Base(err)
+	}
+	if typedRoundtripper, ok := roundtripper.(request.RoundTripperServer); !ok {
+		return nil, newError("failed to type assert roundtripper to RoundTripperServer")
+	} else {
+		serverAssembly.tripper = typedRoundtripper
+	}
+
+	serverAssembly.addr = address
+	serverAssembly.port = port
+	serverAssembly.streamSettings = streamSettings
+
+	serverAssembly.assembler.OnTransportServerAssemblyReady(serverAssembly)
+	serverAssembly.tripper.OnTransportServerAssemblyReady(serverAssembly)
+
+	if err := serverAssembly.tripper.Start(); err != nil {
+		return nil, newError("failed to start tripper").Base(err)
+	}
+
+	return serverAssembly, nil
+}
+
+func init() {
+	common.Must(internet.RegisterTransportListener(protocolName, listenRequest))
+}
diff --git a/transport/internet/request/options.go b/transport/internet/request/options.go
new file mode 100644
index 00000000..725b8fc2
--- /dev/null
+++ b/transport/internet/request/options.go
@@ -0,0 +1,1 @@
+package request
diff --git a/transport/internet/request/request.go b/transport/internet/request/request.go
new file mode 100644
index 00000000..b7841889
--- /dev/null
+++ b/transport/internet/request/request.go
@@ -0,0 +1,42 @@
+package request
+
+import (
+	"context"
+
+	"github.com/v2fly/v2ray-core/v5/common/net"
+)
+
+type TransportClientAssembly interface {
+	Tripper() Tripper
+	AutoImplDialer() Dialer
+}
+
+type TransportClientAssemblyReceiver interface {
+	OnTransportClientAssemblyReady(TransportClientAssembly)
+}
+
+type TransportServerAssembly interface {
+	TripperReceiver() TripperReceiver
+	SessionReceiver() SessionReceiver
+	AutoImplListener() Listener
+}
+
+type TransportServerAssemblyReceiver interface {
+	OnTransportServerAssemblyReady(TransportServerAssembly)
+}
+
+type SessionCreator interface {
+	NewSession(ctx context.Context, opts ...SessionOption) (Session, error)
+}
+
+type SessionReceiver interface {
+	OnNewSession(ctx context.Context, sess Session, opts ...SessionOption) error
+}
+
+type Dialer interface {
+	Dial(ctx context.Context) (net.Conn, error)
+}
+
+type Listener interface {
+	Listen(ctx context.Context) (net.Listener, error)
+}
diff --git a/transport/internet/request/roundtripper.go b/transport/internet/request/roundtripper.go
new file mode 100644
index 00000000..abb8f2f8
--- /dev/null
+++ b/transport/internet/request/roundtripper.go
@@ -0,0 +1,38 @@
+package request
+
+import (
+	"context"
+
+	"github.com/v2fly/v2ray-core/v5/common"
+)
+
+type RoundTripperClient interface {
+	Tripper
+	TransportClientAssemblyReceiver
+}
+
+type RoundTripperServer interface {
+	common.Runnable
+	TransportServerAssemblyReceiver
+}
+
+type Tripper interface {
+	RoundTrip(ctx context.Context, req Request, opts ...RoundTripperOption) (resp Response, err error)
+}
+
+type TripperReceiver interface {
+	OnRoundTrip(ctx context.Context, req Request, opts ...RoundTripperOption) (resp Response, err error)
+}
+
+type RoundTripperOption interface {
+	RoundTripperOption()
+}
+
+type Request struct {
+	Data          []byte
+	ConnectionTag []byte
+}
+
+type Response struct {
+	Data []byte
+}
diff --git a/transport/internet/request/roundtripper/httprt/config.pb.go b/transport/internet/request/roundtripper/httprt/config.pb.go
new file mode 100644
index 00000000..a93feb0a
--- /dev/null
+++ b/transport/internet/request/roundtripper/httprt/config.pb.go
@@ -0,0 +1,320 @@
+package httprt
+
+import (
+	_ "github.com/v2fly/v2ray-core/v5/common/protoext"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type ClientConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Http *HTTPConfig `protobuf:"bytes,1,opt,name=http,proto3" json:"http,omitempty"`
+}
+
+func (x *ClientConfig) Reset() {
+	*x = ClientConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_transport_internet_request_roundtripper_httprt_config_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ClientConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ClientConfig) ProtoMessage() {}
+
+func (x *ClientConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_transport_internet_request_roundtripper_httprt_config_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead.
+func (*ClientConfig) Descriptor() ([]byte, []int) {
+	return file_transport_internet_request_roundtripper_httprt_config_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *ClientConfig) GetHttp() *HTTPConfig {
+	if x != nil {
+		return x.Http
+	}
+	return nil
+}
+
+type ServerConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Http                 *HTTPConfig `protobuf:"bytes,1,opt,name=http,proto3" json:"http,omitempty"`
+	NoDecodingSessionTag bool        `protobuf:"varint,2,opt,name=no_decoding_session_tag,json=noDecodingSessionTag,proto3" json:"no_decoding_session_tag,omitempty"`
+}
+
+func (x *ServerConfig) Reset() {
+	*x = ServerConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_transport_internet_request_roundtripper_httprt_config_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ServerConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ServerConfig) ProtoMessage() {}
+
+func (x *ServerConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_transport_internet_request_roundtripper_httprt_config_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead.
+func (*ServerConfig) Descriptor() ([]byte, []int) {
+	return file_transport_internet_request_roundtripper_httprt_config_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *ServerConfig) GetHttp() *HTTPConfig {
+	if x != nil {
+		return x.Http
+	}
+	return nil
+}
+
+func (x *ServerConfig) GetNoDecodingSessionTag() bool {
+	if x != nil {
+		return x.NoDecodingSessionTag
+	}
+	return false
+}
+
+type HTTPConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Path      string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
+	UrlPrefix string `protobuf:"bytes,2,opt,name=urlPrefix,proto3" json:"urlPrefix,omitempty"`
+}
+
+func (x *HTTPConfig) Reset() {
+	*x = HTTPConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_transport_internet_request_roundtripper_httprt_config_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *HTTPConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HTTPConfig) ProtoMessage() {}
+
+func (x *HTTPConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_transport_internet_request_roundtripper_httprt_config_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use HTTPConfig.ProtoReflect.Descriptor instead.
+func (*HTTPConfig) Descriptor() ([]byte, []int) {
+	return file_transport_internet_request_roundtripper_httprt_config_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *HTTPConfig) GetPath() string {
+	if x != nil {
+		return x.Path
+	}
+	return ""
+}
+
+func (x *HTTPConfig) GetUrlPrefix() string {
+	if x != nil {
+		return x.UrlPrefix
+	}
+	return ""
+}
+
+var File_transport_internet_request_roundtripper_httprt_config_proto protoreflect.FileDescriptor
+
+var file_transport_internet_request_roundtripper_httprt_config_proto_rawDesc = []byte{
+	0x0a, 0x3b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65,
+	0x72, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2f, 0x72, 0x6f, 0x75,
+	0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x72, 0x74,
+	0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x39, 0x76,
+	0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70,
+	0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x2e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65,
+	0x72, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x72, 0x74, 0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
+	0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x65, 0x78, 0x74, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73,
+	0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9e, 0x01, 0x0a, 0x0c, 0x43,
+	0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x59, 0x0a, 0x04, 0x68,
+	0x74, 0x74, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x45, 0x2e, 0x76, 0x32, 0x72, 0x61,
+	0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
+	0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x2e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x2e, 0x68,
+	0x74, 0x74, 0x70, 0x72, 0x74, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x52, 0x04, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x33, 0x82, 0xb5, 0x18, 0x2f, 0x0a, 0x25, 0x74, 0x72,
+	0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e,
+	0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x2e, 0x63, 0x6c, 0x69,
+	0x65, 0x6e, 0x74, 0x12, 0x06, 0x68, 0x74, 0x74, 0x70, 0x72, 0x74, 0x22, 0xd5, 0x01, 0x0a, 0x0c,
+	0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x59, 0x0a, 0x04,
+	0x68, 0x74, 0x74, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x45, 0x2e, 0x76, 0x32, 0x72,
+	0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
+	0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x2e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x2e,
+	0x68, 0x74, 0x74, 0x70, 0x72, 0x74, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x52, 0x04, 0x68, 0x74, 0x74, 0x70, 0x12, 0x35, 0x0a, 0x17, 0x6e, 0x6f, 0x5f, 0x64, 0x65,
+	0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74,
+	0x61, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x6e, 0x6f, 0x44, 0x65, 0x63, 0x6f,
+	0x64, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x61, 0x67, 0x3a, 0x33,
+	0x82, 0xb5, 0x18, 0x2f, 0x0a, 0x25, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e,
+	0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69,
+	0x70, 0x70, 0x65, 0x72, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x06, 0x68, 0x74, 0x74,
+	0x70, 0x72, 0x74, 0x22, 0x3e, 0x0a, 0x0a, 0x48, 0x54, 0x54, 0x50, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x72, 0x6c, 0x50, 0x72, 0x65, 0x66,
+	0x69, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x72, 0x6c, 0x50, 0x72, 0x65,
+	0x66, 0x69, 0x78, 0x42, 0xcc, 0x01, 0x0a, 0x3d, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61,
+	0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
+	0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x2e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x2e, 0x68,
+	0x74, 0x74, 0x70, 0x72, 0x74, 0x50, 0x01, 0x5a, 0x4d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e,
+	0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d,
+	0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x35, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
+	0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x2f,
+	0x68, 0x74, 0x74, 0x70, 0x72, 0x74, 0xaa, 0x02, 0x39, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43,
+	0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e,
+	0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x52,
+	0x6f, 0x75, 0x6e, 0x64, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x72, 0x2e, 0x68, 0x74, 0x74, 0x70,
+	0x72, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_transport_internet_request_roundtripper_httprt_config_proto_rawDescOnce sync.Once
+	file_transport_internet_request_roundtripper_httprt_config_proto_rawDescData = file_transport_internet_request_roundtripper_httprt_config_proto_rawDesc
+)
+
+func file_transport_internet_request_roundtripper_httprt_config_proto_rawDescGZIP() []byte {
+	file_transport_internet_request_roundtripper_httprt_config_proto_rawDescOnce.Do(func() {
+		file_transport_internet_request_roundtripper_httprt_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_request_roundtripper_httprt_config_proto_rawDescData)
+	})
+	return file_transport_internet_request_roundtripper_httprt_config_proto_rawDescData
+}
+
+var file_transport_internet_request_roundtripper_httprt_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
+var file_transport_internet_request_roundtripper_httprt_config_proto_goTypes = []interface{}{
+	(*ClientConfig)(nil), // 0: v2ray.core.transport.internet.request.roundtripper.httprt.ClientConfig
+	(*ServerConfig)(nil), // 1: v2ray.core.transport.internet.request.roundtripper.httprt.ServerConfig
+	(*HTTPConfig)(nil),   // 2: v2ray.core.transport.internet.request.roundtripper.httprt.HTTPConfig
+}
+var file_transport_internet_request_roundtripper_httprt_config_proto_depIdxs = []int32{
+	2, // 0: v2ray.core.transport.internet.request.roundtripper.httprt.ClientConfig.http:type_name -> v2ray.core.transport.internet.request.roundtripper.httprt.HTTPConfig
+	2, // 1: v2ray.core.transport.internet.request.roundtripper.httprt.ServerConfig.http:type_name -> v2ray.core.transport.internet.request.roundtripper.httprt.HTTPConfig
+	2, // [2:2] is the sub-list for method output_type
+	2, // [2:2] is the sub-list for method input_type
+	2, // [2:2] is the sub-list for extension type_name
+	2, // [2:2] is the sub-list for extension extendee
+	0, // [0:2] is the sub-list for field type_name
+}
+
+func init() { file_transport_internet_request_roundtripper_httprt_config_proto_init() }
+func file_transport_internet_request_roundtripper_httprt_config_proto_init() {
+	if File_transport_internet_request_roundtripper_httprt_config_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_transport_internet_request_roundtripper_httprt_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ClientConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_transport_internet_request_roundtripper_httprt_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ServerConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_transport_internet_request_roundtripper_httprt_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*HTTPConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_transport_internet_request_roundtripper_httprt_config_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   3,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_transport_internet_request_roundtripper_httprt_config_proto_goTypes,
+		DependencyIndexes: file_transport_internet_request_roundtripper_httprt_config_proto_depIdxs,
+		MessageInfos:      file_transport_internet_request_roundtripper_httprt_config_proto_msgTypes,
+	}.Build()
+	File_transport_internet_request_roundtripper_httprt_config_proto = out.File
+	file_transport_internet_request_roundtripper_httprt_config_proto_rawDesc = nil
+	file_transport_internet_request_roundtripper_httprt_config_proto_goTypes = nil
+	file_transport_internet_request_roundtripper_httprt_config_proto_depIdxs = nil
+}
diff --git a/transport/internet/request/roundtripper/httprt/config.proto b/transport/internet/request/roundtripper/httprt/config.proto
new file mode 100644
index 00000000..ac2df25d
--- /dev/null
+++ b/transport/internet/request/roundtripper/httprt/config.proto
@@ -0,0 +1,28 @@
+syntax = "proto3";
+
+package v2ray.core.transport.internet.request.roundtripper.httprt;
+option csharp_namespace = "V2Ray.Core.Transport.Internet.Request.Roundtripper.httprt";
+option go_package = "github.com/v2fly/v2ray-core/v5/transport/internet/request/roundtripper/httprt";
+option java_package = "com.v2ray.core.transport.internet.request.roundtripper.httprt";
+option java_multiple_files = true;
+
+import "common/protoext/extensions.proto";
+
+message ClientConfig {
+  option (v2ray.core.common.protoext.message_opt).type = "transport.request.roundtripper.client";
+  option (v2ray.core.common.protoext.message_opt).short_name = "httprt";
+
+  HTTPConfig http = 1;
+}
+
+message ServerConfig {
+  option (v2ray.core.common.protoext.message_opt).type = "transport.request.roundtripper.server";
+  option (v2ray.core.common.protoext.message_opt).short_name = "httprt";
+  HTTPConfig http = 1;
+  bool no_decoding_session_tag = 2;
+}
+
+message HTTPConfig {
+  string path = 1;
+  string urlPrefix = 2;
+}
\ No newline at end of file
diff --git a/transport/internet/request/roundtripper/httprt/errors.generated.go b/transport/internet/request/roundtripper/httprt/errors.generated.go
new file mode 100644
index 00000000..69a8bfd7
--- /dev/null
+++ b/transport/internet/request/roundtripper/httprt/errors.generated.go
@@ -0,0 +1,9 @@
+package httprt
+
+import "github.com/v2fly/v2ray-core/v5/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}
diff --git a/transport/internet/request/roundtripper/httprt/httprt.go b/transport/internet/request/roundtripper/httprt/httprt.go
new file mode 100644
index 00000000..30f1ad66
--- /dev/null
+++ b/transport/internet/request/roundtripper/httprt/httprt.go
@@ -0,0 +1,149 @@
+package httprt
+
+//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
+
+import (
+	"bytes"
+	"context"
+	"encoding/base64"
+	"io"
+	gonet "net"
+	"net/http"
+
+	"github.com/v2fly/v2ray-core/v5/transport/internet/transportcommon"
+
+	"github.com/v2fly/v2ray-core/v5/common"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/transport/internet/request"
+)
+
+func newHTTPRoundTripperClient(ctx context.Context, config *ClientConfig) request.RoundTripperClient {
+	_ = ctx
+	return &httpTripperClient{config: config}
+}
+
+type httpTripperClient struct {
+	httpRTT  http.RoundTripper
+	config   *ClientConfig
+	assembly request.TransportClientAssembly
+}
+
+type unimplementedBackDrop struct{}
+
+func (u unimplementedBackDrop) RoundTrip(r *http.Request) (*http.Response, error) {
+	return nil, newError("unimplemented")
+}
+
+func (h *httpTripperClient) OnTransportClientAssemblyReady(assembly request.TransportClientAssembly) {
+	h.assembly = assembly
+}
+
+func (h *httpTripperClient) RoundTrip(ctx context.Context, req request.Request, opts ...request.RoundTripperOption) (resp request.Response, err error) {
+	if h.httpRTT == nil {
+		h.httpRTT = transportcommon.NewALPNAwareHTTPRoundTripper(ctx, func(ctx context.Context, addr string) (gonet.Conn, error) {
+			return h.assembly.AutoImplDialer().Dial(ctx)
+		}, unimplementedBackDrop{})
+	}
+
+	connectionTagStr := base64.RawURLEncoding.EncodeToString(req.ConnectionTag)
+
+	httpRequest, err := http.NewRequest("POST", h.config.Http.UrlPrefix+h.config.Http.Path, bytes.NewReader(req.Data))
+	if err != nil {
+		return
+	}
+
+	httpRequest.Header.Set("X-Session-ID", connectionTagStr)
+
+	httpResp, err := h.httpRTT.RoundTrip(httpRequest)
+	if err != nil {
+		return
+	}
+	defer httpResp.Body.Close()
+	result, err := io.ReadAll(httpResp.Body)
+	if err != nil {
+		return
+	}
+	return request.Response{Data: result}, err
+}
+
+func newHTTPRoundTripperServer(ctx context.Context, config *ServerConfig) request.RoundTripperServer {
+	return &httpTripperServer{ctx: ctx, config: config}
+}
+
+type httpTripperServer struct {
+	ctx      context.Context
+	listener net.Listener
+	assembly request.TransportServerAssembly
+
+	config *ServerConfig
+}
+
+func (h *httpTripperServer) OnTransportServerAssemblyReady(assembly request.TransportServerAssembly) {
+	h.assembly = assembly
+}
+
+func (h *httpTripperServer) ServeHTTP(writer http.ResponseWriter, r *http.Request) {
+	h.onRequest(writer, r)
+}
+
+func (h *httpTripperServer) onRequest(resp http.ResponseWriter, req *http.Request) {
+	tail := req.Header.Get("X-Session-ID")
+	data := []byte(tail)
+	if !h.config.NoDecodingSessionTag {
+		decodedData, err := base64.RawURLEncoding.DecodeString(tail)
+		if err != nil {
+			newError("unable to decode tag").Base(err).AtInfo().WriteToLog()
+			return
+		}
+		data = decodedData
+	}
+	body, err := io.ReadAll(req.Body)
+	req.Body.Close()
+	if err != nil {
+		newError("unable to read body").Base(err).AtInfo().WriteToLog()
+	}
+	recvResp, err := h.assembly.TripperReceiver().OnRoundTrip(h.ctx, request.Request{Data: body, ConnectionTag: data})
+	if err != nil {
+		newError("unable to process roundtrip").Base(err).AtInfo().WriteToLog()
+	}
+	_, err = io.Copy(resp, bytes.NewReader(recvResp.Data))
+	if err != nil {
+		newError("unable to send response").Base(err).AtInfo().WriteToLog()
+	}
+}
+
+func (h *httpTripperServer) Start() error {
+	listener, err := h.assembly.AutoImplListener().Listen(h.ctx)
+	if err != nil {
+		return newError("unable to create a listener for http tripper server").Base(err)
+	}
+	h.listener = listener
+	go func() {
+		err := http.Serve(listener, h)
+		if err != nil {
+			newError("unable to serve listener for http tripper server").Base(err).WriteToLog()
+		}
+	}()
+	return nil
+}
+
+func (h *httpTripperServer) Close() error {
+	return h.listener.Close()
+}
+
+func init() {
+	common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		clientConfig, ok := config.(*ClientConfig)
+		if !ok {
+			return nil, newError("not a ClientConfig")
+		}
+		return newHTTPRoundTripperClient(ctx, clientConfig), nil
+	}))
+	common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		serverConfig, ok := config.(*ServerConfig)
+		if !ok {
+			return nil, newError("not a ServerConfig")
+		}
+		return newHTTPRoundTripperServer(ctx, serverConfig), nil
+	}))
+}
diff --git a/transport/internet/request/stereotype/meek/config.pb.go b/transport/internet/request/stereotype/meek/config.pb.go
new file mode 100644
index 00000000..72a8a94f
--- /dev/null
+++ b/transport/internet/request/stereotype/meek/config.pb.go
@@ -0,0 +1,156 @@
+package meek
+
+import (
+	_ "github.com/v2fly/v2ray-core/v5/common/protoext"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Config struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
+}
+
+func (x *Config) Reset() {
+	*x = Config{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_transport_internet_request_stereotype_meek_config_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Config) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Config) ProtoMessage() {}
+
+func (x *Config) ProtoReflect() protoreflect.Message {
+	mi := &file_transport_internet_request_stereotype_meek_config_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Config.ProtoReflect.Descriptor instead.
+func (*Config) Descriptor() ([]byte, []int) {
+	return file_transport_internet_request_stereotype_meek_config_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Config) GetUrl() string {
+	if x != nil {
+		return x.Url
+	}
+	return ""
+}
+
+var File_transport_internet_request_stereotype_meek_config_proto protoreflect.FileDescriptor
+
+var file_transport_internet_request_stereotype_meek_config_proto_rawDesc = []byte{
+	0x0a, 0x37, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65,
+	0x72, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2f, 0x73, 0x74, 0x65,
+	0x72, 0x65, 0x6f, 0x74, 0x79, 0x70, 0x65, 0x2f, 0x6d, 0x65, 0x65, 0x6b, 0x2f, 0x63, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x35, 0x76, 0x32, 0x72, 0x61, 0x79,
+	0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e,
+	0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x2e, 0x73, 0x74, 0x65, 0x72, 0x65, 0x6f, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x6d, 0x65, 0x65, 0x6b,
+	0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x65, 0x78,
+	0x74, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x22, 0x31, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x10, 0x0a, 0x03,
+	0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x3a, 0x15,
+	0x82, 0xb5, 0x18, 0x11, 0x0a, 0x09, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x12,
+	0x04, 0x6d, 0x65, 0x65, 0x6b, 0x42, 0xc0, 0x01, 0x0a, 0x39, 0x63, 0x6f, 0x6d, 0x2e, 0x76, 0x32,
+	0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f,
+	0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x2e, 0x73, 0x74, 0x65, 0x72, 0x65, 0x6f, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x6d,
+	0x65, 0x65, 0x6b, 0x50, 0x01, 0x5a, 0x49, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
+	0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f,
+	0x72, 0x65, 0x2f, 0x76, 0x35, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f,
+	0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x2f, 0x73, 0x74, 0x65, 0x72, 0x65, 0x6f, 0x74, 0x79, 0x70, 0x65, 0x2f, 0x6d, 0x65, 0x65, 0x6b,
+	0xaa, 0x02, 0x35, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72,
+	0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
+	0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x53, 0x74, 0x65, 0x72, 0x65, 0x6f, 0x74,
+	0x79, 0x70, 0x65, 0x2e, 0x4d, 0x65, 0x65, 0x6b, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_transport_internet_request_stereotype_meek_config_proto_rawDescOnce sync.Once
+	file_transport_internet_request_stereotype_meek_config_proto_rawDescData = file_transport_internet_request_stereotype_meek_config_proto_rawDesc
+)
+
+func file_transport_internet_request_stereotype_meek_config_proto_rawDescGZIP() []byte {
+	file_transport_internet_request_stereotype_meek_config_proto_rawDescOnce.Do(func() {
+		file_transport_internet_request_stereotype_meek_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_request_stereotype_meek_config_proto_rawDescData)
+	})
+	return file_transport_internet_request_stereotype_meek_config_proto_rawDescData
+}
+
+var file_transport_internet_request_stereotype_meek_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_transport_internet_request_stereotype_meek_config_proto_goTypes = []interface{}{
+	(*Config)(nil), // 0: v2ray.core.transport.internet.request.stereotype.meek.Config
+}
+var file_transport_internet_request_stereotype_meek_config_proto_depIdxs = []int32{
+	0, // [0:0] is the sub-list for method output_type
+	0, // [0:0] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_transport_internet_request_stereotype_meek_config_proto_init() }
+func file_transport_internet_request_stereotype_meek_config_proto_init() {
+	if File_transport_internet_request_stereotype_meek_config_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_transport_internet_request_stereotype_meek_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Config); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_transport_internet_request_stereotype_meek_config_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   1,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_transport_internet_request_stereotype_meek_config_proto_goTypes,
+		DependencyIndexes: file_transport_internet_request_stereotype_meek_config_proto_depIdxs,
+		MessageInfos:      file_transport_internet_request_stereotype_meek_config_proto_msgTypes,
+	}.Build()
+	File_transport_internet_request_stereotype_meek_config_proto = out.File
+	file_transport_internet_request_stereotype_meek_config_proto_rawDesc = nil
+	file_transport_internet_request_stereotype_meek_config_proto_goTypes = nil
+	file_transport_internet_request_stereotype_meek_config_proto_depIdxs = nil
+}
diff --git a/transport/internet/request/stereotype/meek/config.proto b/transport/internet/request/stereotype/meek/config.proto
new file mode 100644
index 00000000..482bc286
--- /dev/null
+++ b/transport/internet/request/stereotype/meek/config.proto
@@ -0,0 +1,16 @@
+syntax = "proto3";
+
+package v2ray.core.transport.internet.request.stereotype.meek;
+option csharp_namespace = "V2Ray.Core.Transport.Internet.Request.Stereotype.Meek";
+option go_package = "github.com/v2fly/v2ray-core/v5/transport/internet/request/stereotype/meek";
+option java_package = "com.v2ray.core.transport.internet.request.stereotype.meek";
+option java_multiple_files = true;
+
+import "common/protoext/extensions.proto";
+
+message Config{
+  option (v2ray.core.common.protoext.message_opt).type = "transport";
+  option (v2ray.core.common.protoext.message_opt).short_name = "meek";
+
+  string url = 1;
+}
\ No newline at end of file
diff --git a/transport/internet/request/stereotype/meek/errors.generated.go b/transport/internet/request/stereotype/meek/errors.generated.go
new file mode 100644
index 00000000..68ac2e47
--- /dev/null
+++ b/transport/internet/request/stereotype/meek/errors.generated.go
@@ -0,0 +1,9 @@
+package meek
+
+import "github.com/v2fly/v2ray-core/v5/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}
diff --git a/transport/internet/request/stereotype/meek/meek.go b/transport/internet/request/stereotype/meek/meek.go
new file mode 100644
index 00000000..cf593fd4
--- /dev/null
+++ b/transport/internet/request/stereotype/meek/meek.go
@@ -0,0 +1,74 @@
+package meek
+
+import (
+	"context"
+
+	"github.com/v2fly/v2ray-core/v5/common"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/common/serial"
+	"github.com/v2fly/v2ray-core/v5/transport/internet"
+	"github.com/v2fly/v2ray-core/v5/transport/internet/request/assembler/simple"
+	"github.com/v2fly/v2ray-core/v5/transport/internet/request/assembly"
+	"github.com/v2fly/v2ray-core/v5/transport/internet/request/roundtripper/httprt"
+)
+
+//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
+
+const protocolName = "meek"
+
+func init() {
+	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		return nil, newError("meek is a transport")
+	}))
+
+	common.Must(internet.RegisterTransportDialer(protocolName, meekDial))
+	common.Must(internet.RegisterTransportListener(protocolName, meekListen))
+}
+
+func meekDial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) {
+	meekSetting := streamSettings.ProtocolSettings.(*Config)
+	simpleAssembler := &simple.ClientConfig{
+		MaxWriteSize:             65536,
+		WaitSubsequentWriteMs:    10,
+		InitialPollingIntervalMs: 100,
+		MaxPollingIntervalMs:     1000,
+		MinPollingIntervalMs:     10,
+		BackoffFactor:            1.5,
+		FailedRetryIntervalMs:    1000,
+	}
+	httprtSetting := &httprt.ClientConfig{
+		Http: &httprt.HTTPConfig{
+			UrlPrefix: meekSetting.Url,
+		},
+	}
+	request := &assembly.Config{
+		Assembler:    serial.ToTypedMessage(simpleAssembler),
+		Roundtripper: serial.ToTypedMessage(httprtSetting),
+	}
+	constructedSetting := &internet.MemoryStreamConfig{
+		ProtocolName:     "request",
+		ProtocolSettings: request,
+		SecurityType:     streamSettings.SecurityType,
+		SecuritySettings: streamSettings.SecuritySettings,
+		SocketSettings:   streamSettings.SocketSettings,
+	}
+	return internet.Dial(ctx, dest, constructedSetting)
+}
+
+func meekListen(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, callback internet.ConnHandler) (internet.Listener, error) {
+	meekSetting := streamSettings.ProtocolSettings.(*Config)
+	simpleAssembler := &simple.ServerConfig{MaxWriteSize: 65536}
+	httprtSetting := &httprt.ServerConfig{NoDecodingSessionTag: true, Http: &httprt.HTTPConfig{UrlPrefix: meekSetting.Url}}
+	request := &assembly.Config{
+		Assembler:    serial.ToTypedMessage(simpleAssembler),
+		Roundtripper: serial.ToTypedMessage(httprtSetting),
+	}
+	constructedSetting := &internet.MemoryStreamConfig{
+		ProtocolName:     "request",
+		ProtocolSettings: request,
+		SecurityType:     streamSettings.SecurityType,
+		SecuritySettings: streamSettings.SecuritySettings,
+		SocketSettings:   streamSettings.SocketSettings,
+	}
+	return internet.ListenTCP(ctx, address, port, constructedSetting, callback)
+}
diff --git a/transport/internet/security/connprop.go b/transport/internet/security/connprop.go
new file mode 100644
index 00000000..13c962c9
--- /dev/null
+++ b/transport/internet/security/connprop.go
@@ -0,0 +1,5 @@
+package security
+
+type ConnectionApplicationProtocol interface {
+	GetConnectionApplicationProtocol() (string, error)
+}
diff --git a/transport/internet/tls/tls.go b/transport/internet/tls/tls.go
index 671cabbf..38931531 100644
--- a/transport/internet/tls/tls.go
+++ b/transport/internet/tls/tls.go
@@ -17,6 +17,13 @@ type Conn struct {
 	*tls.Conn
 }
 
+func (c *Conn) GetConnectionApplicationProtocol() (string, error) {
+	if err := c.Handshake(); err != nil {
+		return "", err
+	}
+	return c.ConnectionState().NegotiatedProtocol, nil
+}
+
 func (c *Conn) WriteMultiBuffer(mb buf.MultiBuffer) error {
 	mb = buf.Compact(mb)
 	mb, err := buf.WriteMultiBuffer(c, mb)
diff --git a/transport/internet/tls/utls/utls.go b/transport/internet/tls/utls/utls.go
index 29349fbd..be4b42e0 100644
--- a/transport/internet/tls/utls/utls.go
+++ b/transport/internet/tls/utls/utls.go
@@ -90,7 +90,18 @@ func (e Engine) Client(conn net.Conn, opts ...security.Option) (security.Conn, e
 	if err != nil {
 		return nil, newError("unable to finish utls handshake").Base(err)
 	}
-	return utlsClientConn, nil
+	return uTLSClientConnection{utlsClientConn}, nil
+}
+
+type uTLSClientConnection struct {
+	*utls.UConn
+}
+
+func (u uTLSClientConnection) GetConnectionApplicationProtocol() (string, error) {
+	if err := u.Handshake(); err != nil {
+		return "", err
+	}
+	return u.ConnectionState().NegotiatedProtocol, nil
 }
 
 func uTLSConfigFromTLSConfig(config *systls.Config) (*utls.Config, error) { // nolint: unparam
diff --git a/transport/internet/transportcommon/dialer.go b/transport/internet/transportcommon/dialer.go
new file mode 100644
index 00000000..412c0c55
--- /dev/null
+++ b/transport/internet/transportcommon/dialer.go
@@ -0,0 +1,34 @@
+package transportcommon
+
+import (
+	"context"
+
+	"github.com/v2fly/v2ray-core/v5/transport/internet/security"
+
+	"github.com/v2fly/v2ray-core/v5/common/environment"
+	"github.com/v2fly/v2ray-core/v5/common/environment/envctx"
+
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/transport/internet"
+)
+
+func DialWithSecuritySettings(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) {
+	transportEnvironment := envctx.EnvironmentFromContext(ctx).(environment.TransportEnvironment)
+	dialer := transportEnvironment.Dialer()
+	conn, err := dialer.Dial(ctx, nil, dest, streamSettings.SocketSettings)
+	if err != nil {
+		return nil, newError("failed to dial to ", dest).Base(err)
+	}
+	securityEngine, err := security.CreateSecurityEngineFromSettings(ctx, streamSettings)
+	if err != nil {
+		return nil, newError("unable to create security engine").Base(err)
+	}
+
+	if securityEngine != nil {
+		conn, err = securityEngine.Client(conn, security.OptionWithDestination{Dest: dest})
+		if err != nil {
+			return nil, newError("unable to create security protocol client from security engine").Base(err)
+		}
+	}
+	return internet.Connection(conn), nil
+}
diff --git a/transport/internet/transportcommon/errors.generated.go b/transport/internet/transportcommon/errors.generated.go
new file mode 100644
index 00000000..b48153a2
--- /dev/null
+++ b/transport/internet/transportcommon/errors.generated.go
@@ -0,0 +1,9 @@
+package transportcommon
+
+import "github.com/v2fly/v2ray-core/v5/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}
diff --git a/transport/internet/transportcommon/httpDialer.go b/transport/internet/transportcommon/httpDialer.go
new file mode 100644
index 00000000..aafebc93
--- /dev/null
+++ b/transport/internet/transportcommon/httpDialer.go
@@ -0,0 +1,222 @@
+package transportcommon
+
+import (
+	"context"
+	"crypto/tls"
+	"errors"
+	"fmt"
+	"net"
+	"net/http"
+	"sync"
+	"time"
+
+	"golang.org/x/net/http2"
+
+	"github.com/v2fly/v2ray-core/v5/transport/internet/security"
+)
+
+type DialerFunc func(ctx context.Context, addr string) (net.Conn, error)
+
+// NewALPNAwareHTTPRoundTripper creates an instance of RoundTripper that dial to remote HTTPS endpoint with
+// an alternative version of TLS implementation.
+func NewALPNAwareHTTPRoundTripper(ctx context.Context, dialer DialerFunc,
+	backdropTransport http.RoundTripper,
+) http.RoundTripper {
+	rtImpl := &alpnAwareHTTPRoundTripperImpl{
+		connectWithH1:     map[string]bool{},
+		backdropTransport: backdropTransport,
+		pendingConn:       map[pendingConnKey]*unclaimedConnection{},
+		dialer:            dialer,
+		ctx:               ctx,
+	}
+	rtImpl.init()
+	return rtImpl
+}
+
+type alpnAwareHTTPRoundTripperImpl struct {
+	accessConnectWithH1 sync.Mutex
+	connectWithH1       map[string]bool
+
+	httpsH1Transport  http.RoundTripper
+	httpsH2Transport  http.RoundTripper
+	backdropTransport http.RoundTripper
+
+	accessDialingConnection sync.Mutex
+	pendingConn             map[pendingConnKey]*unclaimedConnection
+
+	ctx    context.Context
+	dialer DialerFunc
+}
+
+type pendingConnKey struct {
+	isH2 bool
+	dest string
+}
+
+var (
+	errEAGAIN        = errors.New("incorrect ALPN negotiated, try again with another ALPN")
+	errEAGAINTooMany = errors.New("incorrect ALPN negotiated")
+	errExpired       = errors.New("connection have expired")
+)
+
+func (r *alpnAwareHTTPRoundTripperImpl) RoundTrip(req *http.Request) (*http.Response, error) {
+	if req.URL.Scheme != "https" {
+		return r.backdropTransport.RoundTrip(req)
+	}
+	for retryCount := 0; retryCount < 5; retryCount++ {
+		effectivePort := req.URL.Port()
+		if effectivePort == "" {
+			effectivePort = "443"
+		}
+		if r.getShouldConnectWithH1(fmt.Sprintf("%v:%v", req.URL.Hostname(), effectivePort)) {
+			resp, err := r.httpsH1Transport.RoundTrip(req)
+			if errors.Is(err, errEAGAIN) {
+				continue
+			}
+			return resp, err
+		}
+		resp, err := r.httpsH2Transport.RoundTrip(req)
+		if errors.Is(err, errEAGAIN) {
+			continue
+		}
+		return resp, err
+	}
+	return nil, errEAGAINTooMany
+}
+
+func (r *alpnAwareHTTPRoundTripperImpl) getShouldConnectWithH1(domainName string) bool {
+	r.accessConnectWithH1.Lock()
+	defer r.accessConnectWithH1.Unlock()
+	if value, set := r.connectWithH1[domainName]; set {
+		return value
+	}
+	return false
+}
+
+func (r *alpnAwareHTTPRoundTripperImpl) setShouldConnectWithH1(domainName string) {
+	r.accessConnectWithH1.Lock()
+	defer r.accessConnectWithH1.Unlock()
+	r.connectWithH1[domainName] = true
+}
+
+func (r *alpnAwareHTTPRoundTripperImpl) clearShouldConnectWithH1(domainName string) {
+	r.accessConnectWithH1.Lock()
+	defer r.accessConnectWithH1.Unlock()
+	r.connectWithH1[domainName] = false
+}
+
+func getPendingConnectionID(dest string, alpnIsH2 bool) pendingConnKey {
+	return pendingConnKey{isH2: alpnIsH2, dest: dest}
+}
+
+func (r *alpnAwareHTTPRoundTripperImpl) putConn(addr string, alpnIsH2 bool, conn net.Conn) {
+	connID := getPendingConnectionID(addr, alpnIsH2)
+	r.pendingConn[connID] = NewUnclaimedConnection(conn, time.Minute)
+}
+
+func (r *alpnAwareHTTPRoundTripperImpl) getConn(addr string, alpnIsH2 bool) net.Conn {
+	connID := getPendingConnectionID(addr, alpnIsH2)
+	if conn, ok := r.pendingConn[connID]; ok {
+		delete(r.pendingConn, connID)
+		if claimedConnection, err := conn.claimConnection(); err == nil {
+			return claimedConnection
+		}
+	}
+	return nil
+}
+
+func (r *alpnAwareHTTPRoundTripperImpl) dialOrGetTLSWithExpectedALPN(ctx context.Context, addr string, expectedH2 bool) (net.Conn, error) {
+	r.accessDialingConnection.Lock()
+	defer r.accessDialingConnection.Unlock()
+
+	if r.getShouldConnectWithH1(addr) == expectedH2 {
+		return nil, errEAGAIN
+	}
+
+	// Get a cached connection if possible to reduce preflight connection closed without sending data
+	if gconn := r.getConn(addr, expectedH2); gconn != nil {
+		return gconn, nil
+	}
+
+	conn, err := r.dialTLS(ctx, addr)
+	if err != nil {
+		return nil, err
+	}
+
+	protocol := ""
+	if connAPLNGetter, ok := conn.(security.ConnectionApplicationProtocol); ok {
+		connectionALPN, err := connAPLNGetter.GetConnectionApplicationProtocol()
+		if err != nil {
+			return nil, newError("failed to get connection ALPN").Base(err).AtWarning()
+		}
+		protocol = connectionALPN
+	}
+
+	protocolIsH2 := protocol == http2.NextProtoTLS
+
+	if protocolIsH2 == expectedH2 {
+		return conn, err
+	}
+
+	r.putConn(addr, protocolIsH2, conn)
+
+	if protocolIsH2 {
+		r.clearShouldConnectWithH1(addr)
+	} else {
+		r.setShouldConnectWithH1(addr)
+	}
+
+	return nil, errEAGAIN
+}
+
+func (r *alpnAwareHTTPRoundTripperImpl) dialTLS(ctx context.Context, addr string) (net.Conn, error) {
+	_ = ctx
+	return r.dialer(r.ctx, addr)
+}
+
+func (r *alpnAwareHTTPRoundTripperImpl) init() {
+	r.httpsH2Transport = &http2.Transport{
+		DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
+			return r.dialOrGetTLSWithExpectedALPN(context.Background(), addr, true)
+		},
+	}
+	r.httpsH1Transport = &http.Transport{
+		DialTLSContext: func(ctx context.Context, network string, addr string) (net.Conn, error) {
+			return r.dialOrGetTLSWithExpectedALPN(ctx, addr, false)
+		},
+	}
+}
+
+func NewUnclaimedConnection(conn net.Conn, expireTime time.Duration) *unclaimedConnection {
+	c := &unclaimedConnection{
+		Conn: conn,
+	}
+	time.AfterFunc(expireTime, c.tick)
+	return c
+}
+
+type unclaimedConnection struct {
+	net.Conn
+	claimed bool
+	access  sync.Mutex
+}
+
+func (c *unclaimedConnection) claimConnection() (net.Conn, error) {
+	c.access.Lock()
+	defer c.access.Unlock()
+	if !c.claimed {
+		c.claimed = true
+		return c.Conn, nil
+	}
+	return nil, errExpired
+}
+
+func (c *unclaimedConnection) tick() {
+	c.access.Lock()
+	defer c.access.Unlock()
+	if !c.claimed {
+		c.claimed = true
+		c.Conn.Close()
+		c.Conn = nil
+	}
+}
diff --git a/transport/internet/transportcommon/listener.go b/transport/internet/transportcommon/listener.go
new file mode 100644
index 00000000..0aae4251
--- /dev/null
+++ b/transport/internet/transportcommon/listener.go
@@ -0,0 +1,63 @@
+package transportcommon
+
+import (
+	"context"
+
+	"github.com/v2fly/v2ray-core/v5/common/environment"
+	"github.com/v2fly/v2ray-core/v5/common/environment/envctx"
+	"github.com/v2fly/v2ray-core/v5/common/net"
+	"github.com/v2fly/v2ray-core/v5/common/session"
+	"github.com/v2fly/v2ray-core/v5/transport/internet"
+)
+
+type combinedListener struct {
+	net.Listener
+	locker *internet.FileLocker
+}
+
+func (l *combinedListener) Close() error {
+	if l.locker != nil {
+		l.locker.Release()
+	}
+	return l.Listener.Close()
+}
+
+func ListenWithSecuritySettings(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig) (
+	net.Listener, error,
+) {
+	var l combinedListener
+
+	transportEnvironment := envctx.EnvironmentFromContext(ctx).(environment.TransportEnvironment)
+	transportListener := transportEnvironment.Listener()
+
+	if port == net.Port(0) { // unix
+		listener, err := transportListener.Listen(ctx, &net.UnixAddr{
+			Name: address.Domain(),
+			Net:  "unix",
+		}, streamSettings.SocketSettings)
+		if err != nil {
+			return nil, newError("failed to listen unix domain socket on ", address).Base(err)
+		}
+		newError("listening unix domain socket on ", address).WriteToLog(session.ExportIDToError(ctx))
+		locker := ctx.Value(address.Domain())
+		if locker != nil {
+			l.locker = locker.(*internet.FileLocker)
+		}
+		l.Listener = listener
+	} else { // tcp
+		listener, err := transportListener.Listen(ctx, &net.TCPAddr{
+			IP:   address.IP(),
+			Port: int(port),
+		}, streamSettings.SocketSettings)
+		if err != nil {
+			return nil, newError("failed to listen TCP on ", address, ":", port).Base(err)
+		}
+		newError("listening TCP on ", address, ":", port).WriteToLog(session.ExportIDToError(ctx))
+		l.Listener = listener
+	}
+
+	if streamSettings.SocketSettings != nil && streamSettings.SocketSettings.AcceptProxyProtocol {
+		newError("accepting PROXY protocol").AtWarning().WriteToLog(session.ExportIDToError(ctx))
+	}
+	return &l, nil
+}
diff --git a/transport/internet/transportcommon/tansportcommon.go b/transport/internet/transportcommon/tansportcommon.go
new file mode 100644
index 00000000..13d0e143
--- /dev/null
+++ b/transport/internet/transportcommon/tansportcommon.go
@@ -0,0 +1,3 @@
+package transportcommon
+
+//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
diff --git a/v2ray.go b/v2ray.go
index 4ca389f0..2d0397d9 100644
--- a/v2ray.go
+++ b/v2ray.go
@@ -3,10 +3,11 @@ package core
 import (
 	"context"
 	"reflect"
-	"sync"
+	sync "sync"
 
 	"github.com/v2fly/v2ray-core/v5/common"
 	"github.com/v2fly/v2ray-core/v5/common/environment"
+	"github.com/v2fly/v2ray-core/v5/common/environment/systemnetworkimpl"
 	"github.com/v2fly/v2ray-core/v5/common/environment/transientstorageimpl"
 	"github.com/v2fly/v2ray-core/v5/common/serial"
 	"github.com/v2fly/v2ray-core/v5/features"
@@ -191,7 +192,8 @@ func initInstanceWithConfig(config *Config, server *Instance) (bool, error) {
 		return true, err
 	}
 
-	server.env = environment.NewRootEnvImpl(server.ctx, transientstorageimpl.NewScopedTransientStorageImpl())
+	defaultNetworkImpl := systemnetworkimpl.NewSystemNetworkDefault()
+	server.env = environment.NewRootEnvImpl(server.ctx, transientstorageimpl.NewScopedTransientStorageImpl(), defaultNetworkImpl.Dialer(), defaultNetworkImpl.Listener())
 
 	for _, appSettings := range config.App {
 		settings, err := serial.GetInstanceOf(appSettings)


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 983a9b17..827263a9 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -28,7 +28,7 @@ jobs:
         os: [windows-latest, ubuntu-latest, macos-latest]
     steps:
       - name: Set up Go 1.x
-        uses: actions/setup-go@v3
+        uses: actions/setup-go@v4
         with:
           go-version: ^1.19
 
diff --git a/app/proxyman/outbound/handler_test.go b/app/proxyman/outbound/handler_test.go
index a609c587..af5c6ab6 100644
--- a/app/proxyman/outbound/handler_test.go
+++ b/app/proxyman/outbound/handler_test.go
@@ -5,6 +5,12 @@ import (
 	"testing"
 	_ "unsafe"
 
+	"github.com/v2fly/v2ray-core/v5/common/environment/systemnetworkimpl"
+
+	"github.com/v2fly/v2ray-core/v5/common/environment"
+	"github.com/v2fly/v2ray-core/v5/common/environment/envctx"
+	"github.com/v2fly/v2ray-core/v5/common/environment/transientstorageimpl"
+
 	"google.golang.org/protobuf/types/known/anypb"
 
 	core "github.com/v2fly/v2ray-core/v5"
@@ -43,6 +49,10 @@ func TestOutboundWithoutStatCounter(t *testing.T) {
 	v, _ := core.New(config)
 	v.AddFeature((outbound.Manager)(new(Manager)))
 	ctx := toContext(context.Background(), v)
+	defaultNetworkImpl := systemnetworkimpl.NewSystemNetworkDefault()
+	rootEnv := environment.NewRootEnvImpl(ctx, transientstorageimpl.NewScopedTransientStorageImpl(), defaultNetworkImpl.Dialer(), defaultNetworkImpl.Listener())
+	proxyEnvironment := rootEnv.ProxyEnvironment("o")
+	ctx = envctx.ContextWithEnvironment(ctx, proxyEnvironment)
 	h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
 		Tag:           "tag",
 		ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
@@ -72,6 +82,10 @@ func TestOutboundWithStatCounter(t *testing.T) {
 	v, _ := core.New(config)
 	v.AddFeature((outbound.Manager)(new(Manager)))
 	ctx := toContext(context.Background(), v)
+	defaultNetworkImpl := systemnetworkimpl.NewSystemNetworkDefault()
+	rootEnv := environment.NewRootEnvImpl(ctx, transientstorageimpl.NewScopedTransientStorageImpl(), defaultNetworkImpl.Dialer(), defaultNetworkImpl.Listener())
+	proxyEnvironment := rootEnv.ProxyEnvironment("o")
+	ctx = envctx.ContextWithEnvironment(ctx, proxyEnvironment)
 	h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
 		Tag:           "tag",
 		ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
diff --git a/common/buf/multi_buffer_test.go b/common/buf/multi_buffer_test.go
index 6d835c82..c4c85c38 100644
--- a/common/buf/multi_buffer_test.go
+++ b/common/buf/multi_buffer_test.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"crypto/rand"
 	"io"
-	"io/ioutil"
 	"os"
 	"testing"
 
@@ -120,7 +119,7 @@ func TestMultiBufferReadAllToByte(t *testing.T) {
 		common.Must(err)
 		f.Close()
 
-		cnt, err := ioutil.ReadFile(dat)
+		cnt, err := os.ReadFile(dat)
 		common.Must(err)
 
 		if d := cmp.Diff(buf2, cnt); d != "" {
diff --git a/common/log/logger_test.go b/common/log/logger_test.go
index 3e430226..aa130ba9 100644
--- a/common/log/logger_test.go
+++ b/common/log/logger_test.go
@@ -1,7 +1,6 @@
 package log_test
 
 import (
-	"io/ioutil"
 	"os"
 	"strings"
 	"testing"
@@ -13,7 +12,7 @@ import (
 )
 
 func TestFileLogger(t *testing.T) {
-	f, err := ioutil.TempFile("", "vtest")
+	f, err := os.CreateTemp("", "vtest")
 	common.Must(err)
 	path := f.Name()
 	common.Must(f.Close())
diff --git a/proxy/trojan/protocol_test.go b/proxy/trojan/protocol_test.go
index 00bdc13e..5d6a18c9 100644
--- a/proxy/trojan/protocol_test.go
+++ b/proxy/trojan/protocol_test.go
@@ -1,6 +1,7 @@
 package trojan_test
 
 import (
+	"crypto/rand"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
@@ -90,3 +91,48 @@ func TestUDPRequest(t *testing.T) {
 		t.Error("data: ", r)
 	}
 }
+
+func TestLargeUDPRequest(t *testing.T) {
+	user := &protocol.MemoryUser{
+		Email: "love@v2fly.org",
+		Account: toAccount(&Account{
+			Password: "password",
+		}),
+	}
+
+	payload := make([]byte, 4096)
+	common.Must2(rand.Read(payload))
+	data := buf.NewWithSize(int32(len(payload)))
+	common.Must2(data.Write(payload))
+
+	buffer := buf.NewWithSize(2*data.Len() + 1)
+	defer buffer.Release()
+
+	destination := net.Destination{Network: net.Network_UDP, Address: net.LocalHostIP, Port: 1234}
+	writer := &PacketWriter{Writer: &ConnWriter{Writer: buffer, Target: destination, Account: user.Account.(*MemoryAccount)}, Target: destination}
+	common.Must(writer.WriteMultiBuffer(buf.MultiBuffer{data, data}))
+
+	connReader := &ConnReader{Reader: buffer}
+	common.Must(connReader.ParseHeader())
+
+	packetReader := &PacketReader{Reader: connReader}
+	for i := 0; i < 2; i++ {
+		p, err := packetReader.ReadMultiBufferWithMetadata()
+		common.Must(err)
+
+		if p.Buffer.IsEmpty() {
+			t.Error("no request data")
+		}
+
+		if r := cmp.Diff(p.Target, destination); r != "" {
+			t.Error("destination: ", r)
+		}
+
+		mb, decoded := buf.SplitFirst(p.Buffer)
+		buf.ReleaseMulti(mb)
+
+		if r := cmp.Diff(decoded.Bytes(), payload); r != "" {
+			t.Error("data: ", r)
+		}
+	}
+}