mirror of
https://github.com/bolucat/Archive.git
synced 2026-04-22 16:07:49 +08:00
Update On Wed Apr 8 21:30:19 CEST 2026
This commit is contained in:
@@ -1323,3 +1323,4 @@ Update On Sat Apr 4 20:56:37 CEST 2026
|
||||
Update On Sun Apr 5 20:57:38 CEST 2026
|
||||
Update On Mon Apr 6 21:13:19 CEST 2026
|
||||
Update On Tue Apr 7 21:18:54 CEST 2026
|
||||
Update On Wed Apr 8 21:30:11 CEST 2026
|
||||
|
||||
@@ -58,8 +58,8 @@ subprojects {
|
||||
minSdk = 21
|
||||
targetSdk = 35
|
||||
|
||||
versionName = "2.11.25"
|
||||
versionCode = 211025
|
||||
versionName = "2.11.26"
|
||||
versionCode = 211026
|
||||
|
||||
resValue("string", "release_name", "v$versionName")
|
||||
resValue("integer", "release_code", "$versionCode")
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
From b8f897a9da7a82ad8584a22284ceac61262fcb7e Mon Sep 17 00:00:00 2001
|
||||
From: Jorropo <jorropo.pgm@gmail.com>
|
||||
Date: Sun, 22 Feb 2026 01:47:45 +0100
|
||||
Subject: [PATCH] runtime: fix value of ENOSYS on mips from 38 to 89
|
||||
|
||||
Fixes #77731
|
||||
|
||||
Change-Id: Iaca444e2d5f9e19fd2de38414b357b41471a668c
|
||||
---
|
||||
|
||||
diff --git a/src/runtime/defs_linux_mips64x.go b/src/runtime/defs_linux_mips64x.go
|
||||
index 7449d2c..4d0f103 100644
|
||||
--- a/src/runtime/defs_linux_mips64x.go
|
||||
+++ b/src/runtime/defs_linux_mips64x.go
|
||||
@@ -12,7 +12,7 @@
|
||||
_EINTR = 0x4
|
||||
_EAGAIN = 0xb
|
||||
_ENOMEM = 0xc
|
||||
- _ENOSYS = 0x26
|
||||
+ _ENOSYS = 0x59
|
||||
|
||||
_PROT_NONE = 0x0
|
||||
_PROT_READ = 0x1
|
||||
diff --git a/src/runtime/defs_linux_mipsx.go b/src/runtime/defs_linux_mipsx.go
|
||||
index 5a446e0..b8da4d0 100644
|
||||
--- a/src/runtime/defs_linux_mipsx.go
|
||||
+++ b/src/runtime/defs_linux_mipsx.go
|
||||
@@ -12,7 +12,7 @@
|
||||
_EINTR = 0x4
|
||||
_EAGAIN = 0xb
|
||||
_ENOMEM = 0xc
|
||||
- _ENOSYS = 0x26
|
||||
+ _ENOSYS = 0x59
|
||||
|
||||
_PROT_NONE = 0x0
|
||||
_PROT_READ = 0x1
|
||||
-265
@@ -1,265 +0,0 @@
|
||||
From 1a44be4cecdc742ac6cce9825f9ffc19857c99f3 Mon Sep 17 00:00:00 2001
|
||||
From: database64128 <free122448@hotmail.com>
|
||||
Date: Mon, 9 Mar 2026 16:25:16 +0800
|
||||
Subject: [PATCH] [release-branch.go1.26] internal/poll: move rsan to heap on
|
||||
windows
|
||||
|
||||
According to https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsarecvfrom,
|
||||
the memory pointed to by lpFromlen must remain available during the
|
||||
overlapped I/O, and therefore cannot be allocated on the stack.
|
||||
|
||||
CL 685417 moved the rsan field out of the operation struct and placed
|
||||
it on stack, which violates the above requirement and causes stack
|
||||
corruption.
|
||||
|
||||
Unfortunately, it is no longer possible to cleanly revert CL 685417.
|
||||
Instead of attempting to revert it, this CL bundles rsan together
|
||||
with rsa in the same sync.Pool. The new wsaRsa struct is still in the
|
||||
same size class, so no additional overhead is introduced by this
|
||||
change.
|
||||
|
||||
Fixes #78041.
|
||||
|
||||
Change-Id: I5ffbccb332515116ddc03fb7c40ffc9293cad2ab
|
||||
Reviewed-on: https://go-review.googlesource.com/c/go/+/753040
|
||||
Reviewed-by: Quim Muntal <quimmuntal@gmail.com>
|
||||
Reviewed-by: Cherry Mui <cherryyz@google.com>
|
||||
Commit-Queue: Cherry Mui <cherryyz@google.com>
|
||||
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
|
||||
Reviewed-by: Damien Neil <dneil@google.com>
|
||||
Reviewed-on: https://go-review.googlesource.com/c/go/+/753480
|
||||
Reviewed-by: Mark Freeman <markfreeman@google.com>
|
||||
---
|
||||
src/internal/poll/fd_windows.go | 94 +++++++++++++++++++++------------
|
||||
1 file changed, 59 insertions(+), 35 deletions(-)
|
||||
|
||||
diff --git a/src/internal/poll/fd_windows.go b/src/internal/poll/fd_windows.go
|
||||
index 2ba967f990982f..26319548e3c310 100644
|
||||
--- a/src/internal/poll/fd_windows.go
|
||||
+++ b/src/internal/poll/fd_windows.go
|
||||
@@ -149,7 +149,7 @@ var wsaMsgPool = sync.Pool{
|
||||
|
||||
// newWSAMsg creates a new WSAMsg with the provided parameters.
|
||||
// Use [freeWSAMsg] to free it.
|
||||
-func newWSAMsg(p []byte, oob []byte, flags int, unconnected bool) *windows.WSAMsg {
|
||||
+func newWSAMsg(p []byte, oob []byte, flags int, rsa *wsaRsa) *windows.WSAMsg {
|
||||
// The returned object can't be allocated in the stack because it is accessed asynchronously
|
||||
// by Windows in between several system calls. If the stack frame is moved while that happens,
|
||||
// then Windows may access invalid memory.
|
||||
@@ -166,34 +166,46 @@ func newWSAMsg(p []byte, oob []byte, flags int, unconnected bool) *windows.WSAMs
|
||||
}
|
||||
}
|
||||
msg.Flags = uint32(flags)
|
||||
- if unconnected {
|
||||
- msg.Name = wsaRsaPool.Get().(*syscall.RawSockaddrAny)
|
||||
- msg.Namelen = int32(unsafe.Sizeof(syscall.RawSockaddrAny{}))
|
||||
+ if rsa != nil {
|
||||
+ msg.Name = &rsa.name
|
||||
+ msg.Namelen = rsa.namelen
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func freeWSAMsg(msg *windows.WSAMsg) {
|
||||
// Clear pointers to buffers so they can be released by garbage collector.
|
||||
+ msg.Name = nil
|
||||
+ msg.Namelen = 0
|
||||
msg.Buffers.Len = 0
|
||||
msg.Buffers.Buf = nil
|
||||
msg.Control.Len = 0
|
||||
msg.Control.Buf = nil
|
||||
- if msg.Name != nil {
|
||||
- *msg.Name = syscall.RawSockaddrAny{}
|
||||
- wsaRsaPool.Put(msg.Name)
|
||||
- msg.Name = nil
|
||||
- msg.Namelen = 0
|
||||
- }
|
||||
wsaMsgPool.Put(msg)
|
||||
}
|
||||
|
||||
+// wsaRsa bundles a [syscall.RawSockaddrAny] with its length for efficient caching.
|
||||
+//
|
||||
+// When used by WSARecvFrom, wsaRsa must be on the heap. See
|
||||
+// https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsarecvfrom.
|
||||
+type wsaRsa struct {
|
||||
+ name syscall.RawSockaddrAny
|
||||
+ namelen int32
|
||||
+}
|
||||
+
|
||||
var wsaRsaPool = sync.Pool{
|
||||
New: func() any {
|
||||
- return new(syscall.RawSockaddrAny)
|
||||
+ return new(wsaRsa)
|
||||
},
|
||||
}
|
||||
|
||||
+func newWSARsa() *wsaRsa {
|
||||
+ rsa := wsaRsaPool.Get().(*wsaRsa)
|
||||
+ rsa.name = syscall.RawSockaddrAny{}
|
||||
+ rsa.namelen = int32(unsafe.Sizeof(syscall.RawSockaddrAny{}))
|
||||
+ return rsa
|
||||
+}
|
||||
+
|
||||
var operationPool = sync.Pool{
|
||||
New: func() any {
|
||||
return new(operation)
|
||||
@@ -739,19 +751,18 @@ func (fd *FD) ReadFrom(buf []byte) (int, syscall.Sockaddr, error) {
|
||||
|
||||
fd.pin('r', &buf[0])
|
||||
|
||||
- rsa := wsaRsaPool.Get().(*syscall.RawSockaddrAny)
|
||||
+ rsa := newWSARsa()
|
||||
defer wsaRsaPool.Put(rsa)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
- rsan := int32(unsafe.Sizeof(*rsa))
|
||||
var flags uint32
|
||||
- err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, rsa, &rsan, &o.o, nil)
|
||||
+ err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, &rsa.name, &rsa.namelen, &o.o, nil)
|
||||
return qty, err
|
||||
})
|
||||
err = fd.eofError(n, err)
|
||||
if err != nil {
|
||||
return n, nil, err
|
||||
}
|
||||
- sa, _ := rsa.Sockaddr()
|
||||
+ sa, _ := rsa.name.Sockaddr()
|
||||
return n, sa, nil
|
||||
}
|
||||
|
||||
@@ -770,19 +781,18 @@ func (fd *FD) ReadFromInet4(buf []byte, sa4 *syscall.SockaddrInet4) (int, error)
|
||||
|
||||
fd.pin('r', &buf[0])
|
||||
|
||||
- rsa := wsaRsaPool.Get().(*syscall.RawSockaddrAny)
|
||||
+ rsa := newWSARsa()
|
||||
defer wsaRsaPool.Put(rsa)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
- rsan := int32(unsafe.Sizeof(*rsa))
|
||||
var flags uint32
|
||||
- err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, rsa, &rsan, &o.o, nil)
|
||||
+ err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, &rsa.name, &rsa.namelen, &o.o, nil)
|
||||
return qty, err
|
||||
})
|
||||
err = fd.eofError(n, err)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
- rawToSockaddrInet4(rsa, sa4)
|
||||
+ rawToSockaddrInet4(&rsa.name, sa4)
|
||||
return n, err
|
||||
}
|
||||
|
||||
@@ -801,19 +811,18 @@ func (fd *FD) ReadFromInet6(buf []byte, sa6 *syscall.SockaddrInet6) (int, error)
|
||||
|
||||
fd.pin('r', &buf[0])
|
||||
|
||||
- rsa := wsaRsaPool.Get().(*syscall.RawSockaddrAny)
|
||||
+ rsa := newWSARsa()
|
||||
defer wsaRsaPool.Put(rsa)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
- rsan := int32(unsafe.Sizeof(*rsa))
|
||||
var flags uint32
|
||||
- err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, rsa, &rsan, &o.o, nil)
|
||||
+ err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, &rsa.name, &rsa.namelen, &o.o, nil)
|
||||
return qty, err
|
||||
})
|
||||
err = fd.eofError(n, err)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
- rawToSockaddrInet6(rsa, sa6)
|
||||
+ rawToSockaddrInet6(&rsa.name, sa6)
|
||||
return n, err
|
||||
}
|
||||
|
||||
@@ -1373,7 +1382,9 @@ func (fd *FD) ReadMsg(p []byte, oob []byte, flags int) (int, int, int, syscall.S
|
||||
p = p[:maxRW]
|
||||
}
|
||||
|
||||
- msg := newWSAMsg(p, oob, flags, true)
|
||||
+ rsa := newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
+ msg := newWSAMsg(p, oob, flags, rsa)
|
||||
defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSARecvMsg(fd.Sysfd, msg, &qty, &o.o, nil)
|
||||
@@ -1398,7 +1409,9 @@ func (fd *FD) ReadMsgInet4(p []byte, oob []byte, flags int, sa4 *syscall.Sockadd
|
||||
p = p[:maxRW]
|
||||
}
|
||||
|
||||
- msg := newWSAMsg(p, oob, flags, true)
|
||||
+ rsa := newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
+ msg := newWSAMsg(p, oob, flags, rsa)
|
||||
defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSARecvMsg(fd.Sysfd, msg, &qty, &o.o, nil)
|
||||
@@ -1422,7 +1435,9 @@ func (fd *FD) ReadMsgInet6(p []byte, oob []byte, flags int, sa6 *syscall.Sockadd
|
||||
p = p[:maxRW]
|
||||
}
|
||||
|
||||
- msg := newWSAMsg(p, oob, flags, true)
|
||||
+ rsa := newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
+ msg := newWSAMsg(p, oob, flags, rsa)
|
||||
defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSARecvMsg(fd.Sysfd, msg, &qty, &o.o, nil)
|
||||
@@ -1446,15 +1461,18 @@ func (fd *FD) WriteMsg(p []byte, oob []byte, sa syscall.Sockaddr) (int, int, err
|
||||
}
|
||||
defer fd.writeUnlock()
|
||||
|
||||
- msg := newWSAMsg(p, oob, 0, sa != nil)
|
||||
- defer freeWSAMsg(msg)
|
||||
+ var rsa *wsaRsa
|
||||
if sa != nil {
|
||||
+ rsa = newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
var err error
|
||||
- msg.Namelen, err = sockaddrToRaw(msg.Name, sa)
|
||||
+ rsa.namelen, err = sockaddrToRaw(&rsa.name, sa)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
+ msg := newWSAMsg(p, oob, 0, rsa)
|
||||
+ defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSASendMsg(fd.Sysfd, msg, 0, nil, &o.o, nil)
|
||||
return qty, err
|
||||
@@ -1473,11 +1491,14 @@ func (fd *FD) WriteMsgInet4(p []byte, oob []byte, sa *syscall.SockaddrInet4) (in
|
||||
}
|
||||
defer fd.writeUnlock()
|
||||
|
||||
- msg := newWSAMsg(p, oob, 0, sa != nil)
|
||||
- defer freeWSAMsg(msg)
|
||||
+ var rsa *wsaRsa
|
||||
if sa != nil {
|
||||
- msg.Namelen = sockaddrInet4ToRaw(msg.Name, sa)
|
||||
+ rsa = newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
+ rsa.namelen = sockaddrInet4ToRaw(&rsa.name, sa)
|
||||
}
|
||||
+ msg := newWSAMsg(p, oob, 0, rsa)
|
||||
+ defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSASendMsg(fd.Sysfd, msg, 0, nil, &o.o, nil)
|
||||
return qty, err
|
||||
@@ -1496,11 +1517,14 @@ func (fd *FD) WriteMsgInet6(p []byte, oob []byte, sa *syscall.SockaddrInet6) (in
|
||||
}
|
||||
defer fd.writeUnlock()
|
||||
|
||||
- msg := newWSAMsg(p, oob, 0, sa != nil)
|
||||
- defer freeWSAMsg(msg)
|
||||
+ var rsa *wsaRsa
|
||||
if sa != nil {
|
||||
- msg.Namelen = sockaddrInet6ToRaw(msg.Name, sa)
|
||||
+ rsa = newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
+ rsa.namelen = sockaddrInet6ToRaw(&rsa.name, sa)
|
||||
}
|
||||
+ msg := newWSAMsg(p, oob, 0, rsa)
|
||||
+ defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSASendMsg(fd.Sysfd, msg, 0, nil, &o.o, nil)
|
||||
return qty, err
|
||||
+18
-15
@@ -32,7 +32,7 @@ jobs:
|
||||
- { goos: linux, goarch: '386', go386: sse2, output: '386', debian: i386, rpm: i386}
|
||||
- { goos: linux, goarch: '386', go386: softfloat, output: '386-softfloat' }
|
||||
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible} # old style file name will be removed in next released
|
||||
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64, debian: amd64, rpm: x86_64, pacman: x86_64}
|
||||
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, tarball: tarball}
|
||||
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-v1, debian: amd64, rpm: x86_64, pacman: x86_64, test: test }
|
||||
- { goos: linux, goarch: amd64, goamd64: v2, output: amd64-v2, debian: amd64, rpm: x86_64, pacman: x86_64}
|
||||
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64-v3, debian: amd64, rpm: x86_64, pacman: x86_64}
|
||||
@@ -186,20 +186,6 @@ jobs:
|
||||
- name: Verify Go env
|
||||
run: go env
|
||||
|
||||
# TODO: remove after issue77731 fixed, see: https://github.com/golang/go/issues/77731
|
||||
- name: Fix issue77731 for Golang1.26
|
||||
if: ${{ matrix.jobs.goversion == '' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/issue77731.patch
|
||||
|
||||
# TODO: remove after issue77975 fixed, see: https://github.com/golang/go/issues/77975
|
||||
- name: Fix issue77975 for Golang1.26
|
||||
if: ${{ matrix.jobs.goversion == '' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/issue77975.patch
|
||||
|
||||
# TODO: remove after issue77930 fixed, see: https://github.com/golang/go/issues/77930
|
||||
- name: Fix issue77930 for Golang1.26
|
||||
if: ${{ matrix.jobs.goversion == '' }}
|
||||
@@ -312,6 +298,21 @@ jobs:
|
||||
--architecture ${{ matrix.jobs.pacman }} \
|
||||
mihomo=/usr/bin/mihomo
|
||||
|
||||
- name: Pack Golang Toolchain
|
||||
if: ${{ matrix.jobs.goversion == '' && matrix.jobs.tarball == 'tarball' }}
|
||||
run: |
|
||||
mkdir -p $GITHUB_WORKSPACE/toolchain/go
|
||||
cp -r $(go env GOROOT)/* $GITHUB_WORKSPACE/toolchain/go/
|
||||
cd $GITHUB_WORKSPACE/toolchain/
|
||||
tar -czf $GITHUB_WORKSPACE/toolchain.tar.gz .
|
||||
rm -rf $GITHUB_WORKSPACE/toolchain
|
||||
|
||||
- name: Pack GoMod Vendor
|
||||
if: ${{ matrix.jobs.goversion == '' && matrix.jobs.tarball == 'tarball' }}
|
||||
run: |
|
||||
go mod vendor
|
||||
tar -czf $GITHUB_WORKSPACE/vendor.tar.gz vendor
|
||||
|
||||
- name: Save version
|
||||
run: |
|
||||
echo ${VERSION} > version.txt
|
||||
@@ -327,6 +328,8 @@ jobs:
|
||||
mihomo*.rpm
|
||||
mihomo*.pkg.tar.zst
|
||||
mihomo*.zip
|
||||
toolchain.tar.gz
|
||||
vendor.tar.gz
|
||||
version.txt
|
||||
checksums.txt
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ jobs:
|
||||
- 'windows-latest' # amd64 windows
|
||||
- 'macos-latest' # arm64 macos
|
||||
- 'ubuntu-24.04-arm' # arm64 linux
|
||||
- 'windows-11-arm' # arm64 windows
|
||||
- 'macos-15-intel' # amd64 macos
|
||||
go-version:
|
||||
- '1.26'
|
||||
@@ -56,13 +57,6 @@ jobs:
|
||||
- name: Verify Go env
|
||||
run: go env
|
||||
|
||||
# TODO: remove after issue77975 fixed, see: https://github.com/golang/go/issues/77975
|
||||
- name: Fix issue77975 for Golang1.26
|
||||
if: ${{ matrix.go-version == '1.26' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/issue77975.patch
|
||||
|
||||
- name: Remove inbound test for macOS
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
run: |
|
||||
|
||||
@@ -27,7 +27,7 @@ type Trojan struct {
|
||||
hexPassword [trojan.KeyLength]byte
|
||||
|
||||
// for gun mux
|
||||
gunTransport *gun.Transport
|
||||
gunClient *gun.Client
|
||||
|
||||
realityConfig *tlsC.RealityConfig
|
||||
echConfig *ech.Config
|
||||
@@ -115,7 +115,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
|
||||
c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
|
||||
case "grpc":
|
||||
break // already handle in gun transport
|
||||
break // already handle in dialContext
|
||||
default:
|
||||
// default tcp network
|
||||
// handle TLS
|
||||
@@ -175,7 +175,7 @@ func (t *Trojan) writeHeaderContext(ctx context.Context, c net.Conn, metadata *C
|
||||
func (t *Trojan) dialContext(ctx context.Context) (c net.Conn, err error) {
|
||||
switch t.option.Network {
|
||||
case "grpc": // gun transport
|
||||
return t.gunTransport.Dial()
|
||||
return t.gunClient.Dial()
|
||||
default:
|
||||
}
|
||||
return t.dialer.DialContext(ctx, "tcp", t.addr)
|
||||
@@ -236,10 +236,13 @@ func (t *Trojan) ProxyInfo() C.ProxyInfo {
|
||||
|
||||
// Close implements C.ProxyAdapter
|
||||
func (t *Trojan) Close() error {
|
||||
if t.gunTransport != nil {
|
||||
return t.gunTransport.Close()
|
||||
var errs []error
|
||||
if t.gunClient != nil {
|
||||
if err := t.gunClient.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
@@ -320,7 +323,14 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
PingInterval: option.GrpcOpts.PingInterval,
|
||||
}
|
||||
|
||||
t.gunTransport = gun.NewTransport(dialFn, tlsConfig, gunConfig)
|
||||
t.gunClient = gun.NewClient(
|
||||
func() *gun.Transport {
|
||||
return gun.NewTransport(dialFn, tlsConfig, gunConfig)
|
||||
},
|
||||
option.GrpcOpts.MaxConnections,
|
||||
option.GrpcOpts.MinStreams,
|
||||
option.GrpcOpts.MaxStreams,
|
||||
)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
type TrustTunnel struct {
|
||||
*Base
|
||||
client *trusttunnel.Client
|
||||
client *trusttunnel.PoolClient
|
||||
option *TrustTunnelOption
|
||||
}
|
||||
|
||||
@@ -35,10 +35,14 @@ type TrustTunnelOption struct {
|
||||
PrivateKey string `proxy:"private-key,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
HealthCheck bool `proxy:"health-check,omitempty"`
|
||||
|
||||
// quic options
|
||||
Quic bool `proxy:"quic,omitempty"`
|
||||
CongestionController string `proxy:"congestion-controller,omitempty"`
|
||||
CWND int `proxy:"cwnd,omitempty"`
|
||||
// reuse options
|
||||
MaxConnections int `proxy:"max-connections,omitempty"`
|
||||
MinStreams int `proxy:"min-streams,omitempty"`
|
||||
MaxStreams int `proxy:"max-streams,omitempty"`
|
||||
}
|
||||
|
||||
func (t *TrustTunnel) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
@@ -114,6 +118,9 @@ func NewTrustTunnel(option TrustTunnelOption) (*TrustTunnel, error) {
|
||||
QUICCongestionControl: option.CongestionController,
|
||||
QUICCwnd: option.CWND,
|
||||
HealthCheck: option.HealthCheck,
|
||||
MaxConnections: option.MaxConnections,
|
||||
MinStreams: option.MinStreams,
|
||||
MaxStreams: option.MaxStreams,
|
||||
}
|
||||
echConfig, err := option.ECHOpts.Parse()
|
||||
if err != nil {
|
||||
@@ -134,7 +141,7 @@ func NewTrustTunnel(option TrustTunnelOption) (*TrustTunnel, error) {
|
||||
}
|
||||
tOption.TLSConfig = tlsConfig
|
||||
|
||||
client, err := trusttunnel.NewClient(context.TODO(), tOption)
|
||||
client, err := trusttunnel.NewPoolClient(context.TODO(), tOption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ type Vless struct {
|
||||
encryption *encryption.ClientInstance
|
||||
|
||||
// for gun mux
|
||||
gunTransport *gun.Transport
|
||||
gunClient *gun.Client
|
||||
// for xhttp
|
||||
xhttpClient *xhttp.Client
|
||||
|
||||
@@ -76,22 +76,34 @@ type VlessOption struct {
|
||||
}
|
||||
|
||||
type XHTTPOptions struct {
|
||||
Path string `proxy:"path,omitempty"`
|
||||
Host string `proxy:"host,omitempty"`
|
||||
Mode string `proxy:"mode,omitempty"`
|
||||
Headers map[string]string `proxy:"headers,omitempty"`
|
||||
NoGRPCHeader bool `proxy:"no-grpc-header,omitempty"`
|
||||
XPaddingBytes string `proxy:"x-padding-bytes,omitempty"`
|
||||
DownloadSettings *XHTTPDownloadSettings `proxy:"download-settings,omitempty"`
|
||||
Path string `proxy:"path,omitempty"`
|
||||
Host string `proxy:"host,omitempty"`
|
||||
Mode string `proxy:"mode,omitempty"`
|
||||
Headers map[string]string `proxy:"headers,omitempty"`
|
||||
NoGRPCHeader bool `proxy:"no-grpc-header,omitempty"`
|
||||
XPaddingBytes string `proxy:"x-padding-bytes,omitempty"`
|
||||
ScMaxEachPostBytes int `proxy:"sc-max-each-post-bytes,omitempty"`
|
||||
ReuseSettings *XHTTPReuseSettings `proxy:"reuse-settings,omitempty"` // aka XMUX
|
||||
DownloadSettings *XHTTPDownloadSettings `proxy:"download-settings,omitempty"`
|
||||
}
|
||||
|
||||
type XHTTPReuseSettings struct {
|
||||
MaxConnections string `proxy:"max-connections,omitempty"`
|
||||
MaxConcurrency string `proxy:"max-concurrency,omitempty"`
|
||||
CMaxReuseTimes string `proxy:"c-max-reuse-times,omitempty"`
|
||||
HMaxRequestTimes string `proxy:"h-max-request-times,omitempty"`
|
||||
HMaxReusableSecs string `proxy:"h-max-reusable-secs,omitempty"`
|
||||
}
|
||||
|
||||
type XHTTPDownloadSettings struct {
|
||||
// xhttp part
|
||||
Path *string `proxy:"path,omitempty"`
|
||||
Host *string `proxy:"host,omitempty"`
|
||||
Headers *map[string]string `proxy:"headers,omitempty"`
|
||||
NoGRPCHeader *bool `proxy:"no-grpc-header,omitempty"`
|
||||
XPaddingBytes *string `proxy:"x-padding-bytes,omitempty"`
|
||||
Path *string `proxy:"path,omitempty"`
|
||||
Host *string `proxy:"host,omitempty"`
|
||||
Headers *map[string]string `proxy:"headers,omitempty"`
|
||||
NoGRPCHeader *bool `proxy:"no-grpc-header,omitempty"`
|
||||
XPaddingBytes *string `proxy:"x-padding-bytes,omitempty"`
|
||||
ScMaxEachPostBytes *int `proxy:"sc-max-each-post-bytes,omitempty"`
|
||||
ReuseSettings *XHTTPReuseSettings `proxy:"reuse-settings,omitempty"` // aka XMUX
|
||||
// proxy part
|
||||
Server *string `proxy:"server,omitempty"`
|
||||
Port *int `proxy:"port,omitempty"`
|
||||
@@ -133,7 +145,6 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
wsOpts.TLS = true
|
||||
wsOpts.TLSConfig, err = ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
ServerName: host,
|
||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||
NextProtos: []string{"http/1.1"},
|
||||
@@ -187,9 +198,9 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
|
||||
c, err = vmess.StreamH2Conn(ctx, c, h2Opts)
|
||||
case "grpc":
|
||||
break // already handle in gun transport
|
||||
break // already handle in dialContext
|
||||
case "xhttp":
|
||||
break // already handle in xhttp client
|
||||
break // already handle in dialContext
|
||||
default:
|
||||
// default tcp network
|
||||
// handle TLS
|
||||
@@ -271,7 +282,7 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
|
||||
func (v *Vless) dialContext(ctx context.Context) (c net.Conn, err error) {
|
||||
switch v.option.Network {
|
||||
case "grpc": // gun transport
|
||||
return v.gunTransport.Dial()
|
||||
return v.gunClient.Dial()
|
||||
case "xhttp":
|
||||
return v.xhttpClient.Dial()
|
||||
default:
|
||||
@@ -349,8 +360,8 @@ func (v *Vless) ProxyInfo() C.ProxyInfo {
|
||||
// Close implements C.ProxyAdapter
|
||||
func (v *Vless) Close() error {
|
||||
var errs []error
|
||||
if v.gunTransport != nil {
|
||||
if err := v.gunTransport.Close(); err != nil {
|
||||
if v.gunClient != nil {
|
||||
if err := v.gunClient.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
@@ -496,7 +507,14 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
}
|
||||
}
|
||||
|
||||
v.gunTransport = gun.NewTransport(dialFn, tlsConfig, gunConfig)
|
||||
v.gunClient = gun.NewClient(
|
||||
func() *gun.Transport {
|
||||
return gun.NewTransport(dialFn, tlsConfig, gunConfig)
|
||||
},
|
||||
option.GrpcOpts.MaxConnections,
|
||||
option.GrpcOpts.MinStreams,
|
||||
option.GrpcOpts.MaxStreams,
|
||||
)
|
||||
case "xhttp":
|
||||
requestHost := v.option.XHTTPOpts.Host
|
||||
if requestHost == "" {
|
||||
@@ -507,13 +525,26 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
}
|
||||
}
|
||||
|
||||
var reuseCfg *xhttp.ReuseConfig
|
||||
if option.XHTTPOpts.ReuseSettings != nil {
|
||||
reuseCfg = &xhttp.ReuseConfig{
|
||||
MaxConnections: option.XHTTPOpts.ReuseSettings.MaxConnections,
|
||||
MaxConcurrency: option.XHTTPOpts.ReuseSettings.MaxConcurrency,
|
||||
CMaxReuseTimes: option.XHTTPOpts.ReuseSettings.CMaxReuseTimes,
|
||||
HMaxRequestTimes: option.XHTTPOpts.ReuseSettings.HMaxRequestTimes,
|
||||
HMaxReusableSecs: option.XHTTPOpts.ReuseSettings.HMaxReusableSecs,
|
||||
}
|
||||
}
|
||||
|
||||
cfg := &xhttp.Config{
|
||||
Host: requestHost,
|
||||
Path: v.option.XHTTPOpts.Path,
|
||||
Mode: v.option.XHTTPOpts.Mode,
|
||||
Headers: v.option.XHTTPOpts.Headers,
|
||||
NoGRPCHeader: v.option.XHTTPOpts.NoGRPCHeader,
|
||||
XPaddingBytes: v.option.XHTTPOpts.XPaddingBytes,
|
||||
Host: requestHost,
|
||||
Path: v.option.XHTTPOpts.Path,
|
||||
Mode: v.option.XHTTPOpts.Mode,
|
||||
Headers: v.option.XHTTPOpts.Headers,
|
||||
NoGRPCHeader: v.option.XHTTPOpts.NoGRPCHeader,
|
||||
XPaddingBytes: v.option.XHTTPOpts.XPaddingBytes,
|
||||
ScMaxEachPostBytes: v.option.XHTTPOpts.ScMaxEachPostBytes,
|
||||
ReuseConfig: reuseCfg,
|
||||
}
|
||||
|
||||
makeTransport := func() http.RoundTripper {
|
||||
@@ -524,6 +555,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
func(ctx context.Context, raw net.Conn, isH2 bool) (net.Conn, error) {
|
||||
return v.streamTLSConn(ctx, raw, isH2)
|
||||
},
|
||||
v.option.ALPN,
|
||||
)
|
||||
}
|
||||
var makeDownloadTransport func() http.RoundTripper
|
||||
@@ -569,13 +601,26 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
}
|
||||
}
|
||||
|
||||
downloadReuseCfg := reuseCfg
|
||||
if ds.ReuseSettings != nil {
|
||||
downloadReuseCfg = &xhttp.ReuseConfig{
|
||||
MaxConnections: ds.ReuseSettings.MaxConnections,
|
||||
MaxConcurrency: ds.ReuseSettings.MaxConcurrency,
|
||||
CMaxReuseTimes: ds.ReuseSettings.CMaxReuseTimes,
|
||||
HMaxRequestTimes: ds.ReuseSettings.HMaxRequestTimes,
|
||||
HMaxReusableSecs: ds.ReuseSettings.HMaxReusableSecs,
|
||||
}
|
||||
}
|
||||
|
||||
cfg.DownloadConfig = &xhttp.Config{
|
||||
Host: downloadHost,
|
||||
Path: lo.FromPtrOr(ds.Path, v.option.XHTTPOpts.Path),
|
||||
Mode: v.option.XHTTPOpts.Mode,
|
||||
Headers: lo.FromPtrOr(ds.Headers, v.option.XHTTPOpts.Headers),
|
||||
NoGRPCHeader: lo.FromPtrOr(ds.NoGRPCHeader, v.option.XHTTPOpts.NoGRPCHeader),
|
||||
XPaddingBytes: lo.FromPtrOr(ds.XPaddingBytes, v.option.XHTTPOpts.XPaddingBytes),
|
||||
Host: downloadHost,
|
||||
Path: lo.FromPtrOr(ds.Path, v.option.XHTTPOpts.Path),
|
||||
Mode: v.option.XHTTPOpts.Mode,
|
||||
Headers: lo.FromPtrOr(ds.Headers, v.option.XHTTPOpts.Headers),
|
||||
NoGRPCHeader: lo.FromPtrOr(ds.NoGRPCHeader, v.option.XHTTPOpts.NoGRPCHeader),
|
||||
XPaddingBytes: lo.FromPtrOr(ds.XPaddingBytes, v.option.XHTTPOpts.XPaddingBytes),
|
||||
ScMaxEachPostBytes: lo.FromPtrOr(ds.ScMaxEachPostBytes, v.option.XHTTPOpts.ScMaxEachPostBytes),
|
||||
ReuseConfig: downloadReuseCfg,
|
||||
}
|
||||
|
||||
makeDownloadTransport = func() http.RoundTripper {
|
||||
@@ -612,6 +657,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
|
||||
return conn, nil
|
||||
},
|
||||
downloadALPN,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ type Vmess struct {
|
||||
option *VmessOption
|
||||
|
||||
// for gun mux
|
||||
gunTransport *gun.Transport
|
||||
gunClient *gun.Client
|
||||
|
||||
realityConfig *tlsC.RealityConfig
|
||||
echConfig *ech.Config
|
||||
@@ -86,6 +86,9 @@ type GrpcOptions struct {
|
||||
GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
|
||||
GrpcUserAgent string `proxy:"grpc-user-agent,omitempty"`
|
||||
PingInterval int `proxy:"ping-interval,omitempty"`
|
||||
MaxConnections int `proxy:"max-connections,omitempty"`
|
||||
MinStreams int `proxy:"min-streams,omitempty"`
|
||||
MaxStreams int `proxy:"max-streams,omitempty"`
|
||||
}
|
||||
|
||||
type WSOptions struct {
|
||||
@@ -145,24 +148,9 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
c, err = mihomoVMess.StreamWebsocketConn(ctx, c, wsOpts)
|
||||
case "http":
|
||||
// readability first, so just copy default TLS logic
|
||||
if v.option.TLS {
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
tlsOpts := &mihomoVMess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECH: v.echConfig,
|
||||
Reality: v.realityConfig,
|
||||
NextProtos: v.option.ALPN,
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
tlsOpts.Host = v.option.ServerName
|
||||
}
|
||||
c, err = mihomoVMess.StreamTLSConn(ctx, c, tlsOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err = v.streamTLSConn(ctx, c, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
@@ -175,23 +163,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
|
||||
c = mihomoVMess.StreamHTTPConn(c, httpOpts)
|
||||
case "h2":
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
tlsOpts := mihomoVMess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
FingerPrint: v.option.Fingerprint,
|
||||
Certificate: v.option.Certificate,
|
||||
PrivateKey: v.option.PrivateKey,
|
||||
NextProtos: []string{"h2"},
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Reality: v.realityConfig,
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
tlsOpts.Host = v.option.ServerName
|
||||
}
|
||||
|
||||
c, err = mihomoVMess.StreamTLSConn(ctx, c, &tlsOpts)
|
||||
c, err = v.streamTLSConn(ctx, c, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -203,29 +175,11 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
|
||||
c, err = mihomoVMess.StreamH2Conn(ctx, c, h2Opts)
|
||||
case "grpc":
|
||||
break // already handle in gun transport
|
||||
break // already handle in dialContext
|
||||
default:
|
||||
// default tcp network
|
||||
// handle TLS
|
||||
if v.option.TLS {
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
tlsOpts := &mihomoVMess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
FingerPrint: v.option.Fingerprint,
|
||||
Certificate: v.option.Certificate,
|
||||
PrivateKey: v.option.PrivateKey,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECH: v.echConfig,
|
||||
Reality: v.realityConfig,
|
||||
NextProtos: v.option.ALPN,
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
tlsOpts.Host = v.option.ServerName
|
||||
}
|
||||
|
||||
c, err = mihomoVMess.StreamTLSConn(ctx, c, tlsOpts)
|
||||
}
|
||||
c, err = v.streamTLSConn(ctx, c, false)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -290,10 +244,40 @@ func (v *Vmess) streamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
return
|
||||
}
|
||||
|
||||
func (v *Vmess) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (net.Conn, error) {
|
||||
if v.option.TLS {
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
|
||||
tlsOpts := mihomoVMess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
FingerPrint: v.option.Fingerprint,
|
||||
Certificate: v.option.Certificate,
|
||||
PrivateKey: v.option.PrivateKey,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECH: v.echConfig,
|
||||
Reality: v.realityConfig,
|
||||
NextProtos: v.option.ALPN,
|
||||
}
|
||||
|
||||
if isH2 {
|
||||
tlsOpts.NextProtos = []string{"h2"}
|
||||
}
|
||||
|
||||
if v.option.ServerName != "" {
|
||||
tlsOpts.Host = v.option.ServerName
|
||||
}
|
||||
|
||||
return mihomoVMess.StreamTLSConn(ctx, conn, &tlsOpts)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (v *Vmess) dialContext(ctx context.Context) (c net.Conn, err error) {
|
||||
switch v.option.Network {
|
||||
case "grpc": // gun transport
|
||||
return v.gunTransport.Dial()
|
||||
return v.gunClient.Dial()
|
||||
default:
|
||||
}
|
||||
return v.dialer.DialContext(ctx, "tcp", v.addr)
|
||||
@@ -350,10 +334,13 @@ func (v *Vmess) ProxyInfo() C.ProxyInfo {
|
||||
|
||||
// Close implements C.ProxyAdapter
|
||||
func (v *Vmess) Close() error {
|
||||
if v.gunTransport != nil {
|
||||
return v.gunTransport.Close()
|
||||
var errs []error
|
||||
if v.gunClient != nil {
|
||||
if err := v.gunClient.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// SupportUOT implements C.ProxyAdapter
|
||||
@@ -457,7 +444,14 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
}
|
||||
}
|
||||
|
||||
v.gunTransport = gun.NewTransport(dialFn, tlsConfig, gunConfig)
|
||||
v.gunClient = gun.NewClient(
|
||||
func() *gun.Transport {
|
||||
return gun.NewTransport(dialFn, tlsConfig, gunConfig)
|
||||
},
|
||||
option.GrpcOpts.MaxConnections,
|
||||
option.GrpcOpts.MinStreams,
|
||||
option.GrpcOpts.MaxStreams,
|
||||
)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
@@ -134,6 +135,137 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
|
||||
grpcOpts := make(map[string]any)
|
||||
grpcOpts["grpc-service-name"] = query.Get("serviceName")
|
||||
proxy["grpc-opts"] = grpcOpts
|
||||
|
||||
case "xhttp":
|
||||
proxy["network"] = "xhttp"
|
||||
xhttpOpts := make(map[string]any)
|
||||
|
||||
if path := query.Get("path"); path != "" {
|
||||
xhttpOpts["path"] = path
|
||||
}
|
||||
|
||||
if host := query.Get("host"); host != "" {
|
||||
xhttpOpts["host"] = host
|
||||
}
|
||||
|
||||
if mode := query.Get("mode"); mode != "" {
|
||||
xhttpOpts["mode"] = mode
|
||||
}
|
||||
|
||||
if extra := query.Get("extra"); extra != "" {
|
||||
var extraMap map[string]any
|
||||
if err := json.Unmarshal([]byte(extra), &extraMap); err == nil {
|
||||
parseXHTTPExtra(extraMap, xhttpOpts)
|
||||
}
|
||||
}
|
||||
|
||||
proxy["xhttp-opts"] = xhttpOpts
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseXHTTPExtra maps xray-core extra JSON fields to mihomo xhttp-opts fields.
|
||||
func parseXHTTPExtra(extra map[string]any, opts map[string]any) {
|
||||
// xmuxToReuse converts an xmux map to mihomo reuse-settings.
|
||||
xmuxToReuse := func(xmux map[string]any) map[string]any {
|
||||
reuse := make(map[string]any)
|
||||
set := func(src, dst string) {
|
||||
if v, ok := xmux[src]; ok {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
if val != "" {
|
||||
reuse[dst] = val
|
||||
}
|
||||
case float64:
|
||||
reuse[dst] = strconv.FormatInt(int64(val), 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
set("maxConnections", "max-connections")
|
||||
set("maxConcurrency", "max-concurrency")
|
||||
set("cMaxReuseTimes", "c-max-reuse-times")
|
||||
set("hMaxRequestTimes", "h-max-request-times")
|
||||
set("hMaxReusableSecs", "h-max-reusable-secs")
|
||||
return reuse
|
||||
}
|
||||
|
||||
if v, ok := extra["noGRPCHeader"].(bool); ok && v {
|
||||
opts["no-grpc-header"] = true
|
||||
}
|
||||
|
||||
if v, ok := extra["xPaddingBytes"].(string); ok && v != "" {
|
||||
opts["x-padding-bytes"] = v
|
||||
}
|
||||
|
||||
// xmux in root extra → reuse-settings
|
||||
if xmuxAny, ok := extra["xmux"].(map[string]any); ok && len(xmuxAny) > 0 {
|
||||
if reuse := xmuxToReuse(xmuxAny); len(reuse) > 0 {
|
||||
opts["reuse-settings"] = reuse
|
||||
}
|
||||
}
|
||||
|
||||
if dsAny, ok := extra["downloadSettings"].(map[string]any); ok {
|
||||
ds := make(map[string]any)
|
||||
|
||||
if addr, ok := dsAny["address"].(string); ok && addr != "" {
|
||||
ds["server"] = addr
|
||||
}
|
||||
|
||||
if port, ok := dsAny["port"].(float64); ok {
|
||||
ds["port"] = int(port)
|
||||
}
|
||||
|
||||
if sec, ok := dsAny["security"].(string); ok && strings.ToLower(sec) == "tls" {
|
||||
ds["tls"] = true
|
||||
}
|
||||
|
||||
if tlsAny, ok := dsAny["tlsSettings"].(map[string]any); ok {
|
||||
if sn, ok := tlsAny["serverName"].(string); ok && sn != "" {
|
||||
ds["servername"] = sn
|
||||
}
|
||||
if fp, ok := tlsAny["fingerprint"].(string); ok && fp != "" {
|
||||
ds["client-fingerprint"] = fp
|
||||
}
|
||||
if alpnAny, ok := tlsAny["alpn"].([]any); ok && len(alpnAny) > 0 {
|
||||
alpnList := make([]string, 0, len(alpnAny))
|
||||
for _, a := range alpnAny {
|
||||
if s, ok := a.(string); ok {
|
||||
alpnList = append(alpnList, s)
|
||||
}
|
||||
}
|
||||
if len(alpnList) > 0 {
|
||||
ds["alpn"] = alpnList
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if xhttpAny, ok := dsAny["xhttpSettings"].(map[string]any); ok {
|
||||
if path, ok := xhttpAny["path"].(string); ok && path != "" {
|
||||
ds["path"] = path
|
||||
}
|
||||
if host, ok := xhttpAny["host"].(string); ok && host != "" {
|
||||
ds["host"] = host
|
||||
}
|
||||
if v, ok := xhttpAny["noGRPCHeader"].(bool); ok && v {
|
||||
ds["no-grpc-header"] = true
|
||||
}
|
||||
if v, ok := xhttpAny["xPaddingBytes"].(string); ok && v != "" {
|
||||
ds["x-padding-bytes"] = v
|
||||
}
|
||||
|
||||
// xmux inside downloadSettings.xhttpSettings.extra → download-settings.reuse-settings
|
||||
if dsExtraAny, ok := xhttpAny["extra"].(map[string]any); ok {
|
||||
if xmuxAny, ok := dsExtraAny["xmux"].(map[string]any); ok && len(xmuxAny) > 0 {
|
||||
if reuse := xmuxToReuse(xmuxAny); len(reuse) > 0 {
|
||||
ds["reuse-settings"] = reuse
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(ds) > 0 {
|
||||
opts["download-settings"] = ds
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,6 +308,18 @@ func BuildRemovedX25519MLKEM768HandshakeState(c *UConn) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetTLSConnectionState(conn net.Conn) (tlsState tls.ConnectionState) {
|
||||
switch tlsConn := conn.(type) {
|
||||
case interface{ ConnectionState() tls.ConnectionState }:
|
||||
state := tlsConn.ConnectionState()
|
||||
return state
|
||||
case interface{ ConnectionState() utls.ConnectionState }:
|
||||
state := tlsConn.ConnectionState()
|
||||
return tlsConnectionState(state)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var globalFingerprint string
|
||||
|
||||
func SetGlobalFingerprint(fingerprint string) {
|
||||
|
||||
@@ -302,6 +302,8 @@ type RawTun struct {
|
||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
||||
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
|
||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||
IncludeMACAddress []string `yaml:"include-mac-address" json:"include-mac-address,omitempty"`
|
||||
ExcludeMACAddress []string `yaml:"exclude-mac-address" json:"exclude-mac-address,omitempty"`
|
||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
||||
DisableICMPForwarding bool `yaml:"disable-icmp-forwarding" json:"disable-icmp-forwarding,omitempty"`
|
||||
@@ -1680,6 +1682,8 @@ func parseTun(rawTun RawTun, dns *DNS, general *General) error {
|
||||
IncludeAndroidUser: rawTun.IncludeAndroidUser,
|
||||
IncludePackage: rawTun.IncludePackage,
|
||||
ExcludePackage: rawTun.ExcludePackage,
|
||||
IncludeMACAddress: rawTun.IncludeMACAddress,
|
||||
ExcludeMACAddress: rawTun.ExcludeMACAddress,
|
||||
EndpointIndependentNat: rawTun.EndpointIndependentNat,
|
||||
UDPTimeout: rawTun.UDPTimeout,
|
||||
DisableICMPForwarding: rawTun.DisableICMPForwarding,
|
||||
|
||||
@@ -171,6 +171,10 @@ tun:
|
||||
#- 1000
|
||||
# exclude-uid-range: # 排除路由的的用户范围
|
||||
# - 1000:9999
|
||||
# include-mac-address:
|
||||
# - 00:11:22:33:44:55
|
||||
# exclude-mac-address:
|
||||
# - 00:11:22:33:44:55
|
||||
|
||||
# Android 用户和应用规则仅在 Android 下被支持
|
||||
# 并且需要 auto-route
|
||||
@@ -670,6 +674,9 @@ proxies: # socks5
|
||||
grpc-service-name: "example"
|
||||
# grpc-user-agent: "grpc-go/1.36.0"
|
||||
# ping-interval: 0 # 默认关闭,单位为秒
|
||||
# max-connections: 1 # Maximum connections. Conflict with max-streams.
|
||||
# min-streams: 0 # Minimum multiplexed streams in a connection before opening a new connection. Conflict with max-streams.
|
||||
# max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.
|
||||
# ip-version: ipv4
|
||||
|
||||
# vless
|
||||
@@ -761,6 +768,9 @@ proxies: # socks5
|
||||
grpc-service-name: "grpc"
|
||||
# grpc-user-agent: "grpc-go/1.36.0"
|
||||
# ping-interval: 0 # 默认关闭,单位为秒
|
||||
# max-connections: 1 # Maximum connections. Conflict with max-streams.
|
||||
# min-streams: 0 # Minimum multiplexed streams in a connection before opening a new connection. Conflict with max-streams.
|
||||
# max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.
|
||||
|
||||
reality-opts:
|
||||
public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE
|
||||
@@ -816,6 +826,13 @@ proxies: # socks5
|
||||
# X-Forwarded-For: ""
|
||||
# no-grpc-header: false
|
||||
# x-padding-bytes: "100-1000"
|
||||
# sc-max-each-post-bytes: 1000000
|
||||
# reuse-settings: # aka XMUX
|
||||
# max-connections: "16-32"
|
||||
# max-concurrency: "0"
|
||||
# c-max-reuse-times: "0"
|
||||
# h-max-request-times: "600-900"
|
||||
# h-max-reusable-secs: "1800-3000"
|
||||
# download-settings:
|
||||
# ## xhttp part
|
||||
# path: "/"
|
||||
@@ -824,6 +841,13 @@ proxies: # socks5
|
||||
# X-Forwarded-For: ""
|
||||
# no-grpc-header: false
|
||||
# x-padding-bytes: "100-1000"
|
||||
# sc-max-each-post-bytes: 1000000
|
||||
# reuse-settings: # aka XMUX
|
||||
# max-connections: "16-32"
|
||||
# max-concurrency: "0"
|
||||
# c-max-reuse-times: "0"
|
||||
# h-max-request-times: "600-900"
|
||||
# h-max-reusable-secs: "1800-3000"
|
||||
# ## proxy part
|
||||
# server: server
|
||||
# port: 443
|
||||
@@ -884,6 +908,9 @@ proxies: # socks5
|
||||
grpc-service-name: "example"
|
||||
# grpc-user-agent: "grpc-go/1.36.0"
|
||||
# ping-interval: 0 # 默认关闭,单位为秒
|
||||
# max-connections: 1 # Maximum connections. Conflict with max-streams.
|
||||
# min-streams: 0 # Minimum multiplexed streams in a connection before opening a new connection. Conflict with max-streams.
|
||||
# max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.
|
||||
|
||||
- name: trojan-ws
|
||||
server: server
|
||||
@@ -1187,8 +1214,13 @@ proxies: # socks5
|
||||
# alpn:
|
||||
# - h2
|
||||
# skip-cert-verify: true
|
||||
### quic options
|
||||
# quic: true # 默认为false
|
||||
# congestion-controller: bbr
|
||||
### reuse options
|
||||
# max-connections: 1 # Maximum connections. Conflict with max-streams.
|
||||
# min-streams: 0 # Minimum multiplexed streams in a connection before opening a new connection. Conflict with max-streams.
|
||||
# max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.
|
||||
|
||||
# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理
|
||||
- name: "dns-out"
|
||||
@@ -1627,6 +1659,13 @@ listeners:
|
||||
flow: xtls-rprx-vision
|
||||
# ws-path: "/" # 如果不为空则开启 websocket 传输层
|
||||
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
|
||||
# xhttp-config: # 如果不为空则开启 xhttp 传输层
|
||||
# path: "/"
|
||||
# host: ""
|
||||
# mode: auto # Available: "stream-one", "stream-up" or "packet-up"
|
||||
# no-sse-header: false
|
||||
# sc-stream-up-server-secs: "20-80"
|
||||
# sc-max-each-post-bytes: 1000000
|
||||
# -------------------------
|
||||
# vless encryption服务端配置:
|
||||
# (原生外观 / 只 XOR 公钥 / 全随机数。1-RTT 每次下发随机 300 到 600 秒的 ticket 以便 0-RTT 复用 / 只允许 1-RTT)
|
||||
@@ -1867,6 +1906,10 @@ listeners:
|
||||
# - 1000
|
||||
# exclude-uid-range: # 排除路由的的用户范围
|
||||
# - 1000:99999
|
||||
# include-mac-address:
|
||||
# - 00:11:22:33:44:55
|
||||
# exclude-mac-address:
|
||||
# - 00:11:22:33:44:55
|
||||
|
||||
# Android 用户和应用规则仅在 Android 下被支持
|
||||
# 并且需要 auto-route
|
||||
|
||||
@@ -21,7 +21,7 @@ require (
|
||||
github.com/metacubex/edwards25519 v1.2.0
|
||||
github.com/metacubex/fswatch v0.1.1
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||
github.com/metacubex/http v0.1.0
|
||||
github.com/metacubex/http v0.1.1
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604
|
||||
github.com/metacubex/mhurl v0.1.0
|
||||
github.com/metacubex/mlkem v0.1.0
|
||||
@@ -34,12 +34,12 @@ require (
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
|
||||
github.com/metacubex/sing-tun v0.4.16
|
||||
github.com/metacubex/sing-tun v0.4.17
|
||||
github.com/metacubex/sing-vmess v0.2.5
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
|
||||
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443
|
||||
github.com/metacubex/tls v0.1.4
|
||||
github.com/metacubex/tls v0.1.5
|
||||
github.com/metacubex/utls v1.8.4
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f
|
||||
github.com/mroth/weightedrand/v2 v2.1.0
|
||||
|
||||
@@ -103,8 +103,8 @@ github.com/metacubex/hkdf v0.1.0 h1:fPA6VzXK8cU1foc/TOmGCDmSa7pZbxlnqhl3RNsthaA=
|
||||
github.com/metacubex/hkdf v0.1.0/go.mod h1:3seEfds3smgTAXqUGn+tgEJH3uXdsUjOiduG/2EtvZ4=
|
||||
github.com/metacubex/hpke v0.1.0 h1:gu2jUNhraehWi0P/z5HX2md3d7L1FhPQE6/Q0E9r9xQ=
|
||||
github.com/metacubex/hpke v0.1.0/go.mod h1:vfDm6gfgrwlXUxKDkWbcE44hXtmc1uxLDm2BcR11b3U=
|
||||
github.com/metacubex/http v0.1.0 h1:Jcy0I9zKjYijSUaksZU34XEe2xNdoFkgUTB7z7K5q0o=
|
||||
github.com/metacubex/http v0.1.0/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
|
||||
github.com/metacubex/http v0.1.1 h1:zVea0zOoaZvxe51EvJMRWGNQv6MvWqJhkZSuoAjOjVw=
|
||||
github.com/metacubex/http v0.1.1/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI=
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc=
|
||||
github.com/metacubex/mhurl v0.1.0 h1:ZdW4Zxe3j3uJ89gNytOazHu6kbHn5owutN/VfXOI8GE=
|
||||
@@ -135,8 +135,8 @@ github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6w
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
|
||||
github.com/metacubex/sing-tun v0.4.16 h1:LosAe4A6TOPVcD7T1ReV9D2r5501woIXXZiim3D0RRg=
|
||||
github.com/metacubex/sing-tun v0.4.16/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
|
||||
github.com/metacubex/sing-tun v0.4.17 h1:ehzvPLyxG1vmjaKVeB0aEK1eqhR3reEzdbqQfM3+5XA=
|
||||
github.com/metacubex/sing-tun v0.4.17/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
|
||||
github.com/metacubex/sing-vmess v0.2.5 h1:m9Zt5I27lB9fmLMZfism9sH2LcnAfShZfwSkf6/KJoE=
|
||||
github.com/metacubex/sing-vmess v0.2.5/go.mod h1:AwtlzUgf8COe9tRYAKqWZ+leDH7p5U98a0ZUpYehl8Q=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
|
||||
@@ -145,8 +145,8 @@ github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 h1:DK2l6m2Fc85H2Bhi
|
||||
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141/go.mod h1:/yI4OiGOSn0SURhZdJF3CbtPg3nwK700bG8TZLMBvAg=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/tls v0.1.4 h1:Gm5GrkyMUh52gYOMIAQ1kHIym4v4M3Qb87Wsmd8Kpdc=
|
||||
github.com/metacubex/tls v0.1.4/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
|
||||
github.com/metacubex/tls v0.1.5 h1:ECcB83dj+zadnhlKcLnUUf1Sq6+vU0f/zoyU0+9oPTc=
|
||||
github.com/metacubex/tls v0.1.5/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
|
||||
github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg=
|
||||
github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
|
||||
|
||||
@@ -93,6 +93,8 @@ type tunSchema struct {
|
||||
IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
||||
IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"`
|
||||
ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||
IncludeMACAddress *[]string `yaml:"include-mac-address" json:"include-mac-address,omitempty"`
|
||||
ExcludeMACAddress *[]string `yaml:"exclude-mac-address" json:"exclude-mac-address,omitempty"`
|
||||
EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
||||
FileDescriptor *int `yaml:"file-descriptor" json:"file-descriptor"`
|
||||
@@ -242,6 +244,12 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun {
|
||||
if p.ExcludePackage != nil {
|
||||
def.ExcludePackage = *p.ExcludePackage
|
||||
}
|
||||
if p.IncludeMACAddress != nil {
|
||||
def.IncludeMACAddress = *p.IncludeMACAddress
|
||||
}
|
||||
if p.ExcludeMACAddress != nil {
|
||||
def.ExcludeMACAddress = *p.ExcludeMACAddress
|
||||
}
|
||||
if p.EndpointIndependentNat != nil {
|
||||
def.EndpointIndependentNat = *p.EndpointIndependentNat
|
||||
}
|
||||
|
||||
@@ -47,6 +47,8 @@ type Tun struct {
|
||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
||||
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
|
||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||
IncludeMACAddress []string `yaml:"include-mac-address" json:"include-mac-address,omitempty"`
|
||||
ExcludeMACAddress []string `yaml:"exclude-mac-address" json:"exclude-mac-address,omitempty"`
|
||||
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
|
||||
DisableICMPForwarding bool `yaml:"disable-icmp-forwarding" json:"disable-icmp-forwarding,omitempty"`
|
||||
@@ -80,6 +82,8 @@ func (t *Tun) Sort() {
|
||||
slices.Sort(t.IncludeAndroidUser)
|
||||
slices.Sort(t.IncludePackage)
|
||||
slices.Sort(t.ExcludePackage)
|
||||
slices.Sort(t.IncludeMACAddress)
|
||||
slices.Sort(t.ExcludeMACAddress)
|
||||
|
||||
slices.SortFunc(t.Inet4RouteAddress, netipx.ComparePrefix)
|
||||
slices.SortFunc(t.Inet6RouteAddress, netipx.ComparePrefix)
|
||||
@@ -185,6 +189,12 @@ func (t *Tun) Equal(other Tun) bool {
|
||||
if !slices.Equal(t.ExcludePackage, other.ExcludePackage) {
|
||||
return false
|
||||
}
|
||||
if !slices.Equal(t.IncludeMACAddress, other.IncludeMACAddress) {
|
||||
return false
|
||||
}
|
||||
if !slices.Equal(t.ExcludeMACAddress, other.ExcludeMACAddress) {
|
||||
return false
|
||||
}
|
||||
if t.EndpointIndependentNat != other.EndpointIndependentNat {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -31,9 +31,12 @@ type VlessServer struct {
|
||||
}
|
||||
|
||||
type XHTTPConfig struct {
|
||||
Path string
|
||||
Host string
|
||||
Mode string
|
||||
Path string
|
||||
Host string
|
||||
Mode string
|
||||
NoSSEHeader bool
|
||||
ScStreamUpServerSecs string
|
||||
ScMaxEachPostBytes int
|
||||
}
|
||||
|
||||
func (t VlessServer) String() string {
|
||||
|
||||
@@ -43,11 +43,10 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, store auth.AuthStore, additions ...
|
||||
conn := N.NewBufferedConn(c)
|
||||
|
||||
authenticator := store.Authenticator()
|
||||
keepAlive := true
|
||||
trusted := authenticator == nil // disable authenticate if lru is nil
|
||||
lastUser := ""
|
||||
|
||||
for keepAlive {
|
||||
for {
|
||||
peekMutex.Lock()
|
||||
request, err := ReadRequest(conn.Reader())
|
||||
peekMutex.Unlock()
|
||||
@@ -57,13 +56,12 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, store auth.AuthStore, additions ...
|
||||
|
||||
request.RemoteAddr = conn.RemoteAddr().String()
|
||||
|
||||
keepAlive = strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive"
|
||||
keepAlive := strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive"
|
||||
|
||||
var resp *http.Response
|
||||
|
||||
var user string
|
||||
resp, user = authenticate(request, authenticator) // always call authenticate function to get user
|
||||
trusted = trusted || resp == nil
|
||||
resp, user := authenticate(request, authenticator) // always call authenticate function to get user
|
||||
if resp == nil {
|
||||
trusted = true
|
||||
}
|
||||
additions[inUserIdx] = inbound.WithInUser(user)
|
||||
|
||||
if trusted {
|
||||
@@ -130,16 +128,21 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, store auth.AuthStore, additions ...
|
||||
removeHopByHopHeaders(resp.Header)
|
||||
}
|
||||
|
||||
if keepAlive {
|
||||
if !keepAlive {
|
||||
resp.Close = true // close connection if keep-alive is not set
|
||||
}
|
||||
if keepAlive && resp.ContentLength > 0 {
|
||||
resp.Close = false // don't need to close connection if content length is positive numbers
|
||||
}
|
||||
|
||||
if !resp.Close {
|
||||
resp.Header.Set("Proxy-Connection", "keep-alive")
|
||||
resp.Header.Set("Connection", "keep-alive")
|
||||
resp.Header.Set("Keep-Alive", "timeout=4")
|
||||
}
|
||||
|
||||
resp.Close = !keepAlive
|
||||
|
||||
err = resp.Write(conn)
|
||||
if err != nil {
|
||||
if err != nil || resp.Close {
|
||||
break // close connection
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/base64"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -166,6 +167,9 @@ func TestInboundShadowSocks_ShadowTlsv3(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInboundShadowSocks_KcpTun(t *testing.T) {
|
||||
if runtime.GOOS == "windows" && strings.HasPrefix(runtime.Version(), "go1.20") {
|
||||
t.Skip("skip kcptun test on windows go1.20")
|
||||
}
|
||||
inboundOptions := inbound.ShadowSocksOption{
|
||||
KcpTun: inbound.KcpTun{
|
||||
Enable: true,
|
||||
|
||||
@@ -48,6 +48,8 @@ type TunOption struct {
|
||||
IncludeAndroidUser []int `inbound:"include-android-user,omitempty"`
|
||||
IncludePackage []string `inbound:"include-package,omitempty"`
|
||||
ExcludePackage []string `inbound:"exclude-package,omitempty"`
|
||||
IncludeMACAddress []string `inbound:"include-mac-address,omitempty"`
|
||||
ExcludeMACAddress []string `inbound:"exclude-mac-address,omitempty"`
|
||||
EndpointIndependentNat bool `inbound:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout int64 `inbound:"udp-timeout,omitempty"`
|
||||
DisableICMPForwarding bool `inbound:"disable-icmp-forwarding,omitempty"`
|
||||
@@ -123,6 +125,8 @@ func NewTun(options *TunOption) (*Tun, error) {
|
||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||
IncludePackage: options.IncludePackage,
|
||||
ExcludePackage: options.ExcludePackage,
|
||||
IncludeMACAddress: options.IncludeMACAddress,
|
||||
ExcludeMACAddress: options.ExcludeMACAddress,
|
||||
EndpointIndependentNat: options.EndpointIndependentNat,
|
||||
UDPTimeout: options.UDPTimeout,
|
||||
DisableICMPForwarding: options.DisableICMPForwarding,
|
||||
|
||||
@@ -32,16 +32,22 @@ type VlessUser struct {
|
||||
}
|
||||
|
||||
type XHTTPConfig struct {
|
||||
Path string `inbound:"path,omitempty"`
|
||||
Host string `inbound:"host,omitempty"`
|
||||
Mode string `inbound:"mode,omitempty"`
|
||||
Path string `inbound:"path,omitempty"`
|
||||
Host string `inbound:"host,omitempty"`
|
||||
Mode string `inbound:"mode,omitempty"`
|
||||
NoSSEHeader bool `inbound:"no-sse-header,omitempty"`
|
||||
ScStreamUpServerSecs string `inbound:"sc-stream-up-server-secs,omitempty"`
|
||||
ScMaxEachPostBytes int `inbound:"sc-max-each-post-bytes,omitempty"`
|
||||
}
|
||||
|
||||
func (o XHTTPConfig) Build() LC.XHTTPConfig {
|
||||
return LC.XHTTPConfig{
|
||||
Path: o.Path,
|
||||
Host: o.Host,
|
||||
Mode: o.Mode,
|
||||
Path: o.Path,
|
||||
Host: o.Host,
|
||||
Mode: o.Mode,
|
||||
NoSSEHeader: o.NoSSEHeader,
|
||||
ScStreamUpServerSecs: o.ScStreamUpServerSecs,
|
||||
ScMaxEachPostBytes: o.ScMaxEachPostBytes,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -342,105 +342,168 @@ func TestInboundVless_Reality_Grpc(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInboundVless_XHTTP(t *testing.T) {
|
||||
inboundOptions := inbound.VlessOption{
|
||||
Certificate: tlsCertificate,
|
||||
PrivateKey: tlsPrivateKey,
|
||||
XHTTPConfig: inbound.XHTTPConfig{
|
||||
Path: "/vless-xhttp",
|
||||
Host: "example.com",
|
||||
Mode: "auto",
|
||||
},
|
||||
testCases := []struct {
|
||||
mode string
|
||||
}{
|
||||
{mode: "auto"},
|
||||
{mode: "stream-one"},
|
||||
{mode: "stream-up"},
|
||||
{mode: "packet-up"},
|
||||
}
|
||||
outboundOptions := outbound.VlessOption{
|
||||
TLS: true,
|
||||
Fingerprint: tlsFingerprint,
|
||||
Network: "xhttp",
|
||||
XHTTPOpts: outbound.XHTTPOptions{
|
||||
Path: "/vless-xhttp",
|
||||
Host: "example.com",
|
||||
Mode: "auto",
|
||||
},
|
||||
}
|
||||
testInboundVlessTLS(t, inboundOptions, outboundOptions, false)
|
||||
}
|
||||
|
||||
func TestInboundVless_Reality_XHTTP(t *testing.T) {
|
||||
inboundOptions := inbound.VlessOption{
|
||||
RealityConfig: inbound.RealityConfig{
|
||||
Dest: net.JoinHostPort(realityDest, "443"),
|
||||
PrivateKey: realityPrivateKey,
|
||||
ShortID: []string{realityShortid},
|
||||
ServerNames: []string{realityDest},
|
||||
},
|
||||
XHTTPConfig: inbound.XHTTPConfig{
|
||||
Mode: "auto",
|
||||
},
|
||||
}
|
||||
outboundOptions := outbound.VlessOption{
|
||||
TLS: true,
|
||||
ServerName: realityDest,
|
||||
RealityOpts: outbound.RealityOptions{
|
||||
PublicKey: realityPublickey,
|
||||
ShortID: realityShortid,
|
||||
},
|
||||
ClientFingerprint: "chrome",
|
||||
Network: "xhttp",
|
||||
XHTTPOpts: outbound.XHTTPOptions{
|
||||
Mode: "auto",
|
||||
},
|
||||
}
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
}
|
||||
|
||||
func TestInboundVless_XHTTP_DownloadSettings(t *testing.T) {
|
||||
for _, mode := range []string{"stream-up", "packet-up"} {
|
||||
t.Run(mode, func(t *testing.T) {
|
||||
inboundOptions := inbound.VlessOption{
|
||||
Certificate: tlsCertificate,
|
||||
PrivateKey: tlsPrivateKey,
|
||||
XHTTPConfig: inbound.XHTTPConfig{
|
||||
Path: "/vless-xhttp",
|
||||
Host: "example.com",
|
||||
Mode: mode,
|
||||
},
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testCase.mode, func(t *testing.T) {
|
||||
getConfig := func() (inbound.VlessOption, outbound.VlessOption) {
|
||||
inboundOptions := inbound.VlessOption{
|
||||
Certificate: tlsCertificate,
|
||||
PrivateKey: tlsPrivateKey,
|
||||
XHTTPConfig: inbound.XHTTPConfig{
|
||||
Path: "/vless-xhttp",
|
||||
Host: "example.com",
|
||||
Mode: testCase.mode,
|
||||
},
|
||||
}
|
||||
outboundOptions := outbound.VlessOption{
|
||||
TLS: true,
|
||||
Fingerprint: tlsFingerprint,
|
||||
ServerName: "example.org",
|
||||
ClientFingerprint: "chrome",
|
||||
Network: "xhttp",
|
||||
XHTTPOpts: outbound.XHTTPOptions{
|
||||
Path: "/vless-xhttp",
|
||||
Host: "example.com",
|
||||
Mode: testCase.mode,
|
||||
},
|
||||
}
|
||||
return inboundOptions, outboundOptions
|
||||
}
|
||||
outboundOptions := outbound.VlessOption{
|
||||
TLS: true,
|
||||
Fingerprint: tlsFingerprint,
|
||||
ServerName: "example.org",
|
||||
ClientFingerprint: "chrome",
|
||||
Network: "xhttp",
|
||||
XHTTPOpts: outbound.XHTTPOptions{
|
||||
Path: "/vless-xhttp",
|
||||
Host: "example.com",
|
||||
Mode: mode,
|
||||
DownloadSettings: &outbound.XHTTPDownloadSettings{},
|
||||
},
|
||||
}
|
||||
testInboundVlessTLS(t, inboundOptions, outboundOptions, false)
|
||||
|
||||
t.Run("nosplit", func(t *testing.T) {
|
||||
t.Run("single", func(t *testing.T) {
|
||||
inboundOptions, outboundOptions := getConfig()
|
||||
testInboundVlessTLS(t, inboundOptions, outboundOptions, false)
|
||||
})
|
||||
|
||||
t.Run("reuse", func(t *testing.T) {
|
||||
inboundOptions, outboundOptions := getConfig()
|
||||
testInboundVlessTLS(t, inboundOptions, withXHTTPReuse(outboundOptions), false)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("split", func(t *testing.T) {
|
||||
if testCase.mode == "stream-one" { // stream-one not supported download settings
|
||||
return
|
||||
}
|
||||
|
||||
t.Run("single", func(t *testing.T) {
|
||||
inboundOptions, outboundOptions := getConfig()
|
||||
outboundOptions.XHTTPOpts.DownloadSettings = &outbound.XHTTPDownloadSettings{}
|
||||
testInboundVlessTLS(t, inboundOptions, outboundOptions, false)
|
||||
})
|
||||
|
||||
t.Run("reuse", func(t *testing.T) {
|
||||
inboundOptions, outboundOptions := getConfig()
|
||||
outboundOptions.XHTTPOpts.DownloadSettings = &outbound.XHTTPDownloadSettings{}
|
||||
testInboundVlessTLS(t, inboundOptions, withXHTTPReuse(outboundOptions), false)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInboundVless_XHTTP_StreamUp(t *testing.T) {
|
||||
inboundOptions := inbound.VlessOption{
|
||||
Certificate: tlsCertificate,
|
||||
PrivateKey: tlsPrivateKey,
|
||||
XHTTPConfig: inbound.XHTTPConfig{
|
||||
Path: "/vless-xhttp",
|
||||
Host: "example.com",
|
||||
Mode: "stream-up",
|
||||
},
|
||||
func TestInboundVless_XHTTP_Reality(t *testing.T) {
|
||||
testCases := []struct {
|
||||
mode string
|
||||
}{
|
||||
{mode: "auto"},
|
||||
{mode: "stream-one"},
|
||||
{mode: "stream-up"},
|
||||
{mode: "packet-up"},
|
||||
}
|
||||
outboundOptions := outbound.VlessOption{
|
||||
TLS: true,
|
||||
Fingerprint: tlsFingerprint,
|
||||
Network: "xhttp",
|
||||
XHTTPOpts: outbound.XHTTPOptions{
|
||||
Path: "/vless-xhttp",
|
||||
Host: "example.com",
|
||||
Mode: "stream-up",
|
||||
},
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(testCase.mode, func(t *testing.T) {
|
||||
getConfig := func() (inbound.VlessOption, outbound.VlessOption) {
|
||||
inboundOptions := inbound.VlessOption{
|
||||
RealityConfig: inbound.RealityConfig{
|
||||
Dest: net.JoinHostPort(realityDest, "443"),
|
||||
PrivateKey: realityPrivateKey,
|
||||
ShortID: []string{realityShortid},
|
||||
ServerNames: []string{realityDest},
|
||||
},
|
||||
XHTTPConfig: inbound.XHTTPConfig{
|
||||
Path: "/vless-xhttp",
|
||||
Host: "example.com",
|
||||
Mode: testCase.mode,
|
||||
},
|
||||
}
|
||||
outboundOptions := outbound.VlessOption{
|
||||
TLS: true,
|
||||
ServerName: realityDest,
|
||||
RealityOpts: outbound.RealityOptions{
|
||||
PublicKey: realityPublickey,
|
||||
ShortID: realityShortid,
|
||||
},
|
||||
ClientFingerprint: "chrome",
|
||||
Network: "xhttp",
|
||||
XHTTPOpts: outbound.XHTTPOptions{
|
||||
Path: "/vless-xhttp",
|
||||
Host: "example.com",
|
||||
Mode: testCase.mode,
|
||||
},
|
||||
}
|
||||
return inboundOptions, outboundOptions
|
||||
}
|
||||
|
||||
t.Run("nosplit", func(t *testing.T) {
|
||||
t.Run("single", func(t *testing.T) {
|
||||
inboundOptions, outboundOptions := getConfig()
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
|
||||
t.Run("reuse", func(t *testing.T) {
|
||||
inboundOptions, outboundOptions := getConfig()
|
||||
testInboundVless(t, inboundOptions, withXHTTPReuse(outboundOptions))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("split", func(t *testing.T) {
|
||||
if testCase.mode == "stream-one" { // stream-one not supported download settings
|
||||
return
|
||||
}
|
||||
|
||||
t.Run("single", func(t *testing.T) {
|
||||
inboundOptions, outboundOptions := getConfig()
|
||||
outboundOptions.XHTTPOpts.DownloadSettings = &outbound.XHTTPDownloadSettings{}
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
|
||||
t.Run("reuse", func(t *testing.T) {
|
||||
inboundOptions, outboundOptions := getConfig()
|
||||
outboundOptions.XHTTPOpts.DownloadSettings = &outbound.XHTTPDownloadSettings{}
|
||||
testInboundVless(t, inboundOptions, withXHTTPReuse(outboundOptions))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
testInboundVlessTLS(t, inboundOptions, outboundOptions, false)
|
||||
}
|
||||
|
||||
func withXHTTPReuse(out outbound.VlessOption) outbound.VlessOption {
|
||||
out.XHTTPOpts.ReuseSettings = &outbound.XHTTPReuseSettings{
|
||||
MaxConnections: "0",
|
||||
MaxConcurrency: "16-32",
|
||||
CMaxReuseTimes: "0",
|
||||
HMaxRequestTimes: "600-900",
|
||||
HMaxReusableSecs: "1800-3000",
|
||||
}
|
||||
if out.XHTTPOpts.DownloadSettings != nil {
|
||||
out.XHTTPOpts.DownloadSettings.ReuseSettings = &outbound.XHTTPReuseSettings{
|
||||
MaxConnections: "0",
|
||||
MaxConcurrency: "16-32",
|
||||
CMaxReuseTimes: "0",
|
||||
HMaxRequestTimes: "600-900",
|
||||
HMaxReusableSecs: "1800-3000",
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -19,9 +19,6 @@ import (
|
||||
)
|
||||
|
||||
func (h *ListenerHandler) ShouldHijackDns(targetAddr netip.AddrPort) bool {
|
||||
if targetAddr.Addr().IsLoopback() && targetAddr.Port() == 53 { // cause by system stack
|
||||
return true
|
||||
}
|
||||
for _, addrPort := range h.DnsAddrPorts {
|
||||
if addrPort == targetAddr || (addrPort.Addr().IsUnspecified() && targetAddr.Port() == 53) {
|
||||
return true
|
||||
|
||||
@@ -251,6 +251,22 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
|
||||
return nil, E.Cause(err, "parse exclude_dst_port_range")
|
||||
}
|
||||
}
|
||||
var includeMACAddress []net.HardwareAddr
|
||||
for _, mac := range options.IncludeMACAddress {
|
||||
addr, err := net.ParseMAC(mac)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse include_mac_address")
|
||||
}
|
||||
includeMACAddress = append(includeMACAddress, addr)
|
||||
}
|
||||
var excludeMACAddress []net.HardwareAddr
|
||||
for _, mac := range options.ExcludeMACAddress {
|
||||
addr, err := net.ParseMAC(mac)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse exclude_mac_address")
|
||||
}
|
||||
excludeMACAddress = append(excludeMACAddress, addr)
|
||||
}
|
||||
|
||||
var dnsAdds []netip.AddrPort
|
||||
|
||||
@@ -390,6 +406,8 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
|
||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||
IncludePackage: options.IncludePackage,
|
||||
ExcludePackage: options.ExcludePackage,
|
||||
IncludeMACAddress: includeMACAddress,
|
||||
ExcludeMACAddress: excludeMACAddress,
|
||||
FileDescriptor: options.FileDescriptor,
|
||||
InterfaceMonitor: defaultInterfaceMonitor,
|
||||
EXP_RecvMsgX: options.RecvMsgX,
|
||||
|
||||
@@ -155,14 +155,22 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
}
|
||||
if config.XHTTPConfig.Path != "" || config.XHTTPConfig.Host != "" || config.XHTTPConfig.Mode != "" {
|
||||
httpServer.Handler = xhttp.NewServerHandler(xhttp.ServerOption{
|
||||
Path: config.XHTTPConfig.Path,
|
||||
Host: config.XHTTPConfig.Host,
|
||||
Mode: config.XHTTPConfig.Mode,
|
||||
Config: xhttp.Config{
|
||||
Host: config.XHTTPConfig.Host,
|
||||
Path: config.XHTTPConfig.Path,
|
||||
Mode: config.XHTTPConfig.Mode,
|
||||
NoSSEHeader: config.XHTTPConfig.NoSSEHeader,
|
||||
ScStreamUpServerSecs: config.XHTTPConfig.ScStreamUpServerSecs,
|
||||
ScMaxEachPostBytes: config.XHTTPConfig.ScMaxEachPostBytes,
|
||||
},
|
||||
ConnHandler: func(conn net.Conn) {
|
||||
sl.HandleConn(conn, tunnel, additions...)
|
||||
},
|
||||
HttpHandler: httpServer.Handler,
|
||||
})
|
||||
if !slices.Contains(tlsConfig.NextProtos, "http/1.1") {
|
||||
tlsConfig.NextProtos = append([]string{"http/1.1"}, tlsConfig.NextProtos...)
|
||||
}
|
||||
if !slices.Contains(tlsConfig.NextProtos, "h2") {
|
||||
tlsConfig.NextProtos = append([]string{"h2"}, tlsConfig.NextProtos...)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
@@ -51,6 +52,7 @@ type Conn struct {
|
||||
|
||||
closeMutex sync.Mutex
|
||||
closed bool
|
||||
onClose func()
|
||||
|
||||
// deadlines
|
||||
deadline *time.Timer
|
||||
@@ -103,7 +105,7 @@ func (g *Conn) read(b []byte) (n int, err error) {
|
||||
size = len(b)
|
||||
}
|
||||
|
||||
n, err = io.ReadFull(g.reader, b[:size])
|
||||
n, err = g.reader.Read(b[:size])
|
||||
g.remain -= n
|
||||
return
|
||||
}
|
||||
@@ -112,6 +114,9 @@ func (g *Conn) read(b []byte) (n int, err error) {
|
||||
var discard [6]byte
|
||||
_, err = io.ReadFull(g.reader, discard[:])
|
||||
if err != nil {
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
err = io.EOF
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -206,6 +211,10 @@ func (g *Conn) Close() error {
|
||||
}
|
||||
}
|
||||
|
||||
if g.onClose != nil {
|
||||
g.onClose()
|
||||
}
|
||||
|
||||
return errors.Join(errorArr...)
|
||||
}
|
||||
|
||||
@@ -237,6 +246,7 @@ type Transport struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
closeOnce sync.Once
|
||||
count atomic.Int64
|
||||
}
|
||||
|
||||
func (t *Transport) Close() error {
|
||||
@@ -267,19 +277,10 @@ func NewTransport(dialFn DialFn, tlsConfig *vmess.TLSConfig, gunCfg *Config) *Tr
|
||||
}
|
||||
|
||||
if tlsConfig.Reality == nil { // reality doesn't return the negotiated ALPN
|
||||
switch tlsConn := conn.(type) {
|
||||
case interface{ ConnectionState() tls.ConnectionState }:
|
||||
state := tlsConn.ConnectionState()
|
||||
if p := state.NegotiatedProtocol; p != http.Http2NextProtoTLS {
|
||||
_ = conn.Close()
|
||||
return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http.Http2NextProtoTLS)
|
||||
}
|
||||
case interface{ ConnectionState() tlsC.ConnectionState }:
|
||||
state := tlsConn.ConnectionState()
|
||||
if p := state.NegotiatedProtocol; p != http.Http2NextProtoTLS {
|
||||
_ = conn.Close()
|
||||
return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http.Http2NextProtoTLS)
|
||||
}
|
||||
state := tlsC.GetTLSConnectionState(conn)
|
||||
if p := state.NegotiatedProtocol; p != http.Http2NextProtoTLS {
|
||||
_ = conn.Close()
|
||||
return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http.Http2NextProtoTLS)
|
||||
}
|
||||
}
|
||||
return conn, nil
|
||||
@@ -355,6 +356,9 @@ func (t *Transport) Dial() (net.Conn, error) {
|
||||
writer: writer,
|
||||
}
|
||||
|
||||
t.count.Add(1)
|
||||
conn.onClose = func() { t.count.Add(-1) }
|
||||
|
||||
go conn.Init()
|
||||
|
||||
// ensure conn.initOnce.Do has been called before return
|
||||
@@ -364,6 +368,78 @@ func (t *Transport) Dial() (net.Conn, error) {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
mutex sync.Mutex
|
||||
maxConnections int
|
||||
minStreams int
|
||||
maxStreams int
|
||||
transports []*Transport
|
||||
maker func() *Transport
|
||||
}
|
||||
|
||||
func NewClient(maker func() *Transport, maxConnections, minStreams, maxStreams int) *Client {
|
||||
if maxConnections == 0 && minStreams == 0 && maxStreams == 0 {
|
||||
maxConnections = 1
|
||||
}
|
||||
return &Client{
|
||||
maxConnections: maxConnections,
|
||||
minStreams: minStreams,
|
||||
maxStreams: maxStreams,
|
||||
maker: maker,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Dial() (net.Conn, error) {
|
||||
return c.getTransport().Dial()
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
var errs []error
|
||||
for _, t := range c.transports {
|
||||
if err := t.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
c.transports = nil
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (c *Client) getTransport() *Transport {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
var transport *Transport
|
||||
for _, t := range c.transports {
|
||||
if transport == nil || t.count.Load() < transport.count.Load() {
|
||||
transport = t
|
||||
}
|
||||
}
|
||||
if transport == nil {
|
||||
return c.newTransportLocked()
|
||||
}
|
||||
numStreams := int(transport.count.Load())
|
||||
if numStreams == 0 {
|
||||
return transport
|
||||
}
|
||||
if c.maxConnections > 0 {
|
||||
if len(c.transports) >= c.maxConnections || numStreams < c.minStreams {
|
||||
return transport
|
||||
}
|
||||
} else {
|
||||
if c.maxStreams > 0 && numStreams < c.maxStreams {
|
||||
return transport
|
||||
}
|
||||
}
|
||||
return c.newTransportLocked()
|
||||
}
|
||||
|
||||
func (c *Client) newTransportLocked() *Transport {
|
||||
transport := c.maker()
|
||||
c.transports = append(c.transports, transport)
|
||||
return transport
|
||||
}
|
||||
|
||||
func StreamGunWithConn(conn net.Conn, tlsConfig *vmess.TLSConfig, gunCfg *Config) (net.Conn, error) {
|
||||
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return conn, nil
|
||||
|
||||
@@ -112,11 +112,6 @@ func (w *h2ConnWrapper) CloseWrapper() {
|
||||
w.closed = true
|
||||
}
|
||||
|
||||
func (w *h2ConnWrapper) Close() error {
|
||||
w.CloseWrapper()
|
||||
return w.ExtendedConn.Close()
|
||||
}
|
||||
|
||||
func (w *h2ConnWrapper) Upstream() any {
|
||||
return w.ExtendedConn
|
||||
}
|
||||
|
||||
@@ -66,13 +66,7 @@ func WriteKIPMessage(w io.Writer, typ byte, payload []byte) error {
|
||||
hdr[3] = typ
|
||||
binary.BigEndian.PutUint16(hdr[4:], uint16(len(payload)))
|
||||
|
||||
if err := writeFull(w, hdr[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(payload) == 0 {
|
||||
return nil
|
||||
}
|
||||
return writeFull(w, payload)
|
||||
return writeAllChunks(w, hdr[:], payload)
|
||||
}
|
||||
|
||||
func ReadKIPMessage(r io.Reader) (*KIPMessage, error) {
|
||||
|
||||
+1
-18
@@ -173,16 +173,10 @@ func (s *Session) sendFrame(frameType byte, streamID uint32, payload []byte) err
|
||||
s.writeMu.Lock()
|
||||
defer s.writeMu.Unlock()
|
||||
|
||||
if err := writeFull(s.conn, header[:]); err != nil {
|
||||
if err := writeAllChunks(s.conn, header[:], payload); err != nil {
|
||||
s.closeWithError(err)
|
||||
return err
|
||||
}
|
||||
if len(payload) > 0 {
|
||||
if err := writeFull(s.conn, payload); err != nil {
|
||||
s.closeWithError(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -315,17 +309,6 @@ func (s *Session) readLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
func writeFull(w io.Writer, b []byte) error {
|
||||
for len(b) > 0 {
|
||||
n, err := w.Write(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b = b[n:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func trimASCII(b []byte) string {
|
||||
i := 0
|
||||
j := len(b)
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package multiplex
|
||||
|
||||
import "io"
|
||||
|
||||
func writeAllChunks(w io.Writer, chunks ...[]byte) error {
|
||||
for _, chunk := range chunks {
|
||||
for len(chunk) > 0 {
|
||||
n, err := w.Write(chunk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
chunk = chunk[n:]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+36
-70
@@ -3,12 +3,9 @@ package sudoku
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
crypto_rand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const IOBufferSize = 32 * 1024
|
||||
@@ -45,14 +42,17 @@ type Conn struct {
|
||||
table *Table
|
||||
reader *bufio.Reader
|
||||
recorder *bytes.Buffer
|
||||
recording bool
|
||||
recording atomic.Bool
|
||||
recordLock sync.Mutex
|
||||
|
||||
rawBuf []byte
|
||||
pendingData []byte
|
||||
hintBuf []byte
|
||||
pendingData pendingBuffer
|
||||
hintBuf [4]byte
|
||||
hintCount int
|
||||
writeMu sync.Mutex
|
||||
writeBuf []byte
|
||||
|
||||
rng *rand.Rand
|
||||
rng randomSource
|
||||
paddingThreshold uint64
|
||||
}
|
||||
|
||||
@@ -77,33 +77,28 @@ func (sc *Conn) CloseRead() error {
|
||||
}
|
||||
|
||||
func NewConn(c net.Conn, table *Table, pMin, pMax int, record bool) *Conn {
|
||||
var seedBytes [8]byte
|
||||
if _, err := crypto_rand.Read(seedBytes[:]); err != nil {
|
||||
binary.BigEndian.PutUint64(seedBytes[:], uint64(rand.Int63()))
|
||||
}
|
||||
seed := int64(binary.BigEndian.Uint64(seedBytes[:]))
|
||||
localRng := rand.New(rand.NewSource(seed))
|
||||
localRng := newSeededRand()
|
||||
|
||||
sc := &Conn{
|
||||
Conn: c,
|
||||
table: table,
|
||||
reader: bufio.NewReaderSize(c, IOBufferSize),
|
||||
rawBuf: make([]byte, IOBufferSize),
|
||||
pendingData: make([]byte, 0, 4096),
|
||||
hintBuf: make([]byte, 0, 4),
|
||||
pendingData: newPendingBuffer(4096),
|
||||
writeBuf: make([]byte, 0, 4096),
|
||||
rng: localRng,
|
||||
paddingThreshold: pickPaddingThreshold(localRng, pMin, pMax),
|
||||
}
|
||||
if record {
|
||||
sc.recorder = new(bytes.Buffer)
|
||||
sc.recording = true
|
||||
sc.recording.Store(true)
|
||||
}
|
||||
return sc
|
||||
}
|
||||
|
||||
func (sc *Conn) StopRecording() {
|
||||
sc.recordLock.Lock()
|
||||
sc.recording = false
|
||||
sc.recording.Store(false)
|
||||
sc.recorder = nil
|
||||
sc.recordLock.Unlock()
|
||||
}
|
||||
@@ -137,74 +132,50 @@ func (sc *Conn) Write(p []byte) (n int, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
outCapacity := len(p) * 6
|
||||
out := make([]byte, 0, outCapacity)
|
||||
pads := sc.table.PaddingPool
|
||||
padLen := len(pads)
|
||||
sc.writeMu.Lock()
|
||||
defer sc.writeMu.Unlock()
|
||||
|
||||
for _, b := range p {
|
||||
if shouldPad(sc.rng, sc.paddingThreshold) {
|
||||
out = append(out, pads[sc.rng.Intn(padLen)])
|
||||
}
|
||||
|
||||
puzzles := sc.table.EncodeTable[b]
|
||||
puzzle := puzzles[sc.rng.Intn(len(puzzles))]
|
||||
|
||||
perm := perm4[sc.rng.Intn(len(perm4))]
|
||||
for _, idx := range perm {
|
||||
if shouldPad(sc.rng, sc.paddingThreshold) {
|
||||
out = append(out, pads[sc.rng.Intn(padLen)])
|
||||
}
|
||||
out = append(out, puzzle[idx])
|
||||
}
|
||||
}
|
||||
|
||||
if shouldPad(sc.rng, sc.paddingThreshold) {
|
||||
out = append(out, pads[sc.rng.Intn(padLen)])
|
||||
}
|
||||
|
||||
return len(p), writeFull(sc.Conn, out)
|
||||
sc.writeBuf = encodeSudokuPayload(sc.writeBuf[:0], sc.table, sc.rng, sc.paddingThreshold, p)
|
||||
return len(p), writeFull(sc.Conn, sc.writeBuf)
|
||||
}
|
||||
|
||||
func (sc *Conn) Read(p []byte) (n int, err error) {
|
||||
if len(sc.pendingData) > 0 {
|
||||
n = copy(p, sc.pendingData)
|
||||
if n == len(sc.pendingData) {
|
||||
sc.pendingData = sc.pendingData[:0]
|
||||
} else {
|
||||
sc.pendingData = sc.pendingData[n:]
|
||||
}
|
||||
if n, ok := drainPending(p, &sc.pendingData); ok {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
for {
|
||||
if len(sc.pendingData) > 0 {
|
||||
if sc.pendingData.available() > 0 {
|
||||
break
|
||||
}
|
||||
|
||||
nr, rErr := sc.reader.Read(sc.rawBuf)
|
||||
if nr > 0 {
|
||||
chunk := sc.rawBuf[:nr]
|
||||
sc.recordLock.Lock()
|
||||
if sc.recording {
|
||||
sc.recorder.Write(chunk)
|
||||
if sc.recording.Load() {
|
||||
sc.recordLock.Lock()
|
||||
if sc.recording.Load() && sc.recorder != nil {
|
||||
sc.recorder.Write(chunk)
|
||||
}
|
||||
sc.recordLock.Unlock()
|
||||
}
|
||||
sc.recordLock.Unlock()
|
||||
|
||||
layout := sc.table.layout
|
||||
for _, b := range chunk {
|
||||
if !sc.table.layout.isHint(b) {
|
||||
if !layout.hintTable[b] {
|
||||
continue
|
||||
}
|
||||
|
||||
sc.hintBuf = append(sc.hintBuf, b)
|
||||
if len(sc.hintBuf) == 4 {
|
||||
key := packHintsToKey([4]byte{sc.hintBuf[0], sc.hintBuf[1], sc.hintBuf[2], sc.hintBuf[3]})
|
||||
sc.hintBuf[sc.hintCount] = b
|
||||
sc.hintCount++
|
||||
if sc.hintCount == len(sc.hintBuf) {
|
||||
key := packHintsToKey(sc.hintBuf)
|
||||
val, ok := sc.table.DecodeMap[key]
|
||||
if !ok {
|
||||
return 0, errors.New("INVALID_SUDOKU_MAP_MISS")
|
||||
return 0, ErrInvalidSudokuMapMiss
|
||||
}
|
||||
sc.pendingData = append(sc.pendingData, val)
|
||||
sc.hintBuf = sc.hintBuf[:0]
|
||||
sc.pendingData.appendByte(val)
|
||||
sc.hintCount = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,16 +183,11 @@ func (sc *Conn) Read(p []byte) (n int, err error) {
|
||||
if rErr != nil {
|
||||
return 0, rErr
|
||||
}
|
||||
if len(sc.pendingData) > 0 {
|
||||
if sc.pendingData.available() > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
n = copy(p, sc.pendingData)
|
||||
if n == len(sc.pendingData) {
|
||||
sc.pendingData = sc.pendingData[:0]
|
||||
} else {
|
||||
sc.pendingData = sc.pendingData[n:]
|
||||
}
|
||||
n, _ = drainPending(p, &sc.pendingData)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package sudoku
|
||||
|
||||
func encodeSudokuPayload(dst []byte, table *Table, rng randomSource, paddingThreshold uint64, p []byte) []byte {
|
||||
if len(p) == 0 {
|
||||
return dst[:0]
|
||||
}
|
||||
|
||||
outCapacity := len(p)*6 + 1
|
||||
if cap(dst) < outCapacity {
|
||||
dst = make([]byte, 0, outCapacity)
|
||||
}
|
||||
out := dst[:0]
|
||||
pads := table.PaddingPool
|
||||
padLen := len(pads)
|
||||
|
||||
for _, b := range p {
|
||||
if shouldPad(rng, paddingThreshold) {
|
||||
out = append(out, pads[rng.Intn(padLen)])
|
||||
}
|
||||
|
||||
puzzles := table.EncodeTable[b]
|
||||
puzzle := puzzles[rng.Intn(len(puzzles))]
|
||||
perm := perm4[rng.Intn(len(perm4))]
|
||||
for _, idx := range perm {
|
||||
if shouldPad(rng, paddingThreshold) {
|
||||
out = append(out, pads[rng.Intn(padLen)])
|
||||
}
|
||||
out = append(out, puzzle[idx])
|
||||
}
|
||||
}
|
||||
|
||||
if shouldPad(rng, paddingThreshold) {
|
||||
out = append(out, pads[rng.Intn(padLen)])
|
||||
}
|
||||
return out
|
||||
}
|
||||
+112
-78
@@ -14,17 +14,30 @@ type byteLayout struct {
|
||||
padMarker byte
|
||||
paddingPool []byte
|
||||
|
||||
encodeHint func(val, pos byte) byte
|
||||
encodeGroup func(group byte) byte
|
||||
decodeGroup func(b byte) (byte, bool)
|
||||
hintTable [256]bool
|
||||
encodeHint [4][16]byte
|
||||
encodeGroup [64]byte
|
||||
decodeGroup [256]byte
|
||||
groupValid [256]bool
|
||||
}
|
||||
|
||||
func (l *byteLayout) isHint(b byte) bool {
|
||||
if (b & l.hintMask) == l.hintValue {
|
||||
return true
|
||||
return l != nil && l.hintTable[b]
|
||||
}
|
||||
|
||||
func (l *byteLayout) hintByte(val, pos byte) byte {
|
||||
return l.encodeHint[val&0x03][pos&0x0F]
|
||||
}
|
||||
|
||||
func (l *byteLayout) groupByte(group byte) byte {
|
||||
return l.encodeGroup[group&0x3F]
|
||||
}
|
||||
|
||||
func (l *byteLayout) decodePackedGroup(b byte) (byte, bool) {
|
||||
if l == nil {
|
||||
return 0, false
|
||||
}
|
||||
// ASCII layout maps the single non-printable marker (0x7F) to '\n' on the wire.
|
||||
return l.name == "ascii" && b == '\n'
|
||||
return l.decodeGroup[b], l.groupValid[b]
|
||||
}
|
||||
|
||||
// resolveLayout picks the byte layout for a single traffic direction.
|
||||
@@ -50,38 +63,44 @@ func newASCIILayout() *byteLayout {
|
||||
for i := 0; i < 32; i++ {
|
||||
padding = append(padding, byte(0x20+i))
|
||||
}
|
||||
return &byteLayout{
|
||||
|
||||
layout := &byteLayout{
|
||||
name: "ascii",
|
||||
hintMask: 0x40,
|
||||
hintValue: 0x40,
|
||||
padMarker: 0x3F,
|
||||
paddingPool: padding,
|
||||
encodeHint: func(val, pos byte) byte {
|
||||
b := 0x40 | ((val & 0x03) << 4) | (pos & 0x0F)
|
||||
// Avoid DEL (0x7F) in prefer_ascii mode; map it to '\n' to reduce fingerprint.
|
||||
if b == 0x7F {
|
||||
return '\n'
|
||||
}
|
||||
return b
|
||||
},
|
||||
encodeGroup: func(group byte) byte {
|
||||
b := 0x40 | (group & 0x3F)
|
||||
// Avoid DEL (0x7F) in prefer_ascii mode; map it to '\n' to reduce fingerprint.
|
||||
if b == 0x7F {
|
||||
return '\n'
|
||||
}
|
||||
return b
|
||||
},
|
||||
decodeGroup: func(b byte) (byte, bool) {
|
||||
if b == '\n' {
|
||||
return 0x3F, true
|
||||
}
|
||||
if (b & 0x40) == 0 {
|
||||
return 0, false
|
||||
}
|
||||
return b & 0x3F, true
|
||||
},
|
||||
}
|
||||
|
||||
for val := 0; val < 4; val++ {
|
||||
for pos := 0; pos < 16; pos++ {
|
||||
b := byte(0x40 | (byte(val) << 4) | byte(pos))
|
||||
if b == 0x7F {
|
||||
b = '\n'
|
||||
}
|
||||
layout.encodeHint[val][pos] = b
|
||||
}
|
||||
}
|
||||
for group := 0; group < 64; group++ {
|
||||
b := byte(0x40 | byte(group))
|
||||
if b == 0x7F {
|
||||
b = '\n'
|
||||
}
|
||||
layout.encodeGroup[group] = b
|
||||
}
|
||||
for b := 0; b < 256; b++ {
|
||||
wire := byte(b)
|
||||
if (wire & 0x40) == 0x40 {
|
||||
layout.hintTable[wire] = true
|
||||
layout.decodeGroup[wire] = wire & 0x3F
|
||||
layout.groupValid[wire] = true
|
||||
}
|
||||
}
|
||||
layout.hintTable['\n'] = true
|
||||
layout.decodeGroup['\n'] = 0x3F
|
||||
layout.groupValid['\n'] = true
|
||||
|
||||
return layout
|
||||
}
|
||||
|
||||
func newEntropyLayout() *byteLayout {
|
||||
@@ -90,26 +109,35 @@ func newEntropyLayout() *byteLayout {
|
||||
padding = append(padding, byte(0x80+i))
|
||||
padding = append(padding, byte(0x10+i))
|
||||
}
|
||||
return &byteLayout{
|
||||
|
||||
layout := &byteLayout{
|
||||
name: "entropy",
|
||||
hintMask: 0x90,
|
||||
hintValue: 0x00,
|
||||
padMarker: 0x80,
|
||||
paddingPool: padding,
|
||||
encodeHint: func(val, pos byte) byte {
|
||||
return ((val & 0x03) << 5) | (pos & 0x0F)
|
||||
},
|
||||
encodeGroup: func(group byte) byte {
|
||||
v := group & 0x3F
|
||||
return ((v & 0x30) << 1) | (v & 0x0F)
|
||||
},
|
||||
decodeGroup: func(b byte) (byte, bool) {
|
||||
if (b & 0x90) != 0 {
|
||||
return 0, false
|
||||
}
|
||||
return ((b >> 1) & 0x30) | (b & 0x0F), true
|
||||
},
|
||||
}
|
||||
|
||||
for val := 0; val < 4; val++ {
|
||||
for pos := 0; pos < 16; pos++ {
|
||||
layout.encodeHint[val][pos] = (byte(val) << 5) | byte(pos)
|
||||
}
|
||||
}
|
||||
for group := 0; group < 64; group++ {
|
||||
v := byte(group)
|
||||
layout.encodeGroup[group] = ((v & 0x30) << 1) | (v & 0x0F)
|
||||
}
|
||||
for b := 0; b < 256; b++ {
|
||||
wire := byte(b)
|
||||
if (wire & 0x90) != 0 {
|
||||
continue
|
||||
}
|
||||
layout.hintTable[wire] = true
|
||||
layout.decodeGroup[wire] = ((wire >> 1) & 0x30) | (wire & 0x0F)
|
||||
layout.groupValid[wire] = true
|
||||
}
|
||||
|
||||
return layout
|
||||
}
|
||||
|
||||
func newCustomLayout(pattern string) (*byteLayout, error) {
|
||||
@@ -162,26 +190,6 @@ func newCustomLayout(pattern string) (*byteLayout, error) {
|
||||
return out
|
||||
}
|
||||
|
||||
decodeGroup := func(b byte) (byte, bool) {
|
||||
if (b & xMask) != xMask {
|
||||
return 0, false
|
||||
}
|
||||
var val, pos byte
|
||||
if b&(1<<pBits[0]) != 0 {
|
||||
val |= 0x02
|
||||
}
|
||||
if b&(1<<pBits[1]) != 0 {
|
||||
val |= 0x01
|
||||
}
|
||||
for i, bit := range vBits {
|
||||
if b&(1<<bit) != 0 {
|
||||
pos |= 1 << (3 - uint8(i))
|
||||
}
|
||||
}
|
||||
group := (val << 4) | (pos & 0x0F)
|
||||
return group, true
|
||||
}
|
||||
|
||||
paddingSet := make(map[byte]struct{})
|
||||
var padding []byte
|
||||
for drop := range xBits {
|
||||
@@ -202,20 +210,46 @@ func newCustomLayout(pattern string) (*byteLayout, error) {
|
||||
return nil, fmt.Errorf("custom table produced empty padding pool")
|
||||
}
|
||||
|
||||
return &byteLayout{
|
||||
layout := &byteLayout{
|
||||
name: fmt.Sprintf("custom(%s)", cleaned),
|
||||
hintMask: xMask,
|
||||
hintValue: xMask,
|
||||
padMarker: padding[0],
|
||||
paddingPool: padding,
|
||||
encodeHint: func(val, pos byte) byte {
|
||||
return encodeBits(val, pos, -1)
|
||||
},
|
||||
encodeGroup: func(group byte) byte {
|
||||
val := (group >> 4) & 0x03
|
||||
pos := group & 0x0F
|
||||
return encodeBits(val, pos, -1)
|
||||
},
|
||||
decodeGroup: decodeGroup,
|
||||
}, nil
|
||||
}
|
||||
|
||||
for val := 0; val < 4; val++ {
|
||||
for pos := 0; pos < 16; pos++ {
|
||||
layout.encodeHint[val][pos] = encodeBits(byte(val), byte(pos), -1)
|
||||
}
|
||||
}
|
||||
for group := 0; group < 64; group++ {
|
||||
val := byte(group>>4) & 0x03
|
||||
pos := byte(group) & 0x0F
|
||||
layout.encodeGroup[group] = encodeBits(val, pos, -1)
|
||||
}
|
||||
for b := 0; b < 256; b++ {
|
||||
wire := byte(b)
|
||||
if (wire & xMask) != xMask {
|
||||
continue
|
||||
}
|
||||
layout.hintTable[wire] = true
|
||||
|
||||
var val, pos byte
|
||||
if wire&(1<<pBits[0]) != 0 {
|
||||
val |= 0x02
|
||||
}
|
||||
if wire&(1<<pBits[1]) != 0 {
|
||||
val |= 0x01
|
||||
}
|
||||
for i, bit := range vBits {
|
||||
if wire&(1<<bit) != 0 {
|
||||
pos |= 1 << (3 - uint8(i))
|
||||
}
|
||||
}
|
||||
layout.decodeGroup[wire] = (val << 4) | pos
|
||||
layout.groupValid[wire] = true
|
||||
}
|
||||
|
||||
return layout, nil
|
||||
}
|
||||
|
||||
+23
-39
@@ -2,10 +2,7 @@ package sudoku
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
crypto_rand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
@@ -25,7 +22,7 @@ type PackedConn struct {
|
||||
|
||||
// Read-side buffers.
|
||||
rawBuf []byte
|
||||
pendingData []byte
|
||||
pendingData pendingBuffer
|
||||
|
||||
// Write-side state.
|
||||
writeMu sync.Mutex
|
||||
@@ -38,7 +35,7 @@ type PackedConn struct {
|
||||
readBits int
|
||||
|
||||
// Padding selection matches Conn's threshold-based model.
|
||||
rng *rand.Rand
|
||||
rng randomSource
|
||||
paddingThreshold uint64
|
||||
padMarker byte
|
||||
padPool []byte
|
||||
@@ -65,19 +62,14 @@ func (pc *PackedConn) CloseRead() error {
|
||||
}
|
||||
|
||||
func NewPackedConn(c net.Conn, table *Table, pMin, pMax int) *PackedConn {
|
||||
var seedBytes [8]byte
|
||||
if _, err := crypto_rand.Read(seedBytes[:]); err != nil {
|
||||
binary.BigEndian.PutUint64(seedBytes[:], uint64(rand.Int63()))
|
||||
}
|
||||
seed := int64(binary.BigEndian.Uint64(seedBytes[:]))
|
||||
localRng := rand.New(rand.NewSource(seed))
|
||||
localRng := newSeededRand()
|
||||
|
||||
pc := &PackedConn{
|
||||
Conn: c,
|
||||
table: table,
|
||||
reader: bufio.NewReaderSize(c, IOBufferSize),
|
||||
rawBuf: make([]byte, IOBufferSize),
|
||||
pendingData: make([]byte, 0, 4096),
|
||||
pendingData: newPendingBuffer(4096),
|
||||
writeBuf: make([]byte, 0, 4096),
|
||||
rng: localRng,
|
||||
paddingThreshold: pickPaddingThreshold(localRng, pMin, pMax),
|
||||
@@ -104,7 +96,7 @@ func (pc *PackedConn) maybeAddPadding(out []byte) []byte {
|
||||
|
||||
func (pc *PackedConn) appendGroup(out []byte, group byte) []byte {
|
||||
out = pc.maybeAddPadding(out)
|
||||
return append(out, pc.encodeGroup(group))
|
||||
return append(out, pc.table.layout.groupByte(group))
|
||||
}
|
||||
|
||||
func (pc *PackedConn) appendForcedPadding(out []byte) []byte {
|
||||
@@ -156,19 +148,6 @@ func (pc *PackedConn) writeProtectedPrefix(out []byte, p []byte) ([]byte, int) {
|
||||
return out, limit
|
||||
}
|
||||
|
||||
func (pc *PackedConn) drainPendingData(dst []byte) int {
|
||||
n := copy(dst, pc.pendingData)
|
||||
if n == len(pc.pendingData) {
|
||||
pc.pendingData = pc.pendingData[:0]
|
||||
return n
|
||||
}
|
||||
|
||||
remaining := len(pc.pendingData) - n
|
||||
copy(pc.pendingData, pc.pendingData[n:])
|
||||
pc.pendingData = pc.pendingData[:remaining]
|
||||
return n
|
||||
}
|
||||
|
||||
func (pc *PackedConn) Write(p []byte) (int, error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
@@ -282,7 +261,7 @@ func (pc *PackedConn) Flush() error {
|
||||
pc.bitBuf = 0
|
||||
pc.bitCount = 0
|
||||
|
||||
out = append(out, pc.encodeGroup(group&0x3F))
|
||||
out = append(out, pc.table.layout.groupByte(group&0x3F))
|
||||
out = append(out, pc.padMarker)
|
||||
}
|
||||
|
||||
@@ -301,14 +280,17 @@ func writeFull(w io.Writer, b []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
b = b[n:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pc *PackedConn) Read(p []byte) (int, error) {
|
||||
if len(pc.pendingData) > 0 {
|
||||
return pc.drainPendingData(p), nil
|
||||
if n, ok := drainPending(p, &pc.pendingData); ok {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
for {
|
||||
@@ -320,7 +302,7 @@ func (pc *PackedConn) Read(p []byte) (int, error) {
|
||||
layout := pc.table.layout
|
||||
|
||||
for _, b := range pc.rawBuf[:nr] {
|
||||
if !layout.isHint(b) {
|
||||
if !layout.hintTable[b] {
|
||||
if b == padMarker {
|
||||
rBuf = 0
|
||||
rBits = 0
|
||||
@@ -328,7 +310,7 @@ func (pc *PackedConn) Read(p []byte) (int, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
group, ok := layout.decodeGroup(b)
|
||||
group, ok := layout.decodePackedGroup(b)
|
||||
if !ok {
|
||||
return 0, ErrInvalidSudokuMapMiss
|
||||
}
|
||||
@@ -339,7 +321,12 @@ func (pc *PackedConn) Read(p []byte) (int, error) {
|
||||
if rBits >= 8 {
|
||||
rBits -= 8
|
||||
val := byte(rBuf >> rBits)
|
||||
pc.pendingData = append(pc.pendingData, val)
|
||||
pc.pendingData.appendByte(val)
|
||||
if rBits == 0 {
|
||||
rBuf = 0
|
||||
} else {
|
||||
rBuf &= (uint64(1) << rBits) - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,24 +339,21 @@ func (pc *PackedConn) Read(p []byte) (int, error) {
|
||||
pc.readBitBuf = 0
|
||||
pc.readBits = 0
|
||||
}
|
||||
if len(pc.pendingData) > 0 {
|
||||
if pc.pendingData.available() > 0 {
|
||||
break
|
||||
}
|
||||
return 0, rErr
|
||||
}
|
||||
|
||||
if len(pc.pendingData) > 0 {
|
||||
if pc.pendingData.available() > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return pc.drainPendingData(p), nil
|
||||
n, _ := drainPending(p, &pc.pendingData)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (pc *PackedConn) getPaddingByte() byte {
|
||||
return pc.padPool[pc.rng.Intn(len(pc.padPool))]
|
||||
}
|
||||
|
||||
func (pc *PackedConn) encodeGroup(group byte) byte {
|
||||
return pc.table.layout.encodeGroup(group)
|
||||
}
|
||||
|
||||
+2
-4
@@ -1,10 +1,8 @@
|
||||
package sudoku
|
||||
|
||||
import "math/rand"
|
||||
|
||||
const probOne = uint64(1) << 32
|
||||
|
||||
func pickPaddingThreshold(r *rand.Rand, pMin, pMax int) uint64 {
|
||||
func pickPaddingThreshold(r randomSource, pMin, pMax int) uint64 {
|
||||
if r == nil {
|
||||
return 0
|
||||
}
|
||||
@@ -30,7 +28,7 @@ func pickPaddingThreshold(r *rand.Rand, pMin, pMax int) uint64 {
|
||||
return min + (u * (max - min) >> 32)
|
||||
}
|
||||
|
||||
func shouldPad(r *rand.Rand, threshold uint64) bool {
|
||||
func shouldPad(r randomSource, threshold uint64) bool {
|
||||
if threshold == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package sudoku
|
||||
|
||||
type pendingBuffer struct {
|
||||
data []byte
|
||||
off int
|
||||
}
|
||||
|
||||
func newPendingBuffer(capacity int) pendingBuffer {
|
||||
return pendingBuffer{data: make([]byte, 0, capacity)}
|
||||
}
|
||||
|
||||
func (p *pendingBuffer) available() int {
|
||||
if p == nil {
|
||||
return 0
|
||||
}
|
||||
return len(p.data) - p.off
|
||||
}
|
||||
|
||||
func (p *pendingBuffer) reset() {
|
||||
if p == nil {
|
||||
return
|
||||
}
|
||||
p.data = p.data[:0]
|
||||
p.off = 0
|
||||
}
|
||||
|
||||
func (p *pendingBuffer) ensureAppendCapacity(extra int) {
|
||||
if p == nil || extra <= 0 || p.off == 0 {
|
||||
return
|
||||
}
|
||||
if cap(p.data)-len(p.data) >= extra {
|
||||
return
|
||||
}
|
||||
|
||||
unread := len(p.data) - p.off
|
||||
copy(p.data[:unread], p.data[p.off:])
|
||||
p.data = p.data[:unread]
|
||||
p.off = 0
|
||||
}
|
||||
|
||||
func (p *pendingBuffer) appendByte(b byte) {
|
||||
p.ensureAppendCapacity(1)
|
||||
p.data = append(p.data, b)
|
||||
}
|
||||
|
||||
func drainPending(dst []byte, pending *pendingBuffer) (int, bool) {
|
||||
if pending == nil || pending.available() == 0 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
n := copy(dst, pending.data[pending.off:])
|
||||
pending.off += n
|
||||
if pending.off == len(pending.data) {
|
||||
pending.reset()
|
||||
}
|
||||
return n, true
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package sudoku
|
||||
|
||||
import (
|
||||
crypto_rand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"time"
|
||||
)
|
||||
|
||||
type randomSource interface {
|
||||
Uint32() uint32
|
||||
Uint64() uint64
|
||||
Intn(n int) int
|
||||
}
|
||||
|
||||
type sudokuRand struct {
|
||||
state uint64
|
||||
}
|
||||
|
||||
func newSeededRand() *sudokuRand {
|
||||
seed := time.Now().UnixNano()
|
||||
var seedBytes [8]byte
|
||||
if _, err := crypto_rand.Read(seedBytes[:]); err == nil {
|
||||
seed = int64(binary.BigEndian.Uint64(seedBytes[:]))
|
||||
}
|
||||
return newSudokuRand(seed)
|
||||
}
|
||||
|
||||
func newSudokuRand(seed int64) *sudokuRand {
|
||||
state := uint64(seed)
|
||||
if state == 0 {
|
||||
state = 0x9e3779b97f4a7c15
|
||||
}
|
||||
return &sudokuRand{state: state}
|
||||
}
|
||||
|
||||
func (r *sudokuRand) Uint64() uint64 {
|
||||
if r == nil {
|
||||
return 0
|
||||
}
|
||||
r.state += 0x9e3779b97f4a7c15
|
||||
z := r.state
|
||||
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9
|
||||
z = (z ^ (z >> 27)) * 0x94d049bb133111eb
|
||||
return z ^ (z >> 31)
|
||||
}
|
||||
|
||||
func (r *sudokuRand) Uint32() uint32 {
|
||||
return uint32(r.Uint64() >> 32)
|
||||
}
|
||||
|
||||
func (r *sudokuRand) Intn(n int) int {
|
||||
if n <= 1 {
|
||||
return 0
|
||||
}
|
||||
return int((uint64(r.Uint32()) * uint64(n)) >> 32)
|
||||
}
|
||||
@@ -146,7 +146,7 @@ func newSingleDirectionTable(key string, mode string, customPattern string) (*Ta
|
||||
if matchCount == 1 {
|
||||
// 唯一确定,生成最终编码字节
|
||||
for i, p := range rawParts {
|
||||
currentHints[i] = t.layout.encodeHint(p.val-1, p.pos)
|
||||
currentHints[i] = t.layout.hintByte(p.val-1, p.pos)
|
||||
}
|
||||
|
||||
t.EncodeTable[byteVal] = append(t.EncodeTable[byteVal], currentHints)
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -37,42 +36,15 @@ func WriteDatagram(w io.Writer, addr string, payload []byte) error {
|
||||
binary.BigEndian.PutUint16(header[:2], uint16(len(addrBuf)))
|
||||
binary.BigEndian.PutUint16(header[2:], uint16(len(payload)))
|
||||
|
||||
if err := writeFull(w, header[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeFull(w, addrBuf); err != nil {
|
||||
return err
|
||||
}
|
||||
return writeFull(w, payload)
|
||||
return writeAllChunks(w, header[:], addrBuf, payload)
|
||||
}
|
||||
|
||||
// ReadDatagram parses a single UDP datagram frame from the reliable stream.
|
||||
func ReadDatagram(r io.Reader) (string, []byte, error) {
|
||||
var header [4]byte
|
||||
if _, err := io.ReadFull(r, header[:]); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
addrLen := int(binary.BigEndian.Uint16(header[:2]))
|
||||
payloadLen := int(binary.BigEndian.Uint16(header[2:]))
|
||||
|
||||
if addrLen <= 0 || addrLen > maxUoTPayload {
|
||||
return "", nil, fmt.Errorf("invalid address length: %d", addrLen)
|
||||
}
|
||||
if payloadLen < 0 || payloadLen > maxUoTPayload {
|
||||
return "", nil, fmt.Errorf("invalid payload length: %d", payloadLen)
|
||||
}
|
||||
|
||||
addrBuf := make([]byte, addrLen)
|
||||
if _, err := io.ReadFull(r, addrBuf); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
addr, err := DecodeAddress(bytes.NewReader(addrBuf))
|
||||
addr, payloadLen, err := readDatagramHeaderAndAddress(r)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("decode address: %w", err)
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
payload := make([]byte, payloadLen)
|
||||
if _, err := io.ReadFull(r, payload); err != nil {
|
||||
return "", nil, err
|
||||
@@ -93,26 +65,29 @@ func NewUoTPacketConn(conn net.Conn) *UoTPacketConn {
|
||||
|
||||
func (c *UoTPacketConn) ReadFrom(p []byte) (int, net.Addr, error) {
|
||||
for {
|
||||
addrStr, payload, err := ReadDatagram(c.conn)
|
||||
addrStr, payloadLen, err := readDatagramHeaderAndAddress(c.conn)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
if len(payload) > len(p) {
|
||||
udpAddr, err := parseDatagramUDPAddr(addrStr)
|
||||
if payloadLen > len(p) {
|
||||
if discardErr := discardBytes(c.conn, payloadLen); discardErr != nil {
|
||||
return 0, nil, discardErr
|
||||
}
|
||||
return 0, nil, io.ErrShortBuffer
|
||||
}
|
||||
|
||||
host, port, _ := net.SplitHostPort(addrStr)
|
||||
portInt, _ := strconv.ParseUint(port, 10, 16)
|
||||
ip, err := netip.ParseAddr(host)
|
||||
if err != nil { // disallow domain addr at here, just ignore
|
||||
if err != nil {
|
||||
if discardErr := discardBytes(c.conn, payloadLen); discardErr != nil {
|
||||
return 0, nil, discardErr
|
||||
}
|
||||
log.Debugln("[Sudoku][UoT] discard datagram with invalid address %s: %v", addrStr, err)
|
||||
continue
|
||||
}
|
||||
udpAddr := net.UDPAddrFromAddrPort(netip.AddrPortFrom(ip.Unmap(), uint16(portInt)))
|
||||
|
||||
copy(p, payload)
|
||||
return len(payload), udpAddr, nil
|
||||
if _, err := io.ReadFull(c.conn, p[:payloadLen]); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return payloadLen, udpAddr, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,3 +122,46 @@ func (c *UoTPacketConn) SetReadDeadline(t time.Time) error {
|
||||
func (c *UoTPacketConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func readDatagramHeaderAndAddress(r io.Reader) (string, int, error) {
|
||||
var header [4]byte
|
||||
if _, err := io.ReadFull(r, header[:]); err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
addrLen := int(binary.BigEndian.Uint16(header[:2]))
|
||||
payloadLen := int(binary.BigEndian.Uint16(header[2:]))
|
||||
if addrLen <= 0 || addrLen > maxUoTPayload {
|
||||
return "", 0, fmt.Errorf("invalid address length: %d", addrLen)
|
||||
}
|
||||
if payloadLen < 0 || payloadLen > maxUoTPayload {
|
||||
return "", 0, fmt.Errorf("invalid payload length: %d", payloadLen)
|
||||
}
|
||||
|
||||
addrBuf := make([]byte, addrLen)
|
||||
if _, err := io.ReadFull(r, addrBuf); err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
addr, err := DecodeAddress(bytes.NewReader(addrBuf))
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("decode address: %w", err)
|
||||
}
|
||||
return addr, payloadLen, nil
|
||||
}
|
||||
|
||||
func parseDatagramUDPAddr(addr string) (*net.UDPAddr, error) {
|
||||
addrPort, err := netip.ParseAddrPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return net.UDPAddrFromAddrPort(netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port())), nil
|
||||
}
|
||||
|
||||
func discardBytes(r io.Reader, n int) error {
|
||||
if n <= 0 {
|
||||
return nil
|
||||
}
|
||||
_, err := io.CopyN(io.Discard, r, int64(n))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package sudoku
|
||||
|
||||
import "io"
|
||||
|
||||
func writeAllChunks(w io.Writer, chunks ...[]byte) error {
|
||||
for _, chunk := range chunks {
|
||||
for len(chunk) > 0 {
|
||||
n, err := w.Write(chunk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
chunk = chunk[n:]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -9,9 +9,11 @@ import (
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/httputils"
|
||||
"github.com/metacubex/mihomo/common/once"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/vmess"
|
||||
|
||||
@@ -33,6 +35,9 @@ type ClientOptions struct {
|
||||
QUICCongestionControl string
|
||||
QUICCwnd int
|
||||
HealthCheck bool
|
||||
MaxConnections int
|
||||
MinStreams int
|
||||
MaxStreams int
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
@@ -45,6 +50,7 @@ type Client struct {
|
||||
startOnce sync.Once
|
||||
healthCheck bool
|
||||
healthCheckTimer *time.Timer
|
||||
count atomic.Int64
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, options ClientOptions) (client *Client, err error) {
|
||||
@@ -134,6 +140,10 @@ func (c *Client) roundTrip(request *http.Request, conn *httpConn) {
|
||||
writer: pipeWriter,
|
||||
created: make(chan struct{}),
|
||||
}
|
||||
c.count.Add(1)
|
||||
conn.closeFn = once.OnceFunc(func() {
|
||||
c.count.Add(-1)
|
||||
})
|
||||
ctx, cancel := context.WithCancel(c.ctx) // requestCtx must alive during conn not closed
|
||||
conn.cancelFn = cancel // cancel ctx when conn closed
|
||||
go func() {
|
||||
@@ -245,3 +255,108 @@ func (c *Client) HealthCheck(ctx context.Context) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type PoolClient struct {
|
||||
mutex sync.Mutex
|
||||
maxConnections int
|
||||
minStreams int
|
||||
maxStreams int
|
||||
ctx context.Context
|
||||
options ClientOptions
|
||||
clients []*Client
|
||||
}
|
||||
|
||||
func NewPoolClient(ctx context.Context, options ClientOptions) (*PoolClient, error) {
|
||||
maxConnections := options.MaxConnections
|
||||
minStreams := options.MinStreams
|
||||
maxStreams := options.MaxStreams
|
||||
if maxConnections == 0 && minStreams == 0 && maxStreams == 0 {
|
||||
maxConnections = 1
|
||||
}
|
||||
client, err := NewClient(ctx, options) // reserve one client and verify the configuration
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &PoolClient{
|
||||
maxConnections: maxConnections,
|
||||
minStreams: minStreams,
|
||||
maxStreams: maxStreams,
|
||||
ctx: ctx,
|
||||
options: options,
|
||||
clients: []*Client{client},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *PoolClient) Dial(ctx context.Context, host string) (net.Conn, error) {
|
||||
transport, err := c.getClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transport.Dial(ctx, host)
|
||||
}
|
||||
|
||||
func (c *PoolClient) ListenPacket(ctx context.Context) (net.PacketConn, error) {
|
||||
transport, err := c.getClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transport.ListenPacket(ctx)
|
||||
}
|
||||
|
||||
func (c *PoolClient) ListenICMP(ctx context.Context) (*IcmpConn, error) {
|
||||
transport, err := c.getClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return transport.ListenICMP(ctx)
|
||||
}
|
||||
|
||||
func (c *PoolClient) Close() error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
var errs []error
|
||||
for _, t := range c.clients {
|
||||
if err := t.Close(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
c.clients = nil
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (c *PoolClient) getClient() (*Client, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
var transport *Client
|
||||
for _, t := range c.clients {
|
||||
if transport == nil || t.count.Load() < transport.count.Load() {
|
||||
transport = t
|
||||
}
|
||||
}
|
||||
if transport == nil {
|
||||
return c.newTransportLocked()
|
||||
}
|
||||
numStreams := int(transport.count.Load())
|
||||
if numStreams == 0 {
|
||||
return transport, nil
|
||||
}
|
||||
if c.maxConnections > 0 {
|
||||
if len(c.clients) >= c.maxConnections || numStreams < c.minStreams {
|
||||
return transport, nil
|
||||
}
|
||||
} else {
|
||||
if c.maxStreams > 0 && numStreams < c.maxStreams {
|
||||
return transport, nil
|
||||
}
|
||||
}
|
||||
return c.newTransportLocked()
|
||||
}
|
||||
|
||||
func (c *PoolClient) newTransportLocked() (*Client, error) {
|
||||
transport, err := NewClient(c.ctx, c.options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.clients = append(c.clients, transport)
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ type httpConn struct {
|
||||
created chan struct{}
|
||||
createErr error
|
||||
cancelFn func()
|
||||
closeFn func()
|
||||
httputils.NetAddr
|
||||
|
||||
// deadlines
|
||||
@@ -129,6 +130,9 @@ func (h *httpConn) Close() error {
|
||||
if h.cancelFn != nil {
|
||||
h.cancelFn()
|
||||
}
|
||||
if h.closeFn != nil {
|
||||
h.closeFn()
|
||||
}
|
||||
return errors.Join(errorArr...)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@@ -24,6 +25,7 @@ type TransportMaker func() http.RoundTripper
|
||||
|
||||
type PacketUpWriter struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
cfg *Config
|
||||
sessionID string
|
||||
transport http.RoundTripper
|
||||
@@ -34,7 +36,26 @@ type PacketUpWriter struct {
|
||||
func (c *PacketUpWriter) Write(b []byte) (int, error) {
|
||||
c.writeMu.Lock()
|
||||
defer c.writeMu.Unlock()
|
||||
scMaxEachPostBytes := c.cfg.GetNormalizedScMaxEachPostBytes()
|
||||
if len(b) < scMaxEachPostBytes {
|
||||
return c.write(b)
|
||||
}
|
||||
var n int
|
||||
for start := 0; start < len(b); start += scMaxEachPostBytes {
|
||||
end := start + scMaxEachPostBytes
|
||||
if end > len(b) {
|
||||
end = len(b)
|
||||
}
|
||||
_n, err := c.write(b[start:end])
|
||||
n += _n
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (c *PacketUpWriter) write(b []byte) (int, error) {
|
||||
u := url.URL{
|
||||
Scheme: "https",
|
||||
Host: c.cfg.Host,
|
||||
@@ -69,11 +90,12 @@ func (c *PacketUpWriter) Write(b []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (c *PacketUpWriter) Close() error {
|
||||
c.cancel()
|
||||
httputils.CloseTransport(c.transport)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewTransport(dialRaw DialRawFunc, wrapTLS WrapTLSFunc) http.RoundTripper {
|
||||
func NewTransport(dialRaw DialRawFunc, wrapTLS WrapTLSFunc, alpn []string) http.RoundTripper {
|
||||
return &http.Http2Transport{
|
||||
DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) {
|
||||
raw, err := dialRaw(ctx)
|
||||
@@ -91,12 +113,14 @@ func NewTransport(dialRaw DialRawFunc, wrapTLS WrapTLSFunc) http.RoundTripper {
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
mode string
|
||||
cfg *Config
|
||||
makeTransport TransportMaker
|
||||
makeDownloadTransport TransportMaker
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
uploadManager *ReuseManager
|
||||
downloadManager *ReuseManager
|
||||
}
|
||||
|
||||
func NewClient(cfg *Config, makeTransport TransportMaker, makeDownloadTransport TransportMaker, hasReality bool) (*Client, error) {
|
||||
@@ -107,14 +131,50 @@ func NewClient(cfg *Config, makeTransport TransportMaker, makeDownloadTransport
|
||||
return nil, fmt.Errorf("xhttp mode %s is not implemented yet", mode)
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &Client{
|
||||
|
||||
client := &Client{
|
||||
mode: mode,
|
||||
cfg: cfg,
|
||||
makeTransport: makeTransport,
|
||||
makeDownloadTransport: makeDownloadTransport,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}, nil
|
||||
}
|
||||
if cfg.ReuseConfig != nil {
|
||||
var err error
|
||||
client.uploadManager, err = NewReuseManager(cfg.ReuseConfig, makeTransport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cfg.DownloadConfig != nil {
|
||||
if makeDownloadTransport == nil {
|
||||
return nil, fmt.Errorf("xhttp: download manager requires download transport maker")
|
||||
}
|
||||
client.downloadManager, err = NewReuseManager(cfg.DownloadConfig.ReuseConfig, makeDownloadTransport)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
c.cancel()
|
||||
var errs []error
|
||||
if c.uploadManager != nil {
|
||||
err := c.uploadManager.Close()
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if c.downloadManager != nil {
|
||||
err := c.downloadManager.Close()
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func (c *Client) Dial() (net.Conn, error) {
|
||||
@@ -130,13 +190,41 @@ func (c *Client) Dial() (net.Conn, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
c.cancel()
|
||||
return nil
|
||||
// onlyRoundTripper is a wrapper that prevents the underlying transport from being closed.
|
||||
type onlyRoundTripper struct {
|
||||
http.RoundTripper
|
||||
}
|
||||
|
||||
func (c *Client) getTransport() (uploadTransport http.RoundTripper, downloadTransport http.RoundTripper, err error) {
|
||||
if c.uploadManager == nil {
|
||||
uploadTransport = c.makeTransport()
|
||||
downloadTransport = onlyRoundTripper{uploadTransport}
|
||||
if c.makeDownloadTransport != nil {
|
||||
downloadTransport = c.makeDownloadTransport()
|
||||
}
|
||||
} else {
|
||||
uploadTransport, err = c.uploadManager.GetTransport()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
downloadTransport = onlyRoundTripper{uploadTransport}
|
||||
if c.downloadManager != nil {
|
||||
downloadTransport, err = c.downloadManager.GetTransport()
|
||||
if err != nil {
|
||||
httputils.CloseTransport(uploadTransport)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) DialStreamOne() (net.Conn, error) {
|
||||
transport := c.makeTransport()
|
||||
transport, _, err := c.getTransport()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
requestURL := url.URL{
|
||||
Scheme: "https",
|
||||
@@ -151,6 +239,7 @@ func (c *Client) DialStreamOne() (net.Conn, error) {
|
||||
if err != nil {
|
||||
_ = pr.Close()
|
||||
_ = pw.Close()
|
||||
httputils.CloseTransport(transport)
|
||||
return nil, err
|
||||
}
|
||||
req.Host = c.cfg.Host
|
||||
@@ -158,6 +247,7 @@ func (c *Client) DialStreamOne() (net.Conn, error) {
|
||||
if err := c.cfg.FillStreamRequest(req, ""); err != nil {
|
||||
_ = pr.Close()
|
||||
_ = pw.Close()
|
||||
httputils.CloseTransport(transport)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -185,10 +275,9 @@ func (c *Client) DialStreamOne() (net.Conn, error) {
|
||||
}
|
||||
|
||||
func (c *Client) DialStreamUp() (net.Conn, error) {
|
||||
uploadTransport := c.makeTransport()
|
||||
downloadTransport := uploadTransport
|
||||
if c.makeDownloadTransport != nil {
|
||||
downloadTransport = c.makeDownloadTransport()
|
||||
uploadTransport, downloadTransport, err := c.getTransport()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
downloadCfg := c.cfg
|
||||
@@ -221,17 +310,13 @@ func (c *Client) DialStreamUp() (net.Conn, error) {
|
||||
)
|
||||
if err != nil {
|
||||
httputils.CloseTransport(uploadTransport)
|
||||
if downloadTransport != uploadTransport {
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
}
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := downloadCfg.FillDownloadRequest(downloadReq, sessionID); err != nil {
|
||||
httputils.CloseTransport(uploadTransport)
|
||||
if downloadTransport != uploadTransport {
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
}
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
return nil, err
|
||||
}
|
||||
downloadReq.Host = downloadCfg.Host
|
||||
@@ -239,17 +324,13 @@ func (c *Client) DialStreamUp() (net.Conn, error) {
|
||||
downloadResp, err := downloadTransport.RoundTrip(downloadReq)
|
||||
if err != nil {
|
||||
httputils.CloseTransport(uploadTransport)
|
||||
if downloadTransport != uploadTransport {
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
}
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
return nil, err
|
||||
}
|
||||
if downloadResp.StatusCode != http.StatusOK {
|
||||
_ = downloadResp.Body.Close()
|
||||
httputils.CloseTransport(uploadTransport)
|
||||
if downloadTransport != uploadTransport {
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
}
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
return nil, fmt.Errorf("xhttp stream-up download bad status: %s", downloadResp.Status)
|
||||
}
|
||||
|
||||
@@ -264,9 +345,7 @@ func (c *Client) DialStreamUp() (net.Conn, error) {
|
||||
_ = pr.Close()
|
||||
_ = pw.Close()
|
||||
httputils.CloseTransport(uploadTransport)
|
||||
if downloadTransport != uploadTransport {
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
}
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -275,9 +354,7 @@ func (c *Client) DialStreamUp() (net.Conn, error) {
|
||||
_ = pr.Close()
|
||||
_ = pw.Close()
|
||||
httputils.CloseTransport(uploadTransport)
|
||||
if downloadTransport != uploadTransport {
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
}
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
return nil, err
|
||||
}
|
||||
uploadReq.Host = c.cfg.Host
|
||||
@@ -300,19 +377,16 @@ func (c *Client) DialStreamUp() (net.Conn, error) {
|
||||
conn.onClose = func() {
|
||||
_ = pr.Close()
|
||||
httputils.CloseTransport(uploadTransport)
|
||||
if downloadTransport != uploadTransport {
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
}
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *Client) DialPacketUp() (net.Conn, error) {
|
||||
uploadTransport := c.makeTransport()
|
||||
downloadTransport := uploadTransport
|
||||
if c.makeDownloadTransport != nil {
|
||||
downloadTransport = c.makeDownloadTransport()
|
||||
uploadTransport, downloadTransport, err := c.getTransport()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
downloadCfg := c.cfg
|
||||
@@ -327,8 +401,10 @@ func (c *Client) DialPacketUp() (net.Conn, error) {
|
||||
Path: downloadCfg.NormalizedPath(),
|
||||
}
|
||||
|
||||
writerCtx, writerCancel := context.WithCancel(c.ctx)
|
||||
writer := &PacketUpWriter{
|
||||
ctx: c.ctx,
|
||||
ctx: writerCtx,
|
||||
cancel: writerCancel,
|
||||
cfg: c.cfg,
|
||||
sessionID: sessionID,
|
||||
transport: uploadTransport,
|
||||
@@ -336,32 +412,41 @@ func (c *Client) DialPacketUp() (net.Conn, error) {
|
||||
}
|
||||
conn := &Conn{writer: writer}
|
||||
|
||||
downloadReq, err := http.NewRequestWithContext(httputils.NewAddrContext(&conn.NetAddr, c.ctx), http.MethodGet, downloadURL.String(), nil)
|
||||
downloadReq, err := http.NewRequestWithContext(
|
||||
httputils.NewAddrContext(&conn.NetAddr, c.ctx),
|
||||
http.MethodGet,
|
||||
downloadURL.String(),
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
httputils.CloseTransport(uploadTransport)
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
return nil, err
|
||||
}
|
||||
if err := downloadCfg.FillDownloadRequest(downloadReq, sessionID); err != nil {
|
||||
httputils.CloseTransport(uploadTransport)
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
return nil, err
|
||||
}
|
||||
downloadReq.Host = downloadCfg.Host
|
||||
|
||||
resp, err := downloadTransport.RoundTrip(downloadReq)
|
||||
if err != nil {
|
||||
httputils.CloseTransport(uploadTransport)
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
_ = resp.Body.Close()
|
||||
httputils.CloseTransport(uploadTransport)
|
||||
if downloadTransport != uploadTransport {
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
}
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
return nil, fmt.Errorf("xhttp packet-up download bad status: %s", resp.Status)
|
||||
}
|
||||
|
||||
conn.reader = resp.Body
|
||||
conn.onClose = func() {
|
||||
if downloadTransport != uploadTransport {
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
}
|
||||
// uploadTransport already closed by writer
|
||||
httputils.CloseTransport(downloadTransport)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
|
||||
@@ -12,22 +12,25 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Host string
|
||||
Path string
|
||||
Mode string
|
||||
Headers map[string]string
|
||||
NoGRPCHeader bool
|
||||
XPaddingBytes string
|
||||
DownloadConfig *Config
|
||||
Host string
|
||||
Path string
|
||||
Mode string
|
||||
Headers map[string]string
|
||||
NoGRPCHeader bool
|
||||
XPaddingBytes string
|
||||
NoSSEHeader bool // server only
|
||||
ScStreamUpServerSecs string // server only
|
||||
ScMaxEachPostBytes int
|
||||
ReuseConfig *ReuseConfig
|
||||
DownloadConfig *Config
|
||||
}
|
||||
|
||||
type DownloadConfig struct {
|
||||
Host string
|
||||
Path string
|
||||
Mode string
|
||||
ServerName string
|
||||
ClientFingerprint string
|
||||
SkipCertVerify bool
|
||||
type ReuseConfig struct {
|
||||
MaxConnections string
|
||||
MaxConcurrency string
|
||||
CMaxReuseTimes string
|
||||
HMaxRequestTimes string
|
||||
HMaxReusableSecs string
|
||||
}
|
||||
|
||||
func (c *Config) NormalizedMode() string {
|
||||
@@ -115,6 +118,38 @@ func (c *Config) RandomPadding() (string, error) {
|
||||
return strings.Repeat("X", n), nil
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedScStreamUpServerSecs() (int, error) {
|
||||
scStreamUpServerSecs := c.ScStreamUpServerSecs
|
||||
if scStreamUpServerSecs == "" {
|
||||
scStreamUpServerSecs = "20-80"
|
||||
}
|
||||
|
||||
minVal, maxVal, err := parseRange(scStreamUpServerSecs)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if minVal < 0 || maxVal < minVal {
|
||||
return 0, fmt.Errorf("invalid sc-stream-up-server-secs range: %s", scStreamUpServerSecs)
|
||||
}
|
||||
if maxVal == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
n := minVal
|
||||
if maxVal > minVal {
|
||||
n = minVal + rand.Intn(maxVal-minVal+1)
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedScMaxEachPostBytes() int {
|
||||
if c.ScMaxEachPostBytes == 0 {
|
||||
return 1000000
|
||||
}
|
||||
return c.ScMaxEachPostBytes
|
||||
}
|
||||
|
||||
func parseRange(s string) (int, int, error) {
|
||||
parts := strings.Split(strings.TrimSpace(s), "-")
|
||||
if len(parts) == 1 {
|
||||
@@ -139,6 +174,67 @@ func parseRange(s string) (int, int, error) {
|
||||
return minVal, maxVal, nil
|
||||
}
|
||||
|
||||
func resolveRangeValue(s string, fallback int) (int, error) {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return fallback, nil
|
||||
}
|
||||
|
||||
minVal, maxVal, err := parseRange(s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if minVal < 0 || maxVal < minVal {
|
||||
return 0, fmt.Errorf("invalid range: %s", s)
|
||||
}
|
||||
|
||||
if minVal == maxVal {
|
||||
return minVal, nil
|
||||
}
|
||||
|
||||
return minVal + rand.Intn(maxVal-minVal+1), nil
|
||||
}
|
||||
|
||||
func (c *ReuseConfig) ResolveManagerConfig() (int, int, error) {
|
||||
if c == nil {
|
||||
return 0, 0, nil
|
||||
}
|
||||
|
||||
maxConnections, err := resolveRangeValue(c.MaxConnections, 0)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("invalid max-connections: %w", err)
|
||||
}
|
||||
|
||||
maxConcurrency, err := resolveRangeValue(c.MaxConcurrency, 0)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("invalid max-concurrency: %w", err)
|
||||
}
|
||||
|
||||
return maxConnections, maxConcurrency, nil
|
||||
}
|
||||
|
||||
func (c *ReuseConfig) ResolveEntryConfig() (int, int, int, error) {
|
||||
if c == nil {
|
||||
return 0, 0, 0, nil
|
||||
}
|
||||
|
||||
hMaxRequestTimes, err := resolveRangeValue(c.HMaxRequestTimes, 0)
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("invalid h-max-request-times: %w", err)
|
||||
}
|
||||
|
||||
hMaxReusableSecs, err := resolveRangeValue(c.HMaxReusableSecs, 0)
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("invalid h-max-reusable-secs: %w", err)
|
||||
}
|
||||
|
||||
cMaxReuseTimes, err := resolveRangeValue(c.CMaxReuseTimes, 0)
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("invalid c-max-reuse-times: %w", err)
|
||||
}
|
||||
|
||||
return hMaxRequestTimes, hMaxReusableSecs, cMaxReuseTimes, nil
|
||||
}
|
||||
|
||||
func (c *Config) FillStreamRequest(req *http.Request, sessionID string) error {
|
||||
req.Header = c.RequestHeader()
|
||||
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
package xhttp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/httputils"
|
||||
|
||||
"github.com/metacubex/http"
|
||||
)
|
||||
|
||||
type reuseEntry struct {
|
||||
transport http.RoundTripper
|
||||
|
||||
openUsage atomic.Int32
|
||||
leftRequests atomic.Int32
|
||||
reuseCount atomic.Int32
|
||||
maxReuseTimes int32
|
||||
unreusableAt time.Time
|
||||
|
||||
closed atomic.Bool
|
||||
}
|
||||
|
||||
func (e *reuseEntry) isClosed() bool {
|
||||
return e.closed.Load()
|
||||
}
|
||||
|
||||
func (e *reuseEntry) close() {
|
||||
if !e.closed.CompareAndSwap(false, true) {
|
||||
return
|
||||
}
|
||||
httputils.CloseTransport(e.transport)
|
||||
}
|
||||
|
||||
type ReuseTransport struct {
|
||||
entry *reuseEntry
|
||||
manager *ReuseManager
|
||||
removed atomic.Bool
|
||||
}
|
||||
|
||||
func (rt *ReuseTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return rt.entry.transport.RoundTrip(req)
|
||||
}
|
||||
|
||||
func (rt *ReuseTransport) Close() error {
|
||||
if !rt.removed.CompareAndSwap(false, true) {
|
||||
return nil
|
||||
}
|
||||
rt.manager.release(rt.entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ http.RoundTripper = (*ReuseTransport)(nil)
|
||||
|
||||
type ReuseManager struct {
|
||||
cfg *ReuseConfig
|
||||
maxConnections int
|
||||
maxConcurrency int
|
||||
maker TransportMaker
|
||||
mu sync.Mutex
|
||||
entries []*reuseEntry
|
||||
}
|
||||
|
||||
func NewReuseManager(cfg *ReuseConfig, makeTransport TransportMaker) (*ReuseManager, error) {
|
||||
if cfg == nil {
|
||||
return nil, nil
|
||||
}
|
||||
connections, concurrency, err := cfg.ResolveManagerConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, _, _, err = cfg.ResolveEntryConfig() // check if config is valid
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ReuseManager{
|
||||
cfg: cfg,
|
||||
maxConnections: connections,
|
||||
maxConcurrency: concurrency,
|
||||
maker: makeTransport,
|
||||
entries: make([]*reuseEntry, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *ReuseManager) Close() error {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
for _, entry := range m.entries {
|
||||
entry.close()
|
||||
}
|
||||
m.entries = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ReuseManager) cleanupLocked(now time.Time) {
|
||||
kept := m.entries[:0]
|
||||
for _, entry := range m.entries {
|
||||
if entry.isClosed() {
|
||||
continue
|
||||
}
|
||||
if entry.leftRequests.Load() <= 0 && entry.openUsage.Load() == 0 {
|
||||
entry.close()
|
||||
continue
|
||||
}
|
||||
if !entry.unreusableAt.IsZero() && now.After(entry.unreusableAt) && entry.openUsage.Load() == 0 {
|
||||
entry.close()
|
||||
continue
|
||||
}
|
||||
kept = append(kept, entry)
|
||||
}
|
||||
m.entries = kept
|
||||
}
|
||||
|
||||
func (m *ReuseManager) release(entry *reuseEntry) {
|
||||
if entry == nil {
|
||||
return
|
||||
}
|
||||
remaining := entry.openUsage.Add(-1)
|
||||
if remaining < 0 {
|
||||
entry.openUsage.Store(0)
|
||||
remaining = 0
|
||||
}
|
||||
|
||||
if remaining == 0 {
|
||||
now := time.Now()
|
||||
if entry.leftRequests.Load() <= 0 ||
|
||||
(entry.maxReuseTimes > 0 && entry.reuseCount.Load() >= entry.maxReuseTimes) ||
|
||||
(!entry.unreusableAt.IsZero() && now.After(entry.unreusableAt)) {
|
||||
entry.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ReuseManager) pickLocked() *reuseEntry {
|
||||
var best *reuseEntry
|
||||
for _, entry := range m.entries {
|
||||
if entry.isClosed() {
|
||||
continue
|
||||
}
|
||||
if entry.leftRequests.Load() <= 0 {
|
||||
continue
|
||||
}
|
||||
if entry.maxReuseTimes > 0 && entry.reuseCount.Load() >= entry.maxReuseTimes {
|
||||
continue
|
||||
}
|
||||
if m.maxConcurrency > 0 && int(entry.openUsage.Load()) >= m.maxConcurrency {
|
||||
continue
|
||||
}
|
||||
if best == nil || entry.openUsage.Load() < best.openUsage.Load() {
|
||||
best = entry
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
func (m *ReuseManager) canCreateLocked() bool {
|
||||
if m.maxConnections <= 0 {
|
||||
return true
|
||||
}
|
||||
return len(m.entries) < m.maxConnections
|
||||
}
|
||||
|
||||
func (m *ReuseManager) newEntryLocked(transport http.RoundTripper, now time.Time) *reuseEntry {
|
||||
entry := &reuseEntry{transport: transport}
|
||||
|
||||
hMaxRequestTimes, hMaxReusableSecs, cMaxReuseTimes, _ := m.cfg.ResolveEntryConfig() // error already checked in [NewReuseManager]
|
||||
if hMaxRequestTimes > 0 {
|
||||
entry.leftRequests.Store(int32(hMaxRequestTimes))
|
||||
} else {
|
||||
entry.leftRequests.Store(1<<30 - 1)
|
||||
}
|
||||
if hMaxReusableSecs > 0 {
|
||||
entry.unreusableAt = now.Add(time.Duration(hMaxReusableSecs) * time.Second)
|
||||
}
|
||||
if cMaxReuseTimes > 0 {
|
||||
entry.maxReuseTimes = int32(cMaxReuseTimes)
|
||||
}
|
||||
|
||||
m.entries = append(m.entries, entry)
|
||||
return entry
|
||||
}
|
||||
|
||||
func (m *ReuseManager) GetTransport() (*ReuseTransport, error) {
|
||||
now := time.Now()
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
m.cleanupLocked(now)
|
||||
|
||||
entry := m.pickLocked()
|
||||
reused := entry != nil
|
||||
|
||||
if entry == nil {
|
||||
if !m.canCreateLocked() {
|
||||
return nil, fmt.Errorf("manager: no available connection")
|
||||
}
|
||||
transport := m.maker()
|
||||
entry = m.newEntryLocked(transport, now)
|
||||
}
|
||||
|
||||
if reused {
|
||||
entry.reuseCount.Add(1)
|
||||
}
|
||||
|
||||
entry.openUsage.Add(1)
|
||||
if entry.leftRequests.Load() > 0 {
|
||||
entry.leftRequests.Add(-1)
|
||||
}
|
||||
|
||||
return &ReuseTransport{entry: entry, manager: m}, nil
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package xhttp
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/http"
|
||||
)
|
||||
|
||||
type testRoundTripper struct {
|
||||
id int64
|
||||
}
|
||||
|
||||
func (t *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
panic("not used in reuse manager unit tests")
|
||||
}
|
||||
|
||||
func makeTestTransportFactory(counter *atomic.Int64) TransportMaker {
|
||||
return func() http.RoundTripper {
|
||||
id := counter.Add(1)
|
||||
return &testRoundTripper{id: id}
|
||||
}
|
||||
}
|
||||
|
||||
func transportID(rt http.RoundTripper) int64 {
|
||||
return rt.(*testRoundTripper).id
|
||||
}
|
||||
|
||||
func TestManagerReuseSameEntry(t *testing.T) {
|
||||
var created atomic.Int64
|
||||
|
||||
manager, err := NewReuseManager(&ReuseConfig{
|
||||
MaxConnections: "1",
|
||||
MaxConcurrency: "1",
|
||||
HMaxRequestTimes: "10",
|
||||
}, makeTestTransportFactory(&created))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
transport1, err := manager.GetTransport()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id1 := transportID(transport1.entry.transport)
|
||||
|
||||
transport1.Close()
|
||||
|
||||
transport2, err := manager.GetTransport()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id2 := transportID(transport2.entry.transport)
|
||||
|
||||
if id1 != id2 {
|
||||
t.Fatalf("expected same transport to be reused, got %d and %d", id1, id2)
|
||||
}
|
||||
|
||||
transport2.Close()
|
||||
manager.Close()
|
||||
}
|
||||
|
||||
func TestManagerRespectMaxConnections(t *testing.T) {
|
||||
var created atomic.Int64
|
||||
|
||||
manager, err := NewReuseManager(&ReuseConfig{
|
||||
MaxConnections: "2",
|
||||
MaxConcurrency: "1",
|
||||
HMaxRequestTimes: "100",
|
||||
}, makeTestTransportFactory(&created))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
transport1, err := manager.GetTransport()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if transport1 == nil {
|
||||
t.Fatal("expected first entry")
|
||||
}
|
||||
|
||||
transport2, err := manager.GetTransport()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if transport2 == nil {
|
||||
t.Fatal("expected second entry")
|
||||
}
|
||||
|
||||
if transport1.entry == transport2.entry {
|
||||
t.Fatal("expected different entries for first two allocations")
|
||||
}
|
||||
|
||||
transport3, err := manager.GetTransport()
|
||||
if err == nil {
|
||||
t.Fatal("expected error when max-connections reached and all entries are at max-concurrency")
|
||||
}
|
||||
if transport3 != nil {
|
||||
t.Fatal("expected nil entry on allocation failure")
|
||||
}
|
||||
|
||||
transport1.Close()
|
||||
transport2.Close()
|
||||
manager.Close()
|
||||
}
|
||||
|
||||
func TestManagerRotateOnRequestLimit(t *testing.T) {
|
||||
var created atomic.Int64
|
||||
|
||||
manager, err := NewReuseManager(&ReuseConfig{
|
||||
MaxConnections: "1",
|
||||
MaxConcurrency: "1",
|
||||
HMaxRequestTimes: "1",
|
||||
}, makeTestTransportFactory(&created))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
transport1, err := manager.GetTransport()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id1 := transportID(transport1.entry.transport)
|
||||
|
||||
transport1.Close()
|
||||
|
||||
transport2, err := manager.GetTransport()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id2 := transportID(transport2.entry.transport)
|
||||
|
||||
if id1 == id2 {
|
||||
t.Fatalf("expected new transport after request limit, got same id %d", id1)
|
||||
}
|
||||
|
||||
transport2.Close()
|
||||
manager.Close()
|
||||
}
|
||||
|
||||
func TestManagerRotateOnReusableSecs(t *testing.T) {
|
||||
var created atomic.Int64
|
||||
|
||||
manager, err := NewReuseManager(&ReuseConfig{
|
||||
MaxConnections: "1",
|
||||
MaxConcurrency: "1",
|
||||
HMaxRequestTimes: "100",
|
||||
HMaxReusableSecs: "1",
|
||||
}, makeTestTransportFactory(&created))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
transport1, err := manager.GetTransport()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id1 := transportID(transport1.entry.transport)
|
||||
|
||||
time.Sleep(1100 * time.Millisecond)
|
||||
transport1.Close()
|
||||
|
||||
transport2, err := manager.GetTransport()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id2 := transportID(transport2.entry.transport)
|
||||
|
||||
if id1 == id2 {
|
||||
t.Fatalf("expected new transport after reusable timeout, got same id %d", id1)
|
||||
}
|
||||
|
||||
transport2.Close()
|
||||
manager.Close()
|
||||
}
|
||||
|
||||
func TestManagerRotateOnConnReuseLimit(t *testing.T) {
|
||||
var created atomic.Int64
|
||||
|
||||
manager, err := NewReuseManager(&ReuseConfig{
|
||||
MaxConnections: "1",
|
||||
MaxConcurrency: "1",
|
||||
CMaxReuseTimes: "1",
|
||||
HMaxRequestTimes: "100",
|
||||
}, makeTestTransportFactory(&created))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
transport1, err := manager.GetTransport()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id1 := transportID(transport1.entry.transport)
|
||||
|
||||
transport1.Close()
|
||||
|
||||
transport2, err := manager.GetTransport()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id2 := transportID(transport2.entry.transport)
|
||||
|
||||
if id1 != id2 {
|
||||
t.Fatalf("expected first reuse to use same transport, got %d and %d", id1, id2)
|
||||
}
|
||||
|
||||
transport2.Close()
|
||||
|
||||
transport3, err := manager.GetTransport()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id3 := transportID(transport3.entry.transport)
|
||||
|
||||
if id3 == id2 {
|
||||
t.Fatalf("expected new transport after c-max-reuse-times limit, got same id %d", id3)
|
||||
}
|
||||
|
||||
transport3.Close()
|
||||
manager.Close()
|
||||
}
|
||||
@@ -16,9 +16,7 @@ import (
|
||||
)
|
||||
|
||||
type ServerOption struct {
|
||||
Path string
|
||||
Host string
|
||||
Mode string
|
||||
Config
|
||||
ConnHandler func(net.Conn)
|
||||
HttpHandler http.Handler
|
||||
}
|
||||
@@ -96,9 +94,7 @@ func (s *httpSession) markConnected() {
|
||||
}
|
||||
|
||||
type requestHandler struct {
|
||||
path string
|
||||
host string
|
||||
mode string
|
||||
config Config
|
||||
connHandler func(net.Conn)
|
||||
httpHandler http.Handler
|
||||
|
||||
@@ -107,23 +103,10 @@ type requestHandler struct {
|
||||
}
|
||||
|
||||
func NewServerHandler(opt ServerOption) http.Handler {
|
||||
path := opt.Path
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
if !strings.HasSuffix(path, "/") {
|
||||
path += "/"
|
||||
}
|
||||
|
||||
// using h2c.NewHandler to ensure we can work in plain http2
|
||||
// and some tls conn is not *tls.Conn (like *reality.Conn)
|
||||
return h2c.NewHandler(&requestHandler{
|
||||
path: path,
|
||||
host: opt.Host,
|
||||
mode: opt.Mode,
|
||||
config: opt.Config,
|
||||
connHandler: opt.ConnHandler,
|
||||
httpHandler: opt.HttpHandler,
|
||||
sessions: map[string]*httpSession{},
|
||||
@@ -162,27 +145,76 @@ func (h *requestHandler) getSession(sessionID string) *httpSession {
|
||||
return h.sessions[sessionID]
|
||||
}
|
||||
|
||||
func (h *requestHandler) normalizedMode() string {
|
||||
if h.config.Mode == "" {
|
||||
return "auto"
|
||||
}
|
||||
return h.config.Mode
|
||||
}
|
||||
|
||||
func (h *requestHandler) allowStreamOne() bool {
|
||||
switch h.normalizedMode() {
|
||||
case "auto", "stream-one", "stream-up":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (h *requestHandler) allowSessionDownload() bool {
|
||||
switch h.normalizedMode() {
|
||||
case "auto", "stream-up", "packet-up":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (h *requestHandler) allowStreamUpUpload() bool {
|
||||
switch h.normalizedMode() {
|
||||
case "auto", "stream-up":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (h *requestHandler) allowPacketUpUpload() bool {
|
||||
switch h.normalizedMode() {
|
||||
case "auto", "packet-up":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if h.httpHandler != nil && !strings.HasPrefix(r.URL.Path, h.path) {
|
||||
path := h.config.NormalizedPath()
|
||||
if h.httpHandler != nil && !strings.HasPrefix(r.URL.Path, path) {
|
||||
h.httpHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if h.host != "" && !equalHost(r.Host, h.host) {
|
||||
if h.config.Host != "" && !equalHost(r.Host, h.config.Host) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(r.URL.Path, h.path) {
|
||||
if !strings.HasPrefix(r.URL.Path, path) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
rest := strings.TrimPrefix(r.URL.Path, h.path)
|
||||
rest := strings.TrimPrefix(r.URL.Path, path)
|
||||
parts := splitNonEmpty(rest)
|
||||
|
||||
// stream-one: POST /path
|
||||
if r.Method == http.MethodPost && len(parts) == 0 {
|
||||
if !h.allowStreamOne() {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("X-Accel-Buffering", "no")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
@@ -208,14 +240,27 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// packet-up download: GET /path/{session}
|
||||
// stream-up/packet-up download: GET /path/{session}
|
||||
if r.Method == http.MethodGet && len(parts) == 1 {
|
||||
if !h.allowSessionDownload() {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
sessionID := parts[0]
|
||||
session := h.getOrCreateSession(sessionID)
|
||||
session.markConnected()
|
||||
|
||||
// magic header instructs nginx + apache to not buffer response body
|
||||
w.Header().Set("X-Accel-Buffering", "no")
|
||||
// A web-compliant header telling all middleboxes to disable caching.
|
||||
// Should be able to prevent overloading the cache, or stop CDNs from
|
||||
// teeing the response stream into their cache, causing slowdowns.
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
if !h.config.NoSSEHeader {
|
||||
// magic header to make the HTTP middle box consider this as SSE to disable buffer
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if flusher, ok := w.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
@@ -244,6 +289,11 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// stream-up upload: POST /path/{session}
|
||||
if r.Method == http.MethodPost && len(parts) == 1 {
|
||||
if !h.allowStreamUpUpload() {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
sessionID := parts[0]
|
||||
session := h.getSession(sessionID)
|
||||
if session == nil {
|
||||
@@ -251,38 +301,63 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
buf := make([]byte, 32*1024)
|
||||
var seq uint64
|
||||
|
||||
for {
|
||||
n, err := r.Body.Read(buf)
|
||||
if n > 0 {
|
||||
if pushErr := session.uploadQueue.Push(Packet{
|
||||
Seq: seq,
|
||||
Payload: buf[:n],
|
||||
}); pushErr != nil {
|
||||
http.Error(w, pushErr.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
seq++
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
httpSC := newHTTPServerConn(w, r.Body)
|
||||
err := session.uploadQueue.Push(Packet{
|
||||
Reader: httpSC,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// magic header instructs nginx + apache to not buffer response body
|
||||
w.Header().Set("X-Accel-Buffering", "no")
|
||||
// A web-compliant header telling all middleboxes to disable caching.
|
||||
// Should be able to prevent overloading the cache, or stop CDNs from
|
||||
// teeing the response stream into their cache, causing slowdowns.
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
if !h.config.NoSSEHeader {
|
||||
// magic header to make the HTTP middle box consider this as SSE to disable buffer
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
referrer := r.Header.Get("Referer")
|
||||
if referrer != "" {
|
||||
go func() {
|
||||
for {
|
||||
scStreamUpServerSecs, _ := h.config.GetNormalizedScStreamUpServerSecs()
|
||||
if scStreamUpServerSecs == 0 {
|
||||
break
|
||||
}
|
||||
paddingValue, _ := h.config.RandomPadding()
|
||||
if paddingValue == "" {
|
||||
break
|
||||
}
|
||||
_, err = httpSC.Write([]byte(paddingValue))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Duration(scStreamUpServerSecs) * time.Second)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-r.Context().Done():
|
||||
case <-httpSC.Wait():
|
||||
}
|
||||
|
||||
_ = httpSC.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// packet-up upload: POST /path/{session}/{seq}
|
||||
if r.Method == http.MethodPost && len(parts) == 2 {
|
||||
if !h.allowPacketUpUpload() {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
sessionID := parts[0]
|
||||
seq, err := strconv.ParseUint(parts[1], 10, 64)
|
||||
if err != nil {
|
||||
@@ -296,7 +371,13 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
scMaxEachPostBytes := int64(h.config.GetNormalizedScMaxEachPostBytes())
|
||||
if r.ContentLength > scMaxEachPostBytes {
|
||||
http.Error(w, "body too large", http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(io.LimitReader(r.Body, scMaxEachPostBytes+1))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package xhttp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/metacubex/http"
|
||||
"github.com/metacubex/http/httptest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestServerHandlerModeRestrictions(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
mode string
|
||||
method string
|
||||
target string
|
||||
wantStatus int
|
||||
}{
|
||||
{
|
||||
name: "StreamOneAcceptsStreamOne",
|
||||
mode: "stream-one",
|
||||
method: http.MethodPost,
|
||||
target: "https://example.com/xhttp/",
|
||||
wantStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "StreamOneRejectsSessionDownload",
|
||||
mode: "stream-one",
|
||||
method: http.MethodGet,
|
||||
target: "https://example.com/xhttp/session",
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "StreamUpAcceptsStreamOne",
|
||||
mode: "stream-up",
|
||||
method: http.MethodPost,
|
||||
target: "https://example.com/xhttp/",
|
||||
wantStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "StreamUpAllowsDownloadEndpoint",
|
||||
mode: "stream-up",
|
||||
method: http.MethodGet,
|
||||
target: "https://example.com/xhttp/session",
|
||||
wantStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "StreamUpRejectsPacketUpload",
|
||||
mode: "stream-up",
|
||||
method: http.MethodPost,
|
||||
target: "https://example.com/xhttp/session/0",
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "PacketUpAllowsDownloadEndpoint",
|
||||
mode: "packet-up",
|
||||
method: http.MethodGet,
|
||||
target: "https://example.com/xhttp/session",
|
||||
wantStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "PacketUpRejectsStreamOne",
|
||||
mode: "packet-up",
|
||||
method: http.MethodPost,
|
||||
target: "https://example.com/xhttp/",
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "PacketUpRejectsStreamUpUpload",
|
||||
mode: "packet-up",
|
||||
method: http.MethodPost,
|
||||
target: "https://example.com/xhttp/session",
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
handler := NewServerHandler(ServerOption{
|
||||
Config: Config{
|
||||
Path: "/xhttp",
|
||||
Mode: testCase.mode,
|
||||
},
|
||||
ConnHandler: func(conn net.Conn) {
|
||||
_ = conn.Close()
|
||||
},
|
||||
})
|
||||
|
||||
req := httptest.NewRequest(testCase.method, testCase.target, io.NopCloser(http.NoBody))
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, testCase.wantStatus, recorder.Result().StatusCode)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package xhttp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
@@ -8,6 +9,7 @@ import (
|
||||
type Packet struct {
|
||||
Seq uint64
|
||||
Payload []byte
|
||||
Reader io.ReadCloser
|
||||
}
|
||||
|
||||
type uploadQueue struct {
|
||||
@@ -17,6 +19,7 @@ type uploadQueue struct {
|
||||
nextSeq uint64
|
||||
buf []byte
|
||||
closed bool
|
||||
reader io.ReadCloser
|
||||
}
|
||||
|
||||
func NewUploadQueue() *uploadQueue {
|
||||
@@ -35,6 +38,16 @@ func (q *uploadQueue) Push(p Packet) error {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
|
||||
if q.reader != nil {
|
||||
return errors.New("uploadQueue.reader already exists")
|
||||
}
|
||||
|
||||
if p.Reader != nil {
|
||||
q.reader = p.Reader
|
||||
q.cond.Broadcast()
|
||||
return nil
|
||||
}
|
||||
|
||||
cp := make([]byte, len(p.Payload))
|
||||
copy(cp, p.Payload)
|
||||
q.packets[p.Seq] = cp
|
||||
@@ -44,12 +57,12 @@ func (q *uploadQueue) Push(p Packet) error {
|
||||
|
||||
func (q *uploadQueue) Read(b []byte) (int, error) {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
|
||||
for {
|
||||
if len(q.buf) > 0 {
|
||||
n := copy(b, q.buf)
|
||||
q.buf = q.buf[n:]
|
||||
q.mu.Unlock()
|
||||
return n, nil
|
||||
}
|
||||
|
||||
@@ -60,7 +73,13 @@ func (q *uploadQueue) Read(b []byte) (int, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if reader := q.reader; reader != nil {
|
||||
q.mu.Unlock() // unlock before calling q.reader.Read
|
||||
return reader.Read(b)
|
||||
}
|
||||
|
||||
if q.closed {
|
||||
q.mu.Unlock()
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
@@ -72,7 +91,11 @@ func (q *uploadQueue) Close() error {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
|
||||
var err error
|
||||
if q.reader != nil {
|
||||
err = q.reader.Close()
|
||||
}
|
||||
q.closed = true
|
||||
q.cond.Broadcast()
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ require (
|
||||
github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 // indirect
|
||||
github.com/metacubex/hkdf v0.1.0 // indirect
|
||||
github.com/metacubex/hpke v0.1.0 // indirect
|
||||
github.com/metacubex/http v0.1.0 // indirect
|
||||
github.com/metacubex/http v0.1.1 // indirect
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 // indirect
|
||||
github.com/metacubex/mhurl v0.1.0 // indirect
|
||||
github.com/metacubex/mihomo v1.7.0 // indirect
|
||||
@@ -67,12 +67,12 @@ require (
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12 // indirect
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7 // indirect
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect
|
||||
github.com/metacubex/sing-tun v0.4.16 // indirect
|
||||
github.com/metacubex/sing-tun v0.4.17 // indirect
|
||||
github.com/metacubex/sing-vmess v0.2.5 // indirect
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
|
||||
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 // indirect
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 // indirect
|
||||
github.com/metacubex/tls v0.1.4 // indirect
|
||||
github.com/metacubex/tls v0.1.5 // indirect
|
||||
github.com/metacubex/utls v1.8.4 // indirect
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect
|
||||
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect
|
||||
|
||||
@@ -98,8 +98,8 @@ github.com/metacubex/hkdf v0.1.0 h1:fPA6VzXK8cU1foc/TOmGCDmSa7pZbxlnqhl3RNsthaA=
|
||||
github.com/metacubex/hkdf v0.1.0/go.mod h1:3seEfds3smgTAXqUGn+tgEJH3uXdsUjOiduG/2EtvZ4=
|
||||
github.com/metacubex/hpke v0.1.0 h1:gu2jUNhraehWi0P/z5HX2md3d7L1FhPQE6/Q0E9r9xQ=
|
||||
github.com/metacubex/hpke v0.1.0/go.mod h1:vfDm6gfgrwlXUxKDkWbcE44hXtmc1uxLDm2BcR11b3U=
|
||||
github.com/metacubex/http v0.1.0 h1:Jcy0I9zKjYijSUaksZU34XEe2xNdoFkgUTB7z7K5q0o=
|
||||
github.com/metacubex/http v0.1.0/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
|
||||
github.com/metacubex/http v0.1.1 h1:zVea0zOoaZvxe51EvJMRWGNQv6MvWqJhkZSuoAjOjVw=
|
||||
github.com/metacubex/http v0.1.1/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI=
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc=
|
||||
github.com/metacubex/mhurl v0.1.0 h1:ZdW4Zxe3j3uJ89gNytOazHu6kbHn5owutN/VfXOI8GE=
|
||||
@@ -130,8 +130,8 @@ github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6w
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
|
||||
github.com/metacubex/sing-tun v0.4.16 h1:LosAe4A6TOPVcD7T1ReV9D2r5501woIXXZiim3D0RRg=
|
||||
github.com/metacubex/sing-tun v0.4.16/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
|
||||
github.com/metacubex/sing-tun v0.4.17 h1:ehzvPLyxG1vmjaKVeB0aEK1eqhR3reEzdbqQfM3+5XA=
|
||||
github.com/metacubex/sing-tun v0.4.17/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
|
||||
github.com/metacubex/sing-vmess v0.2.5 h1:m9Zt5I27lB9fmLMZfism9sH2LcnAfShZfwSkf6/KJoE=
|
||||
github.com/metacubex/sing-vmess v0.2.5/go.mod h1:AwtlzUgf8COe9tRYAKqWZ+leDH7p5U98a0ZUpYehl8Q=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
|
||||
@@ -140,8 +140,8 @@ github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 h1:DK2l6m2Fc85H2Bhi
|
||||
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141/go.mod h1:/yI4OiGOSn0SURhZdJF3CbtPg3nwK700bG8TZLMBvAg=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/tls v0.1.4 h1:Gm5GrkyMUh52gYOMIAQ1kHIym4v4M3Qb87Wsmd8Kpdc=
|
||||
github.com/metacubex/tls v0.1.4/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
|
||||
github.com/metacubex/tls v0.1.5 h1:ECcB83dj+zadnhlKcLnUUf1Sq6+vU0f/zoyU0+9oPTc=
|
||||
github.com/metacubex/tls v0.1.5/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
|
||||
github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg=
|
||||
github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
|
||||
|
||||
@@ -56,7 +56,7 @@ require (
|
||||
github.com/metacubex/gvisor v0.0.0-20251227095601-261ec1326fe8 // indirect
|
||||
github.com/metacubex/hkdf v0.1.0 // indirect
|
||||
github.com/metacubex/hpke v0.1.0 // indirect
|
||||
github.com/metacubex/http v0.1.0 // indirect
|
||||
github.com/metacubex/http v0.1.1 // indirect
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 // indirect
|
||||
github.com/metacubex/mhurl v0.1.0 // indirect
|
||||
github.com/metacubex/mlkem v0.1.0 // indirect
|
||||
@@ -71,12 +71,12 @@ require (
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12 // indirect
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7 // indirect
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 // indirect
|
||||
github.com/metacubex/sing-tun v0.4.16 // indirect
|
||||
github.com/metacubex/sing-tun v0.4.17 // indirect
|
||||
github.com/metacubex/sing-vmess v0.2.5 // indirect
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f // indirect
|
||||
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 // indirect
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 // indirect
|
||||
github.com/metacubex/tls v0.1.4 // indirect
|
||||
github.com/metacubex/tls v0.1.5 // indirect
|
||||
github.com/metacubex/utls v1.8.4 // indirect
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f // indirect
|
||||
github.com/metacubex/yamux v0.0.0-20250918083631-dd5f17c0be49 // indirect
|
||||
|
||||
@@ -98,8 +98,8 @@ github.com/metacubex/hkdf v0.1.0 h1:fPA6VzXK8cU1foc/TOmGCDmSa7pZbxlnqhl3RNsthaA=
|
||||
github.com/metacubex/hkdf v0.1.0/go.mod h1:3seEfds3smgTAXqUGn+tgEJH3uXdsUjOiduG/2EtvZ4=
|
||||
github.com/metacubex/hpke v0.1.0 h1:gu2jUNhraehWi0P/z5HX2md3d7L1FhPQE6/Q0E9r9xQ=
|
||||
github.com/metacubex/hpke v0.1.0/go.mod h1:vfDm6gfgrwlXUxKDkWbcE44hXtmc1uxLDm2BcR11b3U=
|
||||
github.com/metacubex/http v0.1.0 h1:Jcy0I9zKjYijSUaksZU34XEe2xNdoFkgUTB7z7K5q0o=
|
||||
github.com/metacubex/http v0.1.0/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
|
||||
github.com/metacubex/http v0.1.1 h1:zVea0zOoaZvxe51EvJMRWGNQv6MvWqJhkZSuoAjOjVw=
|
||||
github.com/metacubex/http v0.1.1/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI=
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc=
|
||||
github.com/metacubex/mhurl v0.1.0 h1:ZdW4Zxe3j3uJ89gNytOazHu6kbHn5owutN/VfXOI8GE=
|
||||
@@ -130,8 +130,8 @@ github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6w
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
|
||||
github.com/metacubex/sing-tun v0.4.16 h1:LosAe4A6TOPVcD7T1ReV9D2r5501woIXXZiim3D0RRg=
|
||||
github.com/metacubex/sing-tun v0.4.16/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
|
||||
github.com/metacubex/sing-tun v0.4.17 h1:ehzvPLyxG1vmjaKVeB0aEK1eqhR3reEzdbqQfM3+5XA=
|
||||
github.com/metacubex/sing-tun v0.4.17/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w=
|
||||
github.com/metacubex/sing-vmess v0.2.5 h1:m9Zt5I27lB9fmLMZfism9sH2LcnAfShZfwSkf6/KJoE=
|
||||
github.com/metacubex/sing-vmess v0.2.5/go.mod h1:AwtlzUgf8COe9tRYAKqWZ+leDH7p5U98a0ZUpYehl8Q=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
|
||||
@@ -140,8 +140,8 @@ github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 h1:DK2l6m2Fc85H2Bhi
|
||||
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141/go.mod h1:/yI4OiGOSn0SURhZdJF3CbtPg3nwK700bG8TZLMBvAg=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/tls v0.1.4 h1:Gm5GrkyMUh52gYOMIAQ1kHIym4v4M3Qb87Wsmd8Kpdc=
|
||||
github.com/metacubex/tls v0.1.4/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
|
||||
github.com/metacubex/tls v0.1.5 h1:ECcB83dj+zadnhlKcLnUUf1Sq6+vU0f/zoyU0+9oPTc=
|
||||
github.com/metacubex/tls v0.1.5/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
|
||||
github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg=
|
||||
github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
|
||||
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
From b8f897a9da7a82ad8584a22284ceac61262fcb7e Mon Sep 17 00:00:00 2001
|
||||
From: Jorropo <jorropo.pgm@gmail.com>
|
||||
Date: Sun, 22 Feb 2026 01:47:45 +0100
|
||||
Subject: [PATCH] runtime: fix value of ENOSYS on mips from 38 to 89
|
||||
|
||||
Fixes #77731
|
||||
|
||||
Change-Id: Iaca444e2d5f9e19fd2de38414b357b41471a668c
|
||||
---
|
||||
|
||||
diff --git a/src/runtime/defs_linux_mips64x.go b/src/runtime/defs_linux_mips64x.go
|
||||
index 7449d2c..4d0f103 100644
|
||||
--- a/src/runtime/defs_linux_mips64x.go
|
||||
+++ b/src/runtime/defs_linux_mips64x.go
|
||||
@@ -12,7 +12,7 @@
|
||||
_EINTR = 0x4
|
||||
_EAGAIN = 0xb
|
||||
_ENOMEM = 0xc
|
||||
- _ENOSYS = 0x26
|
||||
+ _ENOSYS = 0x59
|
||||
|
||||
_PROT_NONE = 0x0
|
||||
_PROT_READ = 0x1
|
||||
diff --git a/src/runtime/defs_linux_mipsx.go b/src/runtime/defs_linux_mipsx.go
|
||||
index 5a446e0..b8da4d0 100644
|
||||
--- a/src/runtime/defs_linux_mipsx.go
|
||||
+++ b/src/runtime/defs_linux_mipsx.go
|
||||
@@ -12,7 +12,7 @@
|
||||
_EINTR = 0x4
|
||||
_EAGAIN = 0xb
|
||||
_ENOMEM = 0xc
|
||||
- _ENOSYS = 0x26
|
||||
+ _ENOSYS = 0x59
|
||||
|
||||
_PROT_NONE = 0x0
|
||||
_PROT_READ = 0x1
|
||||
-265
@@ -1,265 +0,0 @@
|
||||
From 1a44be4cecdc742ac6cce9825f9ffc19857c99f3 Mon Sep 17 00:00:00 2001
|
||||
From: database64128 <free122448@hotmail.com>
|
||||
Date: Mon, 9 Mar 2026 16:25:16 +0800
|
||||
Subject: [PATCH] [release-branch.go1.26] internal/poll: move rsan to heap on
|
||||
windows
|
||||
|
||||
According to https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsarecvfrom,
|
||||
the memory pointed to by lpFromlen must remain available during the
|
||||
overlapped I/O, and therefore cannot be allocated on the stack.
|
||||
|
||||
CL 685417 moved the rsan field out of the operation struct and placed
|
||||
it on stack, which violates the above requirement and causes stack
|
||||
corruption.
|
||||
|
||||
Unfortunately, it is no longer possible to cleanly revert CL 685417.
|
||||
Instead of attempting to revert it, this CL bundles rsan together
|
||||
with rsa in the same sync.Pool. The new wsaRsa struct is still in the
|
||||
same size class, so no additional overhead is introduced by this
|
||||
change.
|
||||
|
||||
Fixes #78041.
|
||||
|
||||
Change-Id: I5ffbccb332515116ddc03fb7c40ffc9293cad2ab
|
||||
Reviewed-on: https://go-review.googlesource.com/c/go/+/753040
|
||||
Reviewed-by: Quim Muntal <quimmuntal@gmail.com>
|
||||
Reviewed-by: Cherry Mui <cherryyz@google.com>
|
||||
Commit-Queue: Cherry Mui <cherryyz@google.com>
|
||||
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
|
||||
Reviewed-by: Damien Neil <dneil@google.com>
|
||||
Reviewed-on: https://go-review.googlesource.com/c/go/+/753480
|
||||
Reviewed-by: Mark Freeman <markfreeman@google.com>
|
||||
---
|
||||
src/internal/poll/fd_windows.go | 94 +++++++++++++++++++++------------
|
||||
1 file changed, 59 insertions(+), 35 deletions(-)
|
||||
|
||||
diff --git a/src/internal/poll/fd_windows.go b/src/internal/poll/fd_windows.go
|
||||
index 2ba967f990982f..26319548e3c310 100644
|
||||
--- a/src/internal/poll/fd_windows.go
|
||||
+++ b/src/internal/poll/fd_windows.go
|
||||
@@ -149,7 +149,7 @@ var wsaMsgPool = sync.Pool{
|
||||
|
||||
// newWSAMsg creates a new WSAMsg with the provided parameters.
|
||||
// Use [freeWSAMsg] to free it.
|
||||
-func newWSAMsg(p []byte, oob []byte, flags int, unconnected bool) *windows.WSAMsg {
|
||||
+func newWSAMsg(p []byte, oob []byte, flags int, rsa *wsaRsa) *windows.WSAMsg {
|
||||
// The returned object can't be allocated in the stack because it is accessed asynchronously
|
||||
// by Windows in between several system calls. If the stack frame is moved while that happens,
|
||||
// then Windows may access invalid memory.
|
||||
@@ -166,34 +166,46 @@ func newWSAMsg(p []byte, oob []byte, flags int, unconnected bool) *windows.WSAMs
|
||||
}
|
||||
}
|
||||
msg.Flags = uint32(flags)
|
||||
- if unconnected {
|
||||
- msg.Name = wsaRsaPool.Get().(*syscall.RawSockaddrAny)
|
||||
- msg.Namelen = int32(unsafe.Sizeof(syscall.RawSockaddrAny{}))
|
||||
+ if rsa != nil {
|
||||
+ msg.Name = &rsa.name
|
||||
+ msg.Namelen = rsa.namelen
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func freeWSAMsg(msg *windows.WSAMsg) {
|
||||
// Clear pointers to buffers so they can be released by garbage collector.
|
||||
+ msg.Name = nil
|
||||
+ msg.Namelen = 0
|
||||
msg.Buffers.Len = 0
|
||||
msg.Buffers.Buf = nil
|
||||
msg.Control.Len = 0
|
||||
msg.Control.Buf = nil
|
||||
- if msg.Name != nil {
|
||||
- *msg.Name = syscall.RawSockaddrAny{}
|
||||
- wsaRsaPool.Put(msg.Name)
|
||||
- msg.Name = nil
|
||||
- msg.Namelen = 0
|
||||
- }
|
||||
wsaMsgPool.Put(msg)
|
||||
}
|
||||
|
||||
+// wsaRsa bundles a [syscall.RawSockaddrAny] with its length for efficient caching.
|
||||
+//
|
||||
+// When used by WSARecvFrom, wsaRsa must be on the heap. See
|
||||
+// https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsarecvfrom.
|
||||
+type wsaRsa struct {
|
||||
+ name syscall.RawSockaddrAny
|
||||
+ namelen int32
|
||||
+}
|
||||
+
|
||||
var wsaRsaPool = sync.Pool{
|
||||
New: func() any {
|
||||
- return new(syscall.RawSockaddrAny)
|
||||
+ return new(wsaRsa)
|
||||
},
|
||||
}
|
||||
|
||||
+func newWSARsa() *wsaRsa {
|
||||
+ rsa := wsaRsaPool.Get().(*wsaRsa)
|
||||
+ rsa.name = syscall.RawSockaddrAny{}
|
||||
+ rsa.namelen = int32(unsafe.Sizeof(syscall.RawSockaddrAny{}))
|
||||
+ return rsa
|
||||
+}
|
||||
+
|
||||
var operationPool = sync.Pool{
|
||||
New: func() any {
|
||||
return new(operation)
|
||||
@@ -739,19 +751,18 @@ func (fd *FD) ReadFrom(buf []byte) (int, syscall.Sockaddr, error) {
|
||||
|
||||
fd.pin('r', &buf[0])
|
||||
|
||||
- rsa := wsaRsaPool.Get().(*syscall.RawSockaddrAny)
|
||||
+ rsa := newWSARsa()
|
||||
defer wsaRsaPool.Put(rsa)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
- rsan := int32(unsafe.Sizeof(*rsa))
|
||||
var flags uint32
|
||||
- err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, rsa, &rsan, &o.o, nil)
|
||||
+ err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, &rsa.name, &rsa.namelen, &o.o, nil)
|
||||
return qty, err
|
||||
})
|
||||
err = fd.eofError(n, err)
|
||||
if err != nil {
|
||||
return n, nil, err
|
||||
}
|
||||
- sa, _ := rsa.Sockaddr()
|
||||
+ sa, _ := rsa.name.Sockaddr()
|
||||
return n, sa, nil
|
||||
}
|
||||
|
||||
@@ -770,19 +781,18 @@ func (fd *FD) ReadFromInet4(buf []byte, sa4 *syscall.SockaddrInet4) (int, error)
|
||||
|
||||
fd.pin('r', &buf[0])
|
||||
|
||||
- rsa := wsaRsaPool.Get().(*syscall.RawSockaddrAny)
|
||||
+ rsa := newWSARsa()
|
||||
defer wsaRsaPool.Put(rsa)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
- rsan := int32(unsafe.Sizeof(*rsa))
|
||||
var flags uint32
|
||||
- err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, rsa, &rsan, &o.o, nil)
|
||||
+ err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, &rsa.name, &rsa.namelen, &o.o, nil)
|
||||
return qty, err
|
||||
})
|
||||
err = fd.eofError(n, err)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
- rawToSockaddrInet4(rsa, sa4)
|
||||
+ rawToSockaddrInet4(&rsa.name, sa4)
|
||||
return n, err
|
||||
}
|
||||
|
||||
@@ -801,19 +811,18 @@ func (fd *FD) ReadFromInet6(buf []byte, sa6 *syscall.SockaddrInet6) (int, error)
|
||||
|
||||
fd.pin('r', &buf[0])
|
||||
|
||||
- rsa := wsaRsaPool.Get().(*syscall.RawSockaddrAny)
|
||||
+ rsa := newWSARsa()
|
||||
defer wsaRsaPool.Put(rsa)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
- rsan := int32(unsafe.Sizeof(*rsa))
|
||||
var flags uint32
|
||||
- err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, rsa, &rsan, &o.o, nil)
|
||||
+ err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, &rsa.name, &rsa.namelen, &o.o, nil)
|
||||
return qty, err
|
||||
})
|
||||
err = fd.eofError(n, err)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
- rawToSockaddrInet6(rsa, sa6)
|
||||
+ rawToSockaddrInet6(&rsa.name, sa6)
|
||||
return n, err
|
||||
}
|
||||
|
||||
@@ -1373,7 +1382,9 @@ func (fd *FD) ReadMsg(p []byte, oob []byte, flags int) (int, int, int, syscall.S
|
||||
p = p[:maxRW]
|
||||
}
|
||||
|
||||
- msg := newWSAMsg(p, oob, flags, true)
|
||||
+ rsa := newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
+ msg := newWSAMsg(p, oob, flags, rsa)
|
||||
defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSARecvMsg(fd.Sysfd, msg, &qty, &o.o, nil)
|
||||
@@ -1398,7 +1409,9 @@ func (fd *FD) ReadMsgInet4(p []byte, oob []byte, flags int, sa4 *syscall.Sockadd
|
||||
p = p[:maxRW]
|
||||
}
|
||||
|
||||
- msg := newWSAMsg(p, oob, flags, true)
|
||||
+ rsa := newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
+ msg := newWSAMsg(p, oob, flags, rsa)
|
||||
defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSARecvMsg(fd.Sysfd, msg, &qty, &o.o, nil)
|
||||
@@ -1422,7 +1435,9 @@ func (fd *FD) ReadMsgInet6(p []byte, oob []byte, flags int, sa6 *syscall.Sockadd
|
||||
p = p[:maxRW]
|
||||
}
|
||||
|
||||
- msg := newWSAMsg(p, oob, flags, true)
|
||||
+ rsa := newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
+ msg := newWSAMsg(p, oob, flags, rsa)
|
||||
defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSARecvMsg(fd.Sysfd, msg, &qty, &o.o, nil)
|
||||
@@ -1446,15 +1461,18 @@ func (fd *FD) WriteMsg(p []byte, oob []byte, sa syscall.Sockaddr) (int, int, err
|
||||
}
|
||||
defer fd.writeUnlock()
|
||||
|
||||
- msg := newWSAMsg(p, oob, 0, sa != nil)
|
||||
- defer freeWSAMsg(msg)
|
||||
+ var rsa *wsaRsa
|
||||
if sa != nil {
|
||||
+ rsa = newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
var err error
|
||||
- msg.Namelen, err = sockaddrToRaw(msg.Name, sa)
|
||||
+ rsa.namelen, err = sockaddrToRaw(&rsa.name, sa)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
+ msg := newWSAMsg(p, oob, 0, rsa)
|
||||
+ defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSASendMsg(fd.Sysfd, msg, 0, nil, &o.o, nil)
|
||||
return qty, err
|
||||
@@ -1473,11 +1491,14 @@ func (fd *FD) WriteMsgInet4(p []byte, oob []byte, sa *syscall.SockaddrInet4) (in
|
||||
}
|
||||
defer fd.writeUnlock()
|
||||
|
||||
- msg := newWSAMsg(p, oob, 0, sa != nil)
|
||||
- defer freeWSAMsg(msg)
|
||||
+ var rsa *wsaRsa
|
||||
if sa != nil {
|
||||
- msg.Namelen = sockaddrInet4ToRaw(msg.Name, sa)
|
||||
+ rsa = newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
+ rsa.namelen = sockaddrInet4ToRaw(&rsa.name, sa)
|
||||
}
|
||||
+ msg := newWSAMsg(p, oob, 0, rsa)
|
||||
+ defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSASendMsg(fd.Sysfd, msg, 0, nil, &o.o, nil)
|
||||
return qty, err
|
||||
@@ -1496,11 +1517,14 @@ func (fd *FD) WriteMsgInet6(p []byte, oob []byte, sa *syscall.SockaddrInet6) (in
|
||||
}
|
||||
defer fd.writeUnlock()
|
||||
|
||||
- msg := newWSAMsg(p, oob, 0, sa != nil)
|
||||
- defer freeWSAMsg(msg)
|
||||
+ var rsa *wsaRsa
|
||||
if sa != nil {
|
||||
- msg.Namelen = sockaddrInet6ToRaw(msg.Name, sa)
|
||||
+ rsa = newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
+ rsa.namelen = sockaddrInet6ToRaw(&rsa.name, sa)
|
||||
}
|
||||
+ msg := newWSAMsg(p, oob, 0, rsa)
|
||||
+ defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSASendMsg(fd.Sysfd, msg, 0, nil, &o.o, nil)
|
||||
return qty, err
|
||||
-14
@@ -186,20 +186,6 @@ jobs:
|
||||
- name: Verify Go env
|
||||
run: go env
|
||||
|
||||
# TODO: remove after issue77731 fixed, see: https://github.com/golang/go/issues/77731
|
||||
- name: Fix issue77731 for Golang1.26
|
||||
if: ${{ matrix.jobs.goversion == '' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/issue77731.patch
|
||||
|
||||
# TODO: remove after issue77975 fixed, see: https://github.com/golang/go/issues/77975
|
||||
- name: Fix issue77975 for Golang1.26
|
||||
if: ${{ matrix.jobs.goversion == '' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/issue77975.patch
|
||||
|
||||
# TODO: remove after issue77930 fixed, see: https://github.com/golang/go/issues/77930
|
||||
- name: Fix issue77930 for Golang1.26
|
||||
if: ${{ matrix.jobs.goversion == '' }}
|
||||
|
||||
-7
@@ -57,13 +57,6 @@ jobs:
|
||||
- name: Verify Go env
|
||||
run: go env
|
||||
|
||||
# TODO: remove after issue77975 fixed, see: https://github.com/golang/go/issues/77975
|
||||
- name: Fix issue77975 for Golang1.26
|
||||
if: ${{ matrix.go-version == '1.26' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/issue77975.patch
|
||||
|
||||
- name: Remove inbound test for macOS
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
run: |
|
||||
|
||||
@@ -82,7 +82,7 @@ type XHTTPOptions struct {
|
||||
Headers map[string]string `proxy:"headers,omitempty"`
|
||||
NoGRPCHeader bool `proxy:"no-grpc-header,omitempty"`
|
||||
XPaddingBytes string `proxy:"x-padding-bytes,omitempty"`
|
||||
ScMaxEachPostBytes int `proxy:"sc-max-each-post-bytes,omitempty"`
|
||||
ScMaxEachPostBytes string `proxy:"sc-max-each-post-bytes,omitempty"`
|
||||
ReuseSettings *XHTTPReuseSettings `proxy:"reuse-settings,omitempty"` // aka XMUX
|
||||
DownloadSettings *XHTTPDownloadSettings `proxy:"download-settings,omitempty"`
|
||||
}
|
||||
@@ -102,7 +102,7 @@ type XHTTPDownloadSettings struct {
|
||||
Headers *map[string]string `proxy:"headers,omitempty"`
|
||||
NoGRPCHeader *bool `proxy:"no-grpc-header,omitempty"`
|
||||
XPaddingBytes *string `proxy:"x-padding-bytes,omitempty"`
|
||||
ScMaxEachPostBytes *int `proxy:"sc-max-each-post-bytes,omitempty"`
|
||||
ScMaxEachPostBytes *string `proxy:"sc-max-each-post-bytes,omitempty"`
|
||||
ReuseSettings *XHTTPReuseSettings `proxy:"reuse-settings,omitempty"` // aka XMUX
|
||||
// proxy part
|
||||
Server *string `proxy:"server,omitempty"`
|
||||
|
||||
@@ -1218,8 +1218,8 @@ proxies: # socks5
|
||||
# quic: true # 默认为false
|
||||
# congestion-controller: bbr
|
||||
### reuse options
|
||||
# max-connections: 1 # Maximum connections. Conflict with max-streams.
|
||||
# min-streams: 0 # Minimum multiplexed streams in a connection before opening a new connection. Conflict with max-streams.
|
||||
# max-connections: 8 # Maximum connections. Conflict with max-streams.
|
||||
# min-streams: 5 # Minimum multiplexed streams in a connection before opening a new connection. Conflict with max-streams.
|
||||
# max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.
|
||||
|
||||
# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@ require (
|
||||
github.com/metacubex/edwards25519 v1.2.0
|
||||
github.com/metacubex/fswatch v0.1.1
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||
github.com/metacubex/http v0.1.0
|
||||
github.com/metacubex/http v0.1.1
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604
|
||||
github.com/metacubex/mhurl v0.1.0
|
||||
github.com/metacubex/mlkem v0.1.0
|
||||
@@ -39,7 +39,7 @@ require (
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
|
||||
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443
|
||||
github.com/metacubex/tls v0.1.4
|
||||
github.com/metacubex/tls v0.1.5
|
||||
github.com/metacubex/utls v1.8.4
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f
|
||||
github.com/mroth/weightedrand/v2 v2.1.0
|
||||
|
||||
+4
-4
@@ -103,8 +103,8 @@ github.com/metacubex/hkdf v0.1.0 h1:fPA6VzXK8cU1foc/TOmGCDmSa7pZbxlnqhl3RNsthaA=
|
||||
github.com/metacubex/hkdf v0.1.0/go.mod h1:3seEfds3smgTAXqUGn+tgEJH3uXdsUjOiduG/2EtvZ4=
|
||||
github.com/metacubex/hpke v0.1.0 h1:gu2jUNhraehWi0P/z5HX2md3d7L1FhPQE6/Q0E9r9xQ=
|
||||
github.com/metacubex/hpke v0.1.0/go.mod h1:vfDm6gfgrwlXUxKDkWbcE44hXtmc1uxLDm2BcR11b3U=
|
||||
github.com/metacubex/http v0.1.0 h1:Jcy0I9zKjYijSUaksZU34XEe2xNdoFkgUTB7z7K5q0o=
|
||||
github.com/metacubex/http v0.1.0/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
|
||||
github.com/metacubex/http v0.1.1 h1:zVea0zOoaZvxe51EvJMRWGNQv6MvWqJhkZSuoAjOjVw=
|
||||
github.com/metacubex/http v0.1.1/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI=
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc=
|
||||
github.com/metacubex/mhurl v0.1.0 h1:ZdW4Zxe3j3uJ89gNytOazHu6kbHn5owutN/VfXOI8GE=
|
||||
@@ -145,8 +145,8 @@ github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 h1:DK2l6m2Fc85H2Bhi
|
||||
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141/go.mod h1:/yI4OiGOSn0SURhZdJF3CbtPg3nwK700bG8TZLMBvAg=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/tls v0.1.4 h1:Gm5GrkyMUh52gYOMIAQ1kHIym4v4M3Qb87Wsmd8Kpdc=
|
||||
github.com/metacubex/tls v0.1.4/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
|
||||
github.com/metacubex/tls v0.1.5 h1:ECcB83dj+zadnhlKcLnUUf1Sq6+vU0f/zoyU0+9oPTc=
|
||||
github.com/metacubex/tls v0.1.5/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
|
||||
github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg=
|
||||
github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
|
||||
|
||||
@@ -36,7 +36,7 @@ type XHTTPConfig struct {
|
||||
Mode string
|
||||
NoSSEHeader bool
|
||||
ScStreamUpServerSecs string
|
||||
ScMaxEachPostBytes int
|
||||
ScMaxEachPostBytes string
|
||||
}
|
||||
|
||||
func (t VlessServer) String() string {
|
||||
|
||||
@@ -37,7 +37,7 @@ type XHTTPConfig struct {
|
||||
Mode string `inbound:"mode,omitempty"`
|
||||
NoSSEHeader bool `inbound:"no-sse-header,omitempty"`
|
||||
ScStreamUpServerSecs string `inbound:"sc-stream-up-server-secs,omitempty"`
|
||||
ScMaxEachPostBytes int `inbound:"sc-max-each-post-bytes,omitempty"`
|
||||
ScMaxEachPostBytes string `inbound:"sc-max-each-post-bytes,omitempty"`
|
||||
}
|
||||
|
||||
func (o XHTTPConfig) Build() LC.XHTTPConfig {
|
||||
|
||||
@@ -154,7 +154,7 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
}
|
||||
}
|
||||
if config.XHTTPConfig.Path != "" || config.XHTTPConfig.Host != "" || config.XHTTPConfig.Mode != "" {
|
||||
httpServer.Handler = xhttp.NewServerHandler(xhttp.ServerOption{
|
||||
httpServer.Handler, err = xhttp.NewServerHandler(xhttp.ServerOption{
|
||||
Config: xhttp.Config{
|
||||
Host: config.XHTTPConfig.Host,
|
||||
Path: config.XHTTPConfig.Path,
|
||||
@@ -168,6 +168,9 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
},
|
||||
HttpHandler: httpServer.Handler,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !slices.Contains(tlsConfig.NextProtos, "http/1.1") {
|
||||
tlsConfig.NextProtos = append([]string{"http/1.1"}, tlsConfig.NextProtos...)
|
||||
}
|
||||
|
||||
@@ -168,52 +168,37 @@ func (c *Client) roundTrip(request *http.Request, conn *httpConn) {
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *Client) Dial(ctx context.Context, host string) (net.Conn, error) {
|
||||
func (c *Client) newConnectRequest(host, userAgent string) *http.Request {
|
||||
request := &http.Request{
|
||||
Method: http.MethodConnect,
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: host,
|
||||
Host: c.server, // Use the proxy server authority so the pool keys reuse against the actual proxy endpoint.
|
||||
},
|
||||
Header: make(http.Header),
|
||||
Host: host,
|
||||
Host: host, // Send the actual CONNECT target as the Host header (:authority).
|
||||
}
|
||||
request.Header.Add("User-Agent", TCPUserAgent)
|
||||
request.Header.Add("User-Agent", userAgent)
|
||||
request.Header.Add("Proxy-Authorization", c.auth)
|
||||
return request
|
||||
}
|
||||
|
||||
func (c *Client) Dial(ctx context.Context, host string) (net.Conn, error) {
|
||||
request := c.newConnectRequest(host, TCPUserAgent)
|
||||
conn := &tcpConn{}
|
||||
c.roundTrip(request, &conn.httpConn)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListenPacket(ctx context.Context) (net.PacketConn, error) {
|
||||
request := &http.Request{
|
||||
Method: http.MethodConnect,
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: UDPMagicAddress,
|
||||
},
|
||||
Header: make(http.Header),
|
||||
Host: UDPMagicAddress,
|
||||
}
|
||||
request.Header.Add("User-Agent", UDPUserAgent)
|
||||
request.Header.Add("Proxy-Authorization", c.auth)
|
||||
request := c.newConnectRequest(UDPMagicAddress, UDPUserAgent)
|
||||
conn := &clientPacketConn{}
|
||||
c.roundTrip(request, &conn.httpConn)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListenICMP(ctx context.Context) (*IcmpConn, error) {
|
||||
request := &http.Request{
|
||||
Method: http.MethodConnect,
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: ICMPMagicAddress,
|
||||
},
|
||||
Header: make(http.Header),
|
||||
Host: ICMPMagicAddress,
|
||||
}
|
||||
request.Header.Add("User-Agent", ICMPUserAgent)
|
||||
request.Header.Add("Proxy-Authorization", c.auth)
|
||||
request := c.newConnectRequest(ICMPMagicAddress, ICMPUserAgent)
|
||||
conn := &IcmpConn{}
|
||||
c.roundTrip(request, &conn.httpConn)
|
||||
return conn, nil
|
||||
@@ -234,17 +219,7 @@ func (c *Client) ResetConnections() {
|
||||
|
||||
func (c *Client) HealthCheck(ctx context.Context) error {
|
||||
defer c.resetHealthCheckTimer()
|
||||
request := &http.Request{
|
||||
Method: http.MethodConnect,
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: HealthCheckMagicAddress,
|
||||
},
|
||||
Header: make(http.Header),
|
||||
Host: HealthCheckMagicAddress,
|
||||
}
|
||||
request.Header.Add("User-Agent", HealthCheckUserAgent)
|
||||
request.Header.Add("Proxy-Authorization", c.auth)
|
||||
request := c.newConnectRequest(HealthCheckMagicAddress, HealthCheckUserAgent)
|
||||
response, err := c.roundTripper.RoundTrip(request.WithContext(ctx))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -271,7 +246,8 @@ func NewPoolClient(ctx context.Context, options ClientOptions) (*PoolClient, err
|
||||
minStreams := options.MinStreams
|
||||
maxStreams := options.MaxStreams
|
||||
if maxConnections == 0 && minStreams == 0 && maxStreams == 0 {
|
||||
maxConnections = 1
|
||||
maxConnections = 8
|
||||
minStreams = 5
|
||||
}
|
||||
client, err := NewClient(ctx, options) // reserve one client and verify the configuration
|
||||
if err != nil {
|
||||
|
||||
@@ -24,19 +24,20 @@ type WrapTLSFunc func(ctx context.Context, conn net.Conn, isH2 bool) (net.Conn,
|
||||
type TransportMaker func() http.RoundTripper
|
||||
|
||||
type PacketUpWriter struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
cfg *Config
|
||||
sessionID string
|
||||
transport http.RoundTripper
|
||||
writeMu sync.Mutex
|
||||
seq uint64
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
cfg *Config
|
||||
scMaxEachPostBytes Range
|
||||
sessionID string
|
||||
transport http.RoundTripper
|
||||
writeMu sync.Mutex
|
||||
seq uint64
|
||||
}
|
||||
|
||||
func (c *PacketUpWriter) Write(b []byte) (int, error) {
|
||||
c.writeMu.Lock()
|
||||
defer c.writeMu.Unlock()
|
||||
scMaxEachPostBytes := c.cfg.GetNormalizedScMaxEachPostBytes()
|
||||
scMaxEachPostBytes := c.scMaxEachPostBytes.Rand()
|
||||
if len(b) < scMaxEachPostBytes {
|
||||
return c.write(b)
|
||||
}
|
||||
@@ -117,6 +118,7 @@ type Client struct {
|
||||
cancel context.CancelFunc
|
||||
mode string
|
||||
cfg *Config
|
||||
scMaxEachPostBytes Range
|
||||
makeTransport TransportMaker
|
||||
makeDownloadTransport TransportMaker
|
||||
uploadManager *ReuseManager
|
||||
@@ -130,11 +132,16 @@ func NewClient(cfg *Config, makeTransport TransportMaker, makeDownloadTransport
|
||||
default:
|
||||
return nil, fmt.Errorf("xhttp mode %s is not implemented yet", mode)
|
||||
}
|
||||
scMaxEachPostBytes, err := cfg.GetNormalizedScMaxEachPostBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
client := &Client{
|
||||
mode: mode,
|
||||
cfg: cfg,
|
||||
scMaxEachPostBytes: scMaxEachPostBytes,
|
||||
makeTransport: makeTransport,
|
||||
makeDownloadTransport: makeDownloadTransport,
|
||||
ctx: ctx,
|
||||
@@ -403,12 +410,13 @@ func (c *Client) DialPacketUp() (net.Conn, error) {
|
||||
|
||||
writerCtx, writerCancel := context.WithCancel(c.ctx)
|
||||
writer := &PacketUpWriter{
|
||||
ctx: writerCtx,
|
||||
cancel: writerCancel,
|
||||
cfg: c.cfg,
|
||||
sessionID: sessionID,
|
||||
transport: uploadTransport,
|
||||
seq: 0,
|
||||
ctx: writerCtx,
|
||||
cancel: writerCancel,
|
||||
cfg: c.cfg,
|
||||
scMaxEachPostBytes: c.scMaxEachPostBytes,
|
||||
sessionID: sessionID,
|
||||
transport: uploadTransport,
|
||||
seq: 0,
|
||||
}
|
||||
conn := &Conn{writer: writer}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ type Config struct {
|
||||
XPaddingBytes string
|
||||
NoSSEHeader bool // server only
|
||||
ScStreamUpServerSecs string // server only
|
||||
ScMaxEachPostBytes int
|
||||
ScMaxEachPostBytes string
|
||||
ReuseConfig *ReuseConfig
|
||||
DownloadConfig *Config
|
||||
}
|
||||
@@ -94,142 +94,114 @@ func (c *Config) RequestHeader() http.Header {
|
||||
}
|
||||
|
||||
func (c *Config) RandomPadding() (string, error) {
|
||||
paddingRange := c.XPaddingBytes
|
||||
if paddingRange == "" {
|
||||
paddingRange = "100-1000"
|
||||
}
|
||||
|
||||
minVal, maxVal, err := parseRange(paddingRange)
|
||||
r, err := ParseRange(c.XPaddingBytes, "100-1000")
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", fmt.Errorf("invalid x-padding-bytes: %w", err)
|
||||
}
|
||||
if minVal < 0 || maxVal < minVal {
|
||||
return "", fmt.Errorf("invalid x-padding-bytes range: %s", paddingRange)
|
||||
}
|
||||
if maxVal == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
n := minVal
|
||||
if maxVal > minVal {
|
||||
n = minVal + rand.Intn(maxVal-minVal+1)
|
||||
}
|
||||
|
||||
return strings.Repeat("X", n), nil
|
||||
return strings.Repeat("X", r.Rand()), nil
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedScStreamUpServerSecs() (int, error) {
|
||||
scStreamUpServerSecs := c.ScStreamUpServerSecs
|
||||
if scStreamUpServerSecs == "" {
|
||||
scStreamUpServerSecs = "20-80"
|
||||
}
|
||||
|
||||
minVal, maxVal, err := parseRange(scStreamUpServerSecs)
|
||||
func (c *Config) GetNormalizedScStreamUpServerSecs() (Range, error) {
|
||||
r, err := ParseRange(c.ScStreamUpServerSecs, "20-80")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return Range{}, fmt.Errorf("invalid sc-stream-up-server-secs: %w", err)
|
||||
}
|
||||
if minVal < 0 || maxVal < minVal {
|
||||
return 0, fmt.Errorf("invalid sc-stream-up-server-secs range: %s", scStreamUpServerSecs)
|
||||
}
|
||||
if maxVal == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
n := minVal
|
||||
if maxVal > minVal {
|
||||
n = minVal + rand.Intn(maxVal-minVal+1)
|
||||
}
|
||||
|
||||
return n, nil
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedScMaxEachPostBytes() int {
|
||||
if c.ScMaxEachPostBytes == 0 {
|
||||
return 1000000
|
||||
func (c *Config) GetNormalizedScMaxEachPostBytes() (Range, error) {
|
||||
r, err := ParseRange(c.ScStreamUpServerSecs, "1000000")
|
||||
if err != nil {
|
||||
return Range{}, fmt.Errorf("invalid sc-max-each-post-bytes: %w", err)
|
||||
}
|
||||
return c.ScMaxEachPostBytes
|
||||
if r.Max == 0 {
|
||||
return Range{}, fmt.Errorf("invalid sc-max-each-post-bytes: must be greater than zero")
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func parseRange(s string) (int, int, error) {
|
||||
type Range struct {
|
||||
Min int
|
||||
Max int
|
||||
}
|
||||
|
||||
func (r Range) Rand() int {
|
||||
if r.Min == r.Max {
|
||||
return r.Min
|
||||
}
|
||||
return r.Min + rand.Intn(r.Max-r.Min+1)
|
||||
}
|
||||
|
||||
func ParseRange(s string, fallback string) (Range, error) {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return parseRange(fallback)
|
||||
}
|
||||
return parseRange(s)
|
||||
}
|
||||
|
||||
func parseRange(s string) (Range, error) {
|
||||
parts := strings.Split(strings.TrimSpace(s), "-")
|
||||
if len(parts) == 1 {
|
||||
v, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
return Range{}, err
|
||||
}
|
||||
return v, v, nil
|
||||
return Range{v, v}, nil
|
||||
}
|
||||
if len(parts) != 2 {
|
||||
return 0, 0, fmt.Errorf("invalid range: %s", s)
|
||||
return Range{}, fmt.Errorf("invalid range: %s", s)
|
||||
}
|
||||
|
||||
minVal, err := strconv.Atoi(strings.TrimSpace(parts[0]))
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
return Range{}, err
|
||||
}
|
||||
maxVal, err := strconv.Atoi(strings.TrimSpace(parts[1]))
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return minVal, maxVal, nil
|
||||
}
|
||||
|
||||
func resolveRangeValue(s string, fallback int) (int, error) {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return fallback, nil
|
||||
}
|
||||
|
||||
minVal, maxVal, err := parseRange(s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return Range{}, err
|
||||
}
|
||||
if minVal < 0 || maxVal < minVal {
|
||||
return 0, fmt.Errorf("invalid range: %s", s)
|
||||
return Range{}, fmt.Errorf("invalid range: %s", s)
|
||||
}
|
||||
|
||||
if minVal == maxVal {
|
||||
return minVal, nil
|
||||
}
|
||||
|
||||
return minVal + rand.Intn(maxVal-minVal+1), nil
|
||||
return Range{minVal, maxVal}, nil
|
||||
}
|
||||
|
||||
func (c *ReuseConfig) ResolveManagerConfig() (int, int, error) {
|
||||
func (c *ReuseConfig) ResolveManagerConfig() (Range, Range, error) {
|
||||
if c == nil {
|
||||
return 0, 0, nil
|
||||
return Range{}, Range{}, nil
|
||||
}
|
||||
|
||||
maxConnections, err := resolveRangeValue(c.MaxConnections, 0)
|
||||
maxConnections, err := ParseRange(c.MaxConnections, "0")
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("invalid max-connections: %w", err)
|
||||
return Range{}, Range{}, fmt.Errorf("invalid max-connections: %w", err)
|
||||
}
|
||||
|
||||
maxConcurrency, err := resolveRangeValue(c.MaxConcurrency, 0)
|
||||
maxConcurrency, err := ParseRange(c.MaxConcurrency, "0")
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("invalid max-concurrency: %w", err)
|
||||
return Range{}, Range{}, fmt.Errorf("invalid max-concurrency: %w", err)
|
||||
}
|
||||
|
||||
return maxConnections, maxConcurrency, nil
|
||||
}
|
||||
|
||||
func (c *ReuseConfig) ResolveEntryConfig() (int, int, int, error) {
|
||||
func (c *ReuseConfig) ResolveEntryConfig() (Range, Range, Range, error) {
|
||||
if c == nil {
|
||||
return 0, 0, 0, nil
|
||||
return Range{}, Range{}, Range{}, nil
|
||||
}
|
||||
|
||||
hMaxRequestTimes, err := resolveRangeValue(c.HMaxRequestTimes, 0)
|
||||
hMaxRequestTimes, err := ParseRange(c.HMaxRequestTimes, "0")
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("invalid h-max-request-times: %w", err)
|
||||
return Range{}, Range{}, Range{}, fmt.Errorf("invalid h-max-request-times: %w", err)
|
||||
}
|
||||
|
||||
hMaxReusableSecs, err := resolveRangeValue(c.HMaxReusableSecs, 0)
|
||||
hMaxReusableSecs, err := ParseRange(c.HMaxReusableSecs, "0")
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("invalid h-max-reusable-secs: %w", err)
|
||||
return Range{}, Range{}, Range{}, fmt.Errorf("invalid h-max-reusable-secs: %w", err)
|
||||
}
|
||||
|
||||
cMaxReuseTimes, err := resolveRangeValue(c.CMaxReuseTimes, 0)
|
||||
cMaxReuseTimes, err := ParseRange(c.CMaxReuseTimes, "0")
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("invalid c-max-reuse-times: %w", err)
|
||||
return Range{}, Range{}, Range{}, fmt.Errorf("invalid c-max-reuse-times: %w", err)
|
||||
}
|
||||
|
||||
return hMaxRequestTimes, hMaxReusableSecs, cMaxReuseTimes, nil
|
||||
|
||||
@@ -55,12 +55,14 @@ func (rt *ReuseTransport) Close() error {
|
||||
var _ http.RoundTripper = (*ReuseTransport)(nil)
|
||||
|
||||
type ReuseManager struct {
|
||||
cfg *ReuseConfig
|
||||
maxConnections int
|
||||
maxConcurrency int
|
||||
maker TransportMaker
|
||||
mu sync.Mutex
|
||||
entries []*reuseEntry
|
||||
maxConnections int
|
||||
maxConcurrency int
|
||||
hMaxRequestTimes Range
|
||||
hMaxReusableSecs Range
|
||||
cMaxReuseTimes Range
|
||||
maker TransportMaker
|
||||
mu sync.Mutex
|
||||
entries []*reuseEntry
|
||||
}
|
||||
|
||||
func NewReuseManager(cfg *ReuseConfig, makeTransport TransportMaker) (*ReuseManager, error) {
|
||||
@@ -71,16 +73,18 @@ func NewReuseManager(cfg *ReuseConfig, makeTransport TransportMaker) (*ReuseMana
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, _, _, err = cfg.ResolveEntryConfig() // check if config is valid
|
||||
hMaxRequestTimes, hMaxReusableSecs, cMaxReuseTimes, err := cfg.ResolveEntryConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ReuseManager{
|
||||
cfg: cfg,
|
||||
maxConnections: connections,
|
||||
maxConcurrency: concurrency,
|
||||
maker: makeTransport,
|
||||
entries: make([]*reuseEntry, 0),
|
||||
maxConnections: connections.Rand(),
|
||||
maxConcurrency: concurrency.Rand(),
|
||||
hMaxRequestTimes: hMaxRequestTimes,
|
||||
hMaxReusableSecs: hMaxReusableSecs,
|
||||
cMaxReuseTimes: cMaxReuseTimes,
|
||||
maker: makeTransport,
|
||||
entries: make([]*reuseEntry, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -169,17 +173,16 @@ func (m *ReuseManager) canCreateLocked() bool {
|
||||
func (m *ReuseManager) newEntryLocked(transport http.RoundTripper, now time.Time) *reuseEntry {
|
||||
entry := &reuseEntry{transport: transport}
|
||||
|
||||
hMaxRequestTimes, hMaxReusableSecs, cMaxReuseTimes, _ := m.cfg.ResolveEntryConfig() // error already checked in [NewReuseManager]
|
||||
if hMaxRequestTimes > 0 {
|
||||
entry.leftRequests.Store(int32(hMaxRequestTimes))
|
||||
if m.hMaxRequestTimes.Max > 0 {
|
||||
entry.leftRequests.Store(int32(m.hMaxRequestTimes.Rand()))
|
||||
} else {
|
||||
entry.leftRequests.Store(1<<30 - 1)
|
||||
}
|
||||
if hMaxReusableSecs > 0 {
|
||||
entry.unreusableAt = now.Add(time.Duration(hMaxReusableSecs) * time.Second)
|
||||
if m.hMaxReusableSecs.Max > 0 {
|
||||
entry.unreusableAt = now.Add(time.Duration(m.hMaxReusableSecs.Rand()) * time.Second)
|
||||
}
|
||||
if cMaxReuseTimes > 0 {
|
||||
entry.maxReuseTimes = int32(cMaxReuseTimes)
|
||||
if m.cMaxReuseTimes.Max > 0 {
|
||||
entry.maxReuseTimes = int32(m.cMaxReuseTimes.Rand())
|
||||
}
|
||||
|
||||
m.entries = append(m.entries, entry)
|
||||
|
||||
@@ -98,21 +98,34 @@ type requestHandler struct {
|
||||
connHandler func(net.Conn)
|
||||
httpHandler http.Handler
|
||||
|
||||
scMaxEachPostBytes Range
|
||||
scStreamUpServerSecs Range
|
||||
|
||||
mu sync.Mutex
|
||||
sessions map[string]*httpSession
|
||||
}
|
||||
|
||||
func NewServerHandler(opt ServerOption) http.Handler {
|
||||
func NewServerHandler(opt ServerOption) (http.Handler, error) {
|
||||
scMaxEachPostBytes, err := opt.Config.GetNormalizedScMaxEachPostBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scStreamUpServerSecs, err := opt.Config.GetNormalizedScStreamUpServerSecs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// using h2c.NewHandler to ensure we can work in plain http2
|
||||
// and some tls conn is not *tls.Conn (like *reality.Conn)
|
||||
return h2c.NewHandler(&requestHandler{
|
||||
config: opt.Config,
|
||||
connHandler: opt.ConnHandler,
|
||||
httpHandler: opt.HttpHandler,
|
||||
sessions: map[string]*httpSession{},
|
||||
config: opt.Config,
|
||||
connHandler: opt.ConnHandler,
|
||||
httpHandler: opt.HttpHandler,
|
||||
scMaxEachPostBytes: scMaxEachPostBytes,
|
||||
scStreamUpServerSecs: scStreamUpServerSecs,
|
||||
sessions: map[string]*httpSession{},
|
||||
}, &http.Http2Server{
|
||||
IdleTimeout: 30 * time.Second,
|
||||
})
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (h *requestHandler) getOrCreateSession(sessionID string) *httpSession {
|
||||
@@ -209,12 +222,7 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
parts := splitNonEmpty(rest)
|
||||
|
||||
// stream-one: POST /path
|
||||
if r.Method == http.MethodPost && len(parts) == 0 {
|
||||
if !h.allowStreamOne() {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodPost && len(parts) == 0 && h.allowStreamOne() {
|
||||
w.Header().Set("X-Accel-Buffering", "no")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
@@ -241,12 +249,7 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// stream-up/packet-up download: GET /path/{session}
|
||||
if r.Method == http.MethodGet && len(parts) == 1 {
|
||||
if !h.allowSessionDownload() {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodGet && len(parts) == 1 && h.allowSessionDownload() {
|
||||
sessionID := parts[0]
|
||||
session := h.getOrCreateSession(sessionID)
|
||||
session.markConnected()
|
||||
@@ -288,12 +291,7 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// stream-up upload: POST /path/{session}
|
||||
if r.Method == http.MethodPost && len(parts) == 1 {
|
||||
if !h.allowStreamUpUpload() {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodPost && len(parts) == 1 && h.allowStreamUpUpload() {
|
||||
sessionID := parts[0]
|
||||
session := h.getSession(sessionID)
|
||||
if session == nil {
|
||||
@@ -322,13 +320,9 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
referrer := r.Header.Get("Referer")
|
||||
if referrer != "" {
|
||||
if referrer != "" && h.scStreamUpServerSecs.Max > 0 {
|
||||
go func() {
|
||||
for {
|
||||
scStreamUpServerSecs, _ := h.config.GetNormalizedScStreamUpServerSecs()
|
||||
if scStreamUpServerSecs == 0 {
|
||||
break
|
||||
}
|
||||
paddingValue, _ := h.config.RandomPadding()
|
||||
if paddingValue == "" {
|
||||
break
|
||||
@@ -337,7 +331,7 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Duration(scStreamUpServerSecs) * time.Second)
|
||||
time.Sleep(time.Duration(h.scStreamUpServerSecs.Rand()) * time.Second)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -352,12 +346,7 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// packet-up upload: POST /path/{session}/{seq}
|
||||
if r.Method == http.MethodPost && len(parts) == 2 {
|
||||
if !h.allowPacketUpUpload() {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodPost && len(parts) == 2 && h.allowPacketUpUpload() {
|
||||
sessionID := parts[0]
|
||||
seq, err := strconv.ParseUint(parts[1], 10, 64)
|
||||
if err != nil {
|
||||
@@ -371,13 +360,12 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
scMaxEachPostBytes := int64(h.config.GetNormalizedScMaxEachPostBytes())
|
||||
if r.ContentLength > scMaxEachPostBytes {
|
||||
if r.ContentLength > int64(h.scMaxEachPostBytes.Max) {
|
||||
http.Error(w, "body too large", http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(io.LimitReader(r.Body, scMaxEachPostBytes+1))
|
||||
body, err := io.ReadAll(io.LimitReader(r.Body, int64(h.scMaxEachPostBytes.Max)+1))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
|
||||
@@ -78,7 +78,7 @@ func TestServerHandlerModeRestrictions(t *testing.T) {
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
handler := NewServerHandler(ServerOption{
|
||||
handler, err := NewServerHandler(ServerOption{
|
||||
Config: Config{
|
||||
Path: "/xhttp",
|
||||
Mode: testCase.mode,
|
||||
@@ -87,6 +87,9 @@ func TestServerHandlerModeRestrictions(t *testing.T) {
|
||||
_ = conn.Close()
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(testCase.method, testCase.target, io.NopCloser(http.NoBody))
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"@tanstack/router-zod-adapter": "1.81.5",
|
||||
"@tauri-apps/api": "2.10.1",
|
||||
"@uidotdev/usehooks": "2.4.1",
|
||||
"@uiw/react-color": "2.9.6",
|
||||
"@uiw/react-color": "2.10.0",
|
||||
"ahooks": "3.9.7",
|
||||
"allotment": "1.20.5",
|
||||
"class-variance-authority": "0.7.1",
|
||||
@@ -44,7 +44,7 @@
|
||||
"dayjs": "1.11.20",
|
||||
"framer-motion": "12.38.0",
|
||||
"i18next": "25.10.10",
|
||||
"jotai": "2.19.0",
|
||||
"jotai": "2.19.1",
|
||||
"json-schema": "0.4.0",
|
||||
"material-react-table": "3.2.1",
|
||||
"monaco-editor": "0.55.1",
|
||||
@@ -97,7 +97,7 @@
|
||||
"clsx": "2.1.1",
|
||||
"core-js": "3.49.0",
|
||||
"filesize": "11.0.15",
|
||||
"meta-json-schema": "1.19.22",
|
||||
"meta-json-schema": "1.19.23",
|
||||
"monaco-yaml": "5.4.1",
|
||||
"nanoid": "5.1.7",
|
||||
"sass-embedded": "1.99.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 1,
|
||||
"latest": {
|
||||
"mihomo": "v1.19.22",
|
||||
"mihomo_alpha": "alpha-4730f67",
|
||||
"mihomo_alpha": "alpha-5e8bd85",
|
||||
"clash_rs": "v0.9.6",
|
||||
"clash_premium": "2023-09-05-gdcc8d87",
|
||||
"clash_rs_alpha": "0.9.6-alpha+sha.c414fb7"
|
||||
@@ -69,5 +69,5 @@
|
||||
"linux-armv7hf": "clash-rs-armv7-unknown-linux-gnueabihf"
|
||||
}
|
||||
},
|
||||
"updated_at": "2026-04-06T22:23:54.897Z"
|
||||
"updated_at": "2026-04-07T22:24:28.715Z"
|
||||
}
|
||||
|
||||
@@ -68,11 +68,11 @@
|
||||
"cross-env": "10.1.0",
|
||||
"dedent": "1.7.2",
|
||||
"globals": "17.4.0",
|
||||
"knip": "6.3.0",
|
||||
"knip": "6.3.1",
|
||||
"lint-staged": "16.4.0",
|
||||
"npm-run-all2": "8.0.4",
|
||||
"oxlint": "1.59.0",
|
||||
"postcss": "8.5.8",
|
||||
"postcss": "8.5.9",
|
||||
"postcss-html": "1.8.1",
|
||||
"postcss-import": "16.1.1",
|
||||
"postcss-scss": "4.0.9",
|
||||
|
||||
Generated
+206
-206
@@ -45,7 +45,7 @@ importers:
|
||||
version: 24.11.0
|
||||
autoprefixer:
|
||||
specifier: 10.4.27
|
||||
version: 10.4.27(postcss@8.5.8)
|
||||
version: 10.4.27(postcss@8.5.9)
|
||||
conventional-changelog-conventionalcommits:
|
||||
specifier: 9.3.1
|
||||
version: 9.3.1
|
||||
@@ -59,8 +59,8 @@ importers:
|
||||
specifier: 17.4.0
|
||||
version: 17.4.0
|
||||
knip:
|
||||
specifier: 6.3.0
|
||||
version: 6.3.0
|
||||
specifier: 6.3.1
|
||||
version: 6.3.1
|
||||
lint-staged:
|
||||
specifier: 16.4.0
|
||||
version: 16.4.0
|
||||
@@ -71,17 +71,17 @@ importers:
|
||||
specifier: 1.59.0
|
||||
version: 1.59.0
|
||||
postcss:
|
||||
specifier: 8.5.8
|
||||
version: 8.5.8
|
||||
specifier: 8.5.9
|
||||
version: 8.5.9
|
||||
postcss-html:
|
||||
specifier: 1.8.1
|
||||
version: 1.8.1
|
||||
postcss-import:
|
||||
specifier: 16.1.1
|
||||
version: 16.1.1(postcss@8.5.8)
|
||||
version: 16.1.1(postcss@8.5.9)
|
||||
postcss-scss:
|
||||
specifier: 4.0.9
|
||||
version: 4.0.9(postcss@8.5.8)
|
||||
version: 4.0.9(postcss@8.5.9)
|
||||
prettier:
|
||||
specifier: 3.8.1
|
||||
version: 3.8.1
|
||||
@@ -234,8 +234,8 @@ importers:
|
||||
specifier: 2.4.1
|
||||
version: 2.4.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color':
|
||||
specifier: 2.9.6
|
||||
version: 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
specifier: 2.10.0
|
||||
version: 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
ahooks:
|
||||
specifier: 3.9.7
|
||||
version: 3.9.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
@@ -264,8 +264,8 @@ importers:
|
||||
specifier: 25.10.10
|
||||
version: 25.10.10(typescript@5.9.3)
|
||||
jotai:
|
||||
specifier: 2.19.0
|
||||
version: 2.19.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.4)
|
||||
specifier: 2.19.1
|
||||
version: 2.19.1(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.4)
|
||||
json-schema:
|
||||
specifier: 0.4.0
|
||||
version: 0.4.0
|
||||
@@ -418,8 +418,8 @@ importers:
|
||||
specifier: 11.0.15
|
||||
version: 11.0.15
|
||||
meta-json-schema:
|
||||
specifier: 1.19.22
|
||||
version: 1.19.22
|
||||
specifier: 1.19.23
|
||||
version: 1.19.23
|
||||
monaco-yaml:
|
||||
specifier: 5.4.1
|
||||
version: 5.4.1(monaco-editor@0.55.1)
|
||||
@@ -449,7 +449,7 @@ importers:
|
||||
version: 3.2.2(vite@7.3.2(@types/node@24.11.0)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.2))
|
||||
vite-plugin-sass-dts:
|
||||
specifier: 1.3.37
|
||||
version: 1.3.37(postcss@8.5.8)(prettier@3.8.1)(sass-embedded@1.99.0)(vite@7.3.2(@types/node@24.11.0)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.2))
|
||||
version: 1.3.37(postcss@8.5.9)(prettier@3.8.1)(sass-embedded@1.99.0)(vite@7.3.2(@types/node@24.11.0)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.2))
|
||||
vite-plugin-svgr:
|
||||
specifier: 4.5.0
|
||||
version: 4.5.0(rollup@4.46.2)(typescript@5.9.3)(vite@7.3.2(@types/node@24.11.0)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.2))
|
||||
@@ -4457,151 +4457,151 @@ packages:
|
||||
react: '>=18.0.0'
|
||||
react-dom: '>=18.0.0'
|
||||
|
||||
'@uiw/color-convert@2.9.6':
|
||||
resolution: {integrity: sha512-w8TpU3MRcquurQJxWR1daKcRygu/a0hLP/VGsLMA3ebb41sAZGxMQLHtS+zC/e3ciFNB7BbPrSPlzOcz6w6cRg==}
|
||||
'@uiw/color-convert@2.10.0':
|
||||
resolution: {integrity: sha512-4woviyrzPi3Xauo12lnKTgNlugCk0su2PhjtZiHZuakK0yQNOMeN/QZi9W8ASb/DuRS7bSr8jSyIHLRYml90+g==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
|
||||
'@uiw/react-color-alpha@2.9.6':
|
||||
resolution: {integrity: sha512-DNzEVHZ0Izp4NAwzKqTcl4rLdPjSFjyZCP6Q2vKJEglugZ/bdPsmZaos9IYOrgnd1kPDmTSKZ/p8nI7vBIATGw==}
|
||||
'@uiw/react-color-alpha@2.10.0':
|
||||
resolution: {integrity: sha512-2uGoCuDlqBm3CN/RI3XbOwxIF7GJKwyUUpN/krMEOLg7HhIJMYWxrhDZ4SCQecGPeR/b8o3XTxV7+PHIZMcgMw==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color-block@2.9.6':
|
||||
resolution: {integrity: sha512-Y2D/ejhxRZKjmmaKLfKhRKgFS68R7SmkgVdX1KDZ4lYtfdUbXIccYjoL0dsYvQkx6+8WitEZDPUnQ3qLAR7jhQ==}
|
||||
'@uiw/react-color-block@2.10.0':
|
||||
resolution: {integrity: sha512-uBwxbWwIdGqxbVbeXcDR7O4WT4wfLOfeDit+wogYxrwjLdd6txB1YX/x6ELtFiVx+BhSd9YyVvkPaJGabZhk0Q==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color-chrome@2.9.6':
|
||||
resolution: {integrity: sha512-ah9H6ZpCHyvHdDzS3qrK4UcvWhj0ZaSfbAE4WLSv/m70E0i/uivjtS242jrPG2Kg02wkgd+jyfmOvpgGkGF3FA==}
|
||||
'@uiw/react-color-chrome@2.10.0':
|
||||
resolution: {integrity: sha512-gfKr/pdFESqCx0PywmKHxYPd5NfyVnB0wgFhKsITH1EC7zgAkDxUB3gyA9ihNL53TpqN28O461G59CoaBVBPOg==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color-circle@2.9.6':
|
||||
resolution: {integrity: sha512-65CxtZyAxrsSetdi6c+qNaP3o5hg0H4r3gemLT6h7ruI2e3AVWfopHz3gLQrplpjEQobzx/T9om7k+0tDSlqKg==}
|
||||
'@uiw/react-color-circle@2.10.0':
|
||||
resolution: {integrity: sha512-1E61c0nOqHijAXN+IsWSOer/asZj41gU+/Wda3XkUHWu219ZIhu/koTpvRLEFJKs5akvXLJlO0Oq4BqmugEiMQ==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color-colorful@2.9.6':
|
||||
resolution: {integrity: sha512-h74zo+ve9Rpv7xwb1dRfoa23yN39b6eYScDIm7V2d5FzkXN6hR7jnnJ7ZUD9Joz/rdaCz1eFQD9ig+wp8+wSnQ==}
|
||||
'@uiw/react-color-colorful@2.10.0':
|
||||
resolution: {integrity: sha512-eQVojLvOz+LNjBwIValgu2tNJfXEOvnXZDLCr6jlKYjofwgBluQ+Z2lRrfpfmS4auJmsZsQh5dUXrF8kEwnI6g==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color-compact@2.9.6':
|
||||
resolution: {integrity: sha512-ikcphwfn+vMeldvIMJStnPsM+LGgv3QcI8ZUHRU0OyDGsWilfpkIW/XUFxepLp9L0ybIm9PK7nZc68wrFvL6/g==}
|
||||
'@uiw/react-color-compact@2.10.0':
|
||||
resolution: {integrity: sha512-AI8OPFlj4mBCUjBP4EMU4CL5NxSN29LGqutwX8aWqRtSp1T7cLO+KfRFivyJHCvh4af2Wa+z+5ALRFgvzgeGDg==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color-editable-input-hsla@2.9.6':
|
||||
resolution: {integrity: sha512-qZOUf5/2ilD9ToLnEctHsmvEvrjZhDMfzwI4Ii63q68rF/mD1fUbbKy3tCFn+JgGSKvMdEAMhAGjwtCsWkpFtg==}
|
||||
'@uiw/react-color-editable-input-hsla@2.10.0':
|
||||
resolution: {integrity: sha512-nSful4fAAWuVXSVAk2Qqi5RQzVFtHLgUS1FsQ1fsUOsLSNS5KPkybCabzhaprEIiSMBDTNTyFe4K1SM+poHeiQ==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color-editable-input-rgba@2.9.6':
|
||||
resolution: {integrity: sha512-vzC+uBl6ZaGESVOUbglPqfOACsmwIwMQcmvdELhVw9KiKWp168fE4LqSQf57a2BHaPM9Jl8Lh8GLXn284bWRyw==}
|
||||
'@uiw/react-color-editable-input-rgba@2.10.0':
|
||||
resolution: {integrity: sha512-e4pTP2CLnR4Ha6ZfP6q7GLFd3NkgsVRaO5lHEKljBPlGzPxJmMFlJ4VUHRxwHanISwQkJDmPaU8J3IBZyorPJA==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color-editable-input@2.9.6':
|
||||
resolution: {integrity: sha512-KrbXonGXxSPxGNTcZY1QZ6QCon9/ekNxdMDlAvBgapUjrrcwqZ+nkgsAsUe+BNa6Ods3KnA2OKcOs99v6nIJ8A==}
|
||||
'@uiw/react-color-editable-input@2.10.0':
|
||||
resolution: {integrity: sha512-hMNnuSdr0mtKSyfYwjPODKP0ZJrPKWmUAf0QtlzSwQKkFxdW5hxyVR7aXB2cc+1j44kmTcSyKqQjNS5bx6nY/g==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color-github@2.9.6':
|
||||
resolution: {integrity: sha512-Yk7Q4B+WVAA4wz15i4EPo02SJSr8oPFi4mWnB157+v/DnKakcXrIN/Y9z3fi9qlDRo3LsZUfmzJMz4e4W/Fhrg==}
|
||||
'@uiw/react-color-github@2.10.0':
|
||||
resolution: {integrity: sha512-dahUCuU63mHWFOwfRfEIOuzmV3h2O8vu8wRr4UXF62et6CCCfr+5ZF6Q/XxX1fjBA1TNbMOGnuI8knSEVAZhNg==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color-hue@2.9.6':
|
||||
resolution: {integrity: sha512-B99dW2/AHMD3py83BrXl94bhXeGCZR1FMpU/FNbIIbUrV9QTiIXDs2/SB/tMD9ltcSP59RD5Sc5m2vCb/8anjw==}
|
||||
'@uiw/react-color-hue@2.10.0':
|
||||
resolution: {integrity: sha512-C/l3UsHq/8Re7a5e4WI4lgMOeNQNNOMby10O/uOHUYbzWEd1zC1ZM9WhsN44ZXwR3FbaaQ01YenuXl9c19z77g==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color-material@2.9.6':
|
||||
resolution: {integrity: sha512-V/Tv6NT1vdC0A6G1qIBdaQWkPjv7Q2x1kZ+M9sa28juP5jJU3/DR9zXrFB5NXTK+j8jG2IPQjRYejTtRNTLEyQ==}
|
||||
'@uiw/react-color-material@2.10.0':
|
||||
resolution: {integrity: sha512-H0frUGmsx1qj/vtKRmPf6czu8KzJPnjL6x5fzOU4JhNkg73A5THPDEu+5IurSYII5+ChAYJ7LOwCUQGOd4paMQ==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color-name@2.9.6':
|
||||
resolution: {integrity: sha512-rAwE/JF/mhN3bDqu9XrHCovqVt4L68oDtm7IkamfMkqoydZ9HsjFmanhMEyBS9SU+hSK14u7OtebR48xFBQrDw==}
|
||||
'@uiw/react-color-name@2.10.0':
|
||||
resolution: {integrity: sha512-KdJhTC51Blb22dXI303e2K1q6MrdOy0WWNAb2U71KMQaBSh3hB8olnsI6eF5MyxrV3dL+3AwN+p7s954QzeA2A==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
|
||||
'@uiw/react-color-saturation@2.9.6':
|
||||
resolution: {integrity: sha512-R1tiKbTG2WiJXerkmuaKnBFfzgyZUn08q9OjQSvNH1f3ov2/YeUVlOwQY9MbQE7ytZv+9x+1h0Lpk4QG7AdulQ==}
|
||||
'@uiw/react-color-saturation@2.10.0':
|
||||
resolution: {integrity: sha512-GDEdktEWsr0V/5Pi6QtFGrX/TwJ7ZE90nG7xFeuzSqxnVUqlbu0izKwt2Tyg254fFHSlEPv53CZgbyWZ0vpwLQ==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color-shade-slider@2.9.6':
|
||||
resolution: {integrity: sha512-8WJQfqA1tenocgkHyO1AERDK7DLkvLhymNPxau42X0qaAGy5MgqvK9+sI6lbp+NpNrmC1g6ltta7PmVUMWBsgg==}
|
||||
'@uiw/react-color-shade-slider@2.10.0':
|
||||
resolution: {integrity: sha512-sdQ521RMl3u3U4oHuYPzoxuPayLCMjYHmDh6qsDqBzHLCF652MnLUmoHuSTQJQfPmKXB9AS5kI3YsI4Fqxdw0Q==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color-sketch@2.9.6':
|
||||
resolution: {integrity: sha512-yEB14Q2dYYldiHBfnKpotPSa9DaxI3QnTswA6zFBl7b+mfJxmGl77YOIjd6kl90wodl+EdrkjnyE8HquiuJC3w==}
|
||||
'@uiw/react-color-sketch@2.10.0':
|
||||
resolution: {integrity: sha512-keM9a95mX7HeHxtZ5rglsMdFfakNwFUSAeMkkR0fsV04lC4CgpO5Ou1UdaJx5pkJ/eKksJj0QgAYC1NY6urZMw==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color-slider@2.9.6':
|
||||
resolution: {integrity: sha512-Jc6/YqL5A09t6l0iMTcy/ZRY4Rutq0Zj87JGz0Vg8erEtlowgnLIKt/SUPd/Kpbt4AcNEHKnA9N6SZW1rLMvPw==}
|
||||
'@uiw/react-color-slider@2.10.0':
|
||||
resolution: {integrity: sha512-e/+/HHI+GLU1af3NBtSmskF3uyowI4PecDU4ysPtSjjYPFHkgb+aF9DhCysceQrcTaEZh+Pd7KcXCHWrW8nUXg==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color-swatch@2.9.6':
|
||||
resolution: {integrity: sha512-ekhCz55GdB+Al/na2tj3GxjaoMd6NRtqosYNFZA3axXxhnREG5TfaaOvR8r96yZswo/sKSSFuX2gpmro7BcBSA==}
|
||||
'@uiw/react-color-swatch@2.10.0':
|
||||
resolution: {integrity: sha512-2gyrqmkZrkV/ULXVzhyDv9AvORekeyxeMR+tbOTDQjoV5S/bozceHe0JvsJdisnml9Aq1viVg2x8JUE6LjJo9w==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color-wheel@2.9.6':
|
||||
resolution: {integrity: sha512-a2qw544xrdVBfl2TY1VlmhLM4PMAN/pl1FLdAlSSy8u1xl1f851n9C3lze/9myQwnPO500NfQ8gcXq40nPEqPw==}
|
||||
'@uiw/react-color-wheel@2.10.0':
|
||||
resolution: {integrity: sha512-1bdFdUC+tsoNaSH3obKx3NjWG+kClccoUGAmZ8oeVw8kALvtlJ8GjLMPfQ8AuyztaLdwlaTx4BBh+34Mprw4nA==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-color@2.9.6':
|
||||
resolution: {integrity: sha512-+0k2m6H7M/KSQcUGY/GaR9Gi3NgW45refRSJGg2yWHsMUN+Z8NZhxuZv3J8d6mQaiMijnNStLTgIxikKvfA2xw==}
|
||||
'@uiw/react-color@2.10.0':
|
||||
resolution: {integrity: sha512-8YWAgEXen5FeFi6Qhd0SBYjEGntUZDt0vRExgkscjbkb3ACsY7nZrYNWDF8ED6jInpVp2+uYsOP4+MDubsUjsA==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
react-dom: '>=16.9.0'
|
||||
|
||||
'@uiw/react-drag-event-interactive@2.9.6':
|
||||
resolution: {integrity: sha512-jXzt3Xis/BIYap2Hj2++gB3aEUD0mZoVNGfckurrwjAwxasxNiwkmTGxV5er3due0ZgaVKdOAfTRoYKlgZukSg==}
|
||||
'@uiw/react-drag-event-interactive@2.10.0':
|
||||
resolution: {integrity: sha512-+TIgGJdvC87L6V5JEP5QJ01aVDHBoSQe1inc/fkkhdOQgnkXwWasqLaSrnpO71W0KYHgkMEfTtj8aZVLsUhtjA==}
|
||||
peerDependencies:
|
||||
'@babel/runtime': '>=7.19.0'
|
||||
react: '>=16.9.0'
|
||||
@@ -6002,8 +6002,8 @@ packages:
|
||||
jju@1.4.0:
|
||||
resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==}
|
||||
|
||||
jotai@2.19.0:
|
||||
resolution: {integrity: sha512-r2wwxEXP1F2JteDLZEOPoIpAHhV89paKsN5GWVYndPNMMP/uVZDcC+fNj0A8NjKgaPWzdyO8Vp8YcYKe0uCEqQ==}
|
||||
jotai@2.19.1:
|
||||
resolution: {integrity: sha512-sqm9lVZiqBHZH8aSRk32DSiZDHY3yUIlulXYn9GQj7/LvoUdYXSMti7ZPJGo+6zjzKFt5a25k/I6iBCi43PJcw==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': '>=7.0.0'
|
||||
@@ -6089,8 +6089,8 @@ packages:
|
||||
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
knip@6.3.0:
|
||||
resolution: {integrity: sha512-g6dVPoTw6iNm3cubC5IWxVkVsd0r5hXhTBTbAGIEQN53GdA2ZM/slMTPJ7n5l8pBebNQPHpxjmKxuR4xVQ2/hQ==}
|
||||
knip@6.3.1:
|
||||
resolution: {integrity: sha512-22kLJloVcOVOAudCxlFOC0ICAMme7dKsS7pVTEnrmyKGpswb8ieznvAiSKUeFVDJhb01ect6dkDc1Ha1g1sPpg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
|
||||
@@ -6331,8 +6331,8 @@ packages:
|
||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
meta-json-schema@1.19.22:
|
||||
resolution: {integrity: sha512-j/s7HbG90iZdiL7YBIqmbr/DY0BwGDViCsQxLLKjsIqGryt/SjoV1TgZ1dRaRRa77/m3XQkRqJEWgIAs/yk8Ig==}
|
||||
meta-json-schema@1.19.23:
|
||||
resolution: {integrity: sha512-WIVB7Vg+eeiih/IOlSlvOmZoH+v5lMPrItg0hMzEQ6/udghhhrspZ4T1cIHDhbtAfQnJwcSt5IGfP8LfzOoKAQ==}
|
||||
engines: {node: '>=18', pnpm: '>=9'}
|
||||
|
||||
micromark-core-commonmark@2.0.1:
|
||||
@@ -6793,8 +6793,8 @@ packages:
|
||||
postcss-value-parser@4.2.0:
|
||||
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
||||
|
||||
postcss@8.5.8:
|
||||
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
|
||||
postcss@8.5.9:
|
||||
resolution: {integrity: sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
prettier-plugin-ember-template-tag@2.1.3:
|
||||
@@ -11574,7 +11574,7 @@ snapshots:
|
||||
'@alloc/quick-lru': 5.2.0
|
||||
'@tailwindcss/node': 4.2.2
|
||||
'@tailwindcss/oxide': 4.2.2
|
||||
postcss: 8.5.8
|
||||
postcss: 8.5.9
|
||||
tailwindcss: 4.2.2
|
||||
|
||||
'@tanstack/history@1.161.6': {}
|
||||
@@ -12019,11 +12019,11 @@ snapshots:
|
||||
|
||||
'@types/postcss-modules-local-by-default@4.0.2':
|
||||
dependencies:
|
||||
postcss: 8.5.8
|
||||
postcss: 8.5.9
|
||||
|
||||
'@types/postcss-modules-scope@3.0.4':
|
||||
dependencies:
|
||||
postcss: 8.5.8
|
||||
postcss: 8.5.9
|
||||
|
||||
'@types/prop-types@15.7.15': {}
|
||||
|
||||
@@ -12061,201 +12061,201 @@ snapshots:
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/color-convert@2.9.6(@babel/runtime@7.29.2)':
|
||||
'@uiw/color-convert@2.10.0(@babel/runtime@7.29.2)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
|
||||
'@uiw/react-color-alpha@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-alpha@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-drag-event-interactive': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-drag-event-interactive': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color-block@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-block@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-editable-input': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-swatch': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-editable-input': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-swatch': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color-chrome@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-chrome@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-alpha': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input-hsla': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input-rgba': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-github': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-hue': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-saturation': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-alpha': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input-hsla': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input-rgba': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-github': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-hue': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-saturation': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color-circle@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-circle@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-swatch': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-swatch': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color-colorful@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-colorful@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-alpha': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-hue': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-saturation': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-alpha': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-hue': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-saturation': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color-compact@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-compact@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-editable-input': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input-rgba': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-swatch': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-editable-input': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input-rgba': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-swatch': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color-editable-input-hsla@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-editable-input-hsla@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-editable-input-rgba': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-editable-input-rgba': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color-editable-input-rgba@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-editable-input-rgba@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-editable-input': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-editable-input': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color-editable-input@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-editable-input@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color-github@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-github@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-swatch': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-swatch': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color-hue@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-hue@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-alpha': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-alpha': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color-material@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-material@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-editable-input': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input-rgba': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-editable-input': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input-rgba': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color-name@2.9.6(@babel/runtime@7.29.2)':
|
||||
'@uiw/react-color-name@2.10.0(@babel/runtime@7.29.2)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
colors-named: 1.0.4
|
||||
colors-named-hex: 1.0.3
|
||||
|
||||
'@uiw/react-color-saturation@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-saturation@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-drag-event-interactive': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-drag-event-interactive': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color-shade-slider@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-shade-slider@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-alpha': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-alpha': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color-sketch@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-sketch@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-alpha': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input-rgba': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-hue': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-saturation': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-swatch': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-alpha': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input-rgba': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-hue': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-saturation': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-swatch': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color-slider@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-slider@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-alpha': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-alpha': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color-swatch@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-swatch@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color-wheel@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color-wheel@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-drag-event-interactive': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-drag-event-interactive': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-color@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-color@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
'@uiw/color-convert': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-alpha': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-block': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-chrome': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-circle': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-colorful': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-compact': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input-hsla': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input-rgba': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-github': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-hue': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-material': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-name': 2.9.6(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-saturation': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-shade-slider': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-sketch': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-slider': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-swatch': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-wheel': 2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/color-convert': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-alpha': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-block': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-chrome': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-circle': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-colorful': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-compact': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input-hsla': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-editable-input-rgba': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-github': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-hue': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-material': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-name': 2.10.0(@babel/runtime@7.29.2)
|
||||
'@uiw/react-color-saturation': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-shade-slider': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-sketch': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-slider': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-swatch': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@uiw/react-color-wheel': 2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
|
||||
'@uiw/react-drag-event-interactive@2.9.6(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
'@uiw/react-drag-event-interactive@2.10.0(@babel/runtime@7.29.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.29.2
|
||||
react: 19.2.4
|
||||
@@ -12459,13 +12459,13 @@ snapshots:
|
||||
|
||||
async@3.2.6: {}
|
||||
|
||||
autoprefixer@10.4.27(postcss@8.5.8):
|
||||
autoprefixer@10.4.27(postcss@8.5.9):
|
||||
dependencies:
|
||||
browserslist: 4.28.1
|
||||
caniuse-lite: 1.0.30001776
|
||||
fraction.js: 5.3.4
|
||||
picocolors: 1.1.1
|
||||
postcss: 8.5.8
|
||||
postcss: 8.5.9
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
babel-dead-code-elimination@1.0.12:
|
||||
@@ -13530,9 +13530,9 @@ snapshots:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
icss-utils@5.1.0(postcss@8.5.8):
|
||||
icss-utils@5.1.0(postcss@8.5.9):
|
||||
dependencies:
|
||||
postcss: 8.5.8
|
||||
postcss: 8.5.9
|
||||
|
||||
ieee754@1.2.1: {}
|
||||
|
||||
@@ -13668,7 +13668,7 @@ snapshots:
|
||||
|
||||
jju@1.4.0: {}
|
||||
|
||||
jotai@2.19.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.4):
|
||||
jotai@2.19.1(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@19.2.14)(react@19.2.4):
|
||||
optionalDependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/template': 7.28.6
|
||||
@@ -13722,7 +13722,7 @@ snapshots:
|
||||
|
||||
kind-of@6.0.3: {}
|
||||
|
||||
knip@6.3.0:
|
||||
knip@6.3.1:
|
||||
dependencies:
|
||||
'@nodelib/fs.walk': 1.2.8
|
||||
fast-glob: 3.3.3
|
||||
@@ -14014,7 +14014,7 @@ snapshots:
|
||||
|
||||
merge2@1.4.1: {}
|
||||
|
||||
meta-json-schema@1.19.22: {}
|
||||
meta-json-schema@1.19.23: {}
|
||||
|
||||
micromark-core-commonmark@2.0.1:
|
||||
dependencies:
|
||||
@@ -14557,59 +14557,59 @@ snapshots:
|
||||
dependencies:
|
||||
htmlparser2: 8.0.2
|
||||
js-tokens: 9.0.1
|
||||
postcss: 8.5.8
|
||||
postcss-safe-parser: 6.0.0(postcss@8.5.8)
|
||||
postcss: 8.5.9
|
||||
postcss-safe-parser: 6.0.0(postcss@8.5.9)
|
||||
|
||||
postcss-import@16.1.1(postcss@8.5.8):
|
||||
postcss-import@16.1.1(postcss@8.5.9):
|
||||
dependencies:
|
||||
postcss: 8.5.8
|
||||
postcss: 8.5.9
|
||||
postcss-value-parser: 4.2.0
|
||||
read-cache: 1.0.0
|
||||
resolve: 1.22.8
|
||||
|
||||
postcss-js@4.0.1(postcss@8.5.8):
|
||||
postcss-js@4.0.1(postcss@8.5.9):
|
||||
dependencies:
|
||||
camelcase-css: 2.0.1
|
||||
postcss: 8.5.8
|
||||
postcss: 8.5.9
|
||||
|
||||
postcss-load-config@3.1.4(postcss@8.5.8):
|
||||
postcss-load-config@3.1.4(postcss@8.5.9):
|
||||
dependencies:
|
||||
lilconfig: 2.1.0
|
||||
yaml: 1.10.2
|
||||
optionalDependencies:
|
||||
postcss: 8.5.8
|
||||
postcss: 8.5.9
|
||||
|
||||
postcss-media-query-parser@0.2.3: {}
|
||||
|
||||
postcss-modules-extract-imports@3.1.0(postcss@8.5.8):
|
||||
postcss-modules-extract-imports@3.1.0(postcss@8.5.9):
|
||||
dependencies:
|
||||
postcss: 8.5.8
|
||||
postcss: 8.5.9
|
||||
|
||||
postcss-modules-local-by-default@4.0.5(postcss@8.5.8):
|
||||
postcss-modules-local-by-default@4.0.5(postcss@8.5.9):
|
||||
dependencies:
|
||||
icss-utils: 5.1.0(postcss@8.5.8)
|
||||
postcss: 8.5.8
|
||||
icss-utils: 5.1.0(postcss@8.5.9)
|
||||
postcss: 8.5.9
|
||||
postcss-selector-parser: 6.1.2
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
postcss-modules-scope@3.2.0(postcss@8.5.8):
|
||||
postcss-modules-scope@3.2.0(postcss@8.5.9):
|
||||
dependencies:
|
||||
postcss: 8.5.8
|
||||
postcss: 8.5.9
|
||||
postcss-selector-parser: 6.1.2
|
||||
|
||||
postcss-resolve-nested-selector@0.1.6: {}
|
||||
|
||||
postcss-safe-parser@6.0.0(postcss@8.5.8):
|
||||
postcss-safe-parser@6.0.0(postcss@8.5.9):
|
||||
dependencies:
|
||||
postcss: 8.5.8
|
||||
postcss: 8.5.9
|
||||
|
||||
postcss-safe-parser@7.0.1(postcss@8.5.8):
|
||||
postcss-safe-parser@7.0.1(postcss@8.5.9):
|
||||
dependencies:
|
||||
postcss: 8.5.8
|
||||
postcss: 8.5.9
|
||||
|
||||
postcss-scss@4.0.9(postcss@8.5.8):
|
||||
postcss-scss@4.0.9(postcss@8.5.9):
|
||||
dependencies:
|
||||
postcss: 8.5.8
|
||||
postcss: 8.5.9
|
||||
|
||||
postcss-selector-parser@6.1.2:
|
||||
dependencies:
|
||||
@@ -14621,13 +14621,13 @@ snapshots:
|
||||
cssesc: 3.0.0
|
||||
util-deprecate: 1.0.2
|
||||
|
||||
postcss-sorting@10.0.0(postcss@8.5.8):
|
||||
postcss-sorting@10.0.0(postcss@8.5.9):
|
||||
dependencies:
|
||||
postcss: 8.5.8
|
||||
postcss: 8.5.9
|
||||
|
||||
postcss-value-parser@4.2.0: {}
|
||||
|
||||
postcss@8.5.8:
|
||||
postcss@8.5.9:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
picocolors: 1.1.1
|
||||
@@ -15360,8 +15360,8 @@ snapshots:
|
||||
|
||||
stylelint-order@8.1.1(stylelint@17.6.0(typescript@5.9.3)):
|
||||
dependencies:
|
||||
postcss: 8.5.8
|
||||
postcss-sorting: 10.0.0(postcss@8.5.8)
|
||||
postcss: 8.5.9
|
||||
postcss-sorting: 10.0.0(postcss@8.5.9)
|
||||
stylelint: 17.6.0(typescript@5.9.3)
|
||||
|
||||
stylelint-scss@7.0.0(stylelint@17.6.0(typescript@5.9.3)):
|
||||
@@ -15405,8 +15405,8 @@ snapshots:
|
||||
micromatch: 4.0.8
|
||||
normalize-path: 3.0.0
|
||||
picocolors: 1.1.1
|
||||
postcss: 8.5.8
|
||||
postcss-safe-parser: 7.0.1(postcss@8.5.8)
|
||||
postcss: 8.5.9
|
||||
postcss-safe-parser: 7.0.1(postcss@8.5.9)
|
||||
postcss-selector-parser: 7.1.1
|
||||
postcss-value-parser: 4.2.0
|
||||
string-width: 8.2.0
|
||||
@@ -15587,14 +15587,14 @@ snapshots:
|
||||
'@types/postcss-modules-local-by-default': 4.0.2
|
||||
'@types/postcss-modules-scope': 3.0.4
|
||||
dotenv: 16.4.5
|
||||
icss-utils: 5.1.0(postcss@8.5.8)
|
||||
icss-utils: 5.1.0(postcss@8.5.9)
|
||||
less: 4.2.0
|
||||
lodash.camelcase: 4.3.0
|
||||
postcss: 8.5.8
|
||||
postcss-load-config: 3.1.4(postcss@8.5.8)
|
||||
postcss-modules-extract-imports: 3.1.0(postcss@8.5.8)
|
||||
postcss-modules-local-by-default: 4.0.5(postcss@8.5.8)
|
||||
postcss-modules-scope: 3.2.0(postcss@8.5.8)
|
||||
postcss: 8.5.9
|
||||
postcss-load-config: 3.1.4(postcss@8.5.9)
|
||||
postcss-modules-extract-imports: 3.1.0(postcss@8.5.9)
|
||||
postcss-modules-local-by-default: 4.0.5(postcss@8.5.9)
|
||||
postcss-modules-scope: 3.2.0(postcss@8.5.9)
|
||||
reserved-words: 0.1.2
|
||||
sass: 1.83.0
|
||||
source-map-js: 1.2.1
|
||||
@@ -15856,10 +15856,10 @@ snapshots:
|
||||
pathe: 0.2.0
|
||||
vite: 7.3.2(@types/node@24.11.0)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.2)
|
||||
|
||||
vite-plugin-sass-dts@1.3.37(postcss@8.5.8)(prettier@3.8.1)(sass-embedded@1.99.0)(vite@7.3.2(@types/node@24.11.0)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.2)):
|
||||
vite-plugin-sass-dts@1.3.37(postcss@8.5.9)(prettier@3.8.1)(sass-embedded@1.99.0)(vite@7.3.2(@types/node@24.11.0)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.2)):
|
||||
dependencies:
|
||||
postcss: 8.5.8
|
||||
postcss-js: 4.0.1(postcss@8.5.8)
|
||||
postcss: 8.5.9
|
||||
postcss-js: 4.0.1(postcss@8.5.9)
|
||||
prettier: 3.8.1
|
||||
sass-embedded: 1.99.0
|
||||
vite: 7.3.2(@types/node@24.11.0)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.32.0)(sass-embedded@1.99.0)(sass@1.99.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.2)
|
||||
@@ -15890,7 +15890,7 @@ snapshots:
|
||||
esbuild: 0.27.2
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
postcss: 8.5.8
|
||||
postcss: 8.5.9
|
||||
rollup: 4.46.2
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
|
||||
@@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=ksmbd
|
||||
PKG_VERSION:=3.5.4
|
||||
PKG_RELEASE:=$(AUTORELEASE)
|
||||
PKG_RELEASE:=2
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/cifsd-team/cifsd/tar.gz/$(PKG_VERSION)?
|
||||
@@ -59,13 +59,9 @@ PKG_EXTRA_KCONFIG:=CONFIG_SMB_INSECURE_SERVER=y
|
||||
EXTRA_CFLAGS += -DCONFIG_SMB_INSECURE_SERVER=1
|
||||
endif
|
||||
|
||||
ifdef CONFIG_LINUX_5_10
|
||||
EXTRA_CFLAGS += -DKSMBD_VFS_RENAME_HAS_NODATA_IDMAP=1
|
||||
endif
|
||||
|
||||
define Build/Compile
|
||||
$(KERNEL_MAKE) M="$(PKG_BUILD_DIR)" \
|
||||
EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \
|
||||
KCFLAGS+=" $(EXTRA_CFLAGS)" \
|
||||
$(PKG_EXTRA_KCONFIG) \
|
||||
CONFIG_SMB_SERVER=m \
|
||||
modules
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
--- a/glob.h
|
||||
+++ b/glob.h
|
||||
@@ -9,6 +9,10 @@
|
||||
@@ -9,6 +9,12 @@
|
||||
|
||||
#include <linux/ctype.h>
|
||||
|
||||
+#include <linux/lockdep.h>
|
||||
+
|
||||
+#ifndef lockdep_assert_not_held
|
||||
+#define lockdep_assert_not_held(l) do { } while (0)
|
||||
+#define lockdep_assert_not_held(l) do { (void)(l); } while (0)
|
||||
+#endif
|
||||
+
|
||||
#include "unicode.h"
|
||||
@@ -18,15 +20,24 @@
|
||||
err = -ENOTEMPTY;
|
||||
if (dst_dent != trap_dent && !d_really_is_positive(dst_dent)) {
|
||||
-#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0)
|
||||
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) || defined(KSMBD_VFS_RENAME_HAS_NODATA_IDMAP)
|
||||
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0)
|
||||
struct renamedata rd = {
|
||||
.old_mnt_idmap = src_idmap,
|
||||
@@ -1715,6 +1715,15 @@
|
||||
@@ -1715,7 +1715,7 @@
|
||||
.new_dir = d_inode(dst_dent_parent),
|
||||
.new_dentry = dst_dent,
|
||||
};
|
||||
+#elif defined(KSMBD_VFS_RENAME_HAS_NODATA_IDMAP)
|
||||
-#else
|
||||
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0)
|
||||
struct renamedata rd = {
|
||||
.old_mnt_userns = src_user_ns,
|
||||
.old_dir = d_inode(src_dent_parent),
|
||||
@@ -1724,6 +1724,15 @@
|
||||
.new_dir = d_inode(dst_dent_parent),
|
||||
.new_dentry = dst_dent,
|
||||
};
|
||||
+#else
|
||||
+ struct renamedata rd = {
|
||||
+ .old_dir = d_inode(src_dent_parent),
|
||||
+ .old_dentry = src_dent,
|
||||
@@ -35,6 +46,6 @@
|
||||
+ .delegated_inode = NULL,
|
||||
+ .flags = 0,
|
||||
+ };
|
||||
#endif
|
||||
err = vfs_rename(&rd);
|
||||
#else
|
||||
struct renamedata rd = {
|
||||
.old_mnt_userns = src_user_ns,
|
||||
|
||||
@@ -11,18 +11,16 @@ PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL=$(PROJECT_GIT)/project/iwinfo.git
|
||||
PKG_SOURCE_DATE:=2025-02-06
|
||||
PKG_SOURCE_VERSION:=9cec6b4dd2df80d4c02bad322a5db14203a92cba
|
||||
PKG_MIRROR_HASH:=0541587de92b669bd8dbde0cb1ec70435cb8999eece901e2ea986135c875c396
|
||||
PKG_SOURCE_DATE:=2026-01-14
|
||||
PKG_SOURCE_VERSION:=f5dd57a84cc31a403a1383dd14944fa2e2b5824a
|
||||
PKG_MIRROR_HASH:=a249f1c376c5e3be8fdd3f8414510000c7879b4acb6ae13e2f114d54734f6419
|
||||
PKG_MAINTAINER:=Jo-Philipp Wich <jo@mein.io>
|
||||
PKG_LICENSE:=GPL-2.0
|
||||
|
||||
PKG_BUILD_FLAGS:=no-lto
|
||||
PKG_FLAGS := nonshared
|
||||
|
||||
PKG_CONFIG_DEPENDS := \
|
||||
CONFIG_PACKAGE_kmod-mt7615d_dbdc \
|
||||
CONFIG_PACKAGE_kmod-cfg80211
|
||||
PKG_CONFIG_DEPENDS:=CONFIG_PACKAGE_kmod-mt7615d_dbdc
|
||||
|
||||
IWINFO_ABI_VERSION:=20230701
|
||||
|
||||
@@ -79,19 +77,20 @@ define Build/Configure
|
||||
endef
|
||||
|
||||
IWINFO_BACKENDS := \
|
||||
$(if $(CONFIG_PACKAGE_kmod-mt7615d_dbdc),ra) \
|
||||
$(if $(CONFIG_PACKAGE_kmod-cfg80211),nl80211)
|
||||
nl80211 \
|
||||
$(if $(CONFIG_PACKAGE_kmod-mt7615d_dbdc),ra)
|
||||
|
||||
TARGET_CFLAGS += \
|
||||
-I$(PKG_BUILD_DIR)/include \
|
||||
-I$(STAGING_DIR)/usr/include/libnl-tiny \
|
||||
-I$(STAGING_DIR)/usr/include \
|
||||
-D_GNU_SOURCE
|
||||
|
||||
MAKE_FLAGS += \
|
||||
FPIC="$(FPIC)" \
|
||||
CFLAGS="$(TARGET_CFLAGS)" \
|
||||
CFLAGS="$(TARGET_CFLAGS) $(TARGET_CPPFLAGS)" \
|
||||
LDFLAGS="$(TARGET_LDFLAGS)" \
|
||||
BACKENDS="$(IWINFO_BACKENDS)" \
|
||||
BACKENDS="$(strip $(IWINFO_BACKENDS))" \
|
||||
SOVERSION="$(IWINFO_ABI_VERSION)"
|
||||
|
||||
define Build/InstallDev
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
--- a/Makefile
|
||||
+++ b/Makefile
|
||||
@@ -55,10 +55,10 @@ $(IWINFO_LIB): $(IWINFO_LIB_OBJ)
|
||||
$(CC) $(IWINFO_LDFLAGS) $(IWINFO_LIB_LDFLAGS) -o $(IWINFO_LIB).$(IWINFO_SOVERSION) $(IWINFO_LIB_OBJ) && \
|
||||
ln -sf $(IWINFO_LIB).$(IWINFO_SOVERSION) $(IWINFO_LIB)
|
||||
|
||||
$(IWINFO_LUA): $(IWINFO_LUA_OBJ)
|
||||
- $(CC) $(IWINFO_LDFLAGS) $(IWINFO_LUA_LDFLAGS) -o $(IWINFO_LUA) $(IWINFO_LUA_OBJ)
|
||||
+ $(CC) $(IWINFO_LDFLAGS) -o $(IWINFO_LUA) $(IWINFO_LUA_OBJ) $(IWINFO_LUA_LDFLAGS)
|
||||
|
||||
$(IWINFO_CLI): $(IWINFO_CLI_OBJ)
|
||||
- $(CC) $(IWINFO_LDFLAGS) $(IWINFO_CLI_LDFLAGS) -o $(IWINFO_CLI) $(IWINFO_CLI_OBJ)
|
||||
+ $(CC) $(IWINFO_LDFLAGS) -o $(IWINFO_CLI) $(IWINFO_CLI_OBJ) $(IWINFO_CLI_LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -f *.o $(IWINFO_LIB) $(IWINFO_LUA) $(IWINFO_CLI)
|
||||
@@ -0,0 +1,54 @@
|
||||
--- a/iwinfo_cli.c
|
||||
+++ b/iwinfo_cli.c
|
||||
@@ -343,16 +343,36 @@
|
||||
return buf;
|
||||
}
|
||||
|
||||
+static const uint16_t cli_ht_chan_width[] = {
|
||||
+ 20,
|
||||
+ 2040,
|
||||
+};
|
||||
+
|
||||
+static const uint16_t cli_vht_chan_width[] = {
|
||||
+ 40,
|
||||
+ 80,
|
||||
+ 160,
|
||||
+ 8080,
|
||||
+};
|
||||
+
|
||||
+static const uint16_t cli_eht_chan_width[] = {
|
||||
+ 20,
|
||||
+ 40,
|
||||
+ 80,
|
||||
+ 160,
|
||||
+ 320,
|
||||
+};
|
||||
+
|
||||
static const char* format_chan_width(bool vht, uint8_t width)
|
||||
{
|
||||
- if (!vht && width < ARRAY_SIZE(ht_chan_width))
|
||||
- switch (ht_chan_width[width]) {
|
||||
+ if (!vht && width < ARRAY_SIZE(cli_ht_chan_width))
|
||||
+ switch (cli_ht_chan_width[width]) {
|
||||
case 20: return "20 MHz";
|
||||
case 2040: return "40 MHz or higher";
|
||||
}
|
||||
|
||||
- if (vht && width < ARRAY_SIZE(vht_chan_width))
|
||||
- switch (vht_chan_width[width]) {
|
||||
+ if (vht && width < ARRAY_SIZE(cli_vht_chan_width))
|
||||
+ switch (cli_vht_chan_width[width]) {
|
||||
case 40: return "20 or 40 MHz";
|
||||
case 80: return "80 MHz";
|
||||
case 8080: return "80+80 MHz";
|
||||
@@ -364,8 +384,8 @@
|
||||
|
||||
static const char* format_6ghz_chan_width(uint8_t width)
|
||||
{
|
||||
- if (width < ARRAY_SIZE(eht_chan_width))
|
||||
- switch (eht_chan_width[width]) {
|
||||
+ if (width < ARRAY_SIZE(cli_eht_chan_width))
|
||||
+ switch (cli_eht_chan_width[width]) {
|
||||
case 20: return "20 MHz";
|
||||
case 40: return "40 MHz";
|
||||
case 80: return "80 MHz";
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
From b8f897a9da7a82ad8584a22284ceac61262fcb7e Mon Sep 17 00:00:00 2001
|
||||
From: Jorropo <jorropo.pgm@gmail.com>
|
||||
Date: Sun, 22 Feb 2026 01:47:45 +0100
|
||||
Subject: [PATCH] runtime: fix value of ENOSYS on mips from 38 to 89
|
||||
|
||||
Fixes #77731
|
||||
|
||||
Change-Id: Iaca444e2d5f9e19fd2de38414b357b41471a668c
|
||||
---
|
||||
|
||||
diff --git a/src/runtime/defs_linux_mips64x.go b/src/runtime/defs_linux_mips64x.go
|
||||
index 7449d2c..4d0f103 100644
|
||||
--- a/src/runtime/defs_linux_mips64x.go
|
||||
+++ b/src/runtime/defs_linux_mips64x.go
|
||||
@@ -12,7 +12,7 @@
|
||||
_EINTR = 0x4
|
||||
_EAGAIN = 0xb
|
||||
_ENOMEM = 0xc
|
||||
- _ENOSYS = 0x26
|
||||
+ _ENOSYS = 0x59
|
||||
|
||||
_PROT_NONE = 0x0
|
||||
_PROT_READ = 0x1
|
||||
diff --git a/src/runtime/defs_linux_mipsx.go b/src/runtime/defs_linux_mipsx.go
|
||||
index 5a446e0..b8da4d0 100644
|
||||
--- a/src/runtime/defs_linux_mipsx.go
|
||||
+++ b/src/runtime/defs_linux_mipsx.go
|
||||
@@ -12,7 +12,7 @@
|
||||
_EINTR = 0x4
|
||||
_EAGAIN = 0xb
|
||||
_ENOMEM = 0xc
|
||||
- _ENOSYS = 0x26
|
||||
+ _ENOSYS = 0x59
|
||||
|
||||
_PROT_NONE = 0x0
|
||||
_PROT_READ = 0x1
|
||||
-265
@@ -1,265 +0,0 @@
|
||||
From 1a44be4cecdc742ac6cce9825f9ffc19857c99f3 Mon Sep 17 00:00:00 2001
|
||||
From: database64128 <free122448@hotmail.com>
|
||||
Date: Mon, 9 Mar 2026 16:25:16 +0800
|
||||
Subject: [PATCH] [release-branch.go1.26] internal/poll: move rsan to heap on
|
||||
windows
|
||||
|
||||
According to https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsarecvfrom,
|
||||
the memory pointed to by lpFromlen must remain available during the
|
||||
overlapped I/O, and therefore cannot be allocated on the stack.
|
||||
|
||||
CL 685417 moved the rsan field out of the operation struct and placed
|
||||
it on stack, which violates the above requirement and causes stack
|
||||
corruption.
|
||||
|
||||
Unfortunately, it is no longer possible to cleanly revert CL 685417.
|
||||
Instead of attempting to revert it, this CL bundles rsan together
|
||||
with rsa in the same sync.Pool. The new wsaRsa struct is still in the
|
||||
same size class, so no additional overhead is introduced by this
|
||||
change.
|
||||
|
||||
Fixes #78041.
|
||||
|
||||
Change-Id: I5ffbccb332515116ddc03fb7c40ffc9293cad2ab
|
||||
Reviewed-on: https://go-review.googlesource.com/c/go/+/753040
|
||||
Reviewed-by: Quim Muntal <quimmuntal@gmail.com>
|
||||
Reviewed-by: Cherry Mui <cherryyz@google.com>
|
||||
Commit-Queue: Cherry Mui <cherryyz@google.com>
|
||||
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
|
||||
Reviewed-by: Damien Neil <dneil@google.com>
|
||||
Reviewed-on: https://go-review.googlesource.com/c/go/+/753480
|
||||
Reviewed-by: Mark Freeman <markfreeman@google.com>
|
||||
---
|
||||
src/internal/poll/fd_windows.go | 94 +++++++++++++++++++++------------
|
||||
1 file changed, 59 insertions(+), 35 deletions(-)
|
||||
|
||||
diff --git a/src/internal/poll/fd_windows.go b/src/internal/poll/fd_windows.go
|
||||
index 2ba967f990982f..26319548e3c310 100644
|
||||
--- a/src/internal/poll/fd_windows.go
|
||||
+++ b/src/internal/poll/fd_windows.go
|
||||
@@ -149,7 +149,7 @@ var wsaMsgPool = sync.Pool{
|
||||
|
||||
// newWSAMsg creates a new WSAMsg with the provided parameters.
|
||||
// Use [freeWSAMsg] to free it.
|
||||
-func newWSAMsg(p []byte, oob []byte, flags int, unconnected bool) *windows.WSAMsg {
|
||||
+func newWSAMsg(p []byte, oob []byte, flags int, rsa *wsaRsa) *windows.WSAMsg {
|
||||
// The returned object can't be allocated in the stack because it is accessed asynchronously
|
||||
// by Windows in between several system calls. If the stack frame is moved while that happens,
|
||||
// then Windows may access invalid memory.
|
||||
@@ -166,34 +166,46 @@ func newWSAMsg(p []byte, oob []byte, flags int, unconnected bool) *windows.WSAMs
|
||||
}
|
||||
}
|
||||
msg.Flags = uint32(flags)
|
||||
- if unconnected {
|
||||
- msg.Name = wsaRsaPool.Get().(*syscall.RawSockaddrAny)
|
||||
- msg.Namelen = int32(unsafe.Sizeof(syscall.RawSockaddrAny{}))
|
||||
+ if rsa != nil {
|
||||
+ msg.Name = &rsa.name
|
||||
+ msg.Namelen = rsa.namelen
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func freeWSAMsg(msg *windows.WSAMsg) {
|
||||
// Clear pointers to buffers so they can be released by garbage collector.
|
||||
+ msg.Name = nil
|
||||
+ msg.Namelen = 0
|
||||
msg.Buffers.Len = 0
|
||||
msg.Buffers.Buf = nil
|
||||
msg.Control.Len = 0
|
||||
msg.Control.Buf = nil
|
||||
- if msg.Name != nil {
|
||||
- *msg.Name = syscall.RawSockaddrAny{}
|
||||
- wsaRsaPool.Put(msg.Name)
|
||||
- msg.Name = nil
|
||||
- msg.Namelen = 0
|
||||
- }
|
||||
wsaMsgPool.Put(msg)
|
||||
}
|
||||
|
||||
+// wsaRsa bundles a [syscall.RawSockaddrAny] with its length for efficient caching.
|
||||
+//
|
||||
+// When used by WSARecvFrom, wsaRsa must be on the heap. See
|
||||
+// https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsarecvfrom.
|
||||
+type wsaRsa struct {
|
||||
+ name syscall.RawSockaddrAny
|
||||
+ namelen int32
|
||||
+}
|
||||
+
|
||||
var wsaRsaPool = sync.Pool{
|
||||
New: func() any {
|
||||
- return new(syscall.RawSockaddrAny)
|
||||
+ return new(wsaRsa)
|
||||
},
|
||||
}
|
||||
|
||||
+func newWSARsa() *wsaRsa {
|
||||
+ rsa := wsaRsaPool.Get().(*wsaRsa)
|
||||
+ rsa.name = syscall.RawSockaddrAny{}
|
||||
+ rsa.namelen = int32(unsafe.Sizeof(syscall.RawSockaddrAny{}))
|
||||
+ return rsa
|
||||
+}
|
||||
+
|
||||
var operationPool = sync.Pool{
|
||||
New: func() any {
|
||||
return new(operation)
|
||||
@@ -739,19 +751,18 @@ func (fd *FD) ReadFrom(buf []byte) (int, syscall.Sockaddr, error) {
|
||||
|
||||
fd.pin('r', &buf[0])
|
||||
|
||||
- rsa := wsaRsaPool.Get().(*syscall.RawSockaddrAny)
|
||||
+ rsa := newWSARsa()
|
||||
defer wsaRsaPool.Put(rsa)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
- rsan := int32(unsafe.Sizeof(*rsa))
|
||||
var flags uint32
|
||||
- err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, rsa, &rsan, &o.o, nil)
|
||||
+ err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, &rsa.name, &rsa.namelen, &o.o, nil)
|
||||
return qty, err
|
||||
})
|
||||
err = fd.eofError(n, err)
|
||||
if err != nil {
|
||||
return n, nil, err
|
||||
}
|
||||
- sa, _ := rsa.Sockaddr()
|
||||
+ sa, _ := rsa.name.Sockaddr()
|
||||
return n, sa, nil
|
||||
}
|
||||
|
||||
@@ -770,19 +781,18 @@ func (fd *FD) ReadFromInet4(buf []byte, sa4 *syscall.SockaddrInet4) (int, error)
|
||||
|
||||
fd.pin('r', &buf[0])
|
||||
|
||||
- rsa := wsaRsaPool.Get().(*syscall.RawSockaddrAny)
|
||||
+ rsa := newWSARsa()
|
||||
defer wsaRsaPool.Put(rsa)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
- rsan := int32(unsafe.Sizeof(*rsa))
|
||||
var flags uint32
|
||||
- err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, rsa, &rsan, &o.o, nil)
|
||||
+ err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, &rsa.name, &rsa.namelen, &o.o, nil)
|
||||
return qty, err
|
||||
})
|
||||
err = fd.eofError(n, err)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
- rawToSockaddrInet4(rsa, sa4)
|
||||
+ rawToSockaddrInet4(&rsa.name, sa4)
|
||||
return n, err
|
||||
}
|
||||
|
||||
@@ -801,19 +811,18 @@ func (fd *FD) ReadFromInet6(buf []byte, sa6 *syscall.SockaddrInet6) (int, error)
|
||||
|
||||
fd.pin('r', &buf[0])
|
||||
|
||||
- rsa := wsaRsaPool.Get().(*syscall.RawSockaddrAny)
|
||||
+ rsa := newWSARsa()
|
||||
defer wsaRsaPool.Put(rsa)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
- rsan := int32(unsafe.Sizeof(*rsa))
|
||||
var flags uint32
|
||||
- err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, rsa, &rsan, &o.o, nil)
|
||||
+ err = syscall.WSARecvFrom(fd.Sysfd, newWsaBuf(buf), 1, &qty, &flags, &rsa.name, &rsa.namelen, &o.o, nil)
|
||||
return qty, err
|
||||
})
|
||||
err = fd.eofError(n, err)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
- rawToSockaddrInet6(rsa, sa6)
|
||||
+ rawToSockaddrInet6(&rsa.name, sa6)
|
||||
return n, err
|
||||
}
|
||||
|
||||
@@ -1373,7 +1382,9 @@ func (fd *FD) ReadMsg(p []byte, oob []byte, flags int) (int, int, int, syscall.S
|
||||
p = p[:maxRW]
|
||||
}
|
||||
|
||||
- msg := newWSAMsg(p, oob, flags, true)
|
||||
+ rsa := newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
+ msg := newWSAMsg(p, oob, flags, rsa)
|
||||
defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSARecvMsg(fd.Sysfd, msg, &qty, &o.o, nil)
|
||||
@@ -1398,7 +1409,9 @@ func (fd *FD) ReadMsgInet4(p []byte, oob []byte, flags int, sa4 *syscall.Sockadd
|
||||
p = p[:maxRW]
|
||||
}
|
||||
|
||||
- msg := newWSAMsg(p, oob, flags, true)
|
||||
+ rsa := newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
+ msg := newWSAMsg(p, oob, flags, rsa)
|
||||
defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSARecvMsg(fd.Sysfd, msg, &qty, &o.o, nil)
|
||||
@@ -1422,7 +1435,9 @@ func (fd *FD) ReadMsgInet6(p []byte, oob []byte, flags int, sa6 *syscall.Sockadd
|
||||
p = p[:maxRW]
|
||||
}
|
||||
|
||||
- msg := newWSAMsg(p, oob, flags, true)
|
||||
+ rsa := newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
+ msg := newWSAMsg(p, oob, flags, rsa)
|
||||
defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('r', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSARecvMsg(fd.Sysfd, msg, &qty, &o.o, nil)
|
||||
@@ -1446,15 +1461,18 @@ func (fd *FD) WriteMsg(p []byte, oob []byte, sa syscall.Sockaddr) (int, int, err
|
||||
}
|
||||
defer fd.writeUnlock()
|
||||
|
||||
- msg := newWSAMsg(p, oob, 0, sa != nil)
|
||||
- defer freeWSAMsg(msg)
|
||||
+ var rsa *wsaRsa
|
||||
if sa != nil {
|
||||
+ rsa = newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
var err error
|
||||
- msg.Namelen, err = sockaddrToRaw(msg.Name, sa)
|
||||
+ rsa.namelen, err = sockaddrToRaw(&rsa.name, sa)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
+ msg := newWSAMsg(p, oob, 0, rsa)
|
||||
+ defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSASendMsg(fd.Sysfd, msg, 0, nil, &o.o, nil)
|
||||
return qty, err
|
||||
@@ -1473,11 +1491,14 @@ func (fd *FD) WriteMsgInet4(p []byte, oob []byte, sa *syscall.SockaddrInet4) (in
|
||||
}
|
||||
defer fd.writeUnlock()
|
||||
|
||||
- msg := newWSAMsg(p, oob, 0, sa != nil)
|
||||
- defer freeWSAMsg(msg)
|
||||
+ var rsa *wsaRsa
|
||||
if sa != nil {
|
||||
- msg.Namelen = sockaddrInet4ToRaw(msg.Name, sa)
|
||||
+ rsa = newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
+ rsa.namelen = sockaddrInet4ToRaw(&rsa.name, sa)
|
||||
}
|
||||
+ msg := newWSAMsg(p, oob, 0, rsa)
|
||||
+ defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSASendMsg(fd.Sysfd, msg, 0, nil, &o.o, nil)
|
||||
return qty, err
|
||||
@@ -1496,11 +1517,14 @@ func (fd *FD) WriteMsgInet6(p []byte, oob []byte, sa *syscall.SockaddrInet6) (in
|
||||
}
|
||||
defer fd.writeUnlock()
|
||||
|
||||
- msg := newWSAMsg(p, oob, 0, sa != nil)
|
||||
- defer freeWSAMsg(msg)
|
||||
+ var rsa *wsaRsa
|
||||
if sa != nil {
|
||||
- msg.Namelen = sockaddrInet6ToRaw(msg.Name, sa)
|
||||
+ rsa = newWSARsa()
|
||||
+ defer wsaRsaPool.Put(rsa)
|
||||
+ rsa.namelen = sockaddrInet6ToRaw(&rsa.name, sa)
|
||||
}
|
||||
+ msg := newWSAMsg(p, oob, 0, rsa)
|
||||
+ defer freeWSAMsg(msg)
|
||||
n, err := fd.execIO('w', func(o *operation) (qty uint32, err error) {
|
||||
err = windows.WSASendMsg(fd.Sysfd, msg, 0, nil, &o.o, nil)
|
||||
return qty, err
|
||||
Vendored
-14
@@ -186,20 +186,6 @@ jobs:
|
||||
- name: Verify Go env
|
||||
run: go env
|
||||
|
||||
# TODO: remove after issue77731 fixed, see: https://github.com/golang/go/issues/77731
|
||||
- name: Fix issue77731 for Golang1.26
|
||||
if: ${{ matrix.jobs.goversion == '' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/issue77731.patch
|
||||
|
||||
# TODO: remove after issue77975 fixed, see: https://github.com/golang/go/issues/77975
|
||||
- name: Fix issue77975 for Golang1.26
|
||||
if: ${{ matrix.jobs.goversion == '' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/issue77975.patch
|
||||
|
||||
# TODO: remove after issue77930 fixed, see: https://github.com/golang/go/issues/77930
|
||||
- name: Fix issue77930 for Golang1.26
|
||||
if: ${{ matrix.jobs.goversion == '' }}
|
||||
|
||||
Vendored
-7
@@ -57,13 +57,6 @@ jobs:
|
||||
- name: Verify Go env
|
||||
run: go env
|
||||
|
||||
# TODO: remove after issue77975 fixed, see: https://github.com/golang/go/issues/77975
|
||||
- name: Fix issue77975 for Golang1.26
|
||||
if: ${{ matrix.go-version == '1.26' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
patch --verbose -p 1 < $GITHUB_WORKSPACE/.github/patch/issue77975.patch
|
||||
|
||||
- name: Remove inbound test for macOS
|
||||
if: ${{ runner.os == 'macOS' }}
|
||||
run: |
|
||||
|
||||
@@ -82,7 +82,7 @@ type XHTTPOptions struct {
|
||||
Headers map[string]string `proxy:"headers,omitempty"`
|
||||
NoGRPCHeader bool `proxy:"no-grpc-header,omitempty"`
|
||||
XPaddingBytes string `proxy:"x-padding-bytes,omitempty"`
|
||||
ScMaxEachPostBytes int `proxy:"sc-max-each-post-bytes,omitempty"`
|
||||
ScMaxEachPostBytes string `proxy:"sc-max-each-post-bytes,omitempty"`
|
||||
ReuseSettings *XHTTPReuseSettings `proxy:"reuse-settings,omitempty"` // aka XMUX
|
||||
DownloadSettings *XHTTPDownloadSettings `proxy:"download-settings,omitempty"`
|
||||
}
|
||||
@@ -102,7 +102,7 @@ type XHTTPDownloadSettings struct {
|
||||
Headers *map[string]string `proxy:"headers,omitempty"`
|
||||
NoGRPCHeader *bool `proxy:"no-grpc-header,omitempty"`
|
||||
XPaddingBytes *string `proxy:"x-padding-bytes,omitempty"`
|
||||
ScMaxEachPostBytes *int `proxy:"sc-max-each-post-bytes,omitempty"`
|
||||
ScMaxEachPostBytes *string `proxy:"sc-max-each-post-bytes,omitempty"`
|
||||
ReuseSettings *XHTTPReuseSettings `proxy:"reuse-settings,omitempty"` // aka XMUX
|
||||
// proxy part
|
||||
Server *string `proxy:"server,omitempty"`
|
||||
|
||||
@@ -1218,8 +1218,8 @@ proxies: # socks5
|
||||
# quic: true # 默认为false
|
||||
# congestion-controller: bbr
|
||||
### reuse options
|
||||
# max-connections: 1 # Maximum connections. Conflict with max-streams.
|
||||
# min-streams: 0 # Minimum multiplexed streams in a connection before opening a new connection. Conflict with max-streams.
|
||||
# max-connections: 8 # Maximum connections. Conflict with max-streams.
|
||||
# min-streams: 5 # Minimum multiplexed streams in a connection before opening a new connection. Conflict with max-streams.
|
||||
# max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.
|
||||
|
||||
# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@ require (
|
||||
github.com/metacubex/edwards25519 v1.2.0
|
||||
github.com/metacubex/fswatch v0.1.1
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||
github.com/metacubex/http v0.1.0
|
||||
github.com/metacubex/http v0.1.1
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604
|
||||
github.com/metacubex/mhurl v0.1.0
|
||||
github.com/metacubex/mlkem v0.1.0
|
||||
@@ -39,7 +39,7 @@ require (
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
|
||||
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443
|
||||
github.com/metacubex/tls v0.1.4
|
||||
github.com/metacubex/tls v0.1.5
|
||||
github.com/metacubex/utls v1.8.4
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f
|
||||
github.com/mroth/weightedrand/v2 v2.1.0
|
||||
|
||||
+4
-4
@@ -103,8 +103,8 @@ github.com/metacubex/hkdf v0.1.0 h1:fPA6VzXK8cU1foc/TOmGCDmSa7pZbxlnqhl3RNsthaA=
|
||||
github.com/metacubex/hkdf v0.1.0/go.mod h1:3seEfds3smgTAXqUGn+tgEJH3uXdsUjOiduG/2EtvZ4=
|
||||
github.com/metacubex/hpke v0.1.0 h1:gu2jUNhraehWi0P/z5HX2md3d7L1FhPQE6/Q0E9r9xQ=
|
||||
github.com/metacubex/hpke v0.1.0/go.mod h1:vfDm6gfgrwlXUxKDkWbcE44hXtmc1uxLDm2BcR11b3U=
|
||||
github.com/metacubex/http v0.1.0 h1:Jcy0I9zKjYijSUaksZU34XEe2xNdoFkgUTB7z7K5q0o=
|
||||
github.com/metacubex/http v0.1.0/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
|
||||
github.com/metacubex/http v0.1.1 h1:zVea0zOoaZvxe51EvJMRWGNQv6MvWqJhkZSuoAjOjVw=
|
||||
github.com/metacubex/http v0.1.1/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI=
|
||||
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc=
|
||||
github.com/metacubex/mhurl v0.1.0 h1:ZdW4Zxe3j3uJ89gNytOazHu6kbHn5owutN/VfXOI8GE=
|
||||
@@ -145,8 +145,8 @@ github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 h1:DK2l6m2Fc85H2Bhi
|
||||
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141/go.mod h1:/yI4OiGOSn0SURhZdJF3CbtPg3nwK700bG8TZLMBvAg=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o=
|
||||
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/tls v0.1.4 h1:Gm5GrkyMUh52gYOMIAQ1kHIym4v4M3Qb87Wsmd8Kpdc=
|
||||
github.com/metacubex/tls v0.1.4/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
|
||||
github.com/metacubex/tls v0.1.5 h1:ECcB83dj+zadnhlKcLnUUf1Sq6+vU0f/zoyU0+9oPTc=
|
||||
github.com/metacubex/tls v0.1.5/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
|
||||
github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg=
|
||||
github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
|
||||
|
||||
@@ -36,7 +36,7 @@ type XHTTPConfig struct {
|
||||
Mode string
|
||||
NoSSEHeader bool
|
||||
ScStreamUpServerSecs string
|
||||
ScMaxEachPostBytes int
|
||||
ScMaxEachPostBytes string
|
||||
}
|
||||
|
||||
func (t VlessServer) String() string {
|
||||
|
||||
@@ -37,7 +37,7 @@ type XHTTPConfig struct {
|
||||
Mode string `inbound:"mode,omitempty"`
|
||||
NoSSEHeader bool `inbound:"no-sse-header,omitempty"`
|
||||
ScStreamUpServerSecs string `inbound:"sc-stream-up-server-secs,omitempty"`
|
||||
ScMaxEachPostBytes int `inbound:"sc-max-each-post-bytes,omitempty"`
|
||||
ScMaxEachPostBytes string `inbound:"sc-max-each-post-bytes,omitempty"`
|
||||
}
|
||||
|
||||
func (o XHTTPConfig) Build() LC.XHTTPConfig {
|
||||
|
||||
@@ -154,7 +154,7 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
}
|
||||
}
|
||||
if config.XHTTPConfig.Path != "" || config.XHTTPConfig.Host != "" || config.XHTTPConfig.Mode != "" {
|
||||
httpServer.Handler = xhttp.NewServerHandler(xhttp.ServerOption{
|
||||
httpServer.Handler, err = xhttp.NewServerHandler(xhttp.ServerOption{
|
||||
Config: xhttp.Config{
|
||||
Host: config.XHTTPConfig.Host,
|
||||
Path: config.XHTTPConfig.Path,
|
||||
@@ -168,6 +168,9 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
},
|
||||
HttpHandler: httpServer.Handler,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !slices.Contains(tlsConfig.NextProtos, "http/1.1") {
|
||||
tlsConfig.NextProtos = append([]string{"http/1.1"}, tlsConfig.NextProtos...)
|
||||
}
|
||||
|
||||
@@ -168,52 +168,37 @@ func (c *Client) roundTrip(request *http.Request, conn *httpConn) {
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *Client) Dial(ctx context.Context, host string) (net.Conn, error) {
|
||||
func (c *Client) newConnectRequest(host, userAgent string) *http.Request {
|
||||
request := &http.Request{
|
||||
Method: http.MethodConnect,
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: host,
|
||||
Host: c.server, // Use the proxy server authority so the pool keys reuse against the actual proxy endpoint.
|
||||
},
|
||||
Header: make(http.Header),
|
||||
Host: host,
|
||||
Host: host, // Send the actual CONNECT target as the Host header (:authority).
|
||||
}
|
||||
request.Header.Add("User-Agent", TCPUserAgent)
|
||||
request.Header.Add("User-Agent", userAgent)
|
||||
request.Header.Add("Proxy-Authorization", c.auth)
|
||||
return request
|
||||
}
|
||||
|
||||
func (c *Client) Dial(ctx context.Context, host string) (net.Conn, error) {
|
||||
request := c.newConnectRequest(host, TCPUserAgent)
|
||||
conn := &tcpConn{}
|
||||
c.roundTrip(request, &conn.httpConn)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListenPacket(ctx context.Context) (net.PacketConn, error) {
|
||||
request := &http.Request{
|
||||
Method: http.MethodConnect,
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: UDPMagicAddress,
|
||||
},
|
||||
Header: make(http.Header),
|
||||
Host: UDPMagicAddress,
|
||||
}
|
||||
request.Header.Add("User-Agent", UDPUserAgent)
|
||||
request.Header.Add("Proxy-Authorization", c.auth)
|
||||
request := c.newConnectRequest(UDPMagicAddress, UDPUserAgent)
|
||||
conn := &clientPacketConn{}
|
||||
c.roundTrip(request, &conn.httpConn)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *Client) ListenICMP(ctx context.Context) (*IcmpConn, error) {
|
||||
request := &http.Request{
|
||||
Method: http.MethodConnect,
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: ICMPMagicAddress,
|
||||
},
|
||||
Header: make(http.Header),
|
||||
Host: ICMPMagicAddress,
|
||||
}
|
||||
request.Header.Add("User-Agent", ICMPUserAgent)
|
||||
request.Header.Add("Proxy-Authorization", c.auth)
|
||||
request := c.newConnectRequest(ICMPMagicAddress, ICMPUserAgent)
|
||||
conn := &IcmpConn{}
|
||||
c.roundTrip(request, &conn.httpConn)
|
||||
return conn, nil
|
||||
@@ -234,17 +219,7 @@ func (c *Client) ResetConnections() {
|
||||
|
||||
func (c *Client) HealthCheck(ctx context.Context) error {
|
||||
defer c.resetHealthCheckTimer()
|
||||
request := &http.Request{
|
||||
Method: http.MethodConnect,
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: HealthCheckMagicAddress,
|
||||
},
|
||||
Header: make(http.Header),
|
||||
Host: HealthCheckMagicAddress,
|
||||
}
|
||||
request.Header.Add("User-Agent", HealthCheckUserAgent)
|
||||
request.Header.Add("Proxy-Authorization", c.auth)
|
||||
request := c.newConnectRequest(HealthCheckMagicAddress, HealthCheckUserAgent)
|
||||
response, err := c.roundTripper.RoundTrip(request.WithContext(ctx))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -271,7 +246,8 @@ func NewPoolClient(ctx context.Context, options ClientOptions) (*PoolClient, err
|
||||
minStreams := options.MinStreams
|
||||
maxStreams := options.MaxStreams
|
||||
if maxConnections == 0 && minStreams == 0 && maxStreams == 0 {
|
||||
maxConnections = 1
|
||||
maxConnections = 8
|
||||
minStreams = 5
|
||||
}
|
||||
client, err := NewClient(ctx, options) // reserve one client and verify the configuration
|
||||
if err != nil {
|
||||
|
||||
@@ -24,19 +24,20 @@ type WrapTLSFunc func(ctx context.Context, conn net.Conn, isH2 bool) (net.Conn,
|
||||
type TransportMaker func() http.RoundTripper
|
||||
|
||||
type PacketUpWriter struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
cfg *Config
|
||||
sessionID string
|
||||
transport http.RoundTripper
|
||||
writeMu sync.Mutex
|
||||
seq uint64
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
cfg *Config
|
||||
scMaxEachPostBytes Range
|
||||
sessionID string
|
||||
transport http.RoundTripper
|
||||
writeMu sync.Mutex
|
||||
seq uint64
|
||||
}
|
||||
|
||||
func (c *PacketUpWriter) Write(b []byte) (int, error) {
|
||||
c.writeMu.Lock()
|
||||
defer c.writeMu.Unlock()
|
||||
scMaxEachPostBytes := c.cfg.GetNormalizedScMaxEachPostBytes()
|
||||
scMaxEachPostBytes := c.scMaxEachPostBytes.Rand()
|
||||
if len(b) < scMaxEachPostBytes {
|
||||
return c.write(b)
|
||||
}
|
||||
@@ -117,6 +118,7 @@ type Client struct {
|
||||
cancel context.CancelFunc
|
||||
mode string
|
||||
cfg *Config
|
||||
scMaxEachPostBytes Range
|
||||
makeTransport TransportMaker
|
||||
makeDownloadTransport TransportMaker
|
||||
uploadManager *ReuseManager
|
||||
@@ -130,11 +132,16 @@ func NewClient(cfg *Config, makeTransport TransportMaker, makeDownloadTransport
|
||||
default:
|
||||
return nil, fmt.Errorf("xhttp mode %s is not implemented yet", mode)
|
||||
}
|
||||
scMaxEachPostBytes, err := cfg.GetNormalizedScMaxEachPostBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
client := &Client{
|
||||
mode: mode,
|
||||
cfg: cfg,
|
||||
scMaxEachPostBytes: scMaxEachPostBytes,
|
||||
makeTransport: makeTransport,
|
||||
makeDownloadTransport: makeDownloadTransport,
|
||||
ctx: ctx,
|
||||
@@ -403,12 +410,13 @@ func (c *Client) DialPacketUp() (net.Conn, error) {
|
||||
|
||||
writerCtx, writerCancel := context.WithCancel(c.ctx)
|
||||
writer := &PacketUpWriter{
|
||||
ctx: writerCtx,
|
||||
cancel: writerCancel,
|
||||
cfg: c.cfg,
|
||||
sessionID: sessionID,
|
||||
transport: uploadTransport,
|
||||
seq: 0,
|
||||
ctx: writerCtx,
|
||||
cancel: writerCancel,
|
||||
cfg: c.cfg,
|
||||
scMaxEachPostBytes: c.scMaxEachPostBytes,
|
||||
sessionID: sessionID,
|
||||
transport: uploadTransport,
|
||||
seq: 0,
|
||||
}
|
||||
conn := &Conn{writer: writer}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ type Config struct {
|
||||
XPaddingBytes string
|
||||
NoSSEHeader bool // server only
|
||||
ScStreamUpServerSecs string // server only
|
||||
ScMaxEachPostBytes int
|
||||
ScMaxEachPostBytes string
|
||||
ReuseConfig *ReuseConfig
|
||||
DownloadConfig *Config
|
||||
}
|
||||
@@ -94,142 +94,114 @@ func (c *Config) RequestHeader() http.Header {
|
||||
}
|
||||
|
||||
func (c *Config) RandomPadding() (string, error) {
|
||||
paddingRange := c.XPaddingBytes
|
||||
if paddingRange == "" {
|
||||
paddingRange = "100-1000"
|
||||
}
|
||||
|
||||
minVal, maxVal, err := parseRange(paddingRange)
|
||||
r, err := ParseRange(c.XPaddingBytes, "100-1000")
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", fmt.Errorf("invalid x-padding-bytes: %w", err)
|
||||
}
|
||||
if minVal < 0 || maxVal < minVal {
|
||||
return "", fmt.Errorf("invalid x-padding-bytes range: %s", paddingRange)
|
||||
}
|
||||
if maxVal == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
n := minVal
|
||||
if maxVal > minVal {
|
||||
n = minVal + rand.Intn(maxVal-minVal+1)
|
||||
}
|
||||
|
||||
return strings.Repeat("X", n), nil
|
||||
return strings.Repeat("X", r.Rand()), nil
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedScStreamUpServerSecs() (int, error) {
|
||||
scStreamUpServerSecs := c.ScStreamUpServerSecs
|
||||
if scStreamUpServerSecs == "" {
|
||||
scStreamUpServerSecs = "20-80"
|
||||
}
|
||||
|
||||
minVal, maxVal, err := parseRange(scStreamUpServerSecs)
|
||||
func (c *Config) GetNormalizedScStreamUpServerSecs() (Range, error) {
|
||||
r, err := ParseRange(c.ScStreamUpServerSecs, "20-80")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return Range{}, fmt.Errorf("invalid sc-stream-up-server-secs: %w", err)
|
||||
}
|
||||
if minVal < 0 || maxVal < minVal {
|
||||
return 0, fmt.Errorf("invalid sc-stream-up-server-secs range: %s", scStreamUpServerSecs)
|
||||
}
|
||||
if maxVal == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
n := minVal
|
||||
if maxVal > minVal {
|
||||
n = minVal + rand.Intn(maxVal-minVal+1)
|
||||
}
|
||||
|
||||
return n, nil
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (c *Config) GetNormalizedScMaxEachPostBytes() int {
|
||||
if c.ScMaxEachPostBytes == 0 {
|
||||
return 1000000
|
||||
func (c *Config) GetNormalizedScMaxEachPostBytes() (Range, error) {
|
||||
r, err := ParseRange(c.ScStreamUpServerSecs, "1000000")
|
||||
if err != nil {
|
||||
return Range{}, fmt.Errorf("invalid sc-max-each-post-bytes: %w", err)
|
||||
}
|
||||
return c.ScMaxEachPostBytes
|
||||
if r.Max == 0 {
|
||||
return Range{}, fmt.Errorf("invalid sc-max-each-post-bytes: must be greater than zero")
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func parseRange(s string) (int, int, error) {
|
||||
type Range struct {
|
||||
Min int
|
||||
Max int
|
||||
}
|
||||
|
||||
func (r Range) Rand() int {
|
||||
if r.Min == r.Max {
|
||||
return r.Min
|
||||
}
|
||||
return r.Min + rand.Intn(r.Max-r.Min+1)
|
||||
}
|
||||
|
||||
func ParseRange(s string, fallback string) (Range, error) {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return parseRange(fallback)
|
||||
}
|
||||
return parseRange(s)
|
||||
}
|
||||
|
||||
func parseRange(s string) (Range, error) {
|
||||
parts := strings.Split(strings.TrimSpace(s), "-")
|
||||
if len(parts) == 1 {
|
||||
v, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
return Range{}, err
|
||||
}
|
||||
return v, v, nil
|
||||
return Range{v, v}, nil
|
||||
}
|
||||
if len(parts) != 2 {
|
||||
return 0, 0, fmt.Errorf("invalid range: %s", s)
|
||||
return Range{}, fmt.Errorf("invalid range: %s", s)
|
||||
}
|
||||
|
||||
minVal, err := strconv.Atoi(strings.TrimSpace(parts[0]))
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
return Range{}, err
|
||||
}
|
||||
maxVal, err := strconv.Atoi(strings.TrimSpace(parts[1]))
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return minVal, maxVal, nil
|
||||
}
|
||||
|
||||
func resolveRangeValue(s string, fallback int) (int, error) {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return fallback, nil
|
||||
}
|
||||
|
||||
minVal, maxVal, err := parseRange(s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return Range{}, err
|
||||
}
|
||||
if minVal < 0 || maxVal < minVal {
|
||||
return 0, fmt.Errorf("invalid range: %s", s)
|
||||
return Range{}, fmt.Errorf("invalid range: %s", s)
|
||||
}
|
||||
|
||||
if minVal == maxVal {
|
||||
return minVal, nil
|
||||
}
|
||||
|
||||
return minVal + rand.Intn(maxVal-minVal+1), nil
|
||||
return Range{minVal, maxVal}, nil
|
||||
}
|
||||
|
||||
func (c *ReuseConfig) ResolveManagerConfig() (int, int, error) {
|
||||
func (c *ReuseConfig) ResolveManagerConfig() (Range, Range, error) {
|
||||
if c == nil {
|
||||
return 0, 0, nil
|
||||
return Range{}, Range{}, nil
|
||||
}
|
||||
|
||||
maxConnections, err := resolveRangeValue(c.MaxConnections, 0)
|
||||
maxConnections, err := ParseRange(c.MaxConnections, "0")
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("invalid max-connections: %w", err)
|
||||
return Range{}, Range{}, fmt.Errorf("invalid max-connections: %w", err)
|
||||
}
|
||||
|
||||
maxConcurrency, err := resolveRangeValue(c.MaxConcurrency, 0)
|
||||
maxConcurrency, err := ParseRange(c.MaxConcurrency, "0")
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("invalid max-concurrency: %w", err)
|
||||
return Range{}, Range{}, fmt.Errorf("invalid max-concurrency: %w", err)
|
||||
}
|
||||
|
||||
return maxConnections, maxConcurrency, nil
|
||||
}
|
||||
|
||||
func (c *ReuseConfig) ResolveEntryConfig() (int, int, int, error) {
|
||||
func (c *ReuseConfig) ResolveEntryConfig() (Range, Range, Range, error) {
|
||||
if c == nil {
|
||||
return 0, 0, 0, nil
|
||||
return Range{}, Range{}, Range{}, nil
|
||||
}
|
||||
|
||||
hMaxRequestTimes, err := resolveRangeValue(c.HMaxRequestTimes, 0)
|
||||
hMaxRequestTimes, err := ParseRange(c.HMaxRequestTimes, "0")
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("invalid h-max-request-times: %w", err)
|
||||
return Range{}, Range{}, Range{}, fmt.Errorf("invalid h-max-request-times: %w", err)
|
||||
}
|
||||
|
||||
hMaxReusableSecs, err := resolveRangeValue(c.HMaxReusableSecs, 0)
|
||||
hMaxReusableSecs, err := ParseRange(c.HMaxReusableSecs, "0")
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("invalid h-max-reusable-secs: %w", err)
|
||||
return Range{}, Range{}, Range{}, fmt.Errorf("invalid h-max-reusable-secs: %w", err)
|
||||
}
|
||||
|
||||
cMaxReuseTimes, err := resolveRangeValue(c.CMaxReuseTimes, 0)
|
||||
cMaxReuseTimes, err := ParseRange(c.CMaxReuseTimes, "0")
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("invalid c-max-reuse-times: %w", err)
|
||||
return Range{}, Range{}, Range{}, fmt.Errorf("invalid c-max-reuse-times: %w", err)
|
||||
}
|
||||
|
||||
return hMaxRequestTimes, hMaxReusableSecs, cMaxReuseTimes, nil
|
||||
|
||||
@@ -55,12 +55,14 @@ func (rt *ReuseTransport) Close() error {
|
||||
var _ http.RoundTripper = (*ReuseTransport)(nil)
|
||||
|
||||
type ReuseManager struct {
|
||||
cfg *ReuseConfig
|
||||
maxConnections int
|
||||
maxConcurrency int
|
||||
maker TransportMaker
|
||||
mu sync.Mutex
|
||||
entries []*reuseEntry
|
||||
maxConnections int
|
||||
maxConcurrency int
|
||||
hMaxRequestTimes Range
|
||||
hMaxReusableSecs Range
|
||||
cMaxReuseTimes Range
|
||||
maker TransportMaker
|
||||
mu sync.Mutex
|
||||
entries []*reuseEntry
|
||||
}
|
||||
|
||||
func NewReuseManager(cfg *ReuseConfig, makeTransport TransportMaker) (*ReuseManager, error) {
|
||||
@@ -71,16 +73,18 @@ func NewReuseManager(cfg *ReuseConfig, makeTransport TransportMaker) (*ReuseMana
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, _, _, err = cfg.ResolveEntryConfig() // check if config is valid
|
||||
hMaxRequestTimes, hMaxReusableSecs, cMaxReuseTimes, err := cfg.ResolveEntryConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ReuseManager{
|
||||
cfg: cfg,
|
||||
maxConnections: connections,
|
||||
maxConcurrency: concurrency,
|
||||
maker: makeTransport,
|
||||
entries: make([]*reuseEntry, 0),
|
||||
maxConnections: connections.Rand(),
|
||||
maxConcurrency: concurrency.Rand(),
|
||||
hMaxRequestTimes: hMaxRequestTimes,
|
||||
hMaxReusableSecs: hMaxReusableSecs,
|
||||
cMaxReuseTimes: cMaxReuseTimes,
|
||||
maker: makeTransport,
|
||||
entries: make([]*reuseEntry, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -169,17 +173,16 @@ func (m *ReuseManager) canCreateLocked() bool {
|
||||
func (m *ReuseManager) newEntryLocked(transport http.RoundTripper, now time.Time) *reuseEntry {
|
||||
entry := &reuseEntry{transport: transport}
|
||||
|
||||
hMaxRequestTimes, hMaxReusableSecs, cMaxReuseTimes, _ := m.cfg.ResolveEntryConfig() // error already checked in [NewReuseManager]
|
||||
if hMaxRequestTimes > 0 {
|
||||
entry.leftRequests.Store(int32(hMaxRequestTimes))
|
||||
if m.hMaxRequestTimes.Max > 0 {
|
||||
entry.leftRequests.Store(int32(m.hMaxRequestTimes.Rand()))
|
||||
} else {
|
||||
entry.leftRequests.Store(1<<30 - 1)
|
||||
}
|
||||
if hMaxReusableSecs > 0 {
|
||||
entry.unreusableAt = now.Add(time.Duration(hMaxReusableSecs) * time.Second)
|
||||
if m.hMaxReusableSecs.Max > 0 {
|
||||
entry.unreusableAt = now.Add(time.Duration(m.hMaxReusableSecs.Rand()) * time.Second)
|
||||
}
|
||||
if cMaxReuseTimes > 0 {
|
||||
entry.maxReuseTimes = int32(cMaxReuseTimes)
|
||||
if m.cMaxReuseTimes.Max > 0 {
|
||||
entry.maxReuseTimes = int32(m.cMaxReuseTimes.Rand())
|
||||
}
|
||||
|
||||
m.entries = append(m.entries, entry)
|
||||
|
||||
@@ -98,21 +98,34 @@ type requestHandler struct {
|
||||
connHandler func(net.Conn)
|
||||
httpHandler http.Handler
|
||||
|
||||
scMaxEachPostBytes Range
|
||||
scStreamUpServerSecs Range
|
||||
|
||||
mu sync.Mutex
|
||||
sessions map[string]*httpSession
|
||||
}
|
||||
|
||||
func NewServerHandler(opt ServerOption) http.Handler {
|
||||
func NewServerHandler(opt ServerOption) (http.Handler, error) {
|
||||
scMaxEachPostBytes, err := opt.Config.GetNormalizedScMaxEachPostBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
scStreamUpServerSecs, err := opt.Config.GetNormalizedScStreamUpServerSecs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// using h2c.NewHandler to ensure we can work in plain http2
|
||||
// and some tls conn is not *tls.Conn (like *reality.Conn)
|
||||
return h2c.NewHandler(&requestHandler{
|
||||
config: opt.Config,
|
||||
connHandler: opt.ConnHandler,
|
||||
httpHandler: opt.HttpHandler,
|
||||
sessions: map[string]*httpSession{},
|
||||
config: opt.Config,
|
||||
connHandler: opt.ConnHandler,
|
||||
httpHandler: opt.HttpHandler,
|
||||
scMaxEachPostBytes: scMaxEachPostBytes,
|
||||
scStreamUpServerSecs: scStreamUpServerSecs,
|
||||
sessions: map[string]*httpSession{},
|
||||
}, &http.Http2Server{
|
||||
IdleTimeout: 30 * time.Second,
|
||||
})
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (h *requestHandler) getOrCreateSession(sessionID string) *httpSession {
|
||||
@@ -209,12 +222,7 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
parts := splitNonEmpty(rest)
|
||||
|
||||
// stream-one: POST /path
|
||||
if r.Method == http.MethodPost && len(parts) == 0 {
|
||||
if !h.allowStreamOne() {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodPost && len(parts) == 0 && h.allowStreamOne() {
|
||||
w.Header().Set("X-Accel-Buffering", "no")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
@@ -241,12 +249,7 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// stream-up/packet-up download: GET /path/{session}
|
||||
if r.Method == http.MethodGet && len(parts) == 1 {
|
||||
if !h.allowSessionDownload() {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodGet && len(parts) == 1 && h.allowSessionDownload() {
|
||||
sessionID := parts[0]
|
||||
session := h.getOrCreateSession(sessionID)
|
||||
session.markConnected()
|
||||
@@ -288,12 +291,7 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// stream-up upload: POST /path/{session}
|
||||
if r.Method == http.MethodPost && len(parts) == 1 {
|
||||
if !h.allowStreamUpUpload() {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodPost && len(parts) == 1 && h.allowStreamUpUpload() {
|
||||
sessionID := parts[0]
|
||||
session := h.getSession(sessionID)
|
||||
if session == nil {
|
||||
@@ -322,13 +320,9 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
referrer := r.Header.Get("Referer")
|
||||
if referrer != "" {
|
||||
if referrer != "" && h.scStreamUpServerSecs.Max > 0 {
|
||||
go func() {
|
||||
for {
|
||||
scStreamUpServerSecs, _ := h.config.GetNormalizedScStreamUpServerSecs()
|
||||
if scStreamUpServerSecs == 0 {
|
||||
break
|
||||
}
|
||||
paddingValue, _ := h.config.RandomPadding()
|
||||
if paddingValue == "" {
|
||||
break
|
||||
@@ -337,7 +331,7 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Duration(scStreamUpServerSecs) * time.Second)
|
||||
time.Sleep(time.Duration(h.scStreamUpServerSecs.Rand()) * time.Second)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -352,12 +346,7 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// packet-up upload: POST /path/{session}/{seq}
|
||||
if r.Method == http.MethodPost && len(parts) == 2 {
|
||||
if !h.allowPacketUpUpload() {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodPost && len(parts) == 2 && h.allowPacketUpUpload() {
|
||||
sessionID := parts[0]
|
||||
seq, err := strconv.ParseUint(parts[1], 10, 64)
|
||||
if err != nil {
|
||||
@@ -371,13 +360,12 @@ func (h *requestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
scMaxEachPostBytes := int64(h.config.GetNormalizedScMaxEachPostBytes())
|
||||
if r.ContentLength > scMaxEachPostBytes {
|
||||
if r.ContentLength > int64(h.scMaxEachPostBytes.Max) {
|
||||
http.Error(w, "body too large", http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(io.LimitReader(r.Body, scMaxEachPostBytes+1))
|
||||
body, err := io.ReadAll(io.LimitReader(r.Body, int64(h.scMaxEachPostBytes.Max)+1))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
|
||||
@@ -78,7 +78,7 @@ func TestServerHandlerModeRestrictions(t *testing.T) {
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
handler := NewServerHandler(ServerOption{
|
||||
handler, err := NewServerHandler(ServerOption{
|
||||
Config: Config{
|
||||
Path: "/xhttp",
|
||||
Mode: testCase.mode,
|
||||
@@ -87,6 +87,9 @@ func TestServerHandlerModeRestrictions(t *testing.T) {
|
||||
_ = conn.Close()
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(testCase.method, testCase.target, io.NopCloser(http.NoBody))
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
@@ -274,14 +274,15 @@ int NaiveConnection::DoConnectServer() {
|
||||
return ERR_ADDRESS_INVALID;
|
||||
}
|
||||
|
||||
LOG(INFO) << "Connection " << id_ << " to " << origin.ToString();
|
||||
LOG(INFO) << "Connection " << id_ << " to " << origin.ToString() << " via "
|
||||
<< proxy_info_.ToDebugString();
|
||||
|
||||
// Ignores socket limit set by socket pool for this type of socket.
|
||||
return InitSocketHandleForHttpRequest(
|
||||
std::move(endpoint), LOAD_IGNORE_LIMITS, MAXIMUM_PRIORITY, session_,
|
||||
proxy_info_, {}, PRIVACY_MODE_DISABLED,
|
||||
network_anonymization_key_, SecureDnsPolicy::kDisable, SocketTag(),
|
||||
net_log_, server_socket_handle_.get(), io_callback_,
|
||||
proxy_info_, {}, PRIVACY_MODE_DISABLED, network_anonymization_key_,
|
||||
SecureDnsPolicy::kDisable, SocketTag(), net_log_,
|
||||
server_socket_handle_.get(), io_callback_,
|
||||
ClientSocketPool::ProxyAuthCallback());
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install jq
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
# https://github.com/marketplace/actions/git-changesets
|
||||
- id: changed_files
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user