mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-02-28 01:29:53 +00:00
Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84086a6e6c | ||
|
|
443200a51e | ||
|
|
aca0d97beb | ||
|
|
2605bf78f9 | ||
|
|
d2395fb43a | ||
|
|
e3d9a8e2fd | ||
|
|
1ae050ca3b | ||
|
|
7f38763e22 | ||
|
|
2a8831b0d0 | ||
|
|
cdf5e0c73e | ||
|
|
48f3ea8bc9 | ||
|
|
375e160368 | ||
|
|
b31664beeb | ||
|
|
7960bcae15 | ||
|
|
664ddb8d55 | ||
|
|
e4dfe09744 | ||
|
|
b56068ee1c | ||
|
|
99e888c829 | ||
|
|
873d0deeaa | ||
|
|
7e0a77c99c | ||
|
|
5f09db2655 | ||
|
|
10174d281c | ||
|
|
12c30acdda | ||
|
|
2790481709 | ||
|
|
182f60d424 | ||
|
|
930c70f065 | ||
|
|
fc61715e4e | ||
|
|
438be2d379 | ||
|
|
4e20ed65f2 | ||
|
|
ce760fcf19 | ||
|
|
0f76fdf4c5 | ||
|
|
03f4513f61 | ||
|
|
26f603057f | ||
|
|
b481eca4a4 | ||
|
|
eb028b65fc | ||
|
|
48c1b1cdb2 | ||
|
|
76e40baebc | ||
|
|
946b4025df | ||
|
|
089766b285 | ||
|
|
b643388539 | ||
|
|
0836ec6ee3 | ||
|
|
eeb2ad8dae | ||
|
|
71290b057f | ||
|
|
41b321dfe1 | ||
|
|
a18e99f966 | ||
|
|
f90d0b954c | ||
|
|
0408da2aee | ||
|
|
335d54e488 | ||
|
|
d11f9c895c | ||
|
|
e54ca7ceca | ||
|
|
ce82d49c25 | ||
|
|
8e6be1992b | ||
|
|
0e9102daae | ||
|
|
46dccf26d1 | ||
|
|
854c6a13c3 | ||
|
|
b4c3bbf660 | ||
|
|
6c726d6436 | ||
|
|
a0bdb861a9 | ||
|
|
eca5a27774 | ||
|
|
16d95df100 | ||
|
|
9b90719ddd | ||
|
|
7392529677 | ||
|
|
dc52c38179 | ||
|
|
d7999a32d3 | ||
|
|
b41ea05481 | ||
|
|
e6fe895190 | ||
|
|
adf553a958 | ||
|
|
2a915a5c94 | ||
|
|
1b0c72bfab | ||
|
|
e89af723cd | ||
|
|
e8fddd85af | ||
|
|
f04af734e3 | ||
|
|
00035302a1 | ||
|
|
578e659bb9 | ||
|
|
0f1baeb935 | ||
|
|
16ff9e815b |
40
.github/workflows/build.yml
vendored
40
.github/workflows/build.yml
vendored
@@ -80,6 +80,13 @@ jobs:
|
||||
- { goos: android, goarch: arm, ndk: armv7a-linux-androideabi34, output: armv7 }
|
||||
- { goos: android, goarch: arm64, ndk: aarch64-linux-android34, output: arm64-v8 }
|
||||
|
||||
# Go 1.24 with special patch can work on Windows 7
|
||||
# https://github.com/MetaCubeX/go/commits/release-branch.go1.24/
|
||||
- { goos: windows, goarch: '386', output: '386-go124', goversion: '1.24' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-v1-go124, goversion: '1.24' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v2, output: amd64-v2-go124, goversion: '1.24' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-v3-go124, goversion: '1.24' }
|
||||
|
||||
# Go 1.23 with special patch can work on Windows 7
|
||||
# https://github.com/MetaCubeX/go/commits/release-branch.go1.23/
|
||||
- { goos: windows, goarch: '386', output: '386-go123', goversion: '1.23' }
|
||||
@@ -108,6 +115,12 @@ jobs:
|
||||
- { goos: windows, goarch: amd64, goamd64: v2, output: amd64-v2-go120, goversion: '1.20' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-v3-go120, goversion: '1.20' }
|
||||
|
||||
# Go 1.24 is the last release that will run on macOS 11 Big Sur. Go 1.25 will require macOS 12 Monterey or later.
|
||||
- { goos: darwin, goarch: arm64, output: arm64-go124, goversion: '1.24' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-v1-go124, goversion: '1.24' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v2, output: amd64-v2-go124, goversion: '1.24' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-v3-go124, goversion: '1.24' }
|
||||
|
||||
# Go 1.22 is the last release that will run on macOS 10.15 Catalina. Go 1.23 will require macOS 11 Big Sur or later.
|
||||
- { goos: darwin, goarch: arm64, output: arm64-go122, goversion: '1.22' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-v1-go122, goversion: '1.22' }
|
||||
@@ -139,7 +152,7 @@ jobs:
|
||||
if: ${{ matrix.jobs.goversion == '' && matrix.jobs.abi != '1' }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24'
|
||||
go-version: '1.25'
|
||||
|
||||
- name: Set up Go
|
||||
if: ${{ matrix.jobs.goversion != '' && matrix.jobs.abi != '1' }}
|
||||
@@ -154,6 +167,25 @@ jobs:
|
||||
sudo tar zxf go1.24.0.linux-amd64-abi1.tar.gz -C /usr/local
|
||||
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.25.x
|
||||
# that means after golang1.26 release it must be changed
|
||||
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
|
||||
# revert:
|
||||
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
|
||||
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
||||
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||
- name: Revert Golang1.25 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/8cb5472d94c34b88733a81091bd328e70ee565a4.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/6788c4c6f9fafb56729bad6b660f7ee2272d699f.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/a5b2168bb836ed9d6601c626f95e56c07923f906.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/f56f1e23507e646c85243a71bde7b9629b2f970c.diff | patch --verbose -p 1
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.24.x
|
||||
# that means after golang1.25 release it must be changed
|
||||
@@ -164,8 +196,9 @@ jobs:
|
||||
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||
- name: Revert Golang1.24 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '' }}
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.24' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/7b1fd7d39c6be0185fbe1d929578ab372ac5c632.diff | patch --verbose -p 1
|
||||
@@ -184,6 +217,7 @@ jobs:
|
||||
- name: Revert Golang1.23 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.23' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1
|
||||
@@ -202,6 +236,7 @@ jobs:
|
||||
- name: Revert Golang1.22 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.22' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/9779155f18b6556a034f7bb79fb7fb2aad1e26a9.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/ef0606261340e608017860b423ffae5c1ce78239.diff | patch --verbose -p 1
|
||||
@@ -212,6 +247,7 @@ jobs:
|
||||
- name: Revert Golang1.21 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.21' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/golang/go/commit/9e43850a3298a9b8b1162ba0033d4c53f8637571.diff | patch --verbose -R -p 1
|
||||
|
||||
|
||||
24
.github/workflows/test.yml
vendored
24
.github/workflows/test.yml
vendored
@@ -24,6 +24,7 @@ jobs:
|
||||
- 'ubuntu-24.04-arm' # arm64 linux
|
||||
- 'macos-13' # amd64 macos
|
||||
go-version:
|
||||
- '1.25'
|
||||
- '1.24'
|
||||
- '1.23'
|
||||
- '1.22'
|
||||
@@ -47,6 +48,25 @@ jobs:
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.25.x
|
||||
# that means after golang1.26 release it must be changed
|
||||
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
|
||||
# revert:
|
||||
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
|
||||
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
||||
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||
- name: Revert Golang1.25 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.25' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/8cb5472d94c34b88733a81091bd328e70ee565a4.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/6788c4c6f9fafb56729bad6b660f7ee2272d699f.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/a5b2168bb836ed9d6601c626f95e56c07923f906.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/f56f1e23507e646c85243a71bde7b9629b2f970c.diff | patch --verbose -p 1
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.24.x
|
||||
# that means after golang1.25 release it must be changed
|
||||
@@ -59,6 +79,7 @@ jobs:
|
||||
- name: Revert Golang1.24 commit for Windows7/8
|
||||
if: ${{ runner.os == 'Windows' && matrix.go-version == '1.24' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/7b1fd7d39c6be0185fbe1d929578ab372ac5c632.diff | patch --verbose -p 1
|
||||
@@ -77,6 +98,7 @@ jobs:
|
||||
- name: Revert Golang1.23 commit for Windows7/8
|
||||
if: ${{ runner.os == 'Windows' && matrix.go-version == '1.23' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1
|
||||
@@ -95,6 +117,7 @@ jobs:
|
||||
- name: Revert Golang1.22 commit for Windows7/8
|
||||
if: ${{ runner.os == 'Windows' && matrix.go-version == '1.22' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/9779155f18b6556a034f7bb79fb7fb2aad1e26a9.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/ef0606261340e608017860b423ffae5c1ce78239.diff | patch --verbose -p 1
|
||||
@@ -105,6 +128,7 @@ jobs:
|
||||
- name: Revert Golang1.21 commit for Windows7/8
|
||||
if: ${{ runner.os == 'Windows' && matrix.go-version == '1.21' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/golang/go/commit/9e43850a3298a9b8b1162ba0033d4c53f8637571.diff | patch --verbose -R -p 1
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(option.ALPN) > 0 {
|
||||
if option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
|
||||
tlsConfig.NextProtos = option.ALPN
|
||||
} else {
|
||||
tlsConfig.NextProtos = []string{DefaultALPN}
|
||||
|
||||
@@ -153,7 +153,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(option.ALPN) > 0 {
|
||||
if option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
|
||||
tlsConfig.NextProtos = option.ALPN
|
||||
}
|
||||
|
||||
|
||||
@@ -28,15 +28,16 @@ type Mieru struct {
|
||||
|
||||
type MieruOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port,omitempty"`
|
||||
PortRange string `proxy:"port-range,omitempty"`
|
||||
Transport string `proxy:"transport"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
UserName string `proxy:"username"`
|
||||
Password string `proxy:"password"`
|
||||
Multiplexing string `proxy:"multiplexing,omitempty"`
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port,omitempty"`
|
||||
PortRange string `proxy:"port-range,omitempty"`
|
||||
Transport string `proxy:"transport"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
UserName string `proxy:"username"`
|
||||
Password string `proxy:"password"`
|
||||
Multiplexing string `proxy:"multiplexing,omitempty"`
|
||||
HandshakeMode string `proxy:"handshake-mode,omitempty"`
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
@@ -245,6 +246,9 @@ func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, erro
|
||||
Level: mierupb.MultiplexingLevel(multiplexing).Enum(),
|
||||
}
|
||||
}
|
||||
if handshakeMode, ok := mierupb.HandshakeMode_value[option.HandshakeMode]; ok {
|
||||
config.Profile.HandshakeMode = (*mierupb.HandshakeMode)(&handshakeMode)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
@@ -294,6 +298,11 @@ func validateMieruOption(option MieruOption) error {
|
||||
return fmt.Errorf("invalid multiplexing level: %s", option.Multiplexing)
|
||||
}
|
||||
}
|
||||
if option.HandshakeMode != "" {
|
||||
if _, ok := mierupb.HandshakeMode_value[option.HandshakeMode]; !ok {
|
||||
return fmt.Errorf("invalid handshake mode: %s", option.HandshakeMode)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
}
|
||||
|
||||
alpn := trojan.DefaultWebsocketALPN
|
||||
if len(t.option.ALPN) != 0 {
|
||||
if t.option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
|
||||
alpn = t.option.ALPN
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
// default tcp network
|
||||
// handle TLS
|
||||
alpn := trojan.DefaultALPN
|
||||
if len(t.option.ALPN) != 0 {
|
||||
if t.option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
|
||||
alpn = t.option.ALPN
|
||||
}
|
||||
c, err = vmess.StreamTLSConn(ctx, c, &vmess.TLSConfig{
|
||||
|
||||
@@ -3,14 +3,10 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/metacubex/mihomo/common/convert"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
@@ -23,6 +19,7 @@ import (
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/gun"
|
||||
"github.com/metacubex/mihomo/transport/vless"
|
||||
"github.com/metacubex/mihomo/transport/vless/encryption"
|
||||
"github.com/metacubex/mihomo/transport/vmess"
|
||||
|
||||
vmessSing "github.com/metacubex/sing-vmess"
|
||||
@@ -30,16 +27,13 @@ import (
|
||||
M "github.com/metacubex/sing/common/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
// max packet length
|
||||
maxLength = 1024 << 3
|
||||
)
|
||||
|
||||
type Vless struct {
|
||||
*Base
|
||||
client *vless.Client
|
||||
option *VlessOption
|
||||
|
||||
encryption *encryption.ClientInstance
|
||||
|
||||
// for gun mux
|
||||
gunTLSConfig *tls.Config
|
||||
gunConfig *gun.Config
|
||||
@@ -62,6 +56,7 @@ type VlessOption struct {
|
||||
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
||||
XUDP bool `proxy:"xudp,omitempty"`
|
||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
||||
Encryption string `proxy:"encryption,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||
@@ -173,6 +168,12 @@ func (v *Vless) streamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
done := N.SetupContextForConn(ctx, c)
|
||||
defer done(&err)
|
||||
}
|
||||
if v.encryption != nil {
|
||||
c, err = v.encryption.Handshake(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if metadata.NetWork == C.UDP {
|
||||
if v.option.PacketAddr {
|
||||
metadata = &C.Metadata{
|
||||
@@ -188,9 +189,6 @@ func (v *Vless) streamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
}
|
||||
}
|
||||
conn, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
|
||||
if v.option.PacketAddr {
|
||||
conn = packetaddr.NewBindConn(conn)
|
||||
}
|
||||
} else {
|
||||
conn, err = v.client.StreamConn(c, parseVlessAddr(metadata, false))
|
||||
}
|
||||
@@ -352,12 +350,11 @@ func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metada
|
||||
), v), nil
|
||||
} else if v.option.PacketAddr {
|
||||
return newPacketConn(N.NewThreadSafePacketConn(
|
||||
packetaddr.NewConn(&vlessPacketConn{
|
||||
Conn: c, rAddr: metadata.UDPAddr(),
|
||||
}, M.SocksaddrFromNet(metadata.UDPAddr())),
|
||||
packetaddr.NewConn(v.client.PacketConn(c, metadata.UDPAddr()),
|
||||
M.SocksaddrFromNet(metadata.UDPAddr())),
|
||||
), v), nil
|
||||
}
|
||||
return newPacketConn(N.NewThreadSafePacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}), v), nil
|
||||
return newPacketConn(N.NewThreadSafePacketConn(v.client.PacketConn(c, metadata.UDPAddr())), v), nil
|
||||
}
|
||||
|
||||
// SupportUOT implements C.ProxyAdapter
|
||||
@@ -408,98 +405,6 @@ func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
|
||||
}
|
||||
}
|
||||
|
||||
type vlessPacketConn struct {
|
||||
net.Conn
|
||||
rAddr net.Addr
|
||||
remain int
|
||||
mux sync.Mutex
|
||||
cache [2]byte
|
||||
}
|
||||
|
||||
func (c *vlessPacketConn) writePacket(payload []byte) (int, error) {
|
||||
binary.BigEndian.PutUint16(c.cache[:], uint16(len(payload)))
|
||||
|
||||
if _, err := c.Conn.Write(c.cache[:]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return c.Conn.Write(payload)
|
||||
}
|
||||
|
||||
func (c *vlessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
total := len(b)
|
||||
if total == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if total <= maxLength {
|
||||
return c.writePacket(b)
|
||||
}
|
||||
|
||||
offset := 0
|
||||
|
||||
for offset < total {
|
||||
cursor := offset + maxLength
|
||||
if cursor > total {
|
||||
cursor = total
|
||||
}
|
||||
|
||||
n, err := c.writePacket(b[offset:cursor])
|
||||
if err != nil {
|
||||
return offset + n, err
|
||||
}
|
||||
|
||||
offset = cursor
|
||||
if offset == total {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (c *vlessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
if c.remain > 0 {
|
||||
length := len(b)
|
||||
if c.remain < length {
|
||||
length = c.remain
|
||||
}
|
||||
|
||||
n, err := c.Conn.Read(b[:length])
|
||||
if err != nil {
|
||||
return 0, c.rAddr, err
|
||||
}
|
||||
|
||||
c.remain -= n
|
||||
return n, c.rAddr, nil
|
||||
}
|
||||
|
||||
if _, err := c.Conn.Read(b[:2]); err != nil {
|
||||
return 0, c.rAddr, err
|
||||
}
|
||||
|
||||
total := int(binary.BigEndian.Uint16(b[:2]))
|
||||
if total == 0 {
|
||||
return 0, c.rAddr, nil
|
||||
}
|
||||
|
||||
length := len(b)
|
||||
if length > total {
|
||||
length = total
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(c.Conn, b[:length]); err != nil {
|
||||
return 0, c.rAddr, errors.New("read packet error")
|
||||
}
|
||||
|
||||
c.remain = total - length
|
||||
|
||||
return length, c.rAddr, nil
|
||||
}
|
||||
|
||||
func NewVless(option VlessOption) (*Vless, error) {
|
||||
var addons *vless.Addons
|
||||
if option.Network != "ws" && len(option.Flow) >= 16 {
|
||||
@@ -547,6 +452,11 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
option: &option,
|
||||
}
|
||||
|
||||
v.encryption, err = encryption.NewClient(option.Encryption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v.realityConfig, err = v.option.RealityOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -87,15 +87,26 @@ type WireGuardPeerOption struct {
|
||||
}
|
||||
|
||||
type AmneziaWGOption struct {
|
||||
JC int `proxy:"jc"`
|
||||
JMin int `proxy:"jmin"`
|
||||
JMax int `proxy:"jmax"`
|
||||
S1 int `proxy:"s1"`
|
||||
S2 int `proxy:"s2"`
|
||||
H1 uint32 `proxy:"h1"`
|
||||
H2 uint32 `proxy:"h2"`
|
||||
H3 uint32 `proxy:"h3"`
|
||||
H4 uint32 `proxy:"h4"`
|
||||
JC int `proxy:"jc,omitempty"`
|
||||
JMin int `proxy:"jmin,omitempty"`
|
||||
JMax int `proxy:"jmax,omitempty"`
|
||||
S1 int `proxy:"s1,omitempty"`
|
||||
S2 int `proxy:"s2,omitempty"`
|
||||
H1 uint32 `proxy:"h1,omitempty"`
|
||||
H2 uint32 `proxy:"h2,omitempty"`
|
||||
H3 uint32 `proxy:"h3,omitempty"`
|
||||
H4 uint32 `proxy:"h4,omitempty"`
|
||||
|
||||
// AmneziaWG v1.5
|
||||
I1 string `proxy:"i1,omitempty"`
|
||||
I2 string `proxy:"i2,omitempty"`
|
||||
I3 string `proxy:"i3,omitempty"`
|
||||
I4 string `proxy:"i4,omitempty"`
|
||||
I5 string `proxy:"i5,omitempty"`
|
||||
J1 string `proxy:"j1,omitempty"`
|
||||
J2 string `proxy:"j2,omitempty"`
|
||||
J3 string `proxy:"j3,omitempty"`
|
||||
Itime int64 `proxy:"itime,omitempty"`
|
||||
}
|
||||
|
||||
type wgSingErrorHandler struct {
|
||||
@@ -386,15 +397,60 @@ func (w *WireGuard) genIpcConf(ctx context.Context, updateOnly bool) (string, er
|
||||
if !updateOnly {
|
||||
ipcConf += "private_key=" + w.option.PrivateKey + "\n"
|
||||
if w.option.AmneziaWGOption != nil {
|
||||
ipcConf += "jc=" + strconv.Itoa(w.option.AmneziaWGOption.JC) + "\n"
|
||||
ipcConf += "jmin=" + strconv.Itoa(w.option.AmneziaWGOption.JMin) + "\n"
|
||||
ipcConf += "jmax=" + strconv.Itoa(w.option.AmneziaWGOption.JMax) + "\n"
|
||||
ipcConf += "s1=" + strconv.Itoa(w.option.AmneziaWGOption.S1) + "\n"
|
||||
ipcConf += "s2=" + strconv.Itoa(w.option.AmneziaWGOption.S2) + "\n"
|
||||
ipcConf += "h1=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H1), 10) + "\n"
|
||||
ipcConf += "h2=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H2), 10) + "\n"
|
||||
ipcConf += "h3=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H3), 10) + "\n"
|
||||
ipcConf += "h4=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H4), 10) + "\n"
|
||||
if w.option.AmneziaWGOption.JC != 0 {
|
||||
ipcConf += "jc=" + strconv.Itoa(w.option.AmneziaWGOption.JC) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.JMin != 0 {
|
||||
ipcConf += "jmin=" + strconv.Itoa(w.option.AmneziaWGOption.JMin) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.JMax != 0 {
|
||||
ipcConf += "jmax=" + strconv.Itoa(w.option.AmneziaWGOption.JMax) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.S1 != 0 {
|
||||
ipcConf += "s1=" + strconv.Itoa(w.option.AmneziaWGOption.S1) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.S2 != 0 {
|
||||
ipcConf += "s2=" + strconv.Itoa(w.option.AmneziaWGOption.S2) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.H1 != 0 {
|
||||
ipcConf += "h1=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H1), 10) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.H2 != 0 {
|
||||
ipcConf += "h2=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H2), 10) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.H3 != 0 {
|
||||
ipcConf += "h3=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H3), 10) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.H4 != 0 {
|
||||
ipcConf += "h4=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H4), 10) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.I1 != "" {
|
||||
ipcConf += "i1=" + w.option.AmneziaWGOption.I1 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.I2 != "" {
|
||||
ipcConf += "i2=" + w.option.AmneziaWGOption.I2 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.I3 != "" {
|
||||
ipcConf += "i3=" + w.option.AmneziaWGOption.I3 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.I4 != "" {
|
||||
ipcConf += "i4=" + w.option.AmneziaWGOption.I4 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.I5 != "" {
|
||||
ipcConf += "i5=" + w.option.AmneziaWGOption.I5 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.J1 != "" {
|
||||
ipcConf += "j1=" + w.option.AmneziaWGOption.J1 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.J2 != "" {
|
||||
ipcConf += "j2=" + w.option.AmneziaWGOption.J2 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.J3 != "" {
|
||||
ipcConf += "j3=" + w.option.AmneziaWGOption.J3 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.Itime != 0 {
|
||||
ipcConf += "itime=" + strconv.FormatInt(int64(w.option.AmneziaWGOption.Itime), 10) + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(w.option.Peers) > 0 {
|
||||
|
||||
@@ -5,58 +5,50 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
func DefaultValue[T any]() T {
|
||||
var defaultValue T
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
type TypedValue[T any] struct {
|
||||
_ noCopy
|
||||
value atomic.Value
|
||||
value atomic.Pointer[T]
|
||||
}
|
||||
|
||||
// tValue is a struct with determined type to resolve atomic.Value usages with interface types
|
||||
// https://github.com/golang/go/issues/22550
|
||||
//
|
||||
// The intention to have an atomic value store for errors. However, running this code panics:
|
||||
// panic: sync/atomic: store of inconsistently typed value into Value
|
||||
// This is because atomic.Value requires that the underlying concrete type be the same (which is a reasonable expectation for its implementation).
|
||||
// When going through the atomic.Value.Store method call, the fact that both these are of the error interface is lost.
|
||||
type tValue[T any] struct {
|
||||
value T
|
||||
func (t *TypedValue[T]) Load() (v T) {
|
||||
v, _ = t.LoadOk()
|
||||
return
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) Load() T {
|
||||
value, _ := t.LoadOk()
|
||||
return value
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) LoadOk() (_ T, ok bool) {
|
||||
func (t *TypedValue[T]) LoadOk() (v T, ok bool) {
|
||||
value := t.value.Load()
|
||||
if value == nil {
|
||||
return DefaultValue[T](), false
|
||||
return
|
||||
}
|
||||
return value.(tValue[T]).value, true
|
||||
return *value, true
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) Store(value T) {
|
||||
t.value.Store(tValue[T]{value})
|
||||
t.value.Store(&value)
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) Swap(new T) T {
|
||||
old := t.value.Swap(tValue[T]{new})
|
||||
func (t *TypedValue[T]) Swap(new T) (v T) {
|
||||
old := t.value.Swap(&new)
|
||||
if old == nil {
|
||||
return DefaultValue[T]()
|
||||
return
|
||||
}
|
||||
return old.(tValue[T]).value
|
||||
return *old
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) CompareAndSwap(old, new T) bool {
|
||||
return t.value.CompareAndSwap(tValue[T]{old}, tValue[T]{new}) ||
|
||||
// In the edge-case where [atomic.Value.Store] is uninitialized
|
||||
// and trying to compare with the zero value of T,
|
||||
// then compare-and-swap with the nil any value.
|
||||
(any(old) == any(DefaultValue[T]()) && t.value.CompareAndSwap(any(nil), tValue[T]{new}))
|
||||
for {
|
||||
currentP := t.value.Load()
|
||||
var currentValue T
|
||||
if currentP != nil {
|
||||
currentValue = *currentP
|
||||
}
|
||||
// Compare old and current via runtime equality check.
|
||||
if any(currentValue) != any(old) {
|
||||
return false
|
||||
}
|
||||
if t.value.CompareAndSwap(currentP, &new) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) MarshalJSON() ([]byte, error) {
|
||||
@@ -89,9 +81,3 @@ func NewTypedValue[T any](t T) (v TypedValue[T]) {
|
||||
v.Store(t)
|
||||
return
|
||||
}
|
||||
|
||||
type noCopy struct{}
|
||||
|
||||
// Lock is a no-op used by -copylocks checker from `go vet`.
|
||||
func (*noCopy) Lock() {}
|
||||
func (*noCopy) Unlock() {}
|
||||
|
||||
@@ -7,20 +7,6 @@ import (
|
||||
)
|
||||
|
||||
func TestTypedValue(t *testing.T) {
|
||||
{
|
||||
// Always wrapping should not allocate for simple values
|
||||
// because tValue[T] has the same memory layout as T.
|
||||
var v TypedValue[bool]
|
||||
bools := []bool{true, false}
|
||||
if n := int(testing.AllocsPerRun(1000, func() {
|
||||
for _, b := range bools {
|
||||
v.Store(b)
|
||||
}
|
||||
})); n != 0 {
|
||||
t.Errorf("AllocsPerRun = %d, want 0", n)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var v TypedValue[int]
|
||||
got, gotOk := v.LoadOk()
|
||||
@@ -58,20 +44,126 @@ func TestTypedValue(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
e1, e2, e3 := io.EOF, &os.PathError{}, &os.PathError{}
|
||||
var v TypedValue[error]
|
||||
if v.CompareAndSwap(e1, e2) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != nil {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, nil)
|
||||
}
|
||||
if v.CompareAndSwap(nil, e1) != true {
|
||||
t.Fatalf("CompareAndSwap = false, want true")
|
||||
}
|
||||
if value := v.Load(); value != e1 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, e1)
|
||||
}
|
||||
if v.CompareAndSwap(e2, e3) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != e1 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, e1)
|
||||
}
|
||||
if v.CompareAndSwap(e1, e2) != true {
|
||||
t.Fatalf("CompareAndSwap = false, want true")
|
||||
}
|
||||
if value := v.Load(); value != e2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, e2)
|
||||
}
|
||||
if v.CompareAndSwap(e3, e2) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != e2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, e2)
|
||||
}
|
||||
if v.CompareAndSwap(nil, e3) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != e2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, e2)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
c1, c2, c3 := make(chan struct{}), make(chan struct{}), make(chan struct{})
|
||||
var v TypedValue[chan struct{}]
|
||||
if v.CompareAndSwap(c1, c2) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != nil {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, nil)
|
||||
}
|
||||
if v.CompareAndSwap(nil, c1) != true {
|
||||
t.Fatalf("CompareAndSwap = false, want true")
|
||||
}
|
||||
if value := v.Load(); value != c1 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c1)
|
||||
}
|
||||
if v.CompareAndSwap(c2, c3) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != c1 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c1)
|
||||
}
|
||||
if v.CompareAndSwap(c1, c2) != true {
|
||||
t.Fatalf("CompareAndSwap = false, want true")
|
||||
}
|
||||
if value := v.Load(); value != c2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c2)
|
||||
}
|
||||
if v.CompareAndSwap(c3, c2) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != c2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c2)
|
||||
}
|
||||
if v.CompareAndSwap(nil, c3) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != c2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c2)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
c1, c2, c3 := &io.LimitedReader{}, &io.SectionReader{}, &io.SectionReader{}
|
||||
var v TypedValue[io.Reader]
|
||||
if v.CompareAndSwap(c1, c2) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != nil {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, nil)
|
||||
}
|
||||
if v.CompareAndSwap(nil, c1) != true {
|
||||
t.Fatalf("CompareAndSwap = false, want true")
|
||||
}
|
||||
if value := v.Load(); value != c1 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c1)
|
||||
}
|
||||
if v.CompareAndSwap(c2, c3) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != c1 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c1)
|
||||
}
|
||||
if v.CompareAndSwap(c1, c2) != true {
|
||||
t.Fatalf("CompareAndSwap = false, want true")
|
||||
}
|
||||
if value := v.Load(); value != c2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c2)
|
||||
}
|
||||
if v.CompareAndSwap(c3, c2) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != c2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c2)
|
||||
}
|
||||
if v.CompareAndSwap(nil, c3) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != c2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ var NewPacket = buf.NewPacket
|
||||
var NewSize = buf.NewSize
|
||||
var With = buf.With
|
||||
var As = buf.As
|
||||
var ReleaseMulti = buf.ReleaseMulti
|
||||
|
||||
var (
|
||||
Must = common.Must
|
||||
|
||||
@@ -221,6 +221,9 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
if flow := query.Get("flow"); flow != "" {
|
||||
vless["flow"] = strings.ToLower(flow)
|
||||
}
|
||||
if encryption := query.Get("encryption"); encryption != "" {
|
||||
vless["encryption"] = encryption
|
||||
}
|
||||
proxies = append(proxies, vless)
|
||||
|
||||
case "vmess":
|
||||
|
||||
@@ -149,6 +149,10 @@ func (c *Conn) ReaderReplaceable() bool {
|
||||
return c.disablePipe.Load() || c.deadline.Load().IsZero()
|
||||
}
|
||||
|
||||
func (c *Conn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Conn) Upstream() any {
|
||||
return c.ExtendedConn
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ type ExtendedReader = network.ExtendedReader
|
||||
|
||||
var WriteBuffer = bufio.WriteBuffer
|
||||
|
||||
type ReadWaitOptions = network.ReadWaitOptions
|
||||
|
||||
var NewReadWaitOptions = network.NewReadWaitOptions
|
||||
|
||||
func NewDeadlineConn(conn net.Conn) ExtendedConn {
|
||||
if deadline.IsPipe(conn) || deadline.IsPipe(network.UnwrapReader(conn)) {
|
||||
return NewExtendedConn(conn) // pipe always have correctly deadline implement
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package generater
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
func Main(args []string) {
|
||||
if len(args) < 1 {
|
||||
panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair")
|
||||
}
|
||||
switch args[0] {
|
||||
case "uuid":
|
||||
newUUID, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(newUUID.String())
|
||||
case "reality-keypair":
|
||||
privateKey, err := GeneratePrivateKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
publicKey := privateKey.PublicKey()
|
||||
fmt.Println("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]))
|
||||
fmt.Println("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:]))
|
||||
case "wg-keypair":
|
||||
privateKey, err := GeneratePrivateKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("PrivateKey: " + privateKey.String())
|
||||
fmt.Println("PublicKey: " + privateKey.PublicKey().String())
|
||||
case "ech-keypair":
|
||||
if len(args) < 2 {
|
||||
panic("Using: generate ech-keypair <plain_server_name>")
|
||||
}
|
||||
configBase64, keyPem, err := ech.GenECHConfig(args[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Config:", configBase64)
|
||||
fmt.Println("Key:", keyPem)
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copy from https://github.com/WireGuard/wgctrl-go/blob/a9ab2273dd1075ea74b88c76f8757f8b4003fcbf/wgtypes/types.go#L71-L155
|
||||
|
||||
package generater
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
// KeyLen is the expected key length for a WireGuard key.
|
||||
const KeyLen = 32 // wgh.KeyLen
|
||||
|
||||
// A Key is a public, private, or pre-shared secret key. The Key constructor
|
||||
// functions in this package can be used to create Keys suitable for each of
|
||||
// these applications.
|
||||
type Key [KeyLen]byte
|
||||
|
||||
// GenerateKey generates a Key suitable for use as a pre-shared secret key from
|
||||
// a cryptographically safe source.
|
||||
//
|
||||
// The output Key should not be used as a private key; use GeneratePrivateKey
|
||||
// instead.
|
||||
func GenerateKey() (Key, error) {
|
||||
b := make([]byte, KeyLen)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return Key{}, fmt.Errorf("wgtypes: failed to read random bytes: %v", err)
|
||||
}
|
||||
|
||||
return NewKey(b)
|
||||
}
|
||||
|
||||
// GeneratePrivateKey generates a Key suitable for use as a private key from a
|
||||
// cryptographically safe source.
|
||||
func GeneratePrivateKey() (Key, error) {
|
||||
key, err := GenerateKey()
|
||||
if err != nil {
|
||||
return Key{}, err
|
||||
}
|
||||
|
||||
// Modify random bytes using algorithm described at:
|
||||
// https://cr.yp.to/ecdh.html.
|
||||
key[0] &= 248
|
||||
key[31] &= 127
|
||||
key[31] |= 64
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// NewKey creates a Key from an existing byte slice. The byte slice must be
|
||||
// exactly 32 bytes in length.
|
||||
func NewKey(b []byte) (Key, error) {
|
||||
if len(b) != KeyLen {
|
||||
return Key{}, fmt.Errorf("wgtypes: incorrect key size: %d", len(b))
|
||||
}
|
||||
|
||||
var k Key
|
||||
copy(k[:], b)
|
||||
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// ParseKey parses a Key from a base64-encoded string, as produced by the
|
||||
// Key.String method.
|
||||
func ParseKey(s string) (Key, error) {
|
||||
b, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return Key{}, fmt.Errorf("wgtypes: failed to parse base64-encoded key: %v", err)
|
||||
}
|
||||
|
||||
return NewKey(b)
|
||||
}
|
||||
|
||||
// PublicKey computes a public key from the private key k.
|
||||
//
|
||||
// PublicKey should only be called when k is a private key.
|
||||
func (k Key) PublicKey() Key {
|
||||
var (
|
||||
pub [KeyLen]byte
|
||||
priv = [KeyLen]byte(k)
|
||||
)
|
||||
|
||||
// ScalarBaseMult uses the correct base value per https://cr.yp.to/ecdh.html,
|
||||
// so no need to specify it.
|
||||
curve25519.ScalarBaseMult(&pub, &priv)
|
||||
|
||||
return Key(pub)
|
||||
}
|
||||
|
||||
// String returns the base64-encoded string representation of a Key.
|
||||
//
|
||||
// ParseKey can be used to produce a new Key from this string.
|
||||
func (k Key) String() string {
|
||||
return base64.StdEncoding.EncodeToString(k[:])
|
||||
}
|
||||
73
component/generator/cmd.go
Normal file
73
component/generator/cmd.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
"github.com/metacubex/mihomo/transport/vless/encryption"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
func Main(args []string) {
|
||||
if len(args) < 1 {
|
||||
panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair/vless-mlkem768/vless-x25519")
|
||||
}
|
||||
switch args[0] {
|
||||
case "uuid":
|
||||
newUUID, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(newUUID.String())
|
||||
case "reality-keypair":
|
||||
privateKey, err := GenX25519PrivateKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey.Bytes()))
|
||||
fmt.Println("PublicKey: " + base64.RawURLEncoding.EncodeToString(privateKey.PublicKey().Bytes()))
|
||||
case "wg-keypair":
|
||||
privateKey, err := GenX25519PrivateKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("PrivateKey: " + base64.StdEncoding.EncodeToString(privateKey.Bytes()))
|
||||
fmt.Println("PublicKey: " + base64.StdEncoding.EncodeToString(privateKey.PublicKey().Bytes()))
|
||||
case "ech-keypair":
|
||||
if len(args) < 2 {
|
||||
panic("Using: generate ech-keypair <plain_server_name>")
|
||||
}
|
||||
configBase64, keyPem, err := ech.GenECHConfig(args[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Config:", configBase64)
|
||||
fmt.Println("Key:", keyPem)
|
||||
case "vless-mlkem768":
|
||||
var seed string
|
||||
if len(args) > 1 {
|
||||
seed = args[1]
|
||||
}
|
||||
seedBase64, clientBase64, hash32Base64, err := encryption.GenMLKEM768(seed)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Seed: " + seedBase64)
|
||||
fmt.Println("Client: " + clientBase64)
|
||||
fmt.Println("Hash32: " + hash32Base64)
|
||||
case "vless-x25519":
|
||||
var privateKey string
|
||||
if len(args) > 1 {
|
||||
privateKey = args[1]
|
||||
}
|
||||
privateKeyBase64, passwordBase64, hash32Base64, err := encryption.GenX25519(privateKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("PrivateKey: " + privateKeyBase64)
|
||||
fmt.Println("Password: " + passwordBase64)
|
||||
fmt.Println("Hash32: " + hash32Base64)
|
||||
}
|
||||
}
|
||||
27
component/generator/x25519.go
Normal file
27
component/generator/x25519.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/rand"
|
||||
)
|
||||
|
||||
const X25519KeySize = 32
|
||||
|
||||
func GenX25519PrivateKey() (*ecdh.PrivateKey, error) {
|
||||
var privateKey [X25519KeySize]byte
|
||||
_, err := rand.Read(privateKey[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Avoid generating equivalent X25519 private keys
|
||||
// https://github.com/XTLS/Xray-core/pull/1747
|
||||
//
|
||||
// Modify random bytes using algorithm described at:
|
||||
// https://cr.yp.to/ecdh.html.
|
||||
privateKey[0] &= 248
|
||||
privateKey[31] &= 127
|
||||
privateKey[31] |= 64
|
||||
|
||||
return ecdh.X25519().NewPrivateKey(privateKey[:])
|
||||
}
|
||||
@@ -10,27 +10,27 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
keepAliveIdle = atomic.NewTypedValue[time.Duration](0 * time.Second)
|
||||
keepAliveInterval = atomic.NewTypedValue[time.Duration](0 * time.Second)
|
||||
keepAliveIdle = atomic.NewInt64(0)
|
||||
keepAliveInterval = atomic.NewInt64(0)
|
||||
disableKeepAlive = atomic.NewBool(false)
|
||||
|
||||
SetDisableKeepAliveCallback = utils.NewCallback[bool]()
|
||||
)
|
||||
|
||||
func SetKeepAliveIdle(t time.Duration) {
|
||||
keepAliveIdle.Store(t)
|
||||
keepAliveIdle.Store(int64(t))
|
||||
}
|
||||
|
||||
func SetKeepAliveInterval(t time.Duration) {
|
||||
keepAliveInterval.Store(t)
|
||||
keepAliveInterval.Store(int64(t))
|
||||
}
|
||||
|
||||
func KeepAliveIdle() time.Duration {
|
||||
return keepAliveIdle.Load()
|
||||
return time.Duration(keepAliveIdle.Load())
|
||||
}
|
||||
|
||||
func KeepAliveInterval() time.Duration {
|
||||
return keepAliveInterval.Load()
|
||||
return time.Duration(keepAliveInterval.Load())
|
||||
}
|
||||
|
||||
func SetDisableKeepAlive(disable bool) {
|
||||
|
||||
@@ -170,7 +170,7 @@ type realityVerifier struct {
|
||||
//var pOffset = utils.MustOK(reflect.TypeOf((*utls.Conn)(nil)).Elem().FieldByName("peerCertificates")).Offset
|
||||
|
||||
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
//log.Debugln("REALITY localAddr: %v\t is using X25519MLKEM768 for TLS' communication: %v", c.RemoteAddr(), c.HandshakeState.ServerHello.SelectedGroup == utls.X25519MLKEM768)
|
||||
log.Debugln("REALITY localAddr: %v is using X25519MLKEM768 for TLS' communication: %v", c.RemoteAddr(), c.HandshakeState.ServerHello.ServerShare.Group == utls.X25519MLKEM768)
|
||||
//p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
|
||||
//certs := *(*[]*x509.Certificate)(unsafe.Add(unsafe.Pointer(c.Conn), pOffset))
|
||||
certs := c.Conn.PeerCertificates()
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -33,6 +34,11 @@ const (
|
||||
MaxPackageFileSize = 32 * 1024 * 1024
|
||||
)
|
||||
|
||||
const (
|
||||
ReleaseChannel = "release"
|
||||
AlphaChannel = "alpha"
|
||||
)
|
||||
|
||||
// CoreUpdater is the mihomo updater.
|
||||
// modify from https://github.com/AdguardTeam/AdGuardHome/blob/595484e0b3fb4c457f9bb727a6b94faa78a66c5f/internal/updater/updater.go
|
||||
type CoreUpdater struct {
|
||||
@@ -69,20 +75,28 @@ func (u *CoreUpdater) CoreBaseName() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (u *CoreUpdater) Update(currentExePath string) (err error) {
|
||||
func (u *CoreUpdater) Update(currentExePath string, channel string, force bool) (err error) {
|
||||
u.mu.Lock()
|
||||
defer u.mu.Unlock()
|
||||
|
||||
_, err = os.Stat(currentExePath)
|
||||
info, err := os.Stat(currentExePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check currentExePath %q: %w", currentExePath, err)
|
||||
}
|
||||
|
||||
baseURL := baseAlphaURL
|
||||
versionURL := versionAlphaURL
|
||||
if !strings.HasPrefix(C.Version, "alpha") {
|
||||
switch strings.ToLower(channel) {
|
||||
case ReleaseChannel:
|
||||
baseURL = baseReleaseURL
|
||||
versionURL = versionReleaseURL
|
||||
case AlphaChannel:
|
||||
break
|
||||
default: // auto
|
||||
if !strings.HasPrefix(C.Version, "alpha") {
|
||||
baseURL = baseReleaseURL
|
||||
versionURL = versionReleaseURL
|
||||
}
|
||||
}
|
||||
|
||||
latestVersion, err := u.getLatestVersion(versionURL)
|
||||
@@ -91,7 +105,7 @@ func (u *CoreUpdater) Update(currentExePath string) (err error) {
|
||||
}
|
||||
log.Infoln("current version %s, latest version %s", C.Version, latestVersion)
|
||||
|
||||
if latestVersion == C.Version {
|
||||
if latestVersion == C.Version && !force {
|
||||
// don't change this output, some downstream dependencies on the upgrader's output fields
|
||||
return fmt.Errorf("update error: already using latest version %s", C.Version)
|
||||
}
|
||||
@@ -136,7 +150,7 @@ func (u *CoreUpdater) Update(currentExePath string) (err error) {
|
||||
return fmt.Errorf("downloading: %w", err)
|
||||
}
|
||||
|
||||
err = u.unpack(updateDir, packagePath)
|
||||
err = u.unpack(updateDir, packagePath, info.Mode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unpacking: %w", err)
|
||||
}
|
||||
@@ -146,7 +160,7 @@ func (u *CoreUpdater) Update(currentExePath string) (err error) {
|
||||
return fmt.Errorf("backuping: %w", err)
|
||||
}
|
||||
|
||||
err = u.replace(updateExePath, currentExePath)
|
||||
err = u.copyFile(updateExePath, currentExePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("replacing: %w", err)
|
||||
}
|
||||
@@ -230,16 +244,16 @@ func (u *CoreUpdater) download(updateDir, packagePath, packageURL string) (err e
|
||||
}
|
||||
|
||||
// unpack extracts the files from the downloaded archive.
|
||||
func (u *CoreUpdater) unpack(updateDir, packagePath string) error {
|
||||
func (u *CoreUpdater) unpack(updateDir, packagePath string, fileMode os.FileMode) error {
|
||||
log.Infoln("updater: unpacking package")
|
||||
if strings.HasSuffix(packagePath, ".zip") {
|
||||
_, err := u.zipFileUnpack(packagePath, updateDir)
|
||||
_, err := u.zipFileUnpack(packagePath, updateDir, fileMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf(".zip unpack failed: %w", err)
|
||||
}
|
||||
|
||||
} else if strings.HasSuffix(packagePath, ".gz") {
|
||||
_, err := u.gzFileUnpack(packagePath, updateDir)
|
||||
_, err := u.gzFileUnpack(packagePath, updateDir, fileMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf(".gz unpack failed: %w", err)
|
||||
}
|
||||
@@ -271,21 +285,6 @@ func (u *CoreUpdater) backup(currentExePath, backupExePath, backupDir string) (e
|
||||
return nil
|
||||
}
|
||||
|
||||
// replace moves the current executable with the updated one
|
||||
func (u *CoreUpdater) replace(updateExePath, currentExePath string) error {
|
||||
log.Infoln("replacing: %s to %s", updateExePath, currentExePath)
|
||||
|
||||
// Use copyFile to retain the original file attributes
|
||||
err := u.copyFile(updateExePath, currentExePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infoln("updater: copy: %s to %s", updateExePath, currentExePath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clean removes the temporary directory itself and all it's contents.
|
||||
func (u *CoreUpdater) clean(updateDir string) {
|
||||
_ = os.RemoveAll(updateDir)
|
||||
@@ -295,7 +294,7 @@ func (u *CoreUpdater) clean(updateDir string) {
|
||||
// Existing files are overwritten
|
||||
// All files are created inside outDir, subdirectories are not created
|
||||
// Return the output file name
|
||||
func (u *CoreUpdater) gzFileUnpack(gzfile, outDir string) (outputName string, err error) {
|
||||
func (u *CoreUpdater) gzFileUnpack(gzfile, outDir string, fileMode os.FileMode) (outputName string, err error) {
|
||||
f, err := os.Open(gzfile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("os.Open(): %w", err)
|
||||
@@ -330,7 +329,7 @@ func (u *CoreUpdater) gzFileUnpack(gzfile, outDir string) (outputName string, er
|
||||
outputName = filepath.Join(outDir, originalName)
|
||||
|
||||
// Create the output file
|
||||
wc, err := os.OpenFile(outputName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o755)
|
||||
wc, err := os.OpenFile(outputName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fileMode)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("os.OpenFile(%s): %w", outputName, err)
|
||||
}
|
||||
@@ -355,7 +354,7 @@ func (u *CoreUpdater) gzFileUnpack(gzfile, outDir string) (outputName string, er
|
||||
// Existing files are overwritten
|
||||
// All files are created inside 'outDir', subdirectories are not created
|
||||
// Return the output file name
|
||||
func (u *CoreUpdater) zipFileUnpack(zipfile, outDir string) (outputName string, err error) {
|
||||
func (u *CoreUpdater) zipFileUnpack(zipfile, outDir string, fileMode os.FileMode) (outputName string, err error) {
|
||||
zrc, err := zip.OpenReader(zipfile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("zip.OpenReader(): %w", err)
|
||||
@@ -394,7 +393,7 @@ func (u *CoreUpdater) zipFileUnpack(zipfile, outDir string) (outputName string,
|
||||
}
|
||||
|
||||
var wc io.WriteCloser
|
||||
wc, err = os.OpenFile(outputName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode())
|
||||
wc, err = os.OpenFile(outputName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fileMode)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("os.OpenFile(): %w", err)
|
||||
}
|
||||
@@ -437,7 +436,16 @@ func (u *CoreUpdater) copyFile(src, dst string) (err error) {
|
||||
// otherwise truncates it before writing, without changing permissions.
|
||||
wc, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("os.OpenFile(%s): %w", dst, err)
|
||||
// On some file system (such as Android's /data) maybe return error: "text file busy"
|
||||
// Let's delete the target file and recreate it
|
||||
err = os.Remove(dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("os.Remove(%s): %w", dst, err)
|
||||
}
|
||||
wc, err = os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("os.OpenFile(%s): %w", dst, err)
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
@@ -452,5 +460,13 @@ func (u *CoreUpdater) copyFile(src, dst string) (err error) {
|
||||
return fmt.Errorf("io.Copy(): %w", err)
|
||||
}
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
err = exec.Command("/usr/bin/codesign", "--sign", "-", dst).Run()
|
||||
if err != nil {
|
||||
log.Warnln("codesign failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Infoln("updater: copy: %s to %s", src, dst)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
// Package wildcard modified IGLOU-EU/go-wildcard to support:
|
||||
//
|
||||
// `*` matches zero or more characters
|
||||
// `?` matches exactly one character
|
||||
//
|
||||
// The original go-wildcard library used `.` to match exactly one character, and `?` to match zero or one character.
|
||||
// `.` is a valid delimiter in domain name matching and should not be used as a wildcard.
|
||||
// The `?` matching logic strictly matches only one character in most scenarios.
|
||||
// So, the `?` matching logic in the original go-wildcard library has been removed and its wildcard `.` has been replaced with `?`.
|
||||
package wildcard
|
||||
|
||||
// copy and modified from https://github.com/IGLOU-EU/go-wildcard/tree/ce22b7af48e487517a492d3727d9386492043e21
|
||||
@@ -16,12 +25,10 @@ func Match(pattern, s string) bool {
|
||||
}
|
||||
|
||||
func matchByString(pattern, s string) bool {
|
||||
var lastErotemeCluster byte
|
||||
var patternIndex, sIndex, lastStar, lastEroteme int
|
||||
var patternIndex, sIndex, lastStar int
|
||||
patternLen := len(pattern)
|
||||
sLen := len(s)
|
||||
star := -1
|
||||
eroteme := -1
|
||||
|
||||
Loop:
|
||||
if sIndex >= sLen {
|
||||
@@ -38,14 +45,8 @@ Loop:
|
||||
return false
|
||||
}
|
||||
switch pattern[patternIndex] {
|
||||
// Removed dot matching as it conflicts with dot in domains.
|
||||
// case '.':
|
||||
// It matches any single character. So, we don't need to check anything.
|
||||
case '?':
|
||||
// '?' matches one character. Store its position and match exactly one character in the string.
|
||||
eroteme = patternIndex
|
||||
lastEroteme = sIndex
|
||||
lastErotemeCluster = byte(s[sIndex])
|
||||
// It matches any single character. So, we don't need to check anything.
|
||||
case '*':
|
||||
// '*' matches zero or more characters. Store its position and increment the pattern index.
|
||||
star = patternIndex
|
||||
@@ -53,15 +54,8 @@ Loop:
|
||||
patternIndex++
|
||||
goto Loop
|
||||
default:
|
||||
// If the characters don't match, check if there was a previous '?' or '*' to backtrack.
|
||||
// If the characters don't match, check if there was a previous '*' to backtrack.
|
||||
if pattern[patternIndex] != s[sIndex] {
|
||||
if eroteme != -1 {
|
||||
patternIndex = eroteme + 1
|
||||
sIndex = lastEroteme
|
||||
eroteme = -1
|
||||
goto Loop
|
||||
}
|
||||
|
||||
if star != -1 {
|
||||
patternIndex = star + 1
|
||||
lastStar++
|
||||
@@ -71,29 +65,18 @@ Loop:
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// If the characters match, check if it was not the same to validate the eroteme.
|
||||
if eroteme != -1 && lastErotemeCluster != byte(s[sIndex]) {
|
||||
eroteme = -1
|
||||
}
|
||||
}
|
||||
|
||||
patternIndex++
|
||||
sIndex++
|
||||
goto Loop
|
||||
|
||||
// Check if the remaining pattern characters are '*' or '?', which can match the end of the string.
|
||||
// Check if the remaining pattern characters are '*', which can match the end of the string.
|
||||
checkPattern:
|
||||
if patternIndex < patternLen {
|
||||
if pattern[patternIndex] == '*' {
|
||||
patternIndex++
|
||||
goto checkPattern
|
||||
} else if pattern[patternIndex] == '?' {
|
||||
if sIndex >= sLen {
|
||||
sIndex--
|
||||
}
|
||||
patternIndex++
|
||||
goto checkPattern
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,31 +25,17 @@ func TestMatch(t *testing.T) {
|
||||
{"", "", true},
|
||||
{"", "*", true},
|
||||
{"", "**", true},
|
||||
{"", "?", true},
|
||||
{"", "??", true},
|
||||
{"", "?*", true},
|
||||
{"", "*?", true},
|
||||
{"", ".", false},
|
||||
{"", ".?", false},
|
||||
{"", "?.", false},
|
||||
{"", ".*", false},
|
||||
{"", "*.", false},
|
||||
{"", "*.?", false},
|
||||
{"", "?.*", false},
|
||||
{"", "?", false},
|
||||
{"", "?*", false},
|
||||
{"", "*?", false},
|
||||
|
||||
{"a", "", false},
|
||||
{"a", "a", true},
|
||||
{"a", "*", true},
|
||||
{"a", "**", true},
|
||||
{"a", "?", true},
|
||||
{"a", "??", true},
|
||||
{"a", ".", false},
|
||||
{"a", ".?", false},
|
||||
{"a", "?.", false},
|
||||
{"a", ".*", false},
|
||||
{"a", "*.", false},
|
||||
{"a", "*.?", false},
|
||||
{"a", "?.*", false},
|
||||
{"a", "?*", true},
|
||||
{"a", "*?", true},
|
||||
|
||||
{"match the exact string", "match the exact string", true},
|
||||
{"do not match a different string", "this is a different string", false},
|
||||
@@ -68,22 +54,27 @@ func TestMatch(t *testing.T) {
|
||||
|
||||
{"match a string with a ?", "match ? string with a ?", true},
|
||||
{"match a string with a ? at the beginning", "?atch a string with a ? at the beginning", true},
|
||||
{"match a string with two ?", "match a string with two ??", true},
|
||||
{"match a optional char with a ?", "match a optional? char with a ?", true},
|
||||
{"match a optional char with a ?", "match a optional? char with a ?", true},
|
||||
{"do not match a string with extra and a ?", "do not match ? string with extra and a ? like this", false},
|
||||
{"match a string with two ?", "match a ??ring with two ?", true},
|
||||
{"do not match a string with extra ?", "do not match a string with extra ??", false},
|
||||
|
||||
{"do not match a string with a .", "do not match . string with a .", false},
|
||||
{"do not match a string with a . at the beginning", "do not .atch a string with a . at the beginning", false},
|
||||
{"do not match a string with two .", "do not match a ..ring with two .", false},
|
||||
{"do not match a string with extra .", "do not match a string with extra ..", false},
|
||||
{"abc.edf.hjg", "abc.edf.hjg", true},
|
||||
{"abc.edf.hjg", "ab.cedf.hjg", false},
|
||||
{"abc.edf.hjg", "abc.edfh.jg", false},
|
||||
{"abc.edf.hjg", "abc.edf.hjq", false},
|
||||
|
||||
{"A big brown fox jumps over the lazy dog, with all there wildcards friends", ". big?brown fox jumps over * wildcard. friend??", false},
|
||||
{"A big brown fox fails to jump over the lazy dog, with all there wildcards friends", ". big?brown fox jumps over * wildcard. friend??", false},
|
||||
{"abc.edf.hjg", "abc.*.hjg", true},
|
||||
{"abc.edf.hjg", "abc.*.hjq", false},
|
||||
{"abc.edf.hjg", "abc*hjg", true},
|
||||
{"abc.edf.hjg", "abc*hjq", false},
|
||||
{"abc.edf.hjg", "a*g", true},
|
||||
{"abc.edf.hjg", "a*q", false},
|
||||
|
||||
{"domain a.b.c", "domain a.b.c", true},
|
||||
{"domain adb.c", "domain a.b.c", false},
|
||||
{"aaaa", "a*a", true},
|
||||
{"abc.edf.hjg", "ab?.edf.hjg", true},
|
||||
{"abc.edf.hjg", "?b?.edf.hjg", true},
|
||||
{"abc.edf.hjg", "??c.edf.hjg", true},
|
||||
{"abc.edf.hjg", "a??.edf.hjg", true},
|
||||
{"abc.edf.hjg", "ab??.edf.hjg", false},
|
||||
{"abc.edf.hjg", "??.edf.hjg", false},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
@@ -96,10 +87,106 @@ func TestMatch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func match(pattern, name string) bool { // https://research.swtch.com/glob
|
||||
px := 0
|
||||
nx := 0
|
||||
nextPx := 0
|
||||
nextNx := 0
|
||||
for px < len(pattern) || nx < len(name) {
|
||||
if px < len(pattern) {
|
||||
c := pattern[px]
|
||||
switch c {
|
||||
default: // ordinary character
|
||||
if nx < len(name) && name[nx] == c {
|
||||
px++
|
||||
nx++
|
||||
continue
|
||||
}
|
||||
case '?': // single-character wildcard
|
||||
if nx < len(name) {
|
||||
px++
|
||||
nx++
|
||||
continue
|
||||
}
|
||||
case '*': // zero-or-more-character wildcard
|
||||
// Try to match at nx.
|
||||
// If that doesn't work out,
|
||||
// restart at nx+1 next.
|
||||
nextPx = px
|
||||
nextNx = nx + 1
|
||||
px++
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Mismatch. Maybe restart.
|
||||
if 0 < nextNx && nextNx <= len(name) {
|
||||
px = nextPx
|
||||
nx = nextNx
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
// Matched all of pattern to all of name. Success.
|
||||
return true
|
||||
}
|
||||
|
||||
func FuzzMatch(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, s string) {
|
||||
if !Match(string(s), string(s)) {
|
||||
t.Fatalf("%s does not match %s", s, s)
|
||||
f.Fuzz(func(t *testing.T, pattern, name string) {
|
||||
result1 := Match(pattern, name)
|
||||
result2 := match(pattern, name)
|
||||
if result1 != result2 {
|
||||
t.Fatalf("Match failed for pattern `%s` and name `%s`", pattern, name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkMatch(b *testing.B) {
|
||||
cases := []struct {
|
||||
s string
|
||||
pattern string
|
||||
result bool
|
||||
}{
|
||||
{"abc.edf.hjg", "abc.edf.hjg", true},
|
||||
{"abc.edf.hjg", "ab.cedf.hjg", false},
|
||||
{"abc.edf.hjg", "abc.edfh.jg", false},
|
||||
{"abc.edf.hjg", "abc.edf.hjq", false},
|
||||
|
||||
{"abc.edf.hjg", "abc.*.hjg", true},
|
||||
{"abc.edf.hjg", "abc.*.hjq", false},
|
||||
{"abc.edf.hjg", "abc*hjg", true},
|
||||
{"abc.edf.hjg", "abc*hjq", false},
|
||||
{"abc.edf.hjg", "a*g", true},
|
||||
{"abc.edf.hjg", "a*q", false},
|
||||
|
||||
{"abc.edf.hjg", "ab?.edf.hjg", true},
|
||||
{"abc.edf.hjg", "?b?.edf.hjg", true},
|
||||
{"abc.edf.hjg", "??c.edf.hjg", true},
|
||||
{"abc.edf.hjg", "a??.edf.hjg", true},
|
||||
{"abc.edf.hjg", "ab??.edf.hjg", false},
|
||||
{"abc.edf.hjg", "??.edf.hjg", false},
|
||||
|
||||
{"r4.cdn-aa-wow-this-is-long-a1.video-yajusenpai1145141919810-oh-hell-yeah-this-is-also-very-long-and-sukka-the-fox-has-a-very-big-fluffy-fox-tail-ao-wu-ao-wu-regex-and-wildcard-both-might-have-deadly-back-tracing-issue-be-careful-or-use-linear-matching.com", "*.cdn-*-*.video**.com", true},
|
||||
}
|
||||
|
||||
b.Run("Match", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, c := range cases {
|
||||
result := Match(c.pattern, c.s)
|
||||
if c.result != result {
|
||||
b.Errorf("Test %d: Expected `%v`, found `%v`; With Pattern: `%s` and String: `%s`", i+1, c.result, result, c.pattern, c.s)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("match", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, c := range cases {
|
||||
result := match(c.pattern, c.s)
|
||||
if c.result != result {
|
||||
b.Errorf("Test %d: Expected `%v`, found `%v`; With Pattern: `%s` and String: `%s`", i+1, c.result, result, c.pattern, c.s)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -81,8 +81,8 @@ func (h *ResolverEnhancer) InsertHostByIP(ip netip.Addr, host string) {
|
||||
}
|
||||
|
||||
func (h *ResolverEnhancer) FlushFakeIP() error {
|
||||
if h.fakePool != nil {
|
||||
return h.fakePool.FlushFakeIP()
|
||||
if pool := h.fakePool; pool != nil {
|
||||
return pool.FlushFakeIP()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -632,6 +632,21 @@ proxies: # socks5
|
||||
# fingerprint: xxxx
|
||||
# skip-cert-verify: true
|
||||
|
||||
- name: "vless-encryption"
|
||||
type: vless
|
||||
server: server
|
||||
port: 443
|
||||
uuid: uuid
|
||||
network: tcp
|
||||
# -------------------------
|
||||
# vless encryption客户端配置:
|
||||
# (native/xorpub 的 XTLS 可以 Splice。只使用 1-RTT 模式 / 若服务端发的 ticket 中秒数不为零则 0-RTT 复用)
|
||||
# / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号
|
||||
# -------------------------
|
||||
encryption: "mlkem768x25519plus.native/xorpub/random.1rtt/0rtt.(X25519 Password).(ML-KEM-768 Client)..."
|
||||
tls: false #可以不开启tls
|
||||
udp: true
|
||||
|
||||
- name: "vless-reality-vision"
|
||||
type: vless
|
||||
server: server
|
||||
@@ -846,6 +861,16 @@ proxies: # socks5
|
||||
# h2: 67543
|
||||
# h4: 32345
|
||||
# h3: 123123
|
||||
# # AmneziaWG v1.5
|
||||
# i1: <b 0xf6ab3267fa><c><b 0xf6ab><t><r 10><wt 10>
|
||||
# i2: <b 0xf6ab3267fa><r 100>
|
||||
# i3: ""
|
||||
# i4: ""
|
||||
# i5: ""
|
||||
# j1: <b 0xffffffff><c><b 0xf6ab><t><r 10>
|
||||
# j2: <c><b 0xf6ab><t><wt 1000>
|
||||
# j3: <t><b 0xf6ab><c><r 10>
|
||||
# itime: 60
|
||||
|
||||
# tuic
|
||||
- name: tuic
|
||||
@@ -922,6 +947,8 @@ proxies: # socks5
|
||||
password: password
|
||||
# 可以使用的值包括 MULTIPLEXING_OFF, MULTIPLEXING_LOW, MULTIPLEXING_MIDDLE, MULTIPLEXING_HIGH。其中 MULTIPLEXING_OFF 会关闭多路复用功能。默认值为 MULTIPLEXING_LOW。
|
||||
# multiplexing: MULTIPLEXING_LOW
|
||||
# 如果想开启 0-RTT 握手,请设置为 HANDSHAKE_NO_WAIT,否则请设置为 HANDSHAKE_STANDARD。默认值为 HANDSHAKE_STANDARD
|
||||
# handshake-mode: HANDSHAKE_STANDARD
|
||||
|
||||
# anytls
|
||||
- name: anytls
|
||||
@@ -1336,6 +1363,12 @@ listeners:
|
||||
flow: xtls-rprx-vision
|
||||
# ws-path: "/" # 如果不为空则开启 websocket 传输层
|
||||
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
|
||||
# -------------------------
|
||||
# vless encryption服务端配置:
|
||||
# (原生外观 / 只 XOR 公钥 / 全随机数。只允许 1-RTT 模式 / 同时允许 1-RTT 模式与 600 秒复用的 0-RTT 模式)
|
||||
# / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号
|
||||
# -------------------------
|
||||
# decryption: "mlkem768x25519plus.native/xorpub/random.1rtt/600s.(X25519 PrivateKey).(ML-KEM-768 Seed)..."
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
@@ -1364,7 +1397,7 @@ listeners:
|
||||
after-bytes: 0 # 传输指定字节后开始限速
|
||||
bytes-per-sec: 0 # 基准速率(字节/秒)
|
||||
burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
|
||||
### 注意,对于vless listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 的其中一项 ###
|
||||
### 注意,对于vless listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 或 “decryption” 的其中一项 ###
|
||||
|
||||
- name: anytls-in-1
|
||||
type: anytls
|
||||
@@ -1462,6 +1495,7 @@ listeners:
|
||||
# masquerade: http://127.0.0.1:8080 #作为反向代理
|
||||
# masquerade: https://127.0.0.1:8080 #作为反向代理
|
||||
|
||||
# 注意,listeners中的tun仅提供给高级用户使用,普通用户应使用顶层配置中的tun
|
||||
- name: tun-in-1
|
||||
type: tun
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
|
||||
37
go.mod
37
go.mod
@@ -3,40 +3,40 @@ module github.com/metacubex/mihomo
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/3andne/restls-client-go v0.1.6
|
||||
github.com/bahlo/generic-list-go v0.2.0
|
||||
github.com/coreos/go-iptables v0.8.0
|
||||
github.com/dlclark/regexp2 v1.11.5
|
||||
github.com/enfein/mieru/v3 v3.16.1
|
||||
github.com/go-chi/chi/v5 v5.2.2
|
||||
github.com/enfein/mieru/v3 v3.19.1
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/gobwas/ws v1.4.0
|
||||
github.com/gofrs/uuid/v5 v5.3.2
|
||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
|
||||
github.com/klauspost/compress v1.17.9 // lastest version compatible with golang1.20
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a
|
||||
github.com/metacubex/bart v0.20.5
|
||||
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b
|
||||
github.com/metacubex/blake3 v0.1.0
|
||||
github.com/metacubex/chacha v0.1.5
|
||||
github.com/metacubex/fswatch v0.1.1
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||
github.com/metacubex/quic-go v0.53.1-0.20250628094454-fda5262d1d9c
|
||||
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295
|
||||
github.com/metacubex/randv2 v0.2.0
|
||||
github.com/metacubex/sing v0.5.4
|
||||
github.com/metacubex/sing-mux v0.3.2
|
||||
github.com/metacubex/restls-client-go v0.1.7
|
||||
github.com/metacubex/sing v0.5.5
|
||||
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac
|
||||
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb
|
||||
github.com/metacubex/sing-shadowsocks v0.2.11
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.5
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.6
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
|
||||
github.com/metacubex/sing-tun v0.4.7-0.20250721020617-8e7c37ed3d97
|
||||
github.com/metacubex/sing-vmess v0.2.3
|
||||
github.com/metacubex/sing-tun v0.4.7
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
|
||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee
|
||||
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4
|
||||
github.com/metacubex/utls v1.8.0
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
|
||||
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617
|
||||
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f
|
||||
github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20
|
||||
github.com/mroth/weightedrand/v2 v2.1.0
|
||||
github.com/openacid/low v0.1.21
|
||||
@@ -46,7 +46,7 @@ require (
|
||||
github.com/samber/lo v1.51.0
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 // lastest version compatible with golang1.20
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/stretchr/testify v1.11.0
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7
|
||||
@@ -59,7 +59,6 @@ require (
|
||||
golang.org/x/sys v0.30.0 // lastest version compatible with golang1.20
|
||||
google.golang.org/protobuf v1.34.2 // lastest version compatible with golang1.20
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
lukechampine.com/blake3 v1.3.0 // lastest version compatible with golang1.20
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -69,7 +68,7 @@ require (
|
||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/ebitengine/purego v0.8.3 // indirect
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
||||
@@ -85,11 +84,11 @@ require (
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/metacubex/ascon v0.1.0 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b // indirect
|
||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
||||
|
||||
74
go.sum
74
go.sum
@@ -1,5 +1,3 @@
|
||||
github.com/3andne/restls-client-go v0.1.6 h1:tRx/YilqW7iHpgmEL4E1D8dAsuB0tFF3uvncS+B6I08=
|
||||
github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6bFRNg0BWlP4orEY=
|
||||
github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg=
|
||||
github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM=
|
||||
github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss=
|
||||
@@ -25,10 +23,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
|
||||
github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/enfein/mieru/v3 v3.16.1 h1:CfIt1pQCCQbohkw+HBD2o8V9tnhZvB5yuXGGQIXTLOs=
|
||||
github.com/enfein/mieru/v3 v3.16.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/enfein/mieru/v3 v3.19.1 h1:19b9kgFC7oJXX9RLEO5Pi1gO6yek5cWlpK7IJVUoE8I=
|
||||
github.com/enfein/mieru/v3 v3.19.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
|
||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
||||
@@ -42,8 +40,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
@@ -81,27 +79,27 @@ github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtL
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a h1:c1QSGpacSeQdBdWcEKZKGuWLcqIG2wxHEygAcXuDwS4=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY=
|
||||
github.com/metacubex/ascon v0.1.0 h1:6ZWxmXYszT1XXtwkf6nxfFhc/OTtQ9R3Vyj1jN32lGM=
|
||||
github.com/metacubex/ascon v0.1.0/go.mod h1:eV5oim4cVPPdEL8/EYaTZ0iIKARH9pnhAK/fcT5Kacc=
|
||||
github.com/metacubex/bart v0.20.5 h1:XkgLZ17QxfxkqKdGsojoM2Zu01mmHyyQSFzt2/calTM=
|
||||
github.com/metacubex/bart v0.20.5/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
|
||||
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b h1:j7dadXD8I2KTmMt8jg1JcaP1ANL3JEObJPdANKcSYPY=
|
||||
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b/go.mod h1:+WmP0VJZDkDszvpa83HzfUp6QzARl/IKkMorH4+nODw=
|
||||
github.com/metacubex/blake3 v0.1.0 h1:KGnjh/56REO7U+cgZA8dnBhxdP7jByrG7hTP+bu6cqY=
|
||||
github.com/metacubex/blake3 v0.1.0/go.mod h1:CCkLdzFrqf7xmxCdhQFvJsRRV2mwOLDoSPg6vUTB9Uk=
|
||||
github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M=
|
||||
github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||
github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
|
||||
@@ -112,37 +110,39 @@ github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
|
||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
|
||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
|
||||
github.com/metacubex/quic-go v0.53.1-0.20250628094454-fda5262d1d9c h1:ABQzmOaZddM3q0OYeoZEc0XF+KW+dUdPNvY/c5rsunI=
|
||||
github.com/metacubex/quic-go v0.53.1-0.20250628094454-fda5262d1d9c/go.mod h1:eWlAK3zsKI0P8UhYpXlIsl3mtW4D6MpMNuYLIu8CKWI=
|
||||
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 h1:8JVlYuE8uSJAvmyCd4TjvDxs57xjb0WxEoaWafK5+qs=
|
||||
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295/go.mod h1:1lktQFtCD17FZliVypbrDHwbsFSsmz2xz2TRXydvB5c=
|
||||
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
||||
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
||||
github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k=
|
||||
github.com/metacubex/restls-client-go v0.1.7/go.mod h1:BN/U52vPw7j8VTSh2vleD/MnmVKCov84mS5VcjVHH4g=
|
||||
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing v0.5.4 h1:a4kAOZmF+OXosbzPEcrSc5QD35/ex+MNuZsrcuWskHk=
|
||||
github.com/metacubex/sing v0.5.4/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw=
|
||||
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
|
||||
github.com/metacubex/sing v0.5.5 h1:m5U8iHvRAUxlme3FZlE/LPIGHjU8oMCUzXWGbQQAC1E=
|
||||
github.com/metacubex/sing v0.5.5/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac h1:wDH/Jh/yqWbzPktqJP+Y1cUG8hchcrzKzUxJiSpnaQs=
|
||||
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb h1:U/m3h8lp/j7i8zFgfvScLdZa1/Y8dd74oO7iZaQq80s=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb/go.mod h1:B60FxaPHjR1SeQB0IiLrgwgvKsaoASfOWdiqhLjmMGA=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.11 h1:p2NGNOdF95e6XvdDKipLj1FRRqR8dnbfC/7pw2CCTlw=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.11/go.mod h1:bT1PCTV316zFnlToRMk5zt9HmIQYRBveiT71mplYPfc=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.5 h1:MnPn0hbcDkSJt6TlpI15XImHKK6IqaOwBUGPKyMnJnE=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.5/go.mod h1:Zyh+rAQRyevYfG/COCvDs1c/YMhGqCuknn7QrGmoQIw=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.6 h1:ZR1kYT0f0Vi64iQSS09OdhFfppiNkh7kjgRdMm0SB98=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.6/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.7-0.20250721020617-8e7c37ed3d97 h1:YYpc60UZE2G0pUeHbRw9erDrUDZrPQy8QzWFqA3kHsk=
|
||||
github.com/metacubex/sing-tun v0.4.7-0.20250721020617-8e7c37ed3d97/go.mod h1:2YywXPWW8Z97kTH7RffOeykKzU+l0aiKlglWV1PAS64=
|
||||
github.com/metacubex/sing-vmess v0.2.3 h1:QKLdIk5A2FcR3Y7m2/JO1XhfzgDA8tF4W9/ffsH9opo=
|
||||
github.com/metacubex/sing-vmess v0.2.3/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
|
||||
github.com/metacubex/sing-tun v0.4.7 h1:ZDY/W+1c7PeWWKeKRyUo18fySF/TWjB0i5ui81Ar778=
|
||||
github.com/metacubex/sing-tun v0.4.7/go.mod h1:xHecZRwBnKWe6zG9amAK9cXf91lF6blgjBqm+VvOrmU=
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0 h1:WZepq4TOZa6WewB8tGAZrrL+bL2R2ivoBzuEgAeolWc=
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
|
||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=
|
||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.8.0 h1:mSYi6FMnmc5riARl5UZDmWVy710z+P5b7xuGW0lV9ac=
|
||||
github.com/metacubex/utls v1.8.0/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617 h1:yN3mQ4cT9sPUciw/rO0Isc/8QlO86DB6g9SEMRgQ8Cw=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142 h1:csEbKOzRAxJXffOeZnnS3/kA/F55JiTbKv5jcYqCXms=
|
||||
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142/go.mod h1:67I3skhEY4Sya8f1YxELwWPoeQdXqZCrWNYLvq8gn2U=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4=
|
||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
||||
@@ -196,8 +196,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
|
||||
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
@@ -279,5 +279,3 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
|
||||
@@ -32,7 +32,11 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = updater.DefaultCoreUpdater.Update(execPath)
|
||||
query := r.URL.Query()
|
||||
channel := query.Get("channel")
|
||||
force := query.Get("force") == "true"
|
||||
|
||||
err = updater.DefaultCoreUpdater.Update(execPath, channel, force)
|
||||
if err != nil {
|
||||
log.Warnln("%s", err)
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
@@ -31,7 +31,7 @@ type Listener struct {
|
||||
listeners []net.Listener
|
||||
tlsConfig *tlsC.Config
|
||||
userMap map[[32]byte]string
|
||||
padding atomic.TypedValue[*padding.PaddingFactory]
|
||||
padding atomic.Pointer[padding.PaddingFactory]
|
||||
}
|
||||
|
||||
func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition) (sl *Listener, err error) {
|
||||
|
||||
@@ -17,6 +17,7 @@ type VlessServer struct {
|
||||
Enable bool
|
||||
Listen string
|
||||
Users []VlessUser
|
||||
Decryption string
|
||||
WsPath string
|
||||
GrpcServiceName string
|
||||
Certificate string
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package inbound_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
@@ -10,16 +11,18 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/common/pool"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
"github.com/metacubex/mihomo/component/generater"
|
||||
"github.com/metacubex/mihomo/component/generator"
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
@@ -30,7 +33,7 @@ import (
|
||||
)
|
||||
|
||||
var httpPath = "/inbound_test"
|
||||
var httpData = make([]byte, 10240)
|
||||
var httpData = make([]byte, 2*pool.RelayBufferSize)
|
||||
var remoteAddr = netip.MustParseAddr("1.2.3.4")
|
||||
var userUUID = utils.NewUUIDV4().String()
|
||||
var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = ca.NewRandomTLSKeyPair(ca.KeyPairTypeP256)
|
||||
@@ -46,13 +49,12 @@ var echConfigBase64, echKeyPem, _ = ech.GenECHConfig(echPublicSni)
|
||||
|
||||
func init() {
|
||||
rand.Read(httpData)
|
||||
privateKey, err := generater.GeneratePrivateKey()
|
||||
privateKey, err := generator.GenX25519PrivateKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
publicKey := privateKey.PublicKey()
|
||||
realityPrivateKey = base64.RawURLEncoding.EncodeToString(privateKey[:])
|
||||
realityPublickey = base64.RawURLEncoding.EncodeToString(publicKey[:])
|
||||
realityPrivateKey = base64.RawURLEncoding.EncodeToString(privateKey.Bytes())
|
||||
realityPublickey = base64.RawURLEncoding.EncodeToString(privateKey.PublicKey().Bytes())
|
||||
}
|
||||
|
||||
type TestTunnel struct {
|
||||
@@ -134,14 +136,22 @@ func NewHttpTestTunnel() *TestTunnel {
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Get(httpPath, func(w http.ResponseWriter, r *http.Request) {
|
||||
render.Data(w, r, httpData)
|
||||
query := r.URL.Query()
|
||||
size, err := strconv.Atoi(query.Get("size"))
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.PlainText(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
io.Copy(io.Discard, r.Body)
|
||||
render.Data(w, r, httpData[:size])
|
||||
})
|
||||
h2Server := &http2.Server{}
|
||||
server := http.Server{Handler: r}
|
||||
_ = http2.ConfigureServer(&server, h2Server)
|
||||
go server.Serve(ln)
|
||||
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string) {
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, remoteAddr, httpPath), nil)
|
||||
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string, size int) {
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s?size=%d", proto, remoteAddr, httpPath, size), bytes.NewReader(httpData[:size]))
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
@@ -200,7 +210,7 @@ func NewHttpTestTunnel() *TestTunnel {
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, httpData, data)
|
||||
assert.Equal(t, httpData[:size], data)
|
||||
}
|
||||
tunnel := &TestTunnel{
|
||||
HandleTCPConnFn: func(conn net.Conn, metadata *C.Metadata) {
|
||||
@@ -241,27 +251,30 @@ func NewHttpTestTunnel() *TestTunnel {
|
||||
},
|
||||
CloseFn: ln.Close,
|
||||
DoTestFn: func(t *testing.T, proxy C.ProxyAdapter) {
|
||||
|
||||
// Sequential testing for debugging
|
||||
t.Run("Sequential", func(t *testing.T) {
|
||||
testFn(t, proxy, "http")
|
||||
testFn(t, proxy, "https")
|
||||
testFn(t, proxy, "http", len(httpData))
|
||||
testFn(t, proxy, "https", len(httpData))
|
||||
})
|
||||
|
||||
// Concurrent testing to detect stress
|
||||
t.Run("Concurrent", func(t *testing.T) {
|
||||
wg := sync.WaitGroup{}
|
||||
const num = 50
|
||||
for i := 0; i < num; i++ {
|
||||
num := len(httpData) / 1024
|
||||
for i := 1; i <= num; i++ {
|
||||
i := i
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
testFn(t, proxy, "https")
|
||||
testFn(t, proxy, "https", i*1024)
|
||||
defer wg.Done()
|
||||
}()
|
||||
}
|
||||
for i := 0; i < num; i++ {
|
||||
for i := 1; i <= num; i++ {
|
||||
i := i
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
testFn(t, proxy, "http")
|
||||
testFn(t, proxy, "http", i*1024)
|
||||
defer wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var singMuxProtocolList = []string{"h2mux", "smux"} // don't test "yamux" because it has some confused bugs
|
||||
var singMuxProtocolList = []string{"smux"} // don't test "h2mux" and "yamux" because it has some confused bugs
|
||||
|
||||
// notCloseProxyAdapter is a proxy adapter that does not close the underlying outbound.ProxyAdapter.
|
||||
// The outbound.SingMux will close the underlying outbound.ProxyAdapter when it is closed, but we don't want to close it.
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
type VlessOption struct {
|
||||
BaseOption
|
||||
Users []VlessUser `inbound:"users"`
|
||||
Decryption string `inbound:"decryption,omitempty"`
|
||||
WsPath string `inbound:"ws-path,omitempty"`
|
||||
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
|
||||
Certificate string `inbound:"certificate,omitempty"`
|
||||
@@ -58,6 +59,7 @@ func NewVless(options *VlessOption) (*Vless, error) {
|
||||
Enable: true,
|
||||
Listen: base.RawAddress(),
|
||||
Users: users,
|
||||
Decryption: options.Decryption,
|
||||
WsPath: options.WsPath,
|
||||
GrpcServiceName: options.GrpcServiceName,
|
||||
Certificate: options.Certificate,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/outbound"
|
||||
"github.com/metacubex/mihomo/listener/inbound"
|
||||
"github.com/metacubex/mihomo/transport/vless/encryption"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -87,6 +88,41 @@ func TestInboundVless_TLS(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestInboundVless_Encryption(t *testing.T) {
|
||||
seedBase64, clientBase64, _, err := encryption.GenMLKEM768("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
privateKeyBase64, passwordBase64, _, err := encryption.GenX25519("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
var modes = []string{
|
||||
"native",
|
||||
"xorpub",
|
||||
"random",
|
||||
}
|
||||
for i := range modes {
|
||||
mode := modes[i]
|
||||
t.Run(mode, func(t *testing.T) {
|
||||
inboundOptions := inbound.VlessOption{
|
||||
Decryption: "mlkem768x25519plus." + mode + ".600s." + privateKeyBase64 + "." + seedBase64,
|
||||
}
|
||||
outboundOptions := outbound.VlessOption{
|
||||
Encryption: "mlkem768x25519plus." + mode + ".0rtt." + passwordBase64 + "." + clientBase64,
|
||||
}
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||
outboundOptions := outboundOptions
|
||||
outboundOptions.Flow = "xtls-rprx-vision"
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInboundVless_Wss1(t *testing.T) {
|
||||
inboundOptions := inbound.VlessOption{
|
||||
Certificate: tlsCertificate,
|
||||
|
||||
@@ -113,3 +113,11 @@ func (c realityConnWrapper) Upstream() any {
|
||||
func (c realityConnWrapper) CloseWrite() error {
|
||||
return c.Close()
|
||||
}
|
||||
|
||||
func (c realityConnWrapper) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c realityConnWrapper) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
mux "github.com/metacubex/sing-mux"
|
||||
vmess "github.com/metacubex/sing-vmess"
|
||||
"github.com/metacubex/sing-vmess/packetaddr"
|
||||
"github.com/metacubex/sing/common"
|
||||
"github.com/metacubex/sing/common/buf"
|
||||
"github.com/metacubex/sing/common/bufio"
|
||||
@@ -145,6 +146,10 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error {
|
||||
if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress {
|
||||
conn = packetaddr.NewConn(bufio.NewNetPacketConn(conn), M.Socksaddr{})
|
||||
}
|
||||
|
||||
defer func() { _ = conn.Close() }()
|
||||
mutex := sync.Mutex{}
|
||||
writer := bufio.NewNetPacketWriter(conn) // a new interface to set nil in defer
|
||||
|
||||
@@ -19,16 +19,18 @@ import (
|
||||
"github.com/metacubex/mihomo/listener/sing"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/transport/gun"
|
||||
"github.com/metacubex/mihomo/transport/vless/encryption"
|
||||
mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
|
||||
|
||||
"github.com/metacubex/sing-vmess/vless"
|
||||
"github.com/metacubex/sing/common"
|
||||
"github.com/metacubex/sing/common/metadata"
|
||||
"github.com/metacubex/sing/common/network"
|
||||
)
|
||||
|
||||
func init() {
|
||||
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
|
||||
tlsConn, loaded := common.Cast[*reality.Conn](conn) // *utls.Conn
|
||||
tlsConn, loaded := network.CastReader[*reality.Conn](conn) // *utls.Conn
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
@@ -36,19 +38,28 @@ func init() {
|
||||
})
|
||||
|
||||
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
|
||||
tlsConn, loaded := common.Cast[*tlsC.UConn](conn) // *utls.UConn
|
||||
tlsConn, loaded := network.CastReader[*tlsC.UConn](conn) // *utls.UConn
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn.Conn).Elem(), unsafe.Pointer(tlsConn.Conn)
|
||||
})
|
||||
|
||||
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
|
||||
tlsConn, loaded := network.CastReader[*encryption.CommonConn](conn)
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
return true, tlsConn.Conn, reflect.TypeOf(tlsConn).Elem(), unsafe.Pointer(tlsConn)
|
||||
})
|
||||
}
|
||||
|
||||
type Listener struct {
|
||||
closed bool
|
||||
config LC.VlessServer
|
||||
listeners []net.Listener
|
||||
service *vless.Service[string]
|
||||
closed bool
|
||||
config LC.VlessServer
|
||||
listeners []net.Listener
|
||||
service *vless.Service[string]
|
||||
decryption *encryption.ServerInstance
|
||||
}
|
||||
|
||||
func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition) (sl *Listener, err error) {
|
||||
@@ -80,7 +91,20 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
return it.Flow
|
||||
}))
|
||||
|
||||
sl = &Listener{false, config, nil, service}
|
||||
sl = &Listener{config: config, service: service}
|
||||
|
||||
sl.decryption, err = encryption.NewServer(config.Decryption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sl.decryption != nil {
|
||||
defer func() { // decryption must be closed to avoid the goroutine leak
|
||||
if err != nil {
|
||||
_ = sl.decryption.Close()
|
||||
sl.decryption = nil
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
tlsConfig := &tlsC.Config{}
|
||||
var realityBuilder *reality.Builder
|
||||
@@ -149,8 +173,8 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
} else {
|
||||
l = tlsC.NewListener(l, tlsConfig)
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("disallow using Vless without both certificates/reality config")
|
||||
} else if sl.decryption == nil {
|
||||
return nil, errors.New("disallow using Vless without any certificates/reality/decryption config")
|
||||
}
|
||||
sl.listeners = append(sl.listeners, l)
|
||||
|
||||
@@ -185,6 +209,9 @@ func (l *Listener) Close() error {
|
||||
retErr = err
|
||||
}
|
||||
}
|
||||
if l.decryption != nil {
|
||||
_ = l.decryption.Close()
|
||||
}
|
||||
return retErr
|
||||
}
|
||||
|
||||
@@ -201,6 +228,13 @@ func (l *Listener) AddrList() (addrList []net.Addr) {
|
||||
|
||||
func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) {
|
||||
ctx := sing.WithAdditions(context.TODO(), additions...)
|
||||
if l.decryption != nil {
|
||||
var err error
|
||||
conn, err = l.decryption.Handshake(conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
err := l.service.NewConnection(ctx, conn, metadata.Metadata{
|
||||
Protocol: "vless",
|
||||
Source: metadata.SocksaddrFromNet(conn.RemoteAddr()),
|
||||
|
||||
4
main.go
4
main.go
@@ -14,7 +14,7 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/metacubex/mihomo/component/generater"
|
||||
"github.com/metacubex/mihomo/component/generator"
|
||||
"github.com/metacubex/mihomo/component/geodata"
|
||||
"github.com/metacubex/mihomo/component/updater"
|
||||
"github.com/metacubex/mihomo/config"
|
||||
@@ -73,7 +73,7 @@ func main() {
|
||||
}
|
||||
|
||||
if len(os.Args) > 1 && os.Args[1] == "generate" {
|
||||
generater.Main(os.Args[2:])
|
||||
generator.Main(os.Args[2:])
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
"github.com/metacubex/mihomo/transport/anytls/padding"
|
||||
"github.com/metacubex/mihomo/transport/anytls/session"
|
||||
@@ -33,7 +33,7 @@ type Client struct {
|
||||
dialer N.Dialer
|
||||
server M.Socksaddr
|
||||
sessionClient *session.Client
|
||||
padding atomic.TypedValue[*padding.PaddingFactory]
|
||||
padding atomic.Pointer[padding.PaddingFactory]
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, config ClientConfig) *Client {
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/transport/anytls/util"
|
||||
)
|
||||
|
||||
@@ -31,7 +31,7 @@ type PaddingFactory struct {
|
||||
Md5 string
|
||||
}
|
||||
|
||||
func UpdatePaddingScheme(rawScheme []byte, to *atomic.TypedValue[*PaddingFactory]) bool {
|
||||
func UpdatePaddingScheme(rawScheme []byte, to *atomic.Pointer[PaddingFactory]) bool {
|
||||
if p := NewPaddingFactory(rawScheme); p != nil {
|
||||
to.Store(p)
|
||||
return true
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"math"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/transport/anytls/padding"
|
||||
"github.com/metacubex/mihomo/transport/anytls/skiplist"
|
||||
"github.com/metacubex/mihomo/transport/anytls/util"
|
||||
@@ -29,13 +29,13 @@ type Client struct {
|
||||
sessions map[uint64]*Session
|
||||
sessionsLock sync.Mutex
|
||||
|
||||
padding *atomic.TypedValue[*padding.PaddingFactory]
|
||||
padding *atomic.Pointer[padding.PaddingFactory]
|
||||
|
||||
idleSessionTimeout time.Duration
|
||||
minIdleSession int
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, dialOut util.DialOutFunc, _padding *atomic.TypedValue[*padding.PaddingFactory], idleSessionCheckInterval, idleSessionTimeout time.Duration, minIdleSession int) *Client {
|
||||
func NewClient(ctx context.Context, dialOut util.DialOutFunc, _padding *atomic.Pointer[padding.PaddingFactory], idleSessionCheckInterval, idleSessionTimeout time.Duration, minIdleSession int) *Client {
|
||||
c := &Client{
|
||||
sessions: make(map[uint64]*Session),
|
||||
dialOut: dialOut,
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
"github.com/metacubex/mihomo/common/pool"
|
||||
"github.com/metacubex/mihomo/constant"
|
||||
@@ -38,7 +38,7 @@ type Session struct {
|
||||
// pool
|
||||
seq uint64
|
||||
idleSince time.Time
|
||||
padding *atomic.TypedValue[*padding.PaddingFactory]
|
||||
padding *atomic.Pointer[padding.PaddingFactory]
|
||||
|
||||
peerVersion byte
|
||||
|
||||
@@ -53,7 +53,7 @@ type Session struct {
|
||||
onNewStream func(stream *Stream)
|
||||
}
|
||||
|
||||
func NewClientSession(conn net.Conn, _padding *atomic.TypedValue[*padding.PaddingFactory]) *Session {
|
||||
func NewClientSession(conn net.Conn, _padding *atomic.Pointer[padding.PaddingFactory]) *Session {
|
||||
s := &Session{
|
||||
conn: conn,
|
||||
isClient: true,
|
||||
@@ -65,7 +65,7 @@ func NewClientSession(conn net.Conn, _padding *atomic.TypedValue[*padding.Paddin
|
||||
return s
|
||||
}
|
||||
|
||||
func NewServerSession(conn net.Conn, onNewStream func(stream *Stream), _padding *atomic.TypedValue[*padding.PaddingFactory]) *Session {
|
||||
func NewServerSession(conn net.Conn, onNewStream func(stream *Stream), _padding *atomic.Pointer[padding.PaddingFactory]) *Session {
|
||||
s := &Session{
|
||||
conn: conn,
|
||||
onNewStream: onNewStream,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -16,7 +15,6 @@ import (
|
||||
"github.com/metacubex/mihomo/transport/hysteria/transport"
|
||||
"github.com/metacubex/mihomo/transport/hysteria/utils"
|
||||
|
||||
"github.com/lunixbochs/struc"
|
||||
"github.com/metacubex/quic-go"
|
||||
"github.com/metacubex/quic-go/congestion"
|
||||
"github.com/metacubex/randv2"
|
||||
@@ -104,31 +102,23 @@ func (c *Client) connectToServer(dialer utils.PacketDialer) error {
|
||||
}
|
||||
|
||||
func (c *Client) handleControlStream(qs *quic.Conn, stream *quic.Stream) (bool, string, error) {
|
||||
// Send protocol version
|
||||
_, err := stream.Write([]byte{protocolVersion})
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
// Send client hello
|
||||
err = struc.Pack(stream, &clientHello{
|
||||
Rate: transmissionRate{
|
||||
SendBPS: c.sendBPS,
|
||||
RecvBPS: c.recvBPS,
|
||||
},
|
||||
Auth: c.auth,
|
||||
err := WriteClientHello(stream, ClientHello{
|
||||
SendBPS: c.sendBPS,
|
||||
RecvBPS: c.recvBPS,
|
||||
Auth: c.auth,
|
||||
})
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
// Receive server hello
|
||||
var sh serverHello
|
||||
err = struc.Unpack(stream, &sh)
|
||||
sh, err := ReadServerHello(stream)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
// Set the congestion accordingly
|
||||
if sh.OK && c.congestionFactory != nil {
|
||||
qs.SetCongestionControl(c.congestionFactory(sh.Rate.RecvBPS))
|
||||
qs.SetCongestionControl(c.congestionFactory(sh.RecvBPS))
|
||||
}
|
||||
return sh.OK, sh.Message, nil
|
||||
}
|
||||
@@ -140,7 +130,7 @@ func (c *Client) handleMessage(qs *quic.Conn) {
|
||||
break
|
||||
}
|
||||
var udpMsg udpMessage
|
||||
err = struc.Unpack(bytes.NewBuffer(msg), &udpMsg)
|
||||
err = udpMsg.Unpack(msg)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -200,7 +190,7 @@ func (c *Client) DialTCP(host string, port uint16, dialer utils.PacketDialer) (n
|
||||
return nil, err
|
||||
}
|
||||
// Send request
|
||||
err = struc.Pack(stream, &clientRequest{
|
||||
err = WriteClientRequest(stream, ClientRequest{
|
||||
UDP: false,
|
||||
Host: host,
|
||||
Port: port,
|
||||
@@ -213,8 +203,8 @@ func (c *Client) DialTCP(host string, port uint16, dialer utils.PacketDialer) (n
|
||||
// and defer the response handling to the first Read() call
|
||||
if !c.fastOpen {
|
||||
// Read response
|
||||
var sr serverResponse
|
||||
err = struc.Unpack(stream, &sr)
|
||||
var sr *ServerResponse
|
||||
sr, err = ReadServerResponse(stream)
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
@@ -239,16 +229,16 @@ func (c *Client) DialUDP(dialer utils.PacketDialer) (UDPConn, error) {
|
||||
return nil, err
|
||||
}
|
||||
// Send request
|
||||
err = struc.Pack(stream, &clientRequest{
|
||||
UDP: true,
|
||||
err = WriteClientRequest(stream, ClientRequest{
|
||||
UDP: false,
|
||||
})
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
}
|
||||
// Read response
|
||||
var sr serverResponse
|
||||
err = struc.Unpack(stream, &sr)
|
||||
var sr *ServerResponse
|
||||
sr, err = ReadServerResponse(stream)
|
||||
if err != nil {
|
||||
_ = stream.Close()
|
||||
return nil, err
|
||||
@@ -306,8 +296,8 @@ type quicConn struct {
|
||||
|
||||
func (w *quicConn) Read(b []byte) (n int, err error) {
|
||||
if !w.Established {
|
||||
var sr serverResponse
|
||||
err := struc.Unpack(w.Orig, &sr)
|
||||
var sr *ServerResponse
|
||||
sr, err = ReadServerResponse(w.Orig)
|
||||
if err != nil {
|
||||
_ = w.Close()
|
||||
return 0, err
|
||||
@@ -401,9 +391,7 @@ func (c *quicPktConn) WriteTo(p []byte, addr string) error {
|
||||
Data: p,
|
||||
}
|
||||
// try no frag first
|
||||
var msgBuf bytes.Buffer
|
||||
_ = struc.Pack(&msgBuf, &msg)
|
||||
err = c.Session.SendDatagram(msgBuf.Bytes())
|
||||
err = c.Session.SendDatagram(msg.Pack())
|
||||
if err != nil {
|
||||
var errSize *quic.DatagramTooLargeError
|
||||
if errors.As(err, &errSize) {
|
||||
@@ -411,9 +399,7 @@ func (c *quicPktConn) WriteTo(p []byte, addr string) error {
|
||||
msg.MsgID = uint16(randv2.IntN(0xFFFF)) + 1 // msgID must be > 0 when fragCount > 1
|
||||
fragMsgs := fragUDPMessage(msg, int(errSize.MaxDatagramPayloadSize))
|
||||
for _, fragMsg := range fragMsgs {
|
||||
msgBuf.Reset()
|
||||
_ = struc.Pack(&msgBuf, &fragMsg)
|
||||
err = c.Session.SendDatagram(msgBuf.Bytes())
|
||||
err = c.Session.SendDatagram(fragMsg.Pack())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ func fragUDPMessage(m udpMessage, maxSize int) []udpMessage {
|
||||
frag := m
|
||||
frag.FragID = fragID
|
||||
frag.FragCount = fragCount
|
||||
frag.DataLen = uint16(payloadSize)
|
||||
frag.Data = fullPayload[off : off+payloadSize]
|
||||
frags = append(frags, frag)
|
||||
off += payloadSize
|
||||
@@ -56,7 +55,6 @@ func (d *defragger) Feed(m udpMessage) *udpMessage {
|
||||
for _, frag := range d.frags {
|
||||
data = append(data, frag.Data...)
|
||||
}
|
||||
m.DataLen = uint16(len(data))
|
||||
m.Data = data
|
||||
m.FragID = 0
|
||||
m.FragCount = 1
|
||||
|
||||
@@ -20,13 +20,11 @@ func Test_fragUDPMessage(t *testing.T) {
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
DataLen: 5,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
100,
|
||||
@@ -34,13 +32,11 @@ func Test_fragUDPMessage(t *testing.T) {
|
||||
[]udpMessage{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
DataLen: 5,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
},
|
||||
@@ -50,13 +46,11 @@ func Test_fragUDPMessage(t *testing.T) {
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
DataLen: 5,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
22,
|
||||
@@ -64,24 +58,20 @@ func Test_fragUDPMessage(t *testing.T) {
|
||||
[]udpMessage{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 2,
|
||||
DataLen: 4,
|
||||
Data: []byte("hell"),
|
||||
},
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 1,
|
||||
FragCount: 2,
|
||||
DataLen: 1,
|
||||
Data: []byte("o"),
|
||||
},
|
||||
},
|
||||
@@ -91,13 +81,11 @@ func Test_fragUDPMessage(t *testing.T) {
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
DataLen: 20,
|
||||
Data: []byte("wow wow wow lol lmao"),
|
||||
},
|
||||
23,
|
||||
@@ -105,46 +93,38 @@ func Test_fragUDPMessage(t *testing.T) {
|
||||
[]udpMessage{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 4,
|
||||
DataLen: 5,
|
||||
Data: []byte("wow w"),
|
||||
},
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 1,
|
||||
FragCount: 4,
|
||||
DataLen: 5,
|
||||
Data: []byte("ow wo"),
|
||||
},
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 2,
|
||||
FragCount: 4,
|
||||
DataLen: 5,
|
||||
Data: []byte("w lol"),
|
||||
},
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 3,
|
||||
FragCount: 4,
|
||||
DataLen: 5,
|
||||
Data: []byte(" lmao"),
|
||||
},
|
||||
},
|
||||
@@ -174,25 +154,21 @@ func Test_defragger_Feed(t *testing.T) {
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
DataLen: 5,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
},
|
||||
&udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 123,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
DataLen: 5,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
},
|
||||
@@ -201,13 +177,11 @@ func Test_defragger_Feed(t *testing.T) {
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 666,
|
||||
FragID: 0,
|
||||
FragCount: 3,
|
||||
DataLen: 5,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
},
|
||||
@@ -218,13 +192,11 @@ func Test_defragger_Feed(t *testing.T) {
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 666,
|
||||
FragID: 1,
|
||||
FragCount: 3,
|
||||
DataLen: 8,
|
||||
Data: []byte(" shitty "),
|
||||
},
|
||||
},
|
||||
@@ -235,25 +207,21 @@ func Test_defragger_Feed(t *testing.T) {
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 666,
|
||||
FragID: 2,
|
||||
FragCount: 3,
|
||||
DataLen: 7,
|
||||
Data: []byte("world!!"),
|
||||
},
|
||||
},
|
||||
&udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 666,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
DataLen: 20,
|
||||
Data: []byte("hello shitty world!!"),
|
||||
},
|
||||
},
|
||||
@@ -262,13 +230,11 @@ func Test_defragger_Feed(t *testing.T) {
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 777,
|
||||
FragID: 0,
|
||||
FragCount: 2,
|
||||
DataLen: 5,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
},
|
||||
@@ -279,13 +245,11 @@ func Test_defragger_Feed(t *testing.T) {
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 778,
|
||||
FragID: 1,
|
||||
FragCount: 2,
|
||||
DataLen: 5,
|
||||
Data: []byte(" moto"),
|
||||
},
|
||||
},
|
||||
@@ -296,13 +260,11 @@ func Test_defragger_Feed(t *testing.T) {
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 777,
|
||||
FragID: 1,
|
||||
FragCount: 2,
|
||||
DataLen: 5,
|
||||
Data: []byte(" moto"),
|
||||
},
|
||||
},
|
||||
@@ -313,25 +275,21 @@ func Test_defragger_Feed(t *testing.T) {
|
||||
args{
|
||||
udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 777,
|
||||
FragID: 0,
|
||||
FragCount: 2,
|
||||
DataLen: 5,
|
||||
Data: []byte("hello"),
|
||||
},
|
||||
},
|
||||
&udpMessage{
|
||||
SessionID: 123,
|
||||
HostLen: 4,
|
||||
Host: "test",
|
||||
Port: 123,
|
||||
MsgID: 777,
|
||||
FragID: 0,
|
||||
FragCount: 1,
|
||||
DataLen: 10,
|
||||
Data: []byte("hello moto"),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,60 +1,241 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
protocolVersion = uint8(3)
|
||||
protocolVersionV2 = uint8(2)
|
||||
protocolTimeout = 10 * time.Second
|
||||
protocolVersion = uint8(3)
|
||||
protocolTimeout = 10 * time.Second
|
||||
|
||||
closeErrorCodeGeneric = 0
|
||||
closeErrorCodeProtocol = 1
|
||||
closeErrorCodeAuth = 2
|
||||
)
|
||||
|
||||
type transmissionRate struct {
|
||||
type ClientHello struct {
|
||||
SendBPS uint64
|
||||
RecvBPS uint64
|
||||
}
|
||||
|
||||
type clientHello struct {
|
||||
Rate transmissionRate
|
||||
AuthLen uint16 `struc:"sizeof=Auth"`
|
||||
Auth []byte
|
||||
}
|
||||
|
||||
type serverHello struct {
|
||||
OK bool
|
||||
Rate transmissionRate
|
||||
MessageLen uint16 `struc:"sizeof=Message"`
|
||||
Message string
|
||||
func WriteClientHello(stream io.Writer, hello ClientHello) error {
|
||||
var requestLen int
|
||||
requestLen += 1 // version
|
||||
requestLen += 8 // sendBPS
|
||||
requestLen += 8 // recvBPS
|
||||
requestLen += 2 // auth len
|
||||
requestLen += len(hello.Auth)
|
||||
request := make([]byte, requestLen)
|
||||
request[0] = protocolVersion
|
||||
binary.BigEndian.PutUint64(request[1:9], hello.SendBPS)
|
||||
binary.BigEndian.PutUint64(request[9:17], hello.RecvBPS)
|
||||
binary.BigEndian.PutUint16(request[17:19], uint16(len(hello.Auth)))
|
||||
copy(request[19:], hello.Auth)
|
||||
_, err := stream.Write(request)
|
||||
return err
|
||||
}
|
||||
|
||||
type clientRequest struct {
|
||||
UDP bool
|
||||
HostLen uint16 `struc:"sizeof=Host"`
|
||||
Host string
|
||||
Port uint16
|
||||
func ReadClientHello(stream io.Reader) (*ClientHello, error) {
|
||||
var responseLen int
|
||||
responseLen += 1 // ok
|
||||
responseLen += 8 // sendBPS
|
||||
responseLen += 8 // recvBPS
|
||||
responseLen += 2 // auth len
|
||||
response := make([]byte, responseLen)
|
||||
_, err := io.ReadFull(stream, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if response[0] != protocolVersion {
|
||||
return nil, errors.New("unsupported client version")
|
||||
}
|
||||
var clientHello ClientHello
|
||||
clientHello.SendBPS = binary.BigEndian.Uint64(response[1:9])
|
||||
clientHello.RecvBPS = binary.BigEndian.Uint64(response[9:17])
|
||||
authLen := binary.BigEndian.Uint16(response[17:19])
|
||||
|
||||
if clientHello.SendBPS == 0 || clientHello.RecvBPS == 0 {
|
||||
return nil, errors.New("invalid rate from client")
|
||||
}
|
||||
|
||||
authBytes := make([]byte, authLen)
|
||||
_, err = io.ReadFull(stream, authBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientHello.Auth = authBytes
|
||||
return &clientHello, nil
|
||||
}
|
||||
|
||||
type serverResponse struct {
|
||||
type ServerHello struct {
|
||||
OK bool
|
||||
SendBPS uint64
|
||||
RecvBPS uint64
|
||||
Message string
|
||||
}
|
||||
|
||||
func ReadServerHello(stream io.Reader) (*ServerHello, error) {
|
||||
var responseLen int
|
||||
responseLen += 1 // ok
|
||||
responseLen += 8 // sendBPS
|
||||
responseLen += 8 // recvBPS
|
||||
responseLen += 2 // message len
|
||||
response := make([]byte, responseLen)
|
||||
_, err := io.ReadFull(stream, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var serverHello ServerHello
|
||||
serverHello.OK = response[0] == 1
|
||||
serverHello.SendBPS = binary.BigEndian.Uint64(response[1:9])
|
||||
serverHello.RecvBPS = binary.BigEndian.Uint64(response[9:17])
|
||||
messageLen := binary.BigEndian.Uint16(response[17:19])
|
||||
if messageLen == 0 {
|
||||
return &serverHello, nil
|
||||
}
|
||||
message := make([]byte, messageLen)
|
||||
_, err = io.ReadFull(stream, message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serverHello.Message = string(message)
|
||||
return &serverHello, nil
|
||||
}
|
||||
|
||||
func WriteServerHello(stream io.Writer, hello ServerHello) error {
|
||||
var responseLen int
|
||||
responseLen += 1 // ok
|
||||
responseLen += 8 // sendBPS
|
||||
responseLen += 8 // recvBPS
|
||||
responseLen += 2 // message len
|
||||
responseLen += len(hello.Message)
|
||||
response := make([]byte, responseLen)
|
||||
if hello.OK {
|
||||
response[0] = 1
|
||||
} else {
|
||||
response[0] = 0
|
||||
}
|
||||
binary.BigEndian.PutUint64(response[1:9], hello.SendBPS)
|
||||
binary.BigEndian.PutUint64(response[9:17], hello.RecvBPS)
|
||||
binary.BigEndian.PutUint16(response[17:19], uint16(len(hello.Message)))
|
||||
copy(response[19:], hello.Message)
|
||||
_, err := stream.Write(response)
|
||||
return err
|
||||
}
|
||||
|
||||
type ClientRequest struct {
|
||||
UDP bool
|
||||
Host string
|
||||
Port uint16
|
||||
}
|
||||
|
||||
func ReadClientRequest(stream io.Reader) (*ClientRequest, error) {
|
||||
var clientRequest ClientRequest
|
||||
err := binary.Read(stream, binary.BigEndian, &clientRequest.UDP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var hostLen uint16
|
||||
err = binary.Read(stream, binary.BigEndian, &hostLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
host := make([]byte, hostLen)
|
||||
_, err = io.ReadFull(stream, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientRequest.Host = string(host)
|
||||
err = binary.Read(stream, binary.BigEndian, &clientRequest.Port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &clientRequest, nil
|
||||
}
|
||||
|
||||
func WriteClientRequest(stream io.Writer, request ClientRequest) error {
|
||||
var requestLen int
|
||||
requestLen += 1 // udp
|
||||
requestLen += 2 // host len
|
||||
requestLen += len(request.Host)
|
||||
requestLen += 2 // port
|
||||
buffer := make([]byte, requestLen)
|
||||
if request.UDP {
|
||||
buffer[0] = 1
|
||||
} else {
|
||||
buffer[0] = 0
|
||||
}
|
||||
binary.BigEndian.PutUint16(buffer[1:3], uint16(len(request.Host)))
|
||||
n := copy(buffer[3:], request.Host)
|
||||
binary.BigEndian.PutUint16(buffer[3+n:3+n+2], request.Port)
|
||||
_, err := stream.Write(buffer)
|
||||
return err
|
||||
}
|
||||
|
||||
type ServerResponse struct {
|
||||
OK bool
|
||||
UDPSessionID uint32
|
||||
MessageLen uint16 `struc:"sizeof=Message"`
|
||||
Message string
|
||||
}
|
||||
|
||||
func ReadServerResponse(stream io.Reader) (*ServerResponse, error) {
|
||||
var responseLen int
|
||||
responseLen += 1 // ok
|
||||
responseLen += 4 // udp session id
|
||||
responseLen += 2 // message len
|
||||
response := make([]byte, responseLen)
|
||||
_, err := io.ReadFull(stream, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var serverResponse ServerResponse
|
||||
serverResponse.OK = response[0] == 1
|
||||
serverResponse.UDPSessionID = binary.BigEndian.Uint32(response[1:5])
|
||||
messageLen := binary.BigEndian.Uint16(response[5:7])
|
||||
if messageLen == 0 {
|
||||
return &serverResponse, nil
|
||||
}
|
||||
message := make([]byte, messageLen)
|
||||
_, err = io.ReadFull(stream, message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serverResponse.Message = string(message)
|
||||
return &serverResponse, nil
|
||||
}
|
||||
|
||||
func WriteServerResponse(stream io.Writer, response ServerResponse) error {
|
||||
var responseLen int
|
||||
responseLen += 1 // ok
|
||||
responseLen += 4 // udp session id
|
||||
responseLen += 2 // message len
|
||||
responseLen += len(response.Message)
|
||||
buffer := make([]byte, responseLen)
|
||||
if response.OK {
|
||||
buffer[0] = 1
|
||||
} else {
|
||||
buffer[0] = 0
|
||||
}
|
||||
binary.BigEndian.PutUint32(buffer[1:5], response.UDPSessionID)
|
||||
binary.BigEndian.PutUint16(buffer[5:7], uint16(len(response.Message)))
|
||||
copy(buffer[7:], response.Message)
|
||||
_, err := stream.Write(buffer)
|
||||
return err
|
||||
}
|
||||
|
||||
type udpMessage struct {
|
||||
SessionID uint32
|
||||
HostLen uint16 `struc:"sizeof=Host"`
|
||||
Host string
|
||||
Port uint16
|
||||
MsgID uint16 // doesn't matter when not fragmented, but must not be 0 when fragmented
|
||||
FragID uint8 // doesn't matter when not fragmented, starts at 0 when fragmented
|
||||
FragCount uint8 // must be 1 when not fragmented
|
||||
DataLen uint16 `struc:"sizeof=Data"`
|
||||
Data []byte
|
||||
}
|
||||
|
||||
@@ -66,11 +247,62 @@ func (m udpMessage) Size() int {
|
||||
return m.HeaderSize() + len(m.Data)
|
||||
}
|
||||
|
||||
type udpMessageV2 struct {
|
||||
SessionID uint32
|
||||
HostLen uint16 `struc:"sizeof=Host"`
|
||||
Host string
|
||||
Port uint16
|
||||
DataLen uint16 `struc:"sizeof=Data"`
|
||||
Data []byte
|
||||
func (m udpMessage) Pack() []byte {
|
||||
data := make([]byte, m.Size())
|
||||
buffer := bytes.NewBuffer(data)
|
||||
_ = binary.Write(buffer, binary.BigEndian, m.SessionID)
|
||||
_ = binary.Write(buffer, binary.BigEndian, uint16(len(m.Host)))
|
||||
buffer.WriteString(m.Host)
|
||||
_ = binary.Write(buffer, binary.BigEndian, m.Port)
|
||||
_ = binary.Write(buffer, binary.BigEndian, m.MsgID)
|
||||
_ = binary.Write(buffer, binary.BigEndian, m.FragID)
|
||||
_ = binary.Write(buffer, binary.BigEndian, m.FragCount)
|
||||
_ = binary.Write(buffer, binary.BigEndian, uint16(len(m.Data)))
|
||||
buffer.Write(m.Data)
|
||||
return buffer.Bytes()
|
||||
}
|
||||
|
||||
func (m *udpMessage) Unpack(data []byte) error {
|
||||
reader := bytes.NewReader(data)
|
||||
err := binary.Read(reader, binary.BigEndian, &m.SessionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var hostLen uint16
|
||||
err = binary.Read(reader, binary.BigEndian, &hostLen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hostBytes := make([]byte, hostLen)
|
||||
_, err = io.ReadFull(reader, hostBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.Host = string(hostBytes)
|
||||
err = binary.Read(reader, binary.BigEndian, &m.Port)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = binary.Read(reader, binary.BigEndian, &m.MsgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = binary.Read(reader, binary.BigEndian, &m.FragID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = binary.Read(reader, binary.BigEndian, &m.FragCount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var dataLen uint16
|
||||
err = binary.Read(reader, binary.BigEndian, &dataLen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reader.Len() != int(dataLen) {
|
||||
return errors.New("invalid data length")
|
||||
}
|
||||
m.Data = data[len(data)-reader.Len():]
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
tls "github.com/3andne/restls-client-go"
|
||||
tls "github.com/metacubex/restls-client-go"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
"github.com/metacubex/blake3"
|
||||
"github.com/metacubex/quic-go"
|
||||
"lukechampine.com/blake3"
|
||||
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/socks5"
|
||||
|
||||
@@ -30,26 +30,22 @@ type Conn struct {
|
||||
}
|
||||
|
||||
func (vc *Conn) Read(b []byte) (int, error) {
|
||||
if vc.received {
|
||||
return vc.ExtendedReader.Read(b)
|
||||
if !vc.received {
|
||||
if err := vc.recvResponse(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
vc.received = true
|
||||
}
|
||||
|
||||
if err := vc.recvResponse(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
vc.received = true
|
||||
return vc.ExtendedReader.Read(b)
|
||||
}
|
||||
|
||||
func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error {
|
||||
if vc.received {
|
||||
return vc.ExtendedReader.ReadBuffer(buffer)
|
||||
if !vc.received {
|
||||
if err := vc.recvResponse(); err != nil {
|
||||
return err
|
||||
}
|
||||
vc.received = true
|
||||
}
|
||||
|
||||
if err := vc.recvResponse(); err != nil {
|
||||
return err
|
||||
}
|
||||
vc.received = true
|
||||
return vc.ExtendedReader.ReadBuffer(buffer)
|
||||
}
|
||||
|
||||
@@ -105,26 +101,20 @@ func (vc *Conn) sendRequest(p []byte) bool {
|
||||
}
|
||||
}
|
||||
|
||||
var buffer *buf.Buffer
|
||||
if vc.IsXTLSVisionEnabled() {
|
||||
buffer = buf.New()
|
||||
defer buffer.Release()
|
||||
} else {
|
||||
requestLen := 1 // protocol version
|
||||
requestLen += 16 // UUID
|
||||
requestLen += 1 // addons length
|
||||
requestLen += len(addonsBytes)
|
||||
requestLen += 1 // command
|
||||
if !vc.dst.Mux {
|
||||
requestLen += 2 // port
|
||||
requestLen += 1 // addr type
|
||||
requestLen += len(vc.dst.Addr)
|
||||
}
|
||||
requestLen += len(p)
|
||||
|
||||
buffer = buf.NewSize(requestLen)
|
||||
defer buffer.Release()
|
||||
requestLen := 1 // protocol version
|
||||
requestLen += 16 // UUID
|
||||
requestLen += 1 // addons length
|
||||
requestLen += len(addonsBytes)
|
||||
requestLen += 1 // command
|
||||
if !vc.dst.Mux {
|
||||
requestLen += 2 // port
|
||||
requestLen += 1 // addr type
|
||||
requestLen += len(vc.dst.Addr)
|
||||
}
|
||||
requestLen += len(p)
|
||||
|
||||
buffer := buf.NewSize(requestLen)
|
||||
defer buffer.Release()
|
||||
|
||||
buf.Must(
|
||||
buffer.WriteByte(Version), // protocol version
|
||||
@@ -182,10 +172,6 @@ func (vc *Conn) NeedHandshake() bool {
|
||||
return vc.needHandshake
|
||||
}
|
||||
|
||||
func (vc *Conn) IsXTLSVisionEnabled() bool {
|
||||
return vc.addons != nil && vc.addons.Flow == XRV
|
||||
}
|
||||
|
||||
// newConn return a Conn instance
|
||||
func newConn(conn net.Conn, client *Client, dst *DstAddr) (net.Conn, error) {
|
||||
c := &Conn{
|
||||
@@ -200,7 +186,7 @@ func newConn(conn net.Conn, client *Client, dst *DstAddr) (net.Conn, error) {
|
||||
if client.Addons != nil {
|
||||
switch client.Addons.Flow {
|
||||
case XRV:
|
||||
visionConn, err := vision.NewConn(c, c.id)
|
||||
visionConn, err := vision.NewConn(c, conn, c.id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
201
transport/vless/encryption/client.go
Normal file
201
transport/vless/encryption/client.go
Normal file
@@ -0,0 +1,201 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/ecdh"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/blake3"
|
||||
"github.com/metacubex/utls/mlkem"
|
||||
)
|
||||
|
||||
type ClientInstance struct {
|
||||
NfsPKeys []any
|
||||
NfsPKeysBytes [][]byte
|
||||
Hash32s [][32]byte
|
||||
RelaysLength int
|
||||
XorMode uint32
|
||||
Seconds uint32
|
||||
|
||||
RWLock sync.RWMutex
|
||||
Expire time.Time
|
||||
PfsKey []byte
|
||||
Ticket []byte
|
||||
}
|
||||
|
||||
func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) (err error) {
|
||||
if i.NfsPKeys != nil {
|
||||
err = errors.New("already initialized")
|
||||
return
|
||||
}
|
||||
l := len(nfsPKeysBytes)
|
||||
if l == 0 {
|
||||
err = errors.New("empty nfsPKeysBytes")
|
||||
return
|
||||
}
|
||||
i.NfsPKeys = make([]any, l)
|
||||
i.NfsPKeysBytes = nfsPKeysBytes
|
||||
i.Hash32s = make([][32]byte, l)
|
||||
for j, k := range nfsPKeysBytes {
|
||||
if len(k) == 32 {
|
||||
if i.NfsPKeys[j], err = ecdh.X25519().NewPublicKey(k); err != nil {
|
||||
return
|
||||
}
|
||||
i.RelaysLength += 32 + 32
|
||||
} else {
|
||||
if i.NfsPKeys[j], err = mlkem.NewEncapsulationKey768(k); err != nil {
|
||||
return
|
||||
}
|
||||
i.RelaysLength += 1088 + 32
|
||||
}
|
||||
i.Hash32s[j] = blake3.Sum256(k)
|
||||
}
|
||||
i.RelaysLength -= 32
|
||||
i.XorMode = xorMode
|
||||
i.Seconds = seconds
|
||||
return
|
||||
}
|
||||
|
||||
func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
if i.NfsPKeys == nil {
|
||||
return nil, errors.New("uninitialized")
|
||||
}
|
||||
c := &CommonConn{Conn: conn}
|
||||
|
||||
ivAndRealysLength := 16 + i.RelaysLength
|
||||
pfsKeyExchangeLength := 18 + 1184 + 32 + 16
|
||||
paddingLength := int(randBetween(100, 1000))
|
||||
clientHello := make([]byte, ivAndRealysLength+pfsKeyExchangeLength+paddingLength)
|
||||
|
||||
iv := clientHello[:16]
|
||||
rand.Read(iv)
|
||||
relays := clientHello[16:ivAndRealysLength]
|
||||
var nfsKey []byte
|
||||
var lastCTR cipher.Stream
|
||||
for j, k := range i.NfsPKeys {
|
||||
var index = 32
|
||||
if k, ok := k.(*ecdh.PublicKey); ok {
|
||||
privateKey, _ := ecdh.X25519().GenerateKey(rand.Reader)
|
||||
copy(relays, privateKey.PublicKey().Bytes())
|
||||
var err error
|
||||
nfsKey, err = privateKey.ECDH(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if k, ok := k.(*mlkem.EncapsulationKey768); ok {
|
||||
var ciphertext []byte
|
||||
nfsKey, ciphertext = k.Encapsulate()
|
||||
copy(relays, ciphertext)
|
||||
index = 1088
|
||||
}
|
||||
if i.XorMode > 0 { // this xor can (others can't) be recovered by client's config, revealing an X25519 public key / ML-KEM-768 ciphertext, that's why "native" values
|
||||
NewCTR(i.NfsPKeysBytes[j], iv).XORKeyStream(relays, relays[:index]) // make X25519 public key / ML-KEM-768 ciphertext distinguishable from random bytes
|
||||
}
|
||||
if lastCTR != nil {
|
||||
lastCTR.XORKeyStream(relays, relays[:32]) // make this relay irreplaceable
|
||||
}
|
||||
if j == len(i.NfsPKeys)-1 {
|
||||
break
|
||||
}
|
||||
lastCTR = NewCTR(nfsKey, iv)
|
||||
lastCTR.XORKeyStream(relays[index:], i.Hash32s[j+1][:])
|
||||
relays = relays[index+32:]
|
||||
}
|
||||
nfsGCM := NewGCM(iv, nfsKey)
|
||||
|
||||
if i.Seconds > 0 {
|
||||
i.RWLock.RLock()
|
||||
if time.Now().Before(i.Expire) {
|
||||
c.Client = i
|
||||
c.UnitedKey = append(i.PfsKey, nfsKey...) // different unitedKey for each connection
|
||||
nfsGCM.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil)
|
||||
nfsGCM.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil)
|
||||
i.RWLock.RUnlock()
|
||||
c.PreWrite = clientHello[:ivAndRealysLength+18+32]
|
||||
c.GCM = NewGCM(clientHello[ivAndRealysLength+18:ivAndRealysLength+18+32], c.UnitedKey)
|
||||
if i.XorMode == 2 {
|
||||
c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), nil, len(c.PreWrite), 16)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
i.RWLock.RUnlock()
|
||||
}
|
||||
|
||||
pfsKeyExchange := clientHello[ivAndRealysLength : ivAndRealysLength+pfsKeyExchangeLength]
|
||||
nfsGCM.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil)
|
||||
mlkem768DKey, _ := mlkem.GenerateKey768()
|
||||
x25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader)
|
||||
pfsPublicKey := append(mlkem768DKey.EncapsulationKey().Bytes(), x25519SKey.PublicKey().Bytes()...)
|
||||
nfsGCM.Seal(pfsKeyExchange[:18], nil, pfsPublicKey, nil)
|
||||
|
||||
padding := clientHello[ivAndRealysLength+pfsKeyExchangeLength:]
|
||||
nfsGCM.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
|
||||
nfsGCM.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
|
||||
|
||||
if _, err := conn.Write(clientHello); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
|
||||
|
||||
encryptedPfsPublicKey := make([]byte, 1088+32+16)
|
||||
if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nfsGCM.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil)
|
||||
mlkem768Key, err := mlkem768DKey.Decapsulate(encryptedPfsPublicKey[:1088])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peerX25519PKey, err := ecdh.X25519().NewPublicKey(encryptedPfsPublicKey[1088 : 1088+32])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x25519Key, err := x25519SKey.ECDH(peerX25519PKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pfsKey := make([]byte, 32+32) // no more capacity
|
||||
copy(pfsKey, mlkem768Key)
|
||||
copy(pfsKey[32:], x25519Key)
|
||||
c.UnitedKey = append(pfsKey, nfsKey...)
|
||||
c.GCM = NewGCM(pfsPublicKey, c.UnitedKey)
|
||||
c.PeerGCM = NewGCM(encryptedPfsPublicKey[:1088+32], c.UnitedKey)
|
||||
|
||||
encryptedTicket := make([]byte, 32)
|
||||
if _, err := io.ReadFull(conn, encryptedTicket); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := c.PeerGCM.Open(encryptedTicket[:0], nil, encryptedTicket, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seconds := DecodeLength(encryptedTicket)
|
||||
|
||||
if i.Seconds > 0 && seconds > 0 {
|
||||
i.RWLock.Lock()
|
||||
i.Expire = time.Now().Add(time.Duration(seconds) * time.Second)
|
||||
i.PfsKey = pfsKey
|
||||
i.Ticket = encryptedTicket[:16]
|
||||
i.RWLock.Unlock()
|
||||
}
|
||||
|
||||
encryptedLength := make([]byte, 18)
|
||||
if _, err := io.ReadFull(conn, encryptedLength); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := c.PeerGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
length := DecodeLength(encryptedLength[:2])
|
||||
c.PeerPadding = make([]byte, length) // important: allows server sends padding slowly, eliminating 1-RTT's traffic pattern
|
||||
|
||||
if i.XorMode == 2 {
|
||||
c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), NewCTR(c.UnitedKey, encryptedTicket[:16]), 0, length)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
220
transport/vless/encryption/common.go
Normal file
220
transport/vless/encryption/common.go
Normal file
@@ -0,0 +1,220 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/blake3"
|
||||
)
|
||||
|
||||
type CommonConn struct {
|
||||
net.Conn
|
||||
Client *ClientInstance
|
||||
UnitedKey []byte
|
||||
PreWrite []byte
|
||||
GCM *GCM
|
||||
PeerPadding []byte
|
||||
PeerGCM *GCM
|
||||
input bytes.Reader // PeerCache
|
||||
}
|
||||
|
||||
func (c *CommonConn) Write(b []byte) (int, error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
var data []byte
|
||||
for n := 0; n < len(b); {
|
||||
b := b[n:]
|
||||
if len(b) > 8192 {
|
||||
b = b[:8192] // for avoiding another copy() in peer's Read()
|
||||
}
|
||||
n += len(b)
|
||||
data = make([]byte, 5+len(b)+16)
|
||||
EncodeHeader(data, len(b)+16)
|
||||
max := false
|
||||
if bytes.Equal(c.GCM.Nonce[:], MaxNonce) {
|
||||
max = true
|
||||
}
|
||||
c.GCM.Seal(data[:5], nil, b, data[:5])
|
||||
if max {
|
||||
c.GCM = NewGCM(data[5:], c.UnitedKey)
|
||||
}
|
||||
if c.PreWrite != nil {
|
||||
data = append(c.PreWrite, data...)
|
||||
c.PreWrite = nil
|
||||
}
|
||||
if _, err := c.Conn.Write(data); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (c *CommonConn) Read(b []byte) (int, error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if c.PeerGCM == nil { // client's 0-RTT
|
||||
serverRandom := make([]byte, 16)
|
||||
if _, err := io.ReadFull(c.Conn, serverRandom); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
c.PeerGCM = NewGCM(serverRandom, c.UnitedKey)
|
||||
if xorConn, ok := c.Conn.(*XorConn); ok {
|
||||
xorConn.PeerCTR = NewCTR(c.UnitedKey, serverRandom)
|
||||
}
|
||||
}
|
||||
if c.PeerPadding != nil { // client's 1-RTT
|
||||
if _, err := io.ReadFull(c.Conn, c.PeerPadding); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := c.PeerGCM.Open(c.PeerPadding[:0], nil, c.PeerPadding, nil); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
c.PeerPadding = nil
|
||||
}
|
||||
if c.input.Len() > 0 {
|
||||
return c.input.Read(b)
|
||||
}
|
||||
h, l, err := ReadAndDecodeHeader(c.Conn) // l: 17~17000
|
||||
if err != nil {
|
||||
if c.Client != nil && strings.HasPrefix(err.Error(), "invalid header: ") { // client's 0-RTT
|
||||
c.Client.RWLock.Lock()
|
||||
if bytes.HasPrefix(c.UnitedKey, c.Client.PfsKey) {
|
||||
c.Client.Expire = time.Now() // expired
|
||||
}
|
||||
c.Client.RWLock.Unlock()
|
||||
return 0, errors.New("new handshake needed")
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
c.Client = nil
|
||||
peerData := make([]byte, l)
|
||||
if _, err := io.ReadFull(c.Conn, peerData); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dst := peerData[:l-16]
|
||||
if len(dst) <= len(b) {
|
||||
dst = b[:len(dst)] // avoids another copy()
|
||||
}
|
||||
var newGCM *GCM
|
||||
if bytes.Equal(c.PeerGCM.Nonce[:], MaxNonce) {
|
||||
newGCM = NewGCM(peerData, c.UnitedKey)
|
||||
}
|
||||
_, err = c.PeerGCM.Open(dst[:0], nil, peerData, h)
|
||||
if newGCM != nil {
|
||||
c.PeerGCM = newGCM
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(dst) > len(b) {
|
||||
c.input.Reset(dst[copy(b, dst):])
|
||||
dst = b // for len(dst)
|
||||
}
|
||||
return len(dst), nil
|
||||
}
|
||||
|
||||
type GCM struct {
|
||||
cipher.AEAD
|
||||
Nonce [12]byte
|
||||
}
|
||||
|
||||
func NewGCM(ctx, key []byte) *GCM {
|
||||
k := make([]byte, 32)
|
||||
blake3.DeriveKey(k, string(ctx), key)
|
||||
block, _ := aes.NewCipher(k)
|
||||
aead, _ := cipher.NewGCM(block)
|
||||
return &GCM{AEAD: aead}
|
||||
//chacha20poly1305.New()
|
||||
}
|
||||
|
||||
func (a *GCM) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
|
||||
if nonce == nil {
|
||||
nonce = IncreaseNonce(a.Nonce[:])
|
||||
}
|
||||
return a.AEAD.Seal(dst, nonce, plaintext, additionalData)
|
||||
}
|
||||
|
||||
func (a *GCM) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
||||
if nonce == nil {
|
||||
nonce = IncreaseNonce(a.Nonce[:])
|
||||
}
|
||||
return a.AEAD.Open(dst, nonce, ciphertext, additionalData)
|
||||
}
|
||||
|
||||
func IncreaseNonce(nonce []byte) []byte {
|
||||
for i := 0; i < 12; i++ {
|
||||
nonce[11-i]++
|
||||
if nonce[11-i] != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nonce
|
||||
}
|
||||
|
||||
var MaxNonce = bytes.Repeat([]byte{255}, 12)
|
||||
|
||||
func EncodeLength(l int) []byte {
|
||||
return []byte{byte(l >> 8), byte(l)}
|
||||
}
|
||||
|
||||
func DecodeLength(b []byte) int {
|
||||
return int(b[0])<<8 | int(b[1])
|
||||
}
|
||||
|
||||
func EncodeHeader(h []byte, l int) {
|
||||
h[0] = 23
|
||||
h[1] = 3
|
||||
h[2] = 3
|
||||
h[3] = byte(l >> 8)
|
||||
h[4] = byte(l)
|
||||
}
|
||||
|
||||
func DecodeHeader(h []byte) (l int, err error) {
|
||||
l = int(h[3])<<8 | int(h[4])
|
||||
if h[0] != 23 || h[1] != 3 || h[2] != 3 {
|
||||
l = 0
|
||||
}
|
||||
if l < 17 || l > 17000 { // TODO: TLSv1.3 max length
|
||||
err = fmt.Errorf("invalid header: %v", h[:5]) // DO NOT CHANGE: relied by client's Read()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ReadAndDecodeHeader(conn net.Conn) (h []byte, l int, err error) {
|
||||
h = make([]byte, 5)
|
||||
if _, err = io.ReadFull(conn, h); err != nil {
|
||||
return
|
||||
}
|
||||
l, err = DecodeHeader(h)
|
||||
return
|
||||
}
|
||||
|
||||
func ReadAndDiscardPaddings(conn net.Conn) (h []byte, l int, err error) {
|
||||
for {
|
||||
if h, l, err = ReadAndDecodeHeader(conn); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = io.ReadFull(conn, make([]byte, l)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func randBetween(from int64, to int64) int64 {
|
||||
if from == to {
|
||||
return from
|
||||
}
|
||||
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
|
||||
return from + bigInt.Int64()
|
||||
}
|
||||
23
transport/vless/encryption/doc.go
Normal file
23
transport/vless/encryption/doc.go
Normal file
@@ -0,0 +1,23 @@
|
||||
// Package encryption copy and modify from xray-core
|
||||
// https://github.com/XTLS/Xray-core/commit/f61c14e9c63dc41a8a09135db3aea337974f3f37
|
||||
// https://github.com/XTLS/Xray-core/commit/3e19bf9233bdd9bafc073a71c65b737cc1ffba5e
|
||||
// https://github.com/XTLS/Xray-core/commit/7ffb555fc8ec51bd1e3e60f26f1d6957984dba80
|
||||
// https://github.com/XTLS/Xray-core/commit/ec1cc35188c1a5f38a2ff75e88b5d043ffdc59da
|
||||
// https://github.com/XTLS/Xray-core/commit/5c611420487a92f931faefc01d4bf03869f477f6
|
||||
// https://github.com/XTLS/Xray-core/commit/23d7aad461d232bc5bed52dd6aaa731ecd88ad35
|
||||
// https://github.com/XTLS/Xray-core/commit/3c20bddfcfd8999be5f9a2ac180dc959950e4c61
|
||||
// https://github.com/XTLS/Xray-core/commit/1720be168fa069332c418503d30341fc6e01df7f
|
||||
// https://github.com/XTLS/Xray-core/commit/0fd7691d6b28e05922d7a5a9313d97745a51ea63
|
||||
// https://github.com/XTLS/Xray-core/commit/09cc92c61d9067e0d65c1cae9124664ecfc78f43
|
||||
// https://github.com/XTLS/Xray-core/commit/2807ee432a1fbeb301815647189eacd650b12a8b
|
||||
// https://github.com/XTLS/Xray-core/commit/bfe4820f2f086daf639b1957eb23dc13c843cad1
|
||||
// https://github.com/XTLS/Xray-core/commit/d1fb48521271251a8c74bd64fcc2fc8700717a3b
|
||||
// https://github.com/XTLS/Xray-core/commit/49580705f6029648399304b816a2737f991582a8
|
||||
// https://github.com/XTLS/Xray-core/commit/84835bec7d0d8555d0dd30953ed26a272de814c4
|
||||
// https://github.com/XTLS/Xray-core/commit/373558ed7abdbac3de41745cf30ec04c9adde604
|
||||
// https://github.com/XTLS/Xray-core/commit/38cc306c955c362f044e074049a5e67b6b9fb389
|
||||
// https://github.com/XTLS/Xray-core/commit/b33555cc0a52d0af3c23d2af8fca42f8a685d9af
|
||||
// https://github.com/XTLS/Xray-core/commit/ad7140641c44239c9dcdc3d7215ea639b1f0841c
|
||||
// https://github.com/XTLS/Xray-core/commit/0199dea39988a1a1b846d0bf8598631bade40902
|
||||
// https://github.com/XTLS/Xray-core/commit/fce1195b60f48ca18a953dbd5c7d991869de9a5e
|
||||
package encryption
|
||||
104
transport/vless/encryption/factory.go
Normal file
104
transport/vless/encryption/factory.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewClient new client from encryption string
|
||||
// maybe return a nil *ClientInstance without any error, that means don't need to encrypt
|
||||
func NewClient(encryption string) (*ClientInstance, error) {
|
||||
switch encryption {
|
||||
case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility
|
||||
return nil, nil
|
||||
}
|
||||
if s := strings.Split(encryption, "."); len(s) >= 4 && s[0] == "mlkem768x25519plus" {
|
||||
var xorMode uint32
|
||||
switch s[1] {
|
||||
case "native":
|
||||
case "xorpub":
|
||||
xorMode = 1
|
||||
case "random":
|
||||
xorMode = 2
|
||||
default:
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
|
||||
}
|
||||
var seconds uint32
|
||||
switch s[2] {
|
||||
case "1rtt":
|
||||
case "0rtt":
|
||||
seconds = 1
|
||||
default:
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
|
||||
}
|
||||
var nfsPKeysBytes [][]byte
|
||||
for _, r := range s[3:] {
|
||||
b, err := base64.RawURLEncoding.DecodeString(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
|
||||
}
|
||||
if len(b) != X25519PasswordSize && len(b) != MLKEM768ClientLength {
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
|
||||
}
|
||||
nfsPKeysBytes = append(nfsPKeysBytes, b)
|
||||
}
|
||||
client := &ClientInstance{}
|
||||
if err := client.Init(nfsPKeysBytes, xorMode, seconds); err != nil {
|
||||
return nil, fmt.Errorf("failed to use encryption: %w", err)
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
|
||||
}
|
||||
|
||||
// NewServer new server from decryption string
|
||||
// maybe return a nil *ServerInstance without any error, that means don't need to decrypt
|
||||
func NewServer(decryption string) (*ServerInstance, error) {
|
||||
switch decryption {
|
||||
case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility
|
||||
return nil, nil
|
||||
}
|
||||
if s := strings.Split(decryption, "."); len(s) >= 4 && s[0] == "mlkem768x25519plus" {
|
||||
var xorMode uint32
|
||||
switch s[1] {
|
||||
case "native":
|
||||
case "xorpub":
|
||||
xorMode = 1
|
||||
case "random":
|
||||
xorMode = 2
|
||||
default:
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
|
||||
}
|
||||
var seconds uint32
|
||||
if s[2] != "1rtt" {
|
||||
t := strings.TrimSuffix(s[2], "s")
|
||||
if t == s[0] {
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
|
||||
}
|
||||
i, err := strconv.Atoi(t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
|
||||
}
|
||||
seconds = uint32(i)
|
||||
}
|
||||
var nfsSKeysBytes [][]byte
|
||||
for _, r := range s[3:] {
|
||||
b, err := base64.RawURLEncoding.DecodeString(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
|
||||
}
|
||||
if len(b) != X25519PrivateKeySize && len(b) != MLKEM768SeedLength {
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
|
||||
}
|
||||
nfsSKeysBytes = append(nfsSKeysBytes, b)
|
||||
}
|
||||
server := &ServerInstance{}
|
||||
if err := server.Init(nfsSKeysBytes, xorMode, seconds); err != nil {
|
||||
return nil, fmt.Errorf("failed to use decryption: %w", err)
|
||||
}
|
||||
return server, nil
|
||||
}
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
|
||||
}
|
||||
79
transport/vless/encryption/key.go
Normal file
79
transport/vless/encryption/key.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/metacubex/blake3"
|
||||
"github.com/metacubex/utls/mlkem"
|
||||
)
|
||||
|
||||
const MLKEM768SeedLength = mlkem.SeedSize
|
||||
const MLKEM768ClientLength = mlkem.EncapsulationKeySize768
|
||||
const X25519PasswordSize = 32
|
||||
const X25519PrivateKeySize = 32
|
||||
|
||||
func GenMLKEM768(seedStr string) (seedBase64, clientBase64, hash32Base64 string, err error) {
|
||||
var seed [MLKEM768SeedLength]byte
|
||||
if len(seedStr) > 0 {
|
||||
s, _ := base64.RawURLEncoding.DecodeString(seedStr)
|
||||
if len(s) != MLKEM768SeedLength {
|
||||
err = fmt.Errorf("invalid length of ML-KEM-768 seed: %s", seedStr)
|
||||
return
|
||||
}
|
||||
seed = [MLKEM768SeedLength]byte(s)
|
||||
} else {
|
||||
_, err = rand.Read(seed[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
key, _ := mlkem.NewDecapsulationKey768(seed[:])
|
||||
client := key.EncapsulationKey().Bytes()
|
||||
hash32 := blake3.Sum256(client)
|
||||
seedBase64 = base64.RawURLEncoding.EncodeToString(seed[:])
|
||||
clientBase64 = base64.RawURLEncoding.EncodeToString(client)
|
||||
hash32Base64 = base64.RawURLEncoding.EncodeToString(hash32[:])
|
||||
return
|
||||
}
|
||||
|
||||
func GenX25519(privateKeyStr string) (privateKeyBase64, passwordBase64, hash32Base64 string, err error) {
|
||||
var privateKey [X25519PrivateKeySize]byte
|
||||
if len(privateKeyStr) > 0 {
|
||||
s, _ := base64.RawURLEncoding.DecodeString(privateKeyStr)
|
||||
if len(s) != X25519PrivateKeySize {
|
||||
err = fmt.Errorf("invalid length of X25519 private key: %s", privateKeyStr)
|
||||
return
|
||||
}
|
||||
privateKey = [X25519PrivateKeySize]byte(s)
|
||||
} else {
|
||||
_, err = rand.Read(privateKey[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid generating equivalent X25519 private keys
|
||||
// https://github.com/XTLS/Xray-core/pull/1747
|
||||
//
|
||||
// Modify random bytes using algorithm described at:
|
||||
// https://cr.yp.to/ecdh.html.
|
||||
privateKey[0] &= 248
|
||||
privateKey[31] &= 127
|
||||
privateKey[31] |= 64
|
||||
|
||||
key, err := ecdh.X25519().NewPrivateKey(privateKey[:])
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
password := key.PublicKey().Bytes()
|
||||
hash32 := blake3.Sum256(password)
|
||||
privateKeyBase64 = base64.RawURLEncoding.EncodeToString(privateKey[:])
|
||||
passwordBase64 = base64.RawURLEncoding.EncodeToString(password)
|
||||
hash32Base64 = base64.RawURLEncoding.EncodeToString(hash32[:])
|
||||
return
|
||||
}
|
||||
281
transport/vless/encryption/server.go
Normal file
281
transport/vless/encryption/server.go
Normal file
@@ -0,0 +1,281 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdh"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/blake3"
|
||||
"github.com/metacubex/utls/mlkem"
|
||||
)
|
||||
|
||||
type ServerSession struct {
|
||||
Expire time.Time
|
||||
PfsKey []byte
|
||||
NfsKeys sync.Map
|
||||
}
|
||||
|
||||
type ServerInstance struct {
|
||||
NfsSKeys []any
|
||||
NfsPKeysBytes [][]byte
|
||||
Hash32s [][32]byte
|
||||
RelaysLength int
|
||||
XorMode uint32
|
||||
Seconds uint32
|
||||
|
||||
RWLock sync.RWMutex
|
||||
Sessions map[[16]byte]*ServerSession
|
||||
Closed bool
|
||||
}
|
||||
|
||||
func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (err error) {
|
||||
if i.NfsSKeys != nil {
|
||||
err = errors.New("already initialized")
|
||||
return
|
||||
}
|
||||
l := len(nfsSKeysBytes)
|
||||
if l == 0 {
|
||||
err = errors.New("empty nfsSKeysBytes")
|
||||
return
|
||||
}
|
||||
i.NfsSKeys = make([]any, l)
|
||||
i.NfsPKeysBytes = make([][]byte, l)
|
||||
i.Hash32s = make([][32]byte, l)
|
||||
for j, k := range nfsSKeysBytes {
|
||||
if len(k) == 32 {
|
||||
if i.NfsSKeys[j], err = ecdh.X25519().NewPrivateKey(k); err != nil {
|
||||
return
|
||||
}
|
||||
i.NfsPKeysBytes[j] = i.NfsSKeys[j].(*ecdh.PrivateKey).PublicKey().Bytes()
|
||||
i.RelaysLength += 32 + 32
|
||||
} else {
|
||||
if i.NfsSKeys[j], err = mlkem.NewDecapsulationKey768(k); err != nil {
|
||||
return
|
||||
}
|
||||
i.NfsPKeysBytes[j] = i.NfsSKeys[j].(*mlkem.DecapsulationKey768).EncapsulationKey().Bytes()
|
||||
i.RelaysLength += 1088 + 32
|
||||
}
|
||||
i.Hash32s[j] = blake3.Sum256(i.NfsPKeysBytes[j])
|
||||
}
|
||||
i.RelaysLength -= 32
|
||||
i.XorMode = xorMode
|
||||
if seconds > 0 {
|
||||
i.Seconds = seconds
|
||||
i.Sessions = make(map[[16]byte]*ServerSession)
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(time.Minute)
|
||||
i.RWLock.Lock()
|
||||
if i.Closed {
|
||||
i.RWLock.Unlock()
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
for ticket, session := range i.Sessions {
|
||||
if now.After(session.Expire) {
|
||||
delete(i.Sessions, ticket)
|
||||
}
|
||||
}
|
||||
i.RWLock.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (i *ServerInstance) Close() (err error) {
|
||||
i.RWLock.Lock()
|
||||
i.Closed = true
|
||||
i.RWLock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
if i.NfsSKeys == nil {
|
||||
return nil, errors.New("uninitialized")
|
||||
}
|
||||
c := &CommonConn{Conn: conn}
|
||||
|
||||
ivAndRelays := make([]byte, 16+i.RelaysLength)
|
||||
if _, err := io.ReadFull(conn, ivAndRelays); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iv := ivAndRelays[:16]
|
||||
relays := ivAndRelays[16:]
|
||||
var nfsKey []byte
|
||||
var lastCTR cipher.Stream
|
||||
for j, k := range i.NfsSKeys {
|
||||
if lastCTR != nil {
|
||||
lastCTR.XORKeyStream(relays, relays[:32]) // recover this relay
|
||||
}
|
||||
var index = 32
|
||||
if _, ok := k.(*mlkem.DecapsulationKey768); ok {
|
||||
index = 1088
|
||||
}
|
||||
if i.XorMode > 0 {
|
||||
NewCTR(i.NfsPKeysBytes[j], iv).XORKeyStream(relays, relays[:index]) // we don't use buggy elligator, because we have PSK :)
|
||||
}
|
||||
if k, ok := k.(*ecdh.PrivateKey); ok {
|
||||
publicKey, err := ecdh.X25519().NewPublicKey(relays[:index])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nfsKey, err = k.ECDH(publicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if k, ok := k.(*mlkem.DecapsulationKey768); ok {
|
||||
var err error
|
||||
nfsKey, err = k.Decapsulate(relays[:index])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if j == len(i.NfsSKeys)-1 {
|
||||
break
|
||||
}
|
||||
relays = relays[index:]
|
||||
lastCTR = NewCTR(nfsKey, iv)
|
||||
lastCTR.XORKeyStream(relays, relays[:32])
|
||||
if !bytes.Equal(relays[:32], i.Hash32s[j+1][:]) {
|
||||
return nil, fmt.Errorf("unexpected hash32: %v", relays[:32])
|
||||
}
|
||||
relays = relays[32:]
|
||||
}
|
||||
nfsGCM := NewGCM(iv, nfsKey)
|
||||
|
||||
encryptedLength := make([]byte, 18)
|
||||
if _, err := io.ReadFull(conn, encryptedLength); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := nfsGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
length := DecodeLength(encryptedLength[:2])
|
||||
|
||||
if length == 32 {
|
||||
if i.Seconds == 0 {
|
||||
return nil, errors.New("0-RTT is not allowed")
|
||||
}
|
||||
encryptedTicket := make([]byte, 32)
|
||||
if _, err := io.ReadFull(conn, encryptedTicket); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ticket, err := nfsGCM.Open(nil, nil, encryptedTicket, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i.RWLock.RLock()
|
||||
s := i.Sessions[[16]byte(ticket)]
|
||||
i.RWLock.RUnlock()
|
||||
if s == nil {
|
||||
noises := make([]byte, randBetween(1268, 2268)) // matches 1-RTT's server hello length for "random", though it is not important, just for example
|
||||
var err error
|
||||
for err == nil {
|
||||
rand.Read(noises)
|
||||
_, err = DecodeHeader(noises)
|
||||
}
|
||||
conn.Write(noises) // make client do new handshake
|
||||
return nil, errors.New("expired ticket")
|
||||
}
|
||||
if _, loaded := s.NfsKeys.LoadOrStore([32]byte(nfsKey), true); loaded { // prevents bad client also
|
||||
return nil, errors.New("replay detected")
|
||||
}
|
||||
c.UnitedKey = append(s.PfsKey, nfsKey...) // the same nfsKey links the upload & download (prevents server -> client's another request)
|
||||
c.PreWrite = make([]byte, 16)
|
||||
rand.Read(c.PreWrite) // always trust yourself, not the client (also prevents being parsed as TLS thus causing false interruption for "native" and "xorpub")
|
||||
c.GCM = NewGCM(c.PreWrite, c.UnitedKey)
|
||||
c.PeerGCM = NewGCM(encryptedTicket, c.UnitedKey) // unchangeable ctx (prevents server -> server), and different ctx length for upload / download (prevents client -> client)
|
||||
if i.XorMode == 2 {
|
||||
c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, c.PreWrite), NewCTR(c.UnitedKey, iv), 16, 0) // it doesn't matter if the attacker sends client's iv back to the client
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
if length < 1184+32+16 { // client may send more public keys in the future's version
|
||||
return nil, errors.New("too short length")
|
||||
}
|
||||
encryptedPfsPublicKey := make([]byte, length)
|
||||
if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := nfsGCM.Open(encryptedPfsPublicKey[:0], nil, encryptedPfsPublicKey, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mlkem768EKey, err := mlkem.NewEncapsulationKey768(encryptedPfsPublicKey[:1184])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mlkem768Key, encapsulatedPfsKey := mlkem768EKey.Encapsulate()
|
||||
peerX25519PKey, err := ecdh.X25519().NewPublicKey(encryptedPfsPublicKey[1184 : 1184+32])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader)
|
||||
x25519Key, err := x25519SKey.ECDH(peerX25519PKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pfsKey := make([]byte, 32+32) // no more capacity
|
||||
copy(pfsKey, mlkem768Key)
|
||||
copy(pfsKey[32:], x25519Key)
|
||||
pfsPublicKey := append(encapsulatedPfsKey, x25519SKey.PublicKey().Bytes()...)
|
||||
c.UnitedKey = append(pfsKey, nfsKey...)
|
||||
c.GCM = NewGCM(pfsPublicKey, c.UnitedKey)
|
||||
c.PeerGCM = NewGCM(encryptedPfsPublicKey[:1184+32], c.UnitedKey)
|
||||
ticket := make([]byte, 16)
|
||||
rand.Read(ticket)
|
||||
copy(ticket, EncodeLength(int(i.Seconds*4/5)))
|
||||
|
||||
pfsKeyExchangeLength := 1088 + 32 + 16
|
||||
encryptedTicketLength := 32
|
||||
paddingLength := int(randBetween(100, 1000))
|
||||
serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength)
|
||||
nfsGCM.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil)
|
||||
c.GCM.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil)
|
||||
padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:]
|
||||
c.GCM.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
|
||||
c.GCM.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
|
||||
|
||||
if _, err := conn.Write(serverHello); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
|
||||
|
||||
if i.Seconds > 0 {
|
||||
i.RWLock.Lock()
|
||||
i.Sessions[[16]byte(ticket)] = &ServerSession{
|
||||
Expire: time.Now().Add(time.Duration(i.Seconds) * time.Second),
|
||||
PfsKey: pfsKey,
|
||||
}
|
||||
i.RWLock.Unlock()
|
||||
}
|
||||
|
||||
// important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern
|
||||
if _, err := io.ReadFull(conn, encryptedLength); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := nfsGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptedPadding := make([]byte, DecodeLength(encryptedLength[:2]))
|
||||
if _, err := io.ReadFull(conn, encryptedPadding); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := nfsGCM.Open(encryptedPadding[:0], nil, encryptedPadding, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if i.XorMode == 2 {
|
||||
c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, ticket), NewCTR(c.UnitedKey, iv), 0, 0)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
93
transport/vless/encryption/xor.go
Normal file
93
transport/vless/encryption/xor.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"net"
|
||||
|
||||
"github.com/metacubex/blake3"
|
||||
)
|
||||
|
||||
func NewCTR(key, iv []byte) cipher.Stream {
|
||||
k := make([]byte, 32)
|
||||
blake3.DeriveKey(k, "VLESS", key) // avoids using key directly
|
||||
block, _ := aes.NewCipher(k)
|
||||
return cipher.NewCTR(block, iv)
|
||||
//chacha20.NewUnauthenticatedCipher()
|
||||
}
|
||||
|
||||
type XorConn struct {
|
||||
net.Conn
|
||||
CTR cipher.Stream
|
||||
PeerCTR cipher.Stream
|
||||
OutSkip int
|
||||
OutHeader []byte
|
||||
InSkip int
|
||||
InHeader []byte
|
||||
}
|
||||
|
||||
func NewXorConn(conn net.Conn, ctr, peerCTR cipher.Stream, outSkip, inSkip int) *XorConn {
|
||||
return &XorConn{
|
||||
Conn: conn,
|
||||
CTR: ctr,
|
||||
PeerCTR: peerCTR,
|
||||
OutSkip: outSkip,
|
||||
OutHeader: make([]byte, 0, 5), // important
|
||||
InSkip: inSkip,
|
||||
InHeader: make([]byte, 0, 5), // important
|
||||
}
|
||||
}
|
||||
|
||||
func (c *XorConn) Write(b []byte) (int, error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
for p := b; ; {
|
||||
if len(p) <= c.OutSkip {
|
||||
c.OutSkip -= len(p)
|
||||
break
|
||||
}
|
||||
p = p[c.OutSkip:]
|
||||
c.OutSkip = 0
|
||||
need := 5 - len(c.OutHeader)
|
||||
if len(p) < need {
|
||||
c.OutHeader = append(c.OutHeader, p...)
|
||||
c.CTR.XORKeyStream(p, p)
|
||||
break
|
||||
}
|
||||
c.OutSkip, _ = DecodeHeader(append(c.OutHeader, p[:need]...))
|
||||
c.OutHeader = c.OutHeader[:0]
|
||||
c.CTR.XORKeyStream(p[:need], p[:need])
|
||||
p = p[need:]
|
||||
}
|
||||
if _, err := c.Conn.Write(b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (c *XorConn) Read(b []byte) (int, error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
n, err := c.Conn.Read(b)
|
||||
for p := b[:n]; ; {
|
||||
if len(p) <= c.InSkip {
|
||||
c.InSkip -= len(p)
|
||||
break
|
||||
}
|
||||
p = p[c.InSkip:]
|
||||
c.InSkip = 0
|
||||
need := 5 - len(c.InHeader)
|
||||
if len(p) < need {
|
||||
c.PeerCTR.XORKeyStream(p, p)
|
||||
c.InHeader = append(c.InHeader, p...)
|
||||
break
|
||||
}
|
||||
c.PeerCTR.XORKeyStream(p[:need], p[:need])
|
||||
c.InSkip, _ = DecodeHeader(append(c.InHeader, p[:need]...))
|
||||
c.InHeader = c.InHeader[:0]
|
||||
p = p[need:]
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
57
transport/vless/packet.go
Normal file
57
transport/vless/packet.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package vless
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/metacubex/mihomo/common/pool"
|
||||
)
|
||||
|
||||
type PacketConn struct {
|
||||
net.Conn
|
||||
rAddr net.Addr
|
||||
}
|
||||
|
||||
func (c *PacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
err := binary.Write(c.Conn, binary.BigEndian, uint16(len(b)))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
|
||||
func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
var length uint16
|
||||
err := binary.Read(c.Conn, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if len(b) < int(length) {
|
||||
return 0, nil, io.ErrShortBuffer
|
||||
}
|
||||
n, err := io.ReadFull(c.Conn, b[:length])
|
||||
return n, c.rAddr, err
|
||||
}
|
||||
|
||||
func (c *PacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) {
|
||||
var length uint16
|
||||
err = binary.Read(c.Conn, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
readBuf := pool.Get(int(length))
|
||||
put = func() {
|
||||
_ = pool.Put(readBuf)
|
||||
}
|
||||
n, err := io.ReadFull(c.Conn, readBuf)
|
||||
if err != nil {
|
||||
put()
|
||||
put = nil
|
||||
return
|
||||
}
|
||||
data = readBuf[:n]
|
||||
addr = c.rAddr
|
||||
return
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package vision
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/subtle"
|
||||
gotls "crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -12,7 +11,6 @@ import (
|
||||
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
@@ -23,15 +21,16 @@ var (
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
net.Conn // should be *vless.Conn
|
||||
N.ExtendedReader
|
||||
N.ExtendedWriter
|
||||
upstream net.Conn
|
||||
userUUID *uuid.UUID
|
||||
|
||||
tlsConn net.Conn
|
||||
input *bytes.Reader
|
||||
rawInput *bytes.Buffer
|
||||
// tlsConn and it's internal variables
|
||||
tlsConn net.Conn // maybe [*tls.Conn] or other tls-like conn
|
||||
netConn net.Conn // tlsConn.NetConn()
|
||||
input *bytes.Reader // &tlsConn.input or nil
|
||||
rawInput *bytes.Buffer // &tlsConn.rawInput or nil
|
||||
|
||||
needHandshake bool
|
||||
packetsToFilter int
|
||||
@@ -145,7 +144,7 @@ func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error {
|
||||
}
|
||||
if vc.input == nil && vc.rawInput == nil {
|
||||
vc.readProcess = false
|
||||
vc.ExtendedReader = N.NewExtendedReader(vc.Conn)
|
||||
vc.ExtendedReader = N.NewExtendedReader(vc.netConn)
|
||||
log.Debugln("XTLS Vision direct read start")
|
||||
}
|
||||
if needReturn {
|
||||
@@ -171,7 +170,7 @@ func (vc *Conn) WriteBuffer(buffer *buf.Buffer) (err error) {
|
||||
if vc.needHandshake {
|
||||
vc.needHandshake = false
|
||||
if buffer.IsEmpty() {
|
||||
ApplyPadding(buffer, commandPaddingContinue, vc.userUUID, false)
|
||||
ApplyPadding(buffer, commandPaddingContinue, vc.userUUID, true) // we do a long padding to hide vless header
|
||||
} else {
|
||||
vc.FilterTLS(buffer.Bytes())
|
||||
ApplyPadding(buffer, commandPaddingContinue, vc.userUUID, vc.isTLS)
|
||||
@@ -181,72 +180,47 @@ func (vc *Conn) WriteBuffer(buffer *buf.Buffer) (err error) {
|
||||
buffer.Release()
|
||||
return err
|
||||
}
|
||||
switch underlying := vc.tlsConn.(type) {
|
||||
case *gotls.Conn:
|
||||
if underlying.ConnectionState().Version != gotls.VersionTLS13 {
|
||||
buffer.Release()
|
||||
return ErrNotTLS13
|
||||
}
|
||||
case *tlsC.UConn:
|
||||
if underlying.ConnectionState().Version != tlsC.VersionTLS13 {
|
||||
buffer.Release()
|
||||
return ErrNotTLS13
|
||||
}
|
||||
err = vc.checkTLSVersion()
|
||||
if err != nil {
|
||||
buffer.Release()
|
||||
return err
|
||||
}
|
||||
vc.tlsConn = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
if vc.writeFilterApplicationData {
|
||||
buffer2 := ReshapeBuffer(buffer)
|
||||
defer buffer2.Release()
|
||||
vc.FilterTLS(buffer.Bytes())
|
||||
command := commandPaddingContinue
|
||||
if !vc.isTLS {
|
||||
command = commandPaddingEnd
|
||||
|
||||
// disable XTLS
|
||||
//vc.readProcess = false
|
||||
vc.writeFilterApplicationData = false
|
||||
vc.packetsToFilter = 0
|
||||
} else if buffer.Len() > 6 && bytes.Equal(buffer.To(3), tlsApplicationDataStart) || vc.packetsToFilter <= 0 {
|
||||
command = commandPaddingEnd
|
||||
if vc.enableXTLS {
|
||||
command = commandPaddingDirect
|
||||
vc.writeDirect = true
|
||||
}
|
||||
vc.writeFilterApplicationData = false
|
||||
}
|
||||
ApplyPadding(buffer, command, nil, vc.isTLS)
|
||||
err = vc.ExtendedWriter.WriteBuffer(buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if vc.writeDirect {
|
||||
vc.ExtendedWriter = N.NewExtendedWriter(vc.Conn)
|
||||
log.Debugln("XTLS Vision direct write start")
|
||||
//time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
if buffer2 != nil {
|
||||
if vc.writeDirect || !vc.isTLS {
|
||||
return vc.ExtendedWriter.WriteBuffer(buffer2)
|
||||
}
|
||||
vc.FilterTLS(buffer2.Bytes())
|
||||
command = commandPaddingContinue
|
||||
if buffer2.Len() > 6 && bytes.Equal(buffer2.To(3), tlsApplicationDataStart) || vc.packetsToFilter <= 0 {
|
||||
command = commandPaddingEnd
|
||||
if vc.enableXTLS {
|
||||
command = commandPaddingDirect
|
||||
vc.writeDirect = true
|
||||
buffers := vc.ReshapeBuffer(buffer)
|
||||
applyPadding := true
|
||||
for i, buffer := range buffers {
|
||||
command := commandPaddingContinue
|
||||
if applyPadding {
|
||||
if vc.isTLS && buffer.Len() > 6 && bytes.Equal(buffer.To(3), tlsApplicationDataStart) {
|
||||
command = commandPaddingEnd
|
||||
if vc.enableXTLS {
|
||||
command = commandPaddingDirect
|
||||
vc.writeDirect = true
|
||||
}
|
||||
vc.writeFilterApplicationData = false
|
||||
applyPadding = false
|
||||
} else if !vc.isTLS12orAbove && vc.packetsToFilter <= 1 {
|
||||
command = commandPaddingEnd
|
||||
vc.writeFilterApplicationData = false
|
||||
applyPadding = false
|
||||
}
|
||||
vc.writeFilterApplicationData = false
|
||||
ApplyPadding(buffer, command, nil, vc.isTLS)
|
||||
}
|
||||
ApplyPadding(buffer2, command, nil, vc.isTLS)
|
||||
err = vc.ExtendedWriter.WriteBuffer(buffer2)
|
||||
if vc.writeDirect {
|
||||
vc.ExtendedWriter = N.NewExtendedWriter(vc.Conn)
|
||||
|
||||
err = vc.ExtendedWriter.WriteBuffer(buffer)
|
||||
if err != nil {
|
||||
buf.ReleaseMulti(buffers[i:]) // release unwritten buffers
|
||||
return
|
||||
}
|
||||
if command == commandPaddingDirect {
|
||||
vc.ExtendedWriter = N.NewExtendedWriter(vc.netConn)
|
||||
log.Debugln("XTLS Vision direct write start")
|
||||
//time.Sleep(10 * time.Millisecond)
|
||||
//time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
return err
|
||||
@@ -275,9 +249,9 @@ func (vc *Conn) NeedHandshake() bool {
|
||||
func (vc *Conn) Upstream() any {
|
||||
if vc.writeDirect ||
|
||||
vc.readLastCommand == commandPaddingDirect {
|
||||
return vc.Conn
|
||||
return vc.netConn
|
||||
}
|
||||
return vc.upstream
|
||||
return vc.Conn
|
||||
}
|
||||
|
||||
func (vc *Conn) ReaderPossiblyReplaceable() bool {
|
||||
@@ -302,3 +276,10 @@ func (vc *Conn) WriterReplaceable() bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (vc *Conn) Close() error {
|
||||
if vc.ReaderReplaceable() || vc.WriterReplaceable() { // ignore send closeNotify alert in tls.Conn
|
||||
return vc.netConn.Close()
|
||||
}
|
||||
return vc.Conn.Close()
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
N "github.com/metacubex/sing/common/network"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/metacubex/randv2"
|
||||
@@ -19,30 +20,6 @@ const (
|
||||
commandPaddingDirect byte = 0x02
|
||||
)
|
||||
|
||||
func WriteWithPadding(buffer *buf.Buffer, p []byte, command byte, userUUID *uuid.UUID, paddingTLS bool) {
|
||||
contentLen := int32(len(p))
|
||||
var paddingLen int32
|
||||
if contentLen < 900 {
|
||||
if paddingTLS {
|
||||
//log.Debugln("long padding")
|
||||
paddingLen = randv2.Int32N(500) + 900 - contentLen
|
||||
} else {
|
||||
paddingLen = randv2.Int32N(256)
|
||||
}
|
||||
}
|
||||
if userUUID != nil {
|
||||
buffer.Write(userUUID.Bytes())
|
||||
}
|
||||
|
||||
buffer.WriteByte(command)
|
||||
binary.BigEndian.PutUint16(buffer.Extend(2), uint16(contentLen))
|
||||
binary.BigEndian.PutUint16(buffer.Extend(2), uint16(paddingLen))
|
||||
buffer.Write(p)
|
||||
|
||||
buffer.Extend(int(paddingLen))
|
||||
log.Debugln("XTLS Vision write padding1: command=%v, payloadLen=%v, paddingLen=%v", command, contentLen, paddingLen)
|
||||
}
|
||||
|
||||
func ApplyPadding(buffer *buf.Buffer, command byte, userUUID *uuid.UUID, paddingTLS bool) {
|
||||
contentLen := int32(buffer.Len())
|
||||
var paddingLen int32
|
||||
@@ -63,19 +40,20 @@ func ApplyPadding(buffer *buf.Buffer, command byte, userUUID *uuid.UUID, padding
|
||||
}
|
||||
|
||||
buffer.Extend(int(paddingLen))
|
||||
log.Debugln("XTLS Vision write padding2: command=%d, payloadLen=%d, paddingLen=%d", command, contentLen, paddingLen)
|
||||
log.Debugln("XTLS Vision write padding: command=%d, payloadLen=%d, paddingLen=%d", command, contentLen, paddingLen)
|
||||
}
|
||||
|
||||
func ReshapeBuffer(buffer *buf.Buffer) *buf.Buffer {
|
||||
if buffer.Len() <= buf.BufferSize-PaddingHeaderLen {
|
||||
return nil
|
||||
func (vc *Conn) ReshapeBuffer(buffer *buf.Buffer) []*buf.Buffer {
|
||||
const xrayBufSize = 8192
|
||||
if buffer.Len() <= xrayBufSize-PaddingHeaderLen {
|
||||
return []*buf.Buffer{buffer}
|
||||
}
|
||||
cutAt := bytes.LastIndex(buffer.Bytes(), tlsApplicationDataStart)
|
||||
if cutAt == -1 {
|
||||
cutAt = buf.BufferSize / 2
|
||||
cutAt = xrayBufSize / 2
|
||||
}
|
||||
buffer2 := buf.New()
|
||||
buffer2 := N.NewReadWaitOptions(nil, vc).NewBuffer() // ensure the new buffer can send used in vc.WriteBuffer
|
||||
buffer2.Write(buffer.From(cutAt))
|
||||
buffer.Truncate(cutAt)
|
||||
return buffer2
|
||||
return []*buf.Buffer{buffer, buffer2}
|
||||
}
|
||||
|
||||
@@ -12,24 +12,20 @@ import (
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
"github.com/metacubex/mihomo/transport/vless/encryption"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/metacubex/sing/common"
|
||||
)
|
||||
|
||||
var ErrNotTLS13 = errors.New("XTLS Vision based on TLS 1.3 outer connection")
|
||||
|
||||
type connWithUpstream interface {
|
||||
net.Conn
|
||||
common.WithUpstream
|
||||
}
|
||||
|
||||
func NewConn(conn connWithUpstream, userUUID *uuid.UUID) (*Conn, error) {
|
||||
func NewConn(conn net.Conn, tlsConn net.Conn, userUUID *uuid.UUID) (*Conn, error) {
|
||||
c := &Conn{
|
||||
ExtendedReader: N.NewExtendedReader(conn),
|
||||
ExtendedWriter: N.NewExtendedWriter(conn),
|
||||
upstream: conn,
|
||||
Conn: conn,
|
||||
userUUID: userUUID,
|
||||
tlsConn: tlsConn,
|
||||
packetsToFilter: 6,
|
||||
needHandshake: true,
|
||||
readProcess: true,
|
||||
@@ -38,32 +34,54 @@ func NewConn(conn connWithUpstream, userUUID *uuid.UUID) (*Conn, error) {
|
||||
}
|
||||
var t reflect.Type
|
||||
var p unsafe.Pointer
|
||||
switch underlying := conn.Upstream().(type) {
|
||||
switch underlying := tlsConn.(type) {
|
||||
case *gotls.Conn:
|
||||
//log.Debugln("type tls")
|
||||
c.Conn = underlying.NetConn()
|
||||
c.tlsConn = underlying
|
||||
c.netConn = underlying.NetConn()
|
||||
t = reflect.TypeOf(underlying).Elem()
|
||||
p = unsafe.Pointer(underlying)
|
||||
case *tlsC.Conn:
|
||||
//log.Debugln("type *tlsC.Conn")
|
||||
c.Conn = underlying.NetConn()
|
||||
c.tlsConn = underlying
|
||||
c.netConn = underlying.NetConn()
|
||||
t = reflect.TypeOf(underlying).Elem()
|
||||
p = unsafe.Pointer(underlying)
|
||||
case *tlsC.UConn:
|
||||
//log.Debugln("type *tlsC.UConn")
|
||||
c.Conn = underlying.NetConn()
|
||||
c.tlsConn = underlying
|
||||
c.netConn = underlying.NetConn()
|
||||
t = reflect.TypeOf(underlying.Conn).Elem()
|
||||
//log.Debugln("t:%v", t)
|
||||
p = unsafe.Pointer(underlying.Conn)
|
||||
case *encryption.CommonConn:
|
||||
//log.Debugln("type *encryption.CommonConn")
|
||||
c.netConn = underlying.Conn
|
||||
t = reflect.TypeOf(underlying).Elem()
|
||||
p = unsafe.Pointer(underlying)
|
||||
default:
|
||||
return nil, fmt.Errorf(`failed to use vision, maybe "security" is not "tls" or "utls"`)
|
||||
}
|
||||
i, _ := t.FieldByName("input")
|
||||
r, _ := t.FieldByName("rawInput")
|
||||
c.input = (*bytes.Reader)(unsafe.Add(p, i.Offset))
|
||||
c.rawInput = (*bytes.Buffer)(unsafe.Add(p, r.Offset))
|
||||
if i, ok := t.FieldByName("input"); ok {
|
||||
c.input = (*bytes.Reader)(unsafe.Add(p, i.Offset))
|
||||
}
|
||||
if r, ok := t.FieldByName("rawInput"); ok {
|
||||
c.rawInput = (*bytes.Buffer)(unsafe.Add(p, r.Offset))
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (vc *Conn) checkTLSVersion() error {
|
||||
switch underlying := vc.tlsConn.(type) {
|
||||
case *gotls.Conn:
|
||||
if underlying.ConnectionState().Version != gotls.VersionTLS13 {
|
||||
return ErrNotTLS13
|
||||
}
|
||||
case *tlsC.Conn:
|
||||
if underlying.ConnectionState().Version != tlsC.VersionTLS13 {
|
||||
return ErrNotTLS13
|
||||
}
|
||||
case *tlsC.UConn:
|
||||
if underlying.ConnectionState().Version != tlsC.VersionTLS13 {
|
||||
return ErrNotTLS13
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -51,6 +51,10 @@ func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) {
|
||||
return newConn(conn, c, dst)
|
||||
}
|
||||
|
||||
func (c *Client) PacketConn(conn net.Conn, rAddr net.Addr) net.PacketConn {
|
||||
return &PacketConn{conn, rAddr}
|
||||
}
|
||||
|
||||
// NewClient return Client instance
|
||||
func NewClient(uuidStr string, addons *Addons) (*Client, error) {
|
||||
uid, err := utils.UUIDMap(uuidStr)
|
||||
|
||||
Reference in New Issue
Block a user