mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-16 06:20:28 +00:00
Compare commits
62
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc190f2d8e | ||
|
|
455197dd91 | ||
|
|
fab4bcc1ed | ||
|
|
b99c3e5657 | ||
|
|
583bb4a63f | ||
|
|
9cd9382e3d | ||
|
|
567500c4af | ||
|
|
5aefcb41fb | ||
|
|
be8009c625 | ||
|
|
8734774e4a | ||
|
|
1e036ce1c5 | ||
|
|
c815c2f2df | ||
|
|
986c512e0f | ||
|
|
711aea4e34 | ||
|
|
6412738486 | ||
|
|
ad2e4cb0e1 | ||
|
|
829d54d7be | ||
|
|
862631172d | ||
|
|
d27b3e46e2 | ||
|
|
da21a8f77f | ||
|
|
e10347bf01 | ||
|
|
26a022c905 | ||
|
|
95e9816223 | ||
|
|
3239d21168 | ||
|
|
06b4931743 | ||
|
|
6189d2bfd5 | ||
|
|
a0e9347f1b | ||
|
|
83cf229909 | ||
|
|
2249f8b5c6 | ||
|
|
fdb9b616fc | ||
|
|
d792fba59c | ||
|
|
55956f8d70 | ||
|
|
94ffd50060 | ||
|
|
c4dfcd4c1a | ||
|
|
cb8cd048c1 | ||
|
|
455f6bc2d5 | ||
|
|
ba538619eb | ||
|
|
d43a808ea5 | ||
|
|
ca4b156b57 | ||
|
|
aba22722a6 | ||
|
|
569459c54c | ||
|
|
2b42699623 | ||
|
|
66a8100737 | ||
|
|
36303694d1 | ||
|
|
4dcf802ae3 | ||
|
|
a2cec2e580 | ||
|
|
1cd7d25fec | ||
|
|
09002ab763 | ||
|
|
cb206ddc74 | ||
|
|
fa466f8174 | ||
|
|
e26f5e9548 | ||
|
|
787aa7677b | ||
|
|
d878fc83f8 | ||
|
|
ee2b2c5ab6 | ||
|
|
81d993f49d | ||
|
|
4c3842711d | ||
|
|
ab69985fcc | ||
|
|
56bb63668c | ||
|
|
359a28f876 | ||
|
|
da9ba693cb | ||
|
|
5488b86c67 | ||
|
|
a3c054a54c |
@@ -0,0 +1 @@
|
||||
powershell.exe -ExecutionPolicy Bypass -File ".\xray_no_window.ps1"
|
||||
@@ -0,0 +1 @@
|
||||
Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden
|
||||
@@ -0,0 +1 @@
|
||||
CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
echo "LATEST=$LATEST" >>${GITHUB_ENV}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
@@ -11,6 +11,7 @@ on:
|
||||
jobs:
|
||||
check-assets:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
steps:
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v5
|
||||
@@ -75,13 +76,14 @@ jobs:
|
||||
fail-fast: false
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
env:
|
||||
GOOS: ${{ matrix.goos}}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
CGO_ENABLED: 0
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Show workflow information
|
||||
run: |
|
||||
@@ -132,15 +134,17 @@ jobs:
|
||||
run: |
|
||||
mv -f resources/geo* build_assets/
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
|
||||
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
|
||||
cp .github/build/windows/* build_assets/
|
||||
fi
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
echo 'Adding Wintun into packages'
|
||||
if [[ ${GOARCH} == 'amd64' ]]; then
|
||||
mv resources/wintun/bin/amd64/wintun.dll build_assets/
|
||||
fi
|
||||
if [[ ${GOARCH} == '386' ]]; then
|
||||
mv resources/wintun/bin/x86/wintun.dll build_assets/
|
||||
fi
|
||||
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt
|
||||
mv resources/wintun/LICENSE.txt build_assets/LICENSE-Wintun
|
||||
fi
|
||||
|
||||
- name: Copy README.md & LICENSE
|
||||
|
||||
@@ -11,6 +11,7 @@ on:
|
||||
jobs:
|
||||
check-assets:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
steps:
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v5
|
||||
@@ -161,6 +162,7 @@ jobs:
|
||||
fail-fast: false
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
env:
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
@@ -168,7 +170,7 @@ jobs:
|
||||
CGO_ENABLED: 0
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Set up NDK
|
||||
if: matrix.goos == 'android'
|
||||
@@ -208,6 +210,9 @@ jobs:
|
||||
go build -o build_assets/xray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
||||
# The line below is for without running conhost.exe version. Commented for not being used. Provided for reference.
|
||||
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
|
||||
elif [[ ${GOOS} == 'android' ]]; then
|
||||
echo 'Building Xray for Android...'
|
||||
go build -o build_assets/xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid= -checklinkname=0" -v ./main
|
||||
else
|
||||
echo 'Building Xray...'
|
||||
if [[ ${GOARCH} == 'mips' || ${GOARCH} == 'mipsle' ]]; then
|
||||
@@ -236,8 +241,10 @@ jobs:
|
||||
run: |
|
||||
mv -f resources/geo* build_assets/
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
|
||||
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
|
||||
cp .github/build/windows/* build_assets/
|
||||
fi
|
||||
if [[ ${GOOS} == 'windows' ]]; then
|
||||
echo 'Adding Wintun into packages'
|
||||
if [[ ${GOARCH} == 'amd64' ]]; then
|
||||
mv resources/wintun/bin/amd64/wintun.dll build_assets/
|
||||
fi
|
||||
@@ -247,7 +254,7 @@ jobs:
|
||||
if [[ ${GOARCH} == 'arm64' ]]; then
|
||||
mv resources/wintun/bin/arm64/wintun.dll build_assets/
|
||||
fi
|
||||
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt
|
||||
mv resources/wintun/LICENSE.txt build_assets/LICENSE-Wintun
|
||||
fi
|
||||
|
||||
- name: Copy README.md & LICENSE
|
||||
|
||||
@@ -68,6 +68,9 @@ jobs:
|
||||
wintun:
|
||||
if: github.event.schedule == '30 22 * * *' || github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
ASSETVER: 0.14.1
|
||||
ASSETHASH: 07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51
|
||||
steps:
|
||||
- name: Restore Wintun Cache
|
||||
uses: actions/cache/restore@v5
|
||||
@@ -96,7 +99,6 @@ jobs:
|
||||
echo -e "Checking if wintun.dll for ${ARCHITECTURE} exists..."
|
||||
if [ -s "./resources/wintun/bin/${ARCHITECTURE}/wintun.dll" ]; then
|
||||
echo -e "wintun.dll for ${ARCHITECTURE} exists"
|
||||
continue
|
||||
else
|
||||
echo -e "wintun.dll for ${ARCHITECTURE} is missing"
|
||||
missing=true
|
||||
@@ -113,12 +115,17 @@ jobs:
|
||||
fi
|
||||
if [[ "$missing" == true ]]; then
|
||||
FILENAME=wintun.zip
|
||||
DOWNLOAD_FILE=wintun-0.14.1.zip
|
||||
DOWNLOAD_FILE=wintun-${ASSETVER}.zip
|
||||
echo -e "Downloading https://www.wintun.net/builds/${DOWNLOAD_FILE}..."
|
||||
curl -L "https://www.wintun.net/builds/${DOWNLOAD_FILE}" -o "${FILENAME}"
|
||||
echo -e "Unpacking wintun..."
|
||||
unzip -u ${FILENAME} -d resources/
|
||||
echo "unhit=true" >> $GITHUB_OUTPUT
|
||||
if [[ "$(sha256sum "./${FILENAME}" | awk -F ' ' '{print $1}')" == "${ASSETHASH}" ]]; then
|
||||
echo -e "Unpacking wintun..."
|
||||
unzip -u ${FILENAME} -d resources/
|
||||
echo "unhit=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo -e "Digest of ${FILENAME} mismatch."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Save Wintun Cache
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Test
|
||||
name: Tests and Checkings
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -8,6 +8,7 @@ on:
|
||||
jobs:
|
||||
check-assets:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
steps:
|
||||
- name: Restore Geodat Cache
|
||||
uses: actions/cache/restore@v5
|
||||
@@ -36,9 +37,10 @@ jobs:
|
||||
|
||||
check-proto:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
- name: Check Proto Version Header
|
||||
run: |
|
||||
head -n 4 core/config.pb.go > ref.txt
|
||||
@@ -50,8 +52,28 @@ jobs:
|
||||
fi
|
||||
done
|
||||
|
||||
check-format:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v7
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
cache: false
|
||||
- name: Check Format
|
||||
run: |
|
||||
go install -v mvdan.cc/gofumpt@latest
|
||||
go run ./infra/vformat/main.go -mode check -pwd ./
|
||||
|
||||
test:
|
||||
needs: check-assets
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -61,7 +83,7 @@ jobs:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
steps:
|
||||
- name: Checkout codebase
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)
|
||||
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
|
||||
- Magisk
|
||||
- [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk)
|
||||
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
|
||||
- Homebrew
|
||||
- `brew install xray`
|
||||
@@ -120,6 +119,7 @@
|
||||
- [XrayFA](https://github.com/Q7DF1/XrayFA)
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- [OneXray](https://github.com/OneXray/OneXray)
|
||||
- [AsteriskNG](https://github.com/Asterisk4Magisk/AsteriskNG)
|
||||
- iOS & macOS arm64 & tvOS
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973) | [Happ tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274)
|
||||
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
|
||||
@@ -145,6 +145,8 @@
|
||||
- [v2rayN](https://github.com/2dust/v2rayN)
|
||||
- [GenyConnect](https://github.com/genyleap/GenyConnect)
|
||||
- [OneXray](https://github.com/OneXray/OneXray)
|
||||
- HarmonyOS
|
||||
- [Hey](https://github.com/popsiclelmlm/Hey)
|
||||
|
||||
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
|
||||
|
||||
@@ -162,6 +164,7 @@
|
||||
- [xtls-sdk](https://github.com/remnawave/xtls-sdk)
|
||||
- [xtlsapi](https://github.com/hiddify/xtlsapi)
|
||||
- [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite)
|
||||
- [flutter_vless](https://github.com/XIIIFOX/flutter_vless)
|
||||
- [Xray-core-python](https://github.com/LorenEteval/Xray-core-python)
|
||||
- [xray-api](https://github.com/XVGuardian/xray-api)
|
||||
- [XrayR](https://github.com/XrayR-project/XrayR)
|
||||
@@ -183,6 +186,27 @@
|
||||
- [Xray-core v1.0.0](https://github.com/XTLS/Xray-core/releases/tag/v1.0.0) was forked from [v2fly-core 9a03cc5](https://github.com/v2fly/v2ray-core/commit/9a03cc5c98d04cc28320fcee26dbc236b3291256), and we have made & accumulated a huge number of enhancements over time, check [the release notes for each version](https://github.com/XTLS/Xray-core/releases).
|
||||
- For third-party projects used in [Xray-core](https://github.com/XTLS/Xray-core), check your local or [the latest go.mod](https://github.com/XTLS/Xray-core/blob/main/go.mod).
|
||||
|
||||
### Bundled Third-Party Components Redistribution
|
||||
|
||||
**Certain optional features dynamically load third-party components. These optional components are separate works distributed under their own licenses, and are bundled into the ZIP package for ease of use. Users may replace these components under the licenses from these components.**
|
||||
|
||||
These components include:
|
||||
|
||||
#### Wintun
|
||||
|
||||
This distribution contains unmodified official precompiled and pre-signed Wintun binaries.
|
||||
|
||||
- Project: Wintun
|
||||
- Copyright: Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved.
|
||||
- Redistribution License: Prebuilt Binaries License (PBL) bundled with official precompiled and pre-signed binaries from wintun.net
|
||||
- Component(s): wintun.dll
|
||||
- Source: https://www.wintun.net/
|
||||
- Included in:
|
||||
- Windows x86 (windows-32, win7-32)
|
||||
- Windows x86-64 (windows-64, win7-64)
|
||||
- Windows AArch64 (windows-arm64)
|
||||
- Notes: Wintun is an optional runtime-loaded component only used for TUN inbound functionality on supported Windows platforms.
|
||||
|
||||
## One-line Compilation
|
||||
|
||||
### Windows (PowerShell)
|
||||
@@ -206,6 +230,13 @@ Make sure that you are using the same Go version, and remember to set the git co
|
||||
CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid=" -v ./main
|
||||
```
|
||||
|
||||
For Android:
|
||||
|
||||
```bash
|
||||
GOOS=android GOARCH=arm64 CGO_ENABLED=1 CC=/path/to/aarch64-linux-android24-clang go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid= -checklinkname=0" -v ./main
|
||||
GOOS=android GOARCH=amd64 CGO_ENABLED=1 CC=/path/to/x86_64-linux-android24-clang go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid= -checklinkname=0" -v ./main
|
||||
```
|
||||
|
||||
If you are compiling a 32-bit MIPS/MIPSLE target, use this command instead:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -3,15 +3,15 @@ package commander
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/signal/done"
|
||||
core "github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/outbound"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
@@ -69,16 +69,15 @@ func (c *Commander) Start() error {
|
||||
}
|
||||
c.Unlock()
|
||||
|
||||
var listen = func(listener net.Listener) {
|
||||
listen := func(listener net.Listener) {
|
||||
if err := c.server.Serve(listener); err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to start grpc server")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if len(c.listen) > 0 {
|
||||
var addr net.Addr
|
||||
|
||||
|
||||
if strings.HasPrefix(c.listen, "/") || strings.HasPrefix(c.listen, "@") {
|
||||
addr = &net.UnixAddr{Name: c.listen, Net: "unix"}
|
||||
} else {
|
||||
@@ -89,7 +88,7 @@ func (c *Commander) Start() error {
|
||||
}
|
||||
addr = tcpAddr
|
||||
}
|
||||
l, err := internet.ListenSystem(context.Background(), addr, nil)
|
||||
l, err := internet.ListenSystem(context.Background(), addr, nil)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "API server failed to listen on ", c.listen)
|
||||
return err
|
||||
|
||||
@@ -139,7 +139,8 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
|
||||
|
||||
if lenAfter == 0 {
|
||||
if c.highWatermark >= minSizeForEmptyRebuild {
|
||||
errors.LogDebug(context.Background(), c.name,
|
||||
errors.LogDebug(
|
||||
context.Background(), c.name,
|
||||
" rebuilding empty cache map to reclaim memory.",
|
||||
" size_before_cleanup=", lenBefore,
|
||||
" peak_size_before_rebuild=", c.highWatermark,
|
||||
@@ -153,7 +154,8 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
|
||||
|
||||
if reductionFromPeak := c.highWatermark - lenAfter; reductionFromPeak > shrinkAbsoluteThreshold &&
|
||||
float64(reductionFromPeak) > float64(c.highWatermark)*shrinkRatioThreshold {
|
||||
errors.LogDebug(context.Background(), c.name,
|
||||
errors.LogDebug(
|
||||
context.Background(), c.name,
|
||||
" shrinking cache map to reclaim memory.",
|
||||
" new_size=", lenAfter,
|
||||
" peak_size_before_shrink=", c.highWatermark,
|
||||
@@ -165,7 +167,6 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
|
||||
c.highWatermark = lenAfter
|
||||
go c.migrate()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type migrationEntry struct {
|
||||
|
||||
+2
-2
@@ -86,7 +86,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
||||
return nil, errors.New("failed to create hosts").Base(err)
|
||||
}
|
||||
|
||||
var defaultTag = config.Tag
|
||||
defaultTag := config.Tag
|
||||
if len(config.Tag) == 0 {
|
||||
defaultTag = generateRandomTag()
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
||||
serveExpiredTTL = *ns.ServeExpiredTTL
|
||||
}
|
||||
|
||||
var tag = defaultTag
|
||||
tag := defaultTag
|
||||
if len(ns.Tag) > 0 {
|
||||
tag = ns.Tag
|
||||
}
|
||||
|
||||
@@ -127,15 +127,20 @@ func genEDNS0Options(clientIP net.IP, padding int) *dnsmessage.Resource {
|
||||
return opt
|
||||
}
|
||||
|
||||
func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) []*dnsRequest {
|
||||
func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) ([]*dnsRequest, error) {
|
||||
name, err := dnsmessage.NewName(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
qA := dnsmessage.Question{
|
||||
Name: dnsmessage.MustNewName(domain),
|
||||
Name: name,
|
||||
Type: dnsmessage.TypeA,
|
||||
Class: dnsmessage.ClassINET,
|
||||
}
|
||||
|
||||
qAAAA := dnsmessage.Question{
|
||||
Name: dnsmessage.MustNewName(domain),
|
||||
Name: name,
|
||||
Type: dnsmessage.TypeAAAA,
|
||||
Class: dnsmessage.ClassINET,
|
||||
}
|
||||
@@ -175,7 +180,7 @@ func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() ui
|
||||
})
|
||||
}
|
||||
|
||||
return reqs
|
||||
return reqs, nil
|
||||
}
|
||||
|
||||
// parseResponse parses DNS answers from the returned payload
|
||||
|
||||
@@ -2,6 +2,7 @@ package dns
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -24,7 +25,8 @@ func Test_parseResponse(t *testing.T) {
|
||||
|
||||
ans = new(dns.Msg)
|
||||
ans.Id = 1
|
||||
ans.Answer = append(ans.Answer,
|
||||
ans.Answer = append(
|
||||
ans.Answer,
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")),
|
||||
common.Must2(dns.NewRR("google.com. IN A 8.8.8.8")),
|
||||
@@ -34,7 +36,8 @@ func Test_parseResponse(t *testing.T) {
|
||||
|
||||
ans = new(dns.Msg)
|
||||
ans.Id = 2
|
||||
ans.Answer = append(ans.Answer,
|
||||
ans.Answer = append(
|
||||
ans.Answer,
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")),
|
||||
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
|
||||
@@ -131,10 +134,15 @@ func Test_buildReqMsgs(t *testing.T) {
|
||||
IPv6Enable: false,
|
||||
FakeEnable: false,
|
||||
}, nil}, 0},
|
||||
{"name too long", args{strings.Repeat("a", 256), dns_feature.IPOption{
|
||||
IPv4Enable: true,
|
||||
IPv6Enable: true,
|
||||
FakeEnable: false,
|
||||
}, nil}, 0},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := buildReqMsgs(tt.args.domain, tt.args.option, stubID, tt.args.reqOpts); !(len(got) == tt.want) {
|
||||
if got, _ := buildReqMsgs(tt.args.domain, tt.args.option, stubID, tt.args.reqOpts); !(len(got) == tt.want) {
|
||||
t.Errorf("buildReqMsgs() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -129,15 +129,16 @@ func TestFakeDnsHolderCreateMappingAndRollOver(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFakeDNSMulti(t *testing.T) {
|
||||
fakeMulti, err := NewFakeDNSHolderMulti(&FakeDnsPoolMulti{
|
||||
Pools: []*FakeDnsPool{{
|
||||
IpPool: "240.0.0.0/12",
|
||||
LruSize: 256,
|
||||
}, {
|
||||
IpPool: "fddd:c5b4:ff5f:f4f0::/64",
|
||||
LruSize: 256,
|
||||
}},
|
||||
},
|
||||
fakeMulti, err := NewFakeDNSHolderMulti(
|
||||
&FakeDnsPoolMulti{
|
||||
Pools: []*FakeDnsPool{{
|
||||
IpPool: "240.0.0.0/12",
|
||||
LruSize: 256,
|
||||
}, {
|
||||
IpPool: "fddd:c5b4:ff5f:f4f0::/64",
|
||||
LruSize: 256,
|
||||
}},
|
||||
},
|
||||
)
|
||||
common.Must(err)
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ func NewClient(
|
||||
}
|
||||
}
|
||||
|
||||
var timeoutMs = 4000 * time.Millisecond
|
||||
timeoutMs := 4000 * time.Millisecond
|
||||
if ns.TimeoutMs > 0 {
|
||||
timeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond
|
||||
}
|
||||
|
||||
@@ -137,14 +137,32 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- er
|
||||
if s.Name()+"." == "DOH//"+fqdn {
|
||||
errors.LogError(ctx, s.Name(), " tries to resolve itself! Use IP or set \"hosts\" instead")
|
||||
if noResponseErrCh != nil {
|
||||
noResponseErrCh <- errors.New("tries to resolve itself!", s.Name())
|
||||
err := errors.New("tries to resolve itself!", s.Name())
|
||||
if option.IPv4Enable {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
if option.IPv6Enable {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// As we don't want our traffic pattern looks like DoH, we use Random-Length Padding instead of Block-Length Padding recommended in RFC 8467
|
||||
// Although DoH server like 1.1.1.1 will pad the response to Block-Length 468, at least it is better than no padding for response at all
|
||||
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
|
||||
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
|
||||
if err != nil {
|
||||
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
|
||||
if noResponseErrCh != nil {
|
||||
if option.IPv4Enable {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
if option.IPv6Enable {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var deadline time.Time
|
||||
if d, ok := ctx.Deadline(); ok {
|
||||
|
||||
@@ -18,7 +18,6 @@ type LocalNameServer struct {
|
||||
|
||||
// QueryIP implements Server.
|
||||
func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, option dns.IPOption) (ips []net.IP, ttl uint32, err error) {
|
||||
|
||||
start := time.Now()
|
||||
ips, ttl, err = s.client.LookupIP(domain, option)
|
||||
|
||||
|
||||
@@ -78,7 +78,19 @@ func (s *QUICNameServer) getCacheController() *CacheController { return s.cacheC
|
||||
func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
||||
errors.LogInfo(ctx, s.Name(), " querying: ", fqdn)
|
||||
|
||||
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||
if err != nil {
|
||||
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
|
||||
if noResponseErrCh != nil {
|
||||
if option.IPv4Enable {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
if option.IPv6Enable {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var deadline time.Time
|
||||
if d, ok := ctx.Deadline(); ok {
|
||||
|
||||
@@ -113,7 +113,19 @@ func (s *TCPNameServer) getCacheController() *CacheController {
|
||||
func (s *TCPNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
||||
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
|
||||
|
||||
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||
if err != nil {
|
||||
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
|
||||
if noResponseErrCh != nil {
|
||||
if option.IPv4Enable {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
if option.IPv6Enable {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var deadline time.Time
|
||||
if d, ok := ctx.Deadline(); ok {
|
||||
|
||||
@@ -131,8 +131,6 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
|
||||
newReq.msg = &newMsg
|
||||
s.addPendingRequest(&newReq)
|
||||
b, _ := dns.PackMessage(newReq.msg)
|
||||
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
|
||||
b.UDP = ©Dest
|
||||
s.udpServer.Dispatch(toDnsContext(newReq.ctx, s.address.String()), *s.address, b)
|
||||
return
|
||||
}
|
||||
@@ -163,7 +161,19 @@ func (s *ClassicNameServer) getCacheController() *CacheController {
|
||||
func (s *ClassicNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
|
||||
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
|
||||
|
||||
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
|
||||
if err != nil {
|
||||
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
|
||||
if noResponseErrCh != nil {
|
||||
if option.IPv4Enable {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
if option.IPv6Enable {
|
||||
noResponseErrCh <- err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for _, req := range reqs {
|
||||
udpReq := &udpDnsRequest{
|
||||
@@ -179,8 +189,6 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<
|
||||
}
|
||||
return
|
||||
}
|
||||
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
|
||||
b.UDP = ©Dest
|
||||
s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,6 @@ func (p *MetricsHandler) Type() interface{} {
|
||||
}
|
||||
|
||||
func (p *MetricsHandler) Start() error {
|
||||
|
||||
// direct listen a port if listen is set
|
||||
if p.listen != "" {
|
||||
TCPlistener, err := net.Listen("tcp", p.listen)
|
||||
|
||||
@@ -2,7 +2,6 @@ package burst
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/app/observatory"
|
||||
@@ -72,7 +71,6 @@ func (o *Observer) Start() error {
|
||||
o.hp.StartScheduler(func() ([]string, error) {
|
||||
hs, ok := o.ohm.(outbound.HandlerSelector)
|
||||
if !ok {
|
||||
|
||||
return nil, errors.New("outbound.Manager is not a HandlerSelector")
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/dice"
|
||||
@@ -24,11 +25,12 @@ type HealthPingSettings struct {
|
||||
|
||||
// HealthPing is the health checker for balancers
|
||||
type HealthPing struct {
|
||||
ctx context.Context
|
||||
dispatcher routing.Dispatcher
|
||||
access sync.Mutex
|
||||
ticker *time.Ticker
|
||||
tickerClose chan struct{}
|
||||
ctx context.Context
|
||||
cancelCtx context.CancelFunc
|
||||
cancelPending atomic.Pointer[context.CancelFunc]
|
||||
dispatcher routing.Dispatcher
|
||||
access sync.Mutex
|
||||
ticker *time.Ticker
|
||||
|
||||
Settings *HealthPingSettings
|
||||
Results map[string]*HealthPingRTTS
|
||||
@@ -62,10 +64,10 @@ func NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *H
|
||||
settings.Destination = "https://connectivitycheck.gstatic.com/generate_204"
|
||||
}
|
||||
if settings.Interval == 0 {
|
||||
settings.Interval = time.Duration(1) * time.Minute
|
||||
} else if settings.Interval < 10 {
|
||||
settings.Interval = 1 * time.Minute
|
||||
} else if settings.Interval < 10*time.Second {
|
||||
errors.LogWarning(ctx, "health check interval is too small, 10s is applied")
|
||||
settings.Interval = time.Duration(10) * time.Second
|
||||
settings.Interval = 10 * time.Second
|
||||
}
|
||||
if settings.SamplingCount <= 0 {
|
||||
settings.SamplingCount = 10
|
||||
@@ -73,10 +75,12 @@ func NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *H
|
||||
if settings.Timeout <= 0 {
|
||||
// results are saved after all health pings finish,
|
||||
// a larger timeout could possibly makes checks run longer
|
||||
settings.Timeout = time.Duration(5) * time.Second
|
||||
settings.Timeout = 5 * time.Second
|
||||
}
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
return &HealthPing{
|
||||
ctx: ctx,
|
||||
cancelCtx: cancel,
|
||||
dispatcher: dispatcher,
|
||||
Settings: settings,
|
||||
Results: nil,
|
||||
@@ -90,9 +94,9 @@ func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
|
||||
}
|
||||
interval := h.Settings.Interval * time.Duration(h.Settings.SamplingCount)
|
||||
ticker := time.NewTicker(interval)
|
||||
tickerClose := make(chan struct{})
|
||||
h.ticker = ticker
|
||||
h.tickerClose = tickerClose
|
||||
|
||||
// init run to get a fast check result
|
||||
go func() {
|
||||
tags, err := selector()
|
||||
if err != nil {
|
||||
@@ -110,13 +114,20 @@ func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
|
||||
errors.LogWarning(h.ctx, "error select outbounds for scheduled health check: ", err)
|
||||
return
|
||||
}
|
||||
h.doCheck(tags, interval, h.Settings.SamplingCount)
|
||||
subCtx, cancel := context.WithCancel(h.ctx)
|
||||
old := h.cancelPending.Swap(&cancel)
|
||||
if old != nil {
|
||||
errors.LogDebug(h.ctx, "scheduled health check not finished before next round, canceling previous one")
|
||||
(*old)()
|
||||
}
|
||||
h.doCheck(subCtx, tags, interval, h.Settings.SamplingCount)
|
||||
h.cancelPending.CompareAndSwap(&cancel, nil)
|
||||
h.Cleanup(tags)
|
||||
}()
|
||||
select {
|
||||
case <-ticker.C:
|
||||
continue
|
||||
case <-tickerClose:
|
||||
case <-h.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -130,8 +141,7 @@ func (h *HealthPing) StopScheduler() {
|
||||
}
|
||||
h.ticker.Stop()
|
||||
h.ticker = nil
|
||||
close(h.tickerClose)
|
||||
h.tickerClose = nil
|
||||
h.cancelCtx()
|
||||
}
|
||||
|
||||
// Check implements the HealthChecker
|
||||
@@ -140,7 +150,7 @@ func (h *HealthPing) Check(tags []string) error {
|
||||
return nil
|
||||
}
|
||||
errors.LogInfo(h.ctx, "perform one-time health check for tags ", tags)
|
||||
h.doCheck(tags, 0, 1)
|
||||
h.doCheck(h.ctx, tags, 0, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -151,13 +161,14 @@ type rtt struct {
|
||||
|
||||
// doCheck performs the 'rounds' amount checks in given 'duration'. You should make
|
||||
// sure all tags are valid for current balancer
|
||||
func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int) {
|
||||
// cancel ctx will stop all pending checks
|
||||
func (h *HealthPing) doCheck(ctx context.Context, tags []string, duration time.Duration, rounds int) {
|
||||
count := len(tags) * rounds
|
||||
if count == 0 {
|
||||
return
|
||||
}
|
||||
ch := make(chan *rtt, count)
|
||||
|
||||
timers := make([]*time.Timer, 0, count)
|
||||
for _, tag := range tags {
|
||||
handler := tag
|
||||
client := newPingClient(
|
||||
@@ -172,7 +183,7 @@ func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int)
|
||||
if duration > 0 {
|
||||
delay = time.Duration(dice.RollInt63n(int64(duration)))
|
||||
}
|
||||
time.AfterFunc(delay, func() {
|
||||
timers = append(timers, time.AfterFunc(delay, func() {
|
||||
errors.LogDebug(h.ctx, "checking ", handler)
|
||||
delay, err := client.MeasureDelay(h.Settings.HttpMethod)
|
||||
if err == nil {
|
||||
@@ -200,14 +211,21 @@ func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int)
|
||||
handler: handler,
|
||||
value: rttFailed,
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
rtt := <-ch
|
||||
if rtt.value > 0 {
|
||||
// should not put results when network is down
|
||||
h.PutResult(rtt.handler, rtt.value)
|
||||
select {
|
||||
case rtt := <-ch:
|
||||
if rtt.value > 0 {
|
||||
// should not put results when network is down
|
||||
h.PutResult(rtt.handler, rtt.value)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
for _, timer := range timers {
|
||||
timer.Stop()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func (h *HealthPingRTTS) Put(d time.Duration) {
|
||||
if h.rtts == nil {
|
||||
h.rtts = make([]*pingRTT, h.cap)
|
||||
for i := 0; i < h.cap; i++ {
|
||||
h.rtts[i] = &pingRTT{}
|
||||
h.rtts[i] = &pingRTT{value: rttUntested}
|
||||
}
|
||||
h.idx = -1
|
||||
}
|
||||
@@ -88,7 +88,7 @@ func (h *HealthPingRTTS) getStatistics() *HealthPingStats {
|
||||
validRTTs := make([]time.Duration, 0)
|
||||
for _, rtt := range h.rtts {
|
||||
switch {
|
||||
case rtt.value == 0 || time.Since(rtt.time) > h.validity:
|
||||
case rtt.value == rttUntested || time.Since(rtt.time) > h.validity:
|
||||
continue
|
||||
case rtt.value == rttFailed:
|
||||
stats.Fail++
|
||||
|
||||
@@ -186,7 +186,7 @@ func (o *Observer) probe(outbound string) ProbeResult {
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
var errorMessage = "the outbound " + outbound + " is dead: GET request failed:" + err.Error() + "with outbound handler report underlying connection failed"
|
||||
errorMessage := "the outbound " + outbound + " is dead: GET request failed:" + err.Error() + "with outbound handler report underlying connection failed"
|
||||
errors.LogInfoInner(o.ctx, errorCollectorForRequest.UnderlyingError(), errorMessage)
|
||||
return ProbeResult{Alive: false, LastErrorReason: errorMessage}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ func (s *handlerServer) GetInboundUsers(ctx context.Context, request *GetInbound
|
||||
if len(request.Email) > 0 {
|
||||
return &GetInboundUserResponse{Users: []*protocol.User{protocol.ToProtoUser(um.GetUser(ctx, request.Email))}}, nil
|
||||
}
|
||||
var result = make([]*protocol.User, 0, 100)
|
||||
result := make([]*protocol.User, 0, 100)
|
||||
users := um.GetUsers(ctx)
|
||||
for _, u := range users {
|
||||
result = append(result, protocol.ToProtoUser(u))
|
||||
|
||||
@@ -57,16 +57,23 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set tag and sniffing config in context before creating proxy
|
||||
// This allows proxies like TUN to access these settings
|
||||
ctx = session.ContextWithInbound(ctx, &session.Inbound{Tag: tag})
|
||||
if receiverConfig.SniffingSettings != nil {
|
||||
ctx = session.ContextWithContent(ctx, &session.Content{
|
||||
SniffingRequest: sniffingRequest,
|
||||
})
|
||||
src := net.TCPDestination(net.AnyIP, 0)
|
||||
if receiverConfig.Listen != nil {
|
||||
src.Address = receiverConfig.Listen.AsAddress()
|
||||
}
|
||||
rawProxy, err := common.CreateObject(ctx, proxyConfig)
|
||||
if receiverConfig.PortList != nil && len(receiverConfig.PortList.Range) > 0 {
|
||||
src.Port = net.Port(receiverConfig.PortList.Range[0].From)
|
||||
}
|
||||
mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to parse stream config").Base(err).AtWarning()
|
||||
}
|
||||
|
||||
newCtx := session.ContextWithInbound(ctx, &session.Inbound{Tag: tag, Source: src})
|
||||
newCtx = session.ContextWithContent(newCtx, &session.Content{SniffingRequest: sniffingRequest})
|
||||
newCtx = session.ContextWithStreamSettings(newCtx, mss)
|
||||
|
||||
rawProxy, err := common.CreateObject(newCtx, proxyConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -92,11 +99,6 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
address = net.AnyIP
|
||||
}
|
||||
|
||||
mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to parse stream config").Base(err).AtWarning()
|
||||
}
|
||||
|
||||
if receiverConfig.ReceiveOriginalDestination {
|
||||
if mss.SocketSettings == nil {
|
||||
mss.SocketSettings = &internet.SocketConfig{}
|
||||
@@ -170,6 +172,12 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
|
||||
|
||||
// Start implements common.Runnable.
|
||||
func (h *AlwaysOnInboundHandler) Start() error {
|
||||
// for inbound without worker (TUN)
|
||||
if run, ok := h.proxy.(common.Runnable); ok {
|
||||
if err := run.Start(); err != nil {
|
||||
return errors.New("failed to start proxy").Base(err)
|
||||
}
|
||||
}
|
||||
for _, worker := range h.workers {
|
||||
if err := worker.Start(); err != nil {
|
||||
return err
|
||||
|
||||
@@ -16,10 +16,10 @@ import (
|
||||
|
||||
// Manager manages all inbound handlers.
|
||||
type Manager struct {
|
||||
access sync.RWMutex
|
||||
access sync.RWMutex
|
||||
untaggedHandlers []inbound.Handler
|
||||
taggedHandlers map[string]inbound.Handler
|
||||
running bool
|
||||
taggedHandlers map[string]inbound.Handler
|
||||
running bool
|
||||
}
|
||||
|
||||
// New returns a new Manager for inbound handlers.
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
goerrors "errors"
|
||||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
"github.com/xtls/xray-core/common/dice"
|
||||
|
||||
@@ -109,7 +108,9 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
|
||||
|
||||
ctx = session.ContextWithFullHandler(ctx, h)
|
||||
|
||||
rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
|
||||
newCtx := session.ContextWithStreamSettings(ctx, h.streamSettings)
|
||||
|
||||
rawProxyHandler, err := common.CreateObject(newCtx, proxyConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -306,11 +307,6 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti
|
||||
ob := outbounds[len(outbounds)-1]
|
||||
h.SetOutboundGateway(ctx, ob)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if conn, err := h.getUoTConnection(ctx, dest); err != os.ErrInvalid {
|
||||
return conn, err
|
||||
}
|
||||
|
||||
conn, err := internet.Dial(ctx, dest, h.streamSettings)
|
||||
@@ -348,7 +344,7 @@ func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound)
|
||||
errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String())
|
||||
}
|
||||
}
|
||||
//case addr.Family().IsDomain():
|
||||
// case addr.Family().IsDomain():
|
||||
default:
|
||||
ob.Gateway = addr
|
||||
|
||||
@@ -396,7 +392,6 @@ func (h *Handler) ProxySettings() *serial.TypedMessage {
|
||||
}
|
||||
|
||||
func ParseRandomIP(addr net.Address, prefix string) net.Address {
|
||||
|
||||
_, ipnet, _ := net.ParseCIDR(addr.IP().String() + "/" + prefix)
|
||||
|
||||
ones, bits := ipnet.Mask.Size()
|
||||
|
||||
@@ -22,8 +22,8 @@ import (
|
||||
)
|
||||
|
||||
func TestInterfaces(t *testing.T) {
|
||||
_ = (outbound.Handler)(new(Handler))
|
||||
_ = (outbound.Manager)(new(Manager))
|
||||
_ = outbound.Handler(new(Handler))
|
||||
_ = outbound.Manager(new(Manager))
|
||||
}
|
||||
|
||||
const xrayKey core.XrayKey = 1
|
||||
@@ -43,7 +43,7 @@ func TestOutboundWithoutStatCounter(t *testing.T) {
|
||||
}
|
||||
|
||||
v, _ := core.New(config)
|
||||
v.AddFeature((outbound.Manager)(new(Manager)))
|
||||
v.AddFeature(outbound.Manager(new(Manager)))
|
||||
ctx := context.WithValue(context.Background(), xrayKey, v)
|
||||
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
|
||||
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
|
||||
@@ -73,7 +73,7 @@ func TestOutboundWithStatCounter(t *testing.T) {
|
||||
}
|
||||
|
||||
v, _ := core.New(config)
|
||||
v.AddFeature((outbound.Manager)(new(Manager)))
|
||||
v.AddFeature(outbound.Manager(new(Manager)))
|
||||
ctx := context.WithValue(context.Background(), xrayKey, v)
|
||||
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
|
||||
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
|
||||
@@ -88,7 +88,6 @@ func TestOutboundWithStatCounter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestTagsCache(t *testing.T) {
|
||||
|
||||
test_duration := 10 * time.Second
|
||||
threads_num := 50
|
||||
delay := 10 * time.Millisecond
|
||||
|
||||
@@ -162,7 +162,6 @@ func (m *Manager) ListHandlers(ctx context.Context) []outbound.Handler {
|
||||
|
||||
// Select implements outbound.HandlerSelector.
|
||||
func (m *Manager) Select(selectors []string) []string {
|
||||
|
||||
key := strings.Join(selectors, ",")
|
||||
if cache, ok := m.tagsCache.Load(key); ok {
|
||||
return cache.([]string)
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
)
|
||||
|
||||
func (h *Handler) getUoTConnection(ctx context.Context, dest net.Destination) (stat.Connection, error) {
|
||||
if dest.Address == nil {
|
||||
return nil, errors.New("nil destination address")
|
||||
}
|
||||
if !dest.Address.Family().IsDomain() {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
var uotVersion int
|
||||
if dest.Address.Domain() == uot.MagicAddress {
|
||||
uotVersion = uot.Version
|
||||
} else if dest.Address.Domain() == uot.LegacyMagicAddress {
|
||||
uotVersion = uot.LegacyVersion
|
||||
} else {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
packetConn, err := internet.ListenSystemPacket(ctx, &net.UDPAddr{IP: net.AnyIP.IP(), Port: 0}, h.streamSettings.SocketSettings)
|
||||
if err != nil {
|
||||
return nil, errors.New("unable to listen socket").Base(err)
|
||||
}
|
||||
conn := uot.NewServerConn(packetConn, uotVersion)
|
||||
return h.getStatCouterConnection(conn), nil
|
||||
}
|
||||
@@ -58,7 +58,6 @@ func (s *routingServer) AddRule(ctx context.Context, request *AddRuleRequest) (*
|
||||
return &AddRuleResponse{}, bo.AddRule(request.Config, request.ShouldAppend)
|
||||
}
|
||||
return nil, errors.New("unsupported router implementation")
|
||||
|
||||
}
|
||||
|
||||
func (s *routingServer) RemoveRule(ctx context.Context, request *RemoveRuleRequest) (*RemoveRuleResponse, error) {
|
||||
|
||||
@@ -110,7 +110,6 @@ func (r *Router) PickRoute(ctx routing.Context) (routing.Route, error) {
|
||||
|
||||
// AddRule implements routing.Router.
|
||||
func (r *Router) AddRule(config *serial.TypedMessage, shouldAppend bool) error {
|
||||
|
||||
inst, err := config.GetInstance()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -227,7 +226,6 @@ func (r *Router) RemoveRule(tag string) error {
|
||||
return nil
|
||||
}
|
||||
return errors.New("empty tag name!")
|
||||
|
||||
}
|
||||
|
||||
// ListRule implements routing.Router
|
||||
|
||||
@@ -3,6 +3,7 @@ package router
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"slices"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
@@ -77,7 +78,7 @@ func (s *LeastLoadStrategy) PickOutbound(candidates []string) string {
|
||||
}
|
||||
|
||||
func (s *LeastLoadStrategy) pickOutbounds(candidates []string) []*node {
|
||||
qualified := s.getNodes(candidates, time.Duration(s.settings.MaxRTT))
|
||||
qualified := s.getNodes(candidates)
|
||||
selects := s.selectLeastLoad(qualified)
|
||||
return selects
|
||||
}
|
||||
@@ -138,7 +139,7 @@ func (s *LeastLoadStrategy) selectLeastLoad(nodes []*node) []*node {
|
||||
return nodes[:count]
|
||||
}
|
||||
|
||||
func (s *LeastLoadStrategy) getNodes(candidates []string, maxRTT time.Duration) []*node {
|
||||
func (s *LeastLoadStrategy) getNodes(candidates []string) []*node {
|
||||
if s.observer == nil {
|
||||
errors.LogError(s.ctx, "observer is nil")
|
||||
return make([]*node, 0)
|
||||
@@ -151,12 +152,10 @@ func (s *LeastLoadStrategy) getNodes(candidates []string, maxRTT time.Duration)
|
||||
|
||||
results := observeResult.(*observatory.ObservationResult)
|
||||
|
||||
outboundlist := outboundList(candidates)
|
||||
|
||||
var ret []*node
|
||||
|
||||
for _, v := range results.Status {
|
||||
if v.Alive && (v.Delay < maxRTT.Milliseconds() || maxRTT == 0) && outboundlist.contains(v.OutboundTag) {
|
||||
if s.shouldSelectNode(v, candidates) {
|
||||
record := &node{
|
||||
Tag: v.OutboundTag,
|
||||
CountAll: 1,
|
||||
@@ -172,8 +171,8 @@ func (s *LeastLoadStrategy) getNodes(candidates []string, maxRTT time.Duration)
|
||||
record.RTTDeviationCost = time.Duration(s.costs.Apply(v.OutboundTag, float64(v.HealthPing.Deviation)))
|
||||
record.CountAll = int(v.HealthPing.All)
|
||||
record.CountFail = int(v.HealthPing.Fail)
|
||||
|
||||
}
|
||||
|
||||
ret = append(ret, record)
|
||||
}
|
||||
}
|
||||
@@ -182,6 +181,23 @@ func (s *LeastLoadStrategy) getNodes(candidates []string, maxRTT time.Duration)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *LeastLoadStrategy) shouldSelectNode(v *observatory.OutboundStatus, candidates []string) bool {
|
||||
maxRTT := time.Duration(s.settings.MaxRTT)
|
||||
if !v.Alive {
|
||||
return false
|
||||
}
|
||||
if maxRTT != 0 && v.Delay >= maxRTT.Milliseconds() {
|
||||
return false
|
||||
}
|
||||
if !slices.Contains(candidates, v.OutboundTag) {
|
||||
return false
|
||||
}
|
||||
if v.HealthPing != nil && v.HealthPing.All > 0 && s.settings.Tolerance > 0 && float64(v.HealthPing.Fail)/float64(v.HealthPing.All) > float64(s.settings.Tolerance) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func leastloadSort(nodes []*node) {
|
||||
sort.Slice(nodes, func(i, j int) bool {
|
||||
left := nodes[i]
|
||||
|
||||
@@ -85,6 +85,7 @@ func TestSelectLeastExpected(t *testing.T) {
|
||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectLeastExpected2(t *testing.T) {
|
||||
strategy := &LeastLoadStrategy{
|
||||
settings: &StrategyLeastLoadConfig{
|
||||
@@ -102,6 +103,7 @@ func TestSelectLeastExpected2(t *testing.T) {
|
||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectLeastExpectedAndBaselines(t *testing.T) {
|
||||
strategy := &LeastLoadStrategy{
|
||||
settings: &StrategyLeastLoadConfig{
|
||||
@@ -122,6 +124,7 @@ func TestSelectLeastExpectedAndBaselines(t *testing.T) {
|
||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectLeastExpectedAndBaselines2(t *testing.T) {
|
||||
strategy := &LeastLoadStrategy{
|
||||
settings: &StrategyLeastLoadConfig{
|
||||
@@ -142,6 +145,7 @@ func TestSelectLeastExpectedAndBaselines2(t *testing.T) {
|
||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectLeastLoadBaselines(t *testing.T) {
|
||||
strategy := &LeastLoadStrategy{
|
||||
settings: &StrategyLeastLoadConfig{
|
||||
@@ -160,6 +164,7 @@ func TestSelectLeastLoadBaselines(t *testing.T) {
|
||||
t.Errorf("expected: %v, actual: %v", expected, len(ns))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectLeastLoadBaselinesNoQualified(t *testing.T) {
|
||||
strategy := &LeastLoadStrategy{
|
||||
settings: &StrategyLeastLoadConfig{
|
||||
|
||||
+3
-49
@@ -7,61 +7,15 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
routing_session "github.com/xtls/xray-core/features/routing/session"
|
||||
)
|
||||
|
||||
// parseURL splits a webhook URL into an HTTP URL and an optional Unix socket
|
||||
// path. For regular http/https URLs the input is returned unchanged with an
|
||||
// empty socketPath. For Unix sockets the format is:
|
||||
//
|
||||
// /path/to/socket.sock:/http/path
|
||||
// @abstract:/http/path
|
||||
// @@padded:/http/path
|
||||
//
|
||||
// The :/ separator after the socket path delimits the HTTP request path.
|
||||
// If omitted, "/" is used.
|
||||
func parseURL(raw string) (httpURL, socketPath string) {
|
||||
if len(raw) == 0 || (!filepath.IsAbs(raw) && raw[0] != '@') {
|
||||
return raw, ""
|
||||
}
|
||||
if idx := strings.Index(raw, ":/"); idx >= 0 {
|
||||
return "http://localhost" + raw[idx+1:], raw[:idx]
|
||||
}
|
||||
return "http://localhost/", raw
|
||||
}
|
||||
|
||||
// resolveSocketPath applies platform-specific transformations to a Unix
|
||||
// socket path, matching the behaviour of the listen side in
|
||||
// transport/internet/system_listener.go.
|
||||
//
|
||||
// For abstract sockets (prefix @) on Linux/Android:
|
||||
// - single @ — used as-is (lock-free abstract socket)
|
||||
// - double @@ — stripped to single @ and padded to
|
||||
// syscall.RawSockaddrUnix{}.Path length (HAProxy compat)
|
||||
func resolveSocketPath(path string) string {
|
||||
if len(path) == 0 || path[0] != '@' {
|
||||
return path
|
||||
}
|
||||
if runtime.GOOS != "linux" && runtime.GOOS != "android" {
|
||||
return path
|
||||
}
|
||||
if len(path) > 1 && path[1] == '@' {
|
||||
fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path))
|
||||
copy(fullAddr, path[1:])
|
||||
return string(fullAddr)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
type event struct {
|
||||
@@ -96,7 +50,7 @@ func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
httpURL, socketPath := parseURL(cfg.Url)
|
||||
httpURL, socketPath := utils.SplitHTTPUnixURL(cfg.Url)
|
||||
h := &WebhookNotifier{
|
||||
url: httpURL,
|
||||
deduplication: cfg.Deduplication,
|
||||
@@ -107,7 +61,7 @@ func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
|
||||
}
|
||||
|
||||
if socketPath != "" {
|
||||
dialAddr := resolveSocketPath(socketPath)
|
||||
dialAddr := utils.ResolveSocketPath(socketPath)
|
||||
h.client.Transport = &http.Transport{
|
||||
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
var d net.Dialer
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func TestInterface(t *testing.T) {
|
||||
_ = (stats.Manager)(new(Manager))
|
||||
_ = stats.Manager(new(Manager))
|
||||
}
|
||||
|
||||
func TestStatsChannelRunnable(t *testing.T) {
|
||||
|
||||
@@ -2,10 +2,11 @@ package version
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
)
|
||||
|
||||
type Version struct {
|
||||
|
||||
@@ -38,8 +38,8 @@ func MergeMulti(dest MultiBuffer, src MultiBuffer) (MultiBuffer, MultiBuffer) {
|
||||
// MergeBytes merges the given bytes into MultiBuffer and return the new address of the merged MultiBuffer.
|
||||
func MergeBytes(dest MultiBuffer, src []byte) MultiBuffer {
|
||||
n := len(dest)
|
||||
if n > 0 && !(dest)[n-1].IsFull() {
|
||||
nBytes, _ := (dest)[n-1].Write(src)
|
||||
if n > 0 && !dest[n-1].IsFull() {
|
||||
nBytes, _ := dest[n-1].Write(src)
|
||||
src = src[nBytes:]
|
||||
}
|
||||
|
||||
|
||||
@@ -121,11 +121,11 @@ func TestPacketReader_ReadMultiBuffer(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReaderInterface(t *testing.T) {
|
||||
_ = (io.Reader)(new(ReadVReader))
|
||||
_ = (Reader)(new(ReadVReader))
|
||||
_ = io.Reader(new(ReadVReader))
|
||||
_ = Reader(new(ReadVReader))
|
||||
|
||||
_ = (Reader)(new(BufferedReader))
|
||||
_ = (io.Reader)(new(BufferedReader))
|
||||
_ = (io.ByteReader)(new(BufferedReader))
|
||||
_ = (io.WriterTo)(new(BufferedReader))
|
||||
_ = Reader(new(BufferedReader))
|
||||
_ = io.Reader(new(BufferedReader))
|
||||
_ = io.ByteReader(new(BufferedReader))
|
||||
_ = io.WriterTo(new(BufferedReader))
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ func (r *posixReader) Init(bs []*Buffer) {
|
||||
}
|
||||
for idx, b := range bs {
|
||||
iovecs = append(iovecs, syscall.Iovec{
|
||||
Base: &(b.v[0]),
|
||||
Base: &b.v[0],
|
||||
})
|
||||
iovecs[idx].SetLen(int(Size))
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata/strmatcher"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
)
|
||||
|
||||
type DomainMatcher interface {
|
||||
@@ -25,7 +26,7 @@ type DomainMatcherFactory interface {
|
||||
|
||||
type MphDomainMatcherFactory struct {
|
||||
sync.Mutex
|
||||
shared map[string]strmatcher.MatcherGroup // TODO: cleanup
|
||||
shared *utils.WeakCacheMap[string, strmatcher.MphValueMatcher]
|
||||
}
|
||||
|
||||
func buildDomainRulesKey(rules []*DomainRule) string {
|
||||
@@ -65,7 +66,7 @@ func (f *MphDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatch
|
||||
if key != "" {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
if g := f.shared[key]; g != nil {
|
||||
if g, ok := f.shared.Load(key); ok {
|
||||
errors.LogDebug(context.Background(), "geodata mph domain matcher cache HIT for ", len(rules), " rules")
|
||||
return g, nil
|
||||
}
|
||||
@@ -102,14 +103,14 @@ func (f *MphDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatch
|
||||
return nil, err
|
||||
}
|
||||
if key != "" {
|
||||
f.shared[key] = g
|
||||
f.shared.Store(key, g)
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
||||
type CompactDomainMatcherFactory struct {
|
||||
sync.Mutex
|
||||
shared map[string]strmatcher.MatcherSet // TODO: cleanup
|
||||
shared *utils.WeakCacheMap[string, strmatcher.LinearAnyMatcher]
|
||||
}
|
||||
|
||||
func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmatcher.MatcherSet, error) {
|
||||
@@ -118,7 +119,7 @@ func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmat
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if s := f.shared[key]; s != nil {
|
||||
if s, ok := f.shared.Load(key); ok {
|
||||
errors.LogDebug(context.Background(), "geodata geosite matcher cache HIT ", key)
|
||||
return s, nil
|
||||
}
|
||||
@@ -138,7 +139,7 @@ func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmat
|
||||
}
|
||||
s.Add(m)
|
||||
}
|
||||
f.shared[key] = s
|
||||
f.shared.Store(key, s)
|
||||
return s, err
|
||||
}
|
||||
|
||||
@@ -219,7 +220,7 @@ func parseDomain(d *Domain) (strmatcher.Matcher, error) {
|
||||
case Domain_Regex:
|
||||
return strmatcher.Regex.New(d.Value)
|
||||
case Domain_Domain:
|
||||
return strmatcher.Domain.New(d.Value)
|
||||
return strmatcher.Domain.New(strings.ToLower(d.Value))
|
||||
case Domain_Full:
|
||||
return strmatcher.Full.New(strings.ToLower(d.Value))
|
||||
default:
|
||||
@@ -230,8 +231,8 @@ func parseDomain(d *Domain) (strmatcher.Matcher, error) {
|
||||
func newDomainMatcherFactory() DomainMatcherFactory {
|
||||
switch runtime.GOOS {
|
||||
case "ios", "android":
|
||||
return &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
|
||||
return &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()}
|
||||
default:
|
||||
return &MphDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}
|
||||
return &MphDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.MphValueMatcher]()}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/common/geodata/strmatcher"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
)
|
||||
|
||||
func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) {
|
||||
factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
|
||||
factory := &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()}
|
||||
matcher, err := factory.BuildMatcher([]*DomainRule{
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
|
||||
@@ -31,7 +32,7 @@ func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) {
|
||||
func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
|
||||
factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
|
||||
factory := &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()}
|
||||
matcher, err := factory.BuildMatcher([]*DomainRule{
|
||||
{Value: &DomainRule_Geosite{Geosite: &GeoSiteRule{File: DefaultGeoSiteDat, Code: "CN"}}},
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "163.com"}}},
|
||||
@@ -50,10 +51,11 @@ func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMphDomainMatcher_MatchReturnsDetachedSlice(t *testing.T) {
|
||||
matcher, err := (&MphDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}).BuildMatcher([]*DomainRule{
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
|
||||
})
|
||||
matcher, err := (&MphDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.MphValueMatcher]()}).
|
||||
BuildMatcher([]*DomainRule{
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
|
||||
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BuildMatcher() failed: %v", err)
|
||||
}
|
||||
|
||||
@@ -6,12 +6,14 @@ import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
)
|
||||
|
||||
type DomainRegistry struct {
|
||||
mu sync.Mutex
|
||||
factory DomainMatcherFactory
|
||||
matchers []*DynamicDomainMatcher
|
||||
matchers *utils.WeakCacheMap[uuid.UUID, DynamicDomainMatcher]
|
||||
}
|
||||
|
||||
func (r *DomainRegistry) BuildDomainMatcher(rules []*DomainRule) (DomainMatcher, error) {
|
||||
@@ -24,7 +26,7 @@ func (r *DomainRegistry) BuildDomainMatcher(rules []*DomainRule) (DomainMatcher,
|
||||
}
|
||||
|
||||
d := NewDynamicDomainMatcher(rules, m)
|
||||
r.matchers = append(r.matchers, d)
|
||||
r.matchers.Store(uuid.New(), d)
|
||||
return d, nil
|
||||
}
|
||||
|
||||
@@ -32,15 +34,20 @@ func (r *DomainRegistry) Reload() error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
errors.LogInfo(context.Background(), "reloading GeoSite data for ", len(r.matchers), " domain matcher(s)")
|
||||
var matchers []*DynamicDomainMatcher
|
||||
r.matchers.Range(func(_ uuid.UUID, matcher *DynamicDomainMatcher) bool {
|
||||
matchers = append(matchers, matcher)
|
||||
return true
|
||||
})
|
||||
errors.LogInfo(context.Background(), "reloading GeoSite data for ", len(matchers), " domain matcher(s)")
|
||||
|
||||
factory := newDomainMatcherFactory()
|
||||
type reloadEntry struct {
|
||||
dynamic *DynamicDomainMatcher
|
||||
matcher DomainMatcher
|
||||
}
|
||||
reloaded := make([]reloadEntry, len(r.matchers))
|
||||
for i, d := range r.matchers {
|
||||
reloaded := make([]reloadEntry, len(matchers))
|
||||
for i, d := range matchers {
|
||||
m, err := factory.BuildMatcher(d.rules)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to reload GeoSite data for domain matcher ", i)
|
||||
@@ -52,13 +59,14 @@ func (r *DomainRegistry) Reload() error {
|
||||
entry.dynamic.Reload(entry.matcher)
|
||||
}
|
||||
r.factory = factory
|
||||
errors.LogInfo(context.Background(), "reloaded GeoSite data for ", len(r.matchers), " domain matcher(s)")
|
||||
errors.LogInfo(context.Background(), "reloaded GeoSite data for ", len(matchers), " domain matcher(s)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func newDomainRegistry() *DomainRegistry {
|
||||
return &DomainRegistry{
|
||||
factory: newDomainMatcherFactory(),
|
||||
factory: newDomainMatcherFactory(),
|
||||
matchers: utils.NewWeakCacheMap[uuid.UUID, DynamicDomainMatcher](),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
@@ -806,7 +807,7 @@ func (mm *HeuristicMultiIPMatcher) SetReverse(reverse bool) {
|
||||
|
||||
type IPSetFactory struct {
|
||||
sync.Mutex
|
||||
shared map[string]*IPSet // TODO: cleanup
|
||||
shared *utils.WeakCacheMap[string, IPSet]
|
||||
}
|
||||
|
||||
func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, error) {
|
||||
@@ -815,7 +816,7 @@ func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, er
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if ipset := f.shared[key]; ipset != nil {
|
||||
if ipset, ok := f.shared.Load(key); ok {
|
||||
errors.LogDebug(context.Background(), "geodata geoip matcher cache HIT ", key)
|
||||
return ipset, nil
|
||||
}
|
||||
@@ -835,7 +836,7 @@ func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, er
|
||||
return nil
|
||||
})
|
||||
if err == nil {
|
||||
f.shared[key] = ipset
|
||||
f.shared.Store(key, ipset)
|
||||
}
|
||||
return ipset, err
|
||||
}
|
||||
@@ -1018,5 +1019,5 @@ func buildOptimizedIPMatcher(f *IPSetFactory, rules []*IPRule) (IPMatcher, error
|
||||
}
|
||||
|
||||
func newIPSetFactory() *IPSetFactory {
|
||||
return &IPSetFactory{shared: make(map[string]*IPSet)}
|
||||
return &IPSetFactory{shared: utils.NewWeakCacheMap[string, IPSet]()}
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ func TestIPMatcherAnyMatchAndMatches(t *testing.T) {
|
||||
}
|
||||
|
||||
if !matcher.AnyMatch([]net.IP{
|
||||
net.IP{},
|
||||
{},
|
||||
ip("1.1.1.1"),
|
||||
ip("8.8.8.8"),
|
||||
}) {
|
||||
@@ -345,7 +345,7 @@ func TestIPMatcherAnyMatchAndMatches(t *testing.T) {
|
||||
|
||||
if matcher.Matches([]net.IP{
|
||||
ip("8.8.8.8"),
|
||||
net.IP{},
|
||||
{},
|
||||
}) {
|
||||
t.Fatal("expect Matches to be false when any IP is invalid")
|
||||
}
|
||||
@@ -362,7 +362,7 @@ func TestIPMatcherFilterIPs(t *testing.T) {
|
||||
}
|
||||
|
||||
matched, unmatched := matcher.FilterIPs([]net.IP{
|
||||
net.IP{},
|
||||
{},
|
||||
ip("8.8.8.8"),
|
||||
ip("91.108.255.254"),
|
||||
ip("1.1.1.1"),
|
||||
|
||||
@@ -7,25 +7,27 @@ import (
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/utils"
|
||||
"github.com/xtls/xray-core/common/uuid"
|
||||
)
|
||||
|
||||
type IPRegistry struct {
|
||||
mu sync.Mutex
|
||||
ipsetFactory *IPSetFactory
|
||||
matchers []*DynamicIPMatcher
|
||||
mu sync.Mutex
|
||||
factory *IPSetFactory
|
||||
matchers *utils.WeakCacheMap[uuid.UUID, DynamicIPMatcher]
|
||||
}
|
||||
|
||||
func (r *IPRegistry) BuildIPMatcher(rules []*IPRule) (IPMatcher, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
m, err := buildOptimizedIPMatcher(r.ipsetFactory, rules)
|
||||
m, err := buildOptimizedIPMatcher(r.factory, rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := NewDynamicIPMatcher(rules, m)
|
||||
r.matchers = append(r.matchers, d)
|
||||
r.matchers.Store(uuid.New(), d)
|
||||
return d, nil
|
||||
}
|
||||
|
||||
@@ -33,15 +35,20 @@ func (r *IPRegistry) Reload() error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
errors.LogInfo(context.Background(), "reloading GeoIP data for ", len(r.matchers), " IP matcher(s)")
|
||||
var matchers []*DynamicIPMatcher
|
||||
r.matchers.Range(func(_ uuid.UUID, matcher *DynamicIPMatcher) bool {
|
||||
matchers = append(matchers, matcher)
|
||||
return true
|
||||
})
|
||||
errors.LogInfo(context.Background(), "reloading GeoIP data for ", len(matchers), " IP matcher(s)")
|
||||
|
||||
factory := newIPSetFactory()
|
||||
type reloadEntry struct {
|
||||
dynamic *DynamicIPMatcher
|
||||
matcher IPMatcher
|
||||
}
|
||||
reloaded := make([]reloadEntry, len(r.matchers))
|
||||
for i, d := range r.matchers {
|
||||
reloaded := make([]reloadEntry, len(matchers))
|
||||
for i, d := range matchers {
|
||||
m, err := buildOptimizedIPMatcher(factory, d.rules)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to reload GeoIP data for IP matcher ", i)
|
||||
@@ -52,14 +59,15 @@ func (r *IPRegistry) Reload() error {
|
||||
for _, entry := range reloaded {
|
||||
entry.dynamic.Reload(entry.matcher)
|
||||
}
|
||||
r.ipsetFactory = factory
|
||||
errors.LogInfo(context.Background(), "reloaded GeoIP data for ", len(r.matchers), " IP matcher(s)")
|
||||
r.factory = factory
|
||||
errors.LogInfo(context.Background(), "reloaded GeoIP data for ", len(matchers), " IP matcher(s)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func newIPRegistry() *IPRegistry {
|
||||
return &IPRegistry{
|
||||
ipsetFactory: newIPSetFactory(),
|
||||
factory: newIPSetFactory(),
|
||||
matchers: utils.NewWeakCacheMap[uuid.UUID, DynamicIPMatcher](),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ func ParseDomainRule(r string, defaultType Domain_Type) (*DomainRule, error) {
|
||||
}
|
||||
|
||||
prefix := 0
|
||||
for _, ext := range [...]string{"ext:", "ext-domain:"} {
|
||||
for _, ext := range [...]string{"ext:", "ext-domain:", "ext-site:"} {
|
||||
if strings.HasPrefix(r, ext) {
|
||||
prefix = len(ext)
|
||||
break
|
||||
@@ -167,7 +167,7 @@ func ParseDomainRules(rules []string, defaultType Domain_Type) ([]*DomainRule, e
|
||||
}
|
||||
|
||||
prefix := 0
|
||||
for _, ext := range [...]string{"ext:", "ext-domain:"} {
|
||||
for _, ext := range [...]string{"ext:", "ext-domain:", "ext-site:"} {
|
||||
if strings.HasPrefix(r, ext) {
|
||||
prefix = len(ext)
|
||||
break
|
||||
|
||||
@@ -98,7 +98,6 @@ func (l *generalLogger) run() {
|
||||
}
|
||||
|
||||
func (l *generalLogger) Handle(msg Message) {
|
||||
|
||||
select {
|
||||
case l.buffer <- msg:
|
||||
default:
|
||||
|
||||
@@ -36,9 +36,10 @@ func TestNormalizeEnvName(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnvFlag(t *testing.T) {
|
||||
if v := (EnvFlag{
|
||||
v := EnvFlag{
|
||||
Name: "xxxxx.y",
|
||||
}.GetValueAsInt(10)); v != 10 {
|
||||
}.GetValueAsInt(10)
|
||||
if v != 10 {
|
||||
t.Error("env value: ", v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,41 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
)
|
||||
|
||||
// ParseXForwardedFor parses X-Forwarded-For header in http headers, and return the IP list in it.
|
||||
func ParseXForwardedFor(header http.Header) []net.Address {
|
||||
xff := header.Get("X-Forwarded-For")
|
||||
if xff == "" {
|
||||
return nil
|
||||
// ApplyTrustedXForwardedFor returns remoteAddr overridden by X-Forwarded-For only when a configured trusted header is present.
|
||||
func ApplyTrustedXForwardedFor(header http.Header, trusted []string, remoteAddr net.Addr) net.Addr {
|
||||
value := header.Get("X-Forwarded-For")
|
||||
if value == "" {
|
||||
return remoteAddr
|
||||
}
|
||||
list := strings.Split(xff, ",")
|
||||
addrs := make([]net.Address, 0, len(list))
|
||||
for _, proxy := range list {
|
||||
addrs = append(addrs, net.ParseAddress(proxy))
|
||||
for _, t := range trusted {
|
||||
if len(header.Values(t)) > 0 {
|
||||
if idx := strings.IndexByte(value, ','); idx >= 0 {
|
||||
value = value[:idx]
|
||||
}
|
||||
if addr := net.ParseAddress(value); addr.Family().IsIP() {
|
||||
return &net.TCPAddr{
|
||||
IP: addr.IP(),
|
||||
Port: 0,
|
||||
}
|
||||
}
|
||||
return remoteAddr
|
||||
}
|
||||
}
|
||||
return addrs
|
||||
if len(trusted) == 0 {
|
||||
errors.LogWarning(context.Background(), `received "X-Forwarded-For" from `, remoteAddr, ` but "sockopt.trustedXForwardedFor" is not configured; ignoring it and using the real remote address`)
|
||||
} else {
|
||||
errors.LogError(context.Background(), `ignored potentially forged "X-Forwarded-For" from `, remoteAddr, `: `, value)
|
||||
}
|
||||
return remoteAddr
|
||||
}
|
||||
|
||||
// RemoveHopByHopHeaders removes hop by hop headers in http header list.
|
||||
|
||||
@@ -2,23 +2,48 @@ package http_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
gonet "net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
. "github.com/xtls/xray-core/common/protocol/http"
|
||||
)
|
||||
|
||||
func TestParseXForwardedFor(t *testing.T) {
|
||||
header := http.Header{}
|
||||
header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103")
|
||||
addrs := ParseXForwardedFor(header)
|
||||
if r := cmp.Diff(addrs, []net.Address{net.ParseAddress("129.78.138.66"), net.ParseAddress("129.78.64.103")}); r != "" {
|
||||
t.Error(r)
|
||||
}
|
||||
func TestApplyTrustedXForwardedFor(t *testing.T) {
|
||||
remoteAddr := &gonet.TCPAddr{IP: gonet.ParseIP("127.0.0.1"), Port: 12345}
|
||||
|
||||
t.Run("ignore X-Forwarded-For without trusted header", func(t *testing.T) {
|
||||
header := http.Header{}
|
||||
header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103")
|
||||
|
||||
if addr := ApplyTrustedXForwardedFor(header, nil, remoteAddr); addr != remoteAddr {
|
||||
t.Fatalf("unexpected remote address: %v", addr)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("trust X-Forwarded-For", func(t *testing.T) {
|
||||
header := http.Header{}
|
||||
header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103")
|
||||
header.Add("X-Trusted-CDN", "")
|
||||
|
||||
addr := ApplyTrustedXForwardedFor(header, []string{"X-Trusted-CDN"}, remoteAddr)
|
||||
if addr.String() != "129.78.138.66:0" {
|
||||
t.Fatalf("unexpected remote address: %v", addr)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ignore non-IP X-Forwarded-For", func(t *testing.T) {
|
||||
header := http.Header{}
|
||||
header.Add("X-Forwarded-For", "example.com")
|
||||
header.Add("X-Trusted-CDN", "")
|
||||
|
||||
if addr := ApplyTrustedXForwardedFor(header, []string{"X-Trusted-CDN"}, remoteAddr); addr != remoteAddr {
|
||||
t.Fatalf("unexpected remote address: %v", addr)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestHopByHopHeadersRemoving(t *testing.T) {
|
||||
|
||||
@@ -19,7 +19,7 @@ type ID struct {
|
||||
|
||||
// Equals returns true if this ID equals to the other one.
|
||||
func (id *ID) Equals(another *ID) bool {
|
||||
return id.uuid.Equals(&(another.uuid))
|
||||
return id.uuid.Equals(&another.uuid)
|
||||
}
|
||||
|
||||
func (id *ID) Bytes() []byte {
|
||||
|
||||
@@ -229,7 +229,7 @@ func TestSniffQUICComplex(t *testing.T) {
|
||||
t.Errorf("SniffQUIC() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if (errors.Is(err, protocol.ErrProtoNeedMoreData)) != tt.needsMoreData {
|
||||
if errors.Is(err, protocol.ErrProtoNeedMoreData) != tt.needsMoreData {
|
||||
t.Errorf("SniffQUIC() error = %v, expectsNoClue %v", err, tt.needsMoreData)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
)
|
||||
|
||||
type ServerSpec struct {
|
||||
Destination net.Destination
|
||||
User *MemoryUser
|
||||
Destination net.Destination
|
||||
User *MemoryUser
|
||||
}
|
||||
|
||||
func NewServerSpec(dest net.Destination, user *MemoryUser) *ServerSpec {
|
||||
|
||||
@@ -228,7 +228,6 @@ func isValueKind(kind reflect.Kind) bool {
|
||||
}
|
||||
|
||||
func marshalInterface(v interface{}, ignoreNullValue bool, insertTypeInfo bool) interface{} {
|
||||
|
||||
if r, ok := marshalKnownType(v, ignoreNullValue, insertTypeInfo); ok {
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ func TestMashalAccount(t *testing.T) {
|
||||
|
||||
j, ok := MarshalToJson(user, false)
|
||||
if !ok || strings.Contains(j, "_TypedMessage_") {
|
||||
|
||||
t.Error("marshal account failed")
|
||||
}
|
||||
|
||||
@@ -79,13 +78,12 @@ func TestMashalStruct(t *testing.T) {
|
||||
|
||||
v := (*f2.Arr)[0]["foo"]["hello"]
|
||||
|
||||
if f1.N != f2.N || *(f1.Np) != *(f2.Np) || f1.S != f2.S || v != "world" {
|
||||
if f1.N != f2.N || *f1.Np != *f2.Np || f1.S != f2.S || v != "world" {
|
||||
t.Error("f1 not equal to f2")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalConfigJson(t *testing.T) {
|
||||
|
||||
buf := bytes.NewBufferString(getConfig())
|
||||
config, err := iserial.DecodeJSONConfig(buf)
|
||||
if err != nil {
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"testing"
|
||||
|
||||
. "github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
||||
)
|
||||
|
||||
func TestGetInstance(t *testing.T) {
|
||||
@@ -23,31 +22,3 @@ func TestConvertingNilMessage(t *testing.T) {
|
||||
t.Error("expect nil, but actually not")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypedMessageRoundTripPreservesFinalmaskCustomUDPMode(t *testing.T) {
|
||||
msg := &custom.UDPConfig{
|
||||
Mode: "standalone",
|
||||
Client: []*custom.UDPItem{
|
||||
{Rand: 12, Save: "txid"},
|
||||
},
|
||||
}
|
||||
|
||||
tm := ToTypedMessage(msg)
|
||||
if tm == nil {
|
||||
t.Fatal("expected typed message")
|
||||
}
|
||||
|
||||
roundTrip, err := tm.GetInstance()
|
||||
if err != nil {
|
||||
t.Fatalf("GetInstance() failed: %v", err)
|
||||
}
|
||||
|
||||
udp, ok := roundTrip.(*custom.UDPConfig)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected round-trip type: %T", roundTrip)
|
||||
}
|
||||
|
||||
if udp.GetMode() != "standalone" {
|
||||
t.Fatalf("mode lost during typed message round-trip: got %q", udp.GetMode())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ const (
|
||||
fullHandlerKey ctx.SessionKey = 10 // outbound gets full handler
|
||||
mitmAlpn11Key ctx.SessionKey = 11 // used by TLS dialer
|
||||
mitmServerNameKey ctx.SessionKey = 12 // used by TLS dialer
|
||||
|
||||
streamSettingsKey ctx.SessionKey = 13
|
||||
)
|
||||
|
||||
func ContextWithInbound(ctx context.Context, inbound *Inbound) context.Context {
|
||||
@@ -192,3 +194,11 @@ func MitmServerNameFromContext(ctx context.Context) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func ContextWithStreamSettings(ctx context.Context, streamSettings any) context.Context {
|
||||
return context.WithValue(ctx, streamSettingsKey, streamSettings)
|
||||
}
|
||||
|
||||
func StreamSettingsFromContext(ctx context.Context) any {
|
||||
return ctx.Value(streamSettingsKey)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package singbridge
|
||||
import (
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
)
|
||||
|
||||
@@ -17,14 +18,14 @@ func ToNetwork(network string) net.Network {
|
||||
}
|
||||
}
|
||||
|
||||
func ToDestination(socksaddr M.Socksaddr, network net.Network) net.Destination {
|
||||
func ToDestination(socksaddr M.Socksaddr, network net.Network) (net.Destination, error) {
|
||||
// IsFqdn() implicitly checks if the domain name is valid
|
||||
if socksaddr.IsFqdn() {
|
||||
return net.Destination{
|
||||
Network: network,
|
||||
Address: net.DomainAddress(socksaddr.Fqdn),
|
||||
Port: net.Port(socksaddr.Port),
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsIP() implicitly checks if the IP address is valid
|
||||
@@ -33,10 +34,10 @@ func ToDestination(socksaddr M.Socksaddr, network net.Network) net.Destination {
|
||||
Network: network,
|
||||
Address: net.IPAddress(socksaddr.Addr.AsSlice()),
|
||||
Port: net.Port(socksaddr.Port),
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
return net.Destination{}
|
||||
return net.Destination{}, errors.New("invalid socks address: ", socksaddr)
|
||||
}
|
||||
|
||||
func ToSocksaddr(destination net.Destination) M.Socksaddr {
|
||||
|
||||
@@ -26,7 +26,11 @@ func NewDialer(dialer internet.Dialer) *XrayDialer {
|
||||
}
|
||||
|
||||
func (d *XrayDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
return d.Dialer.Dial(ctx, ToDestination(destination, ToNetwork(network)))
|
||||
dest, err := ToDestination(destination, ToNetwork(network))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.Dialer.Dial(ctx, dest)
|
||||
}
|
||||
|
||||
func (d *XrayDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
@@ -43,13 +47,17 @@ func NewOutboundDialer(outbound proxy.Outbound, dialer internet.Dialer) *XrayOut
|
||||
}
|
||||
|
||||
func (d *XrayOutboundDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
dest, err := ToDestination(destination, ToNetwork(network))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outbounds := session.OutboundsFromContext(ctx)
|
||||
if len(outbounds) == 0 {
|
||||
outbounds = []*session.Outbound{{}}
|
||||
ctx = session.ContextWithOutbounds(ctx, outbounds)
|
||||
}
|
||||
ob := outbounds[len(outbounds)-1]
|
||||
ob.Target = ToDestination(destination, ToNetwork(network))
|
||||
ob.Target = dest
|
||||
|
||||
opts := []pipe.Option{pipe.WithSizeLimit(64 * 1024)}
|
||||
uplinkReader, uplinkWriter := pipe.New(opts...)
|
||||
|
||||
@@ -31,15 +31,23 @@ func NewDispatcher(dispatcher routing.Dispatcher, newErrorFunc func(values ...an
|
||||
}
|
||||
|
||||
func (d *Dispatcher) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
dest, err := ToDestination(metadata.Destination, net.Network_TCP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
xConn := NewConn(conn)
|
||||
return d.upstream.DispatchLink(ctx, ToDestination(metadata.Destination, net.Network_TCP), &transport.Link{
|
||||
return d.upstream.DispatchLink(ctx, dest, &transport.Link{
|
||||
Reader: xConn,
|
||||
Writer: xConn,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Dispatcher) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
return d.upstream.DispatchLink(ctx, ToDestination(metadata.Destination, net.Network_UDP), &transport.Link{
|
||||
dest, err := ToDestination(metadata.Destination, net.Network_UDP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.upstream.DispatchLink(ctx, dest, &transport.Link{
|
||||
Reader: buf.NewPacketReader(conn.(io.Reader)),
|
||||
Writer: buf.NewWriter(conn.(io.Writer)),
|
||||
})
|
||||
|
||||
+31
-30
@@ -10,15 +10,21 @@ import (
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/signal"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
)
|
||||
|
||||
func CopyPacketConn(ctx context.Context, inboundConn net.Conn, link *transport.Link, destination net.Destination, serverConn net.PacketConn) error {
|
||||
cancel := func() {
|
||||
common.Interrupt(link.Reader)
|
||||
common.Interrupt(serverConn)
|
||||
}
|
||||
conn := &PacketConnWrapper{
|
||||
Reader: link.Reader,
|
||||
Writer: link.Writer,
|
||||
Dest: destination,
|
||||
Conn: inboundConn,
|
||||
T: signal.CancelAfterInactivity(ctx, cancel, 300*time.Second),
|
||||
}
|
||||
return ReturnError(bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(serverConn)))
|
||||
}
|
||||
@@ -29,11 +35,19 @@ type PacketConnWrapper struct {
|
||||
net.Conn
|
||||
Dest net.Destination
|
||||
cached buf.MultiBuffer
|
||||
|
||||
// A simple patch to avoid goroutine leak since sing infra cannot awake read block by write err
|
||||
T *signal.ActivityTimer
|
||||
}
|
||||
|
||||
// This ReadPacket implemented a timeout to avoid goroutine leak like PipeConnWrapper.Read()
|
||||
// as a temporarily solution
|
||||
func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) {
|
||||
func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (addr M.Socksaddr, err error) {
|
||||
w.T.Update()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// uplinkonly
|
||||
w.T.SetTimeout(2 * time.Second)
|
||||
}
|
||||
}()
|
||||
if w.cached != nil {
|
||||
mb, bb := buf.SplitFirst(w.cached)
|
||||
if bb == nil {
|
||||
@@ -51,30 +65,7 @@ func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) {
|
||||
return ToSocksaddr(destination), nil
|
||||
}
|
||||
}
|
||||
|
||||
// timeout
|
||||
type readResult struct {
|
||||
mb buf.MultiBuffer
|
||||
err error
|
||||
}
|
||||
c := make(chan readResult, 1)
|
||||
go func() {
|
||||
mb, err := w.ReadMultiBuffer()
|
||||
c <- readResult{mb: mb, err: err}
|
||||
}()
|
||||
var mb buf.MultiBuffer
|
||||
select {
|
||||
case <-time.After(60 * time.Second):
|
||||
common.Close(w.Reader)
|
||||
common.Interrupt(w.Reader)
|
||||
return M.Socksaddr{}, buf.ErrReadTimeout
|
||||
case result := <-c:
|
||||
if result.err != nil {
|
||||
return M.Socksaddr{}, result.err
|
||||
}
|
||||
mb = result.mb
|
||||
}
|
||||
|
||||
mb, err := w.ReadMultiBuffer()
|
||||
nb, bb := buf.SplitFirst(mb)
|
||||
if bb == nil {
|
||||
return M.Socksaddr{}, nil
|
||||
@@ -92,12 +83,22 @@ func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *PacketConnWrapper) WritePacket(buffer *B.Buffer, destination M.Socksaddr) error {
|
||||
func (w *PacketConnWrapper) WritePacket(buffer *B.Buffer, destination M.Socksaddr) (err error) {
|
||||
w.T.Update()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// downlinkonly
|
||||
w.T.SetTimeout(5 * time.Second)
|
||||
}
|
||||
}()
|
||||
endpoint, err := ToDestination(destination, net.Network_UDP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
vBuf := buf.New()
|
||||
vBuf.Write(buffer.Bytes())
|
||||
endpoint := ToDestination(destination, net.Network_UDP)
|
||||
vBuf.UDP = &endpoint
|
||||
return w.Writer.WriteMultiBuffer(buf.MultiBuffer{vBuf})
|
||||
return w.WriteMultiBuffer(buf.MultiBuffer{vBuf})
|
||||
}
|
||||
|
||||
func (w *PacketConnWrapper) Close() error {
|
||||
|
||||
+18
-18
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/common/signal"
|
||||
"github.com/xtls/xray-core/transport"
|
||||
)
|
||||
|
||||
@@ -22,6 +23,11 @@ func CopyConn(ctx context.Context, inboundConn net.Conn, link *transport.Link, s
|
||||
} else {
|
||||
conn.R = &buf.BufferedReader{Reader: link.Reader}
|
||||
}
|
||||
cancel := func() {
|
||||
common.Interrupt(link.Reader)
|
||||
common.Interrupt(serverConn)
|
||||
}
|
||||
conn.T = signal.CancelAfterInactivity(ctx, cancel, 300*time.Second)
|
||||
return ReturnError(bufio.CopyConn(ctx, conn, serverConn))
|
||||
}
|
||||
|
||||
@@ -29,35 +35,27 @@ type PipeConnWrapper struct {
|
||||
R io.Reader
|
||||
W buf.Writer
|
||||
net.Conn
|
||||
|
||||
// A simple patch to avoid goroutine leak since sing infra cannot awake read block by write err
|
||||
T *signal.ActivityTimer
|
||||
}
|
||||
|
||||
func (w *PipeConnWrapper) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This Read implemented a timeout to avoid goroutine leak.
|
||||
// as a temporarily solution
|
||||
func (w *PipeConnWrapper) Read(b []byte) (n int, err error) {
|
||||
type readResult struct {
|
||||
n int
|
||||
err error
|
||||
}
|
||||
c := make(chan readResult, 1)
|
||||
go func() {
|
||||
n, err := w.R.Read(b)
|
||||
c <- readResult{n: n, err: err}
|
||||
}()
|
||||
select {
|
||||
case result := <-c:
|
||||
return result.n, result.err
|
||||
case <-time.After(300 * time.Second):
|
||||
common.Close(w.R)
|
||||
common.Interrupt(w.R)
|
||||
return 0, buf.ErrReadTimeout
|
||||
w.T.Update()
|
||||
n, err = w.R.Read(b)
|
||||
if err != nil {
|
||||
// uplinkonly
|
||||
w.T.SetTimeout(2 * time.Second)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (w *PipeConnWrapper) Write(p []byte) (n int, err error) {
|
||||
w.T.Update()
|
||||
n = len(p)
|
||||
var mb buf.MultiBuffer
|
||||
pLen := len(p)
|
||||
@@ -76,6 +74,8 @@ func (w *PipeConnWrapper) Write(p []byte) (n int, err error) {
|
||||
if err != nil {
|
||||
n = 0
|
||||
buf.ReleaseMulti(mb)
|
||||
// downlinkonly
|
||||
w.T.SetTimeout(5 * time.Second)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -10,37 +10,44 @@ func TestByteSizes(t *testing.T) {
|
||||
size := units.ByteSize(0)
|
||||
assertSizeString(t, size, "0")
|
||||
size++
|
||||
assertSizeValue(t,
|
||||
assertSizeValue(
|
||||
t,
|
||||
assertSizeString(t, size, "1.00B"),
|
||||
size,
|
||||
)
|
||||
size <<= 10
|
||||
assertSizeValue(t,
|
||||
assertSizeValue(
|
||||
t,
|
||||
assertSizeString(t, size, "1.00KB"),
|
||||
size,
|
||||
)
|
||||
size <<= 10
|
||||
assertSizeValue(t,
|
||||
assertSizeValue(
|
||||
t,
|
||||
assertSizeString(t, size, "1.00MB"),
|
||||
size,
|
||||
)
|
||||
size <<= 10
|
||||
assertSizeValue(t,
|
||||
assertSizeValue(
|
||||
t,
|
||||
assertSizeString(t, size, "1.00GB"),
|
||||
size,
|
||||
)
|
||||
size <<= 10
|
||||
assertSizeValue(t,
|
||||
assertSizeValue(
|
||||
t,
|
||||
assertSizeString(t, size, "1.00TB"),
|
||||
size,
|
||||
)
|
||||
size <<= 10
|
||||
assertSizeValue(t,
|
||||
assertSizeValue(
|
||||
t,
|
||||
assertSizeString(t, size, "1.00PB"),
|
||||
size,
|
||||
)
|
||||
size <<= 10
|
||||
assertSizeValue(t,
|
||||
assertSizeValue(
|
||||
t,
|
||||
assertSizeString(t, size, "1.00EB"),
|
||||
size,
|
||||
)
|
||||
|
||||
+77
-43
@@ -4,10 +4,10 @@ import (
|
||||
"hash/fnv"
|
||||
"math"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/klauspost/cpuid/v2"
|
||||
)
|
||||
@@ -18,6 +18,7 @@ func GetRandomizer() *rand.Rand {
|
||||
fnvHash.Write([]byte(strconv.Itoa(cpuid.CPU.Family) + strconv.Itoa(cpuid.CPU.Model) + strconv.Itoa(cpuid.CPU.PhysicalCores) + strconv.Itoa(cpuid.CPU.LogicalCores) + strconv.Itoa(cpuid.CPU.CacheLine) + strconv.Itoa(cpuid.CPU.ThreadsPerCore)))
|
||||
return rand.New(rand.NewSource(int64(fnvHash.Sum64())))
|
||||
}
|
||||
|
||||
var globalRng *rand.Rand = GetRandomizer()
|
||||
|
||||
// The Chrome version generator will suffer from deviation of a normal distribution.
|
||||
@@ -26,83 +27,114 @@ func ChromeVersion() int {
|
||||
var startVersion int = 144
|
||||
var timeStart int64 = time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC).Unix() / 86400
|
||||
var timeCurrent int64 = time.Now().Unix() / 86400
|
||||
var timeDiff int = int((timeCurrent - timeStart - 35)) - int(math.Floor(math.Pow(globalRng.Float64(), 2) * 105))
|
||||
var timeDiff int = int((timeCurrent - timeStart - 35)) - int(math.Floor(math.Pow(globalRng.Float64(), 2)*105))
|
||||
return startVersion + (timeDiff / 35) // It's 31.15 currently.
|
||||
}
|
||||
|
||||
var safariMinorMap [25]int = [25]int{0, 0, 0, 1, 1,
|
||||
var safariMinorMap [25]int = [25]int{
|
||||
0, 0, 0, 1, 1,
|
||||
1, 2, 2, 2, 2, 3, 3, 3, 4, 4,
|
||||
4, 5, 5, 5, 5, 5, 6, 6, 6, 6}
|
||||
4, 5, 5, 5, 5, 5, 6, 6, 6, 6,
|
||||
}
|
||||
|
||||
// The following version generators use deterministic generators, but with the distribution scaled by a curve.
|
||||
func CurlVersion() string {
|
||||
// curl 8.0.0 was released on 20/03/2023.
|
||||
var timeCurrent int64 = time.Now().Unix() / 86400
|
||||
var timeStart int64 = time.Date(2023, 3, 20, 0, 0, 0, 0, time.UTC).Unix() / 86400
|
||||
var timeDiff int = int((timeCurrent - timeStart - 60)) - int(math.Floor(math.Pow(globalRng.Float64(), 2) * 165))
|
||||
var timeDiff int = int((timeCurrent - timeStart - 60)) - int(math.Floor(math.Pow(globalRng.Float64(), 2)*165))
|
||||
var minorValue int = int(timeDiff / 57) // The release cadence is actually 56.67 days.
|
||||
return "8." + strconv.Itoa(minorValue) + ".0"
|
||||
}
|
||||
|
||||
func FirefoxVersion() int {
|
||||
// Firefox 128 ESR was released on 09/07/2023.
|
||||
var timeCurrent int64 = time.Now().Unix() / 86400
|
||||
var timeStart int64 = time.Date(2024, 7, 29, 0, 0, 0, 0, time.UTC).Unix() / 86400
|
||||
var timeDiff = timeCurrent - timeStart - 25 - int64(math.Floor(math.Pow(globalRng.Float64(), 2) * 50))
|
||||
return int(timeDiff / 30) + 128
|
||||
timeDiff := timeCurrent - timeStart - 25 - int64(math.Floor(math.Pow(globalRng.Float64(), 2)*50))
|
||||
return int(timeDiff/30) + 128
|
||||
}
|
||||
|
||||
func SafariVersion() string {
|
||||
var anchoredTime time.Time = time.Now()
|
||||
var releaseYear int = anchoredTime.Year()
|
||||
var splitPoint time.Time = time.Date(releaseYear, 9, 23, 0, 0, 0, 0, time.UTC)
|
||||
var delayedDays = int(math.Floor(math.Pow(globalRng.Float64(), 3) * 75))
|
||||
delayedDays := int(math.Floor(math.Pow(globalRng.Float64(), 3) * 75))
|
||||
splitPoint = splitPoint.AddDate(0, 0, delayedDays)
|
||||
if (anchoredTime.Compare(splitPoint) < 0) {
|
||||
releaseYear --
|
||||
if anchoredTime.Compare(splitPoint) < 0 {
|
||||
releaseYear--
|
||||
splitPoint = time.Date(releaseYear, 9, 23, 0, 0, 0, 0, time.UTC)
|
||||
splitPoint = splitPoint.AddDate(0, 0, delayedDays)
|
||||
}
|
||||
var minorVersion = safariMinorMap[(anchoredTime.Unix() - splitPoint.Unix()) / 1296000]
|
||||
return strconv.Itoa(releaseYear - 1999) + "." + strconv.Itoa(minorVersion)
|
||||
minorVersion := safariMinorMap[(anchoredTime.Unix()-splitPoint.Unix())/1296000]
|
||||
return strconv.Itoa(releaseYear-1999) + "." + strconv.Itoa(minorVersion)
|
||||
}
|
||||
|
||||
// The full Chromium brand GREASE implementation
|
||||
var clientHintGreaseNA = []string{" ", "(", ":", "-", ".", "/", ")", ";", "=", "?", "_"}
|
||||
var clientHintVersionNA = []string{"8", "99", "24"}
|
||||
var clientHintShuffle3 = [][3]int{{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}}
|
||||
var clientHintShuffle4 = [][4]int{
|
||||
{0, 1, 2, 3}, {0, 1, 3, 2}, {0, 2, 1, 3}, {0, 2, 3, 1}, {0, 3, 1, 2}, {0, 3, 2, 1},
|
||||
{1, 0, 2, 3}, {1, 0, 3, 2}, {1, 2, 0, 3}, {1, 2, 3, 0}, {1, 3, 0, 2}, {1, 3, 2, 0},
|
||||
{2, 0, 1, 3}, {2, 0, 3, 1}, {2, 1, 0, 3}, {2, 1, 3, 0}, {2, 3, 0, 1}, {2, 3, 1, 0},
|
||||
{3, 0, 1, 2}, {3, 0, 2, 1}, {3, 1, 0, 2}, {3, 1, 2, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}}
|
||||
var (
|
||||
clientHintGreaseNA = []string{" ", "(", ":", "-", ".", "/", ")", ";", "=", "?", "_"}
|
||||
clientHintVersionNA = []string{"8", "99", "24"}
|
||||
clientHintShuffle3 = [][3]int{{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}}
|
||||
clientHintShuffle4 = [][4]int{
|
||||
{0, 1, 2, 3},
|
||||
{0, 1, 3, 2},
|
||||
{0, 2, 1, 3},
|
||||
{0, 2, 3, 1},
|
||||
{0, 3, 1, 2},
|
||||
{0, 3, 2, 1},
|
||||
{1, 0, 2, 3},
|
||||
{1, 0, 3, 2},
|
||||
{1, 2, 0, 3},
|
||||
{1, 2, 3, 0},
|
||||
{1, 3, 0, 2},
|
||||
{1, 3, 2, 0},
|
||||
{2, 0, 1, 3},
|
||||
{2, 0, 3, 1},
|
||||
{2, 1, 0, 3},
|
||||
{2, 1, 3, 0},
|
||||
{2, 3, 0, 1},
|
||||
{2, 3, 1, 0},
|
||||
{3, 0, 1, 2},
|
||||
{3, 0, 2, 1},
|
||||
{3, 1, 0, 2},
|
||||
{3, 1, 2, 0},
|
||||
{3, 2, 0, 1},
|
||||
{3, 2, 1, 0},
|
||||
}
|
||||
)
|
||||
|
||||
func getGreasedChInvalidBrand(seed int) string {
|
||||
return "\"Not" + clientHintGreaseNA[seed % len(clientHintGreaseNA)] + "A" + clientHintGreaseNA[(seed + 1) % len(clientHintGreaseNA)] + "Brand\";v=\"" + clientHintVersionNA[seed % len(clientHintVersionNA)] + "\"";
|
||||
return "\"Not" + clientHintGreaseNA[seed%len(clientHintGreaseNA)] + "A" + clientHintGreaseNA[(seed+1)%len(clientHintGreaseNA)] + "Brand\";v=\"" + clientHintVersionNA[seed%len(clientHintVersionNA)] + "\""
|
||||
}
|
||||
|
||||
func getGreasedChOrder(brandLength int, seed int) []int {
|
||||
switch brandLength {
|
||||
case 1:
|
||||
return []int{0}
|
||||
case 2:
|
||||
return []int{seed % brandLength, (seed + 1) % brandLength}
|
||||
case 3:
|
||||
return clientHintShuffle3[seed % len(clientHintShuffle3)][:]
|
||||
default:
|
||||
return clientHintShuffle4[seed % len(clientHintShuffle4)][:]
|
||||
case 1:
|
||||
return []int{0}
|
||||
case 2:
|
||||
return []int{seed % brandLength, (seed + 1) % brandLength}
|
||||
case 3:
|
||||
return clientHintShuffle3[seed%len(clientHintShuffle3)][:]
|
||||
default:
|
||||
return clientHintShuffle4[seed%len(clientHintShuffle4)][:]
|
||||
}
|
||||
//return []int{}
|
||||
}
|
||||
|
||||
func getUngreasedChUa(majorVersion int, forkName string) []string {
|
||||
// Set the capacity to 4, the maximum allowed brand size, so Go will never allocate memory twice
|
||||
baseChUa := make([]string, 0, 4)
|
||||
baseChUa = append(baseChUa, getGreasedChInvalidBrand(majorVersion),
|
||||
"\"Chromium\";v=\"" + strconv.Itoa(majorVersion) + "\"")
|
||||
"\"Chromium\";v=\""+strconv.Itoa(majorVersion)+"\"")
|
||||
switch forkName {
|
||||
case "chrome":
|
||||
baseChUa = append(baseChUa, "\"Google Chrome\";v=\"" + strconv.Itoa(majorVersion) + "\"")
|
||||
baseChUa = append(baseChUa, "\"Google Chrome\";v=\""+strconv.Itoa(majorVersion)+"\"")
|
||||
case "edge":
|
||||
baseChUa = append(baseChUa, "\"Microsoft Edge\";v=\"" + strconv.Itoa(majorVersion) + "\"")
|
||||
baseChUa = append(baseChUa, "\"Microsoft Edge\";v=\""+strconv.Itoa(majorVersion)+"\"")
|
||||
}
|
||||
return baseChUa
|
||||
}
|
||||
|
||||
func getGreasedChUa(majorVersion int, forkName string) string {
|
||||
ungreasedCh := getUngreasedChUa(majorVersion, forkName)
|
||||
shuffleMap := getGreasedChOrder(len(ungreasedCh), majorVersion)
|
||||
@@ -114,16 +146,18 @@ func getGreasedChUa(majorVersion int, forkName string) string {
|
||||
}
|
||||
|
||||
// The code below provides a coherent default browser user agent string based on a CPU-seeded PRNG.
|
||||
var CurlUA = "curl/" + CurlVersion()
|
||||
var AnchoredFirefoxVersion = strconv.Itoa(FirefoxVersion())
|
||||
var FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:" + AnchoredFirefoxVersion + ".0) Gecko/20100101 Firefox/" + AnchoredFirefoxVersion + ".0"
|
||||
var SafariUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/" + SafariVersion() + " Safari/605.1.15"
|
||||
// Chromium browsers.
|
||||
var AnchoredChromeVersion = ChromeVersion()
|
||||
var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 Safari/537.36"
|
||||
var ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome")
|
||||
var MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0"
|
||||
var MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge")
|
||||
var (
|
||||
CurlUA = "curl/" + CurlVersion()
|
||||
AnchoredFirefoxVersion = strconv.Itoa(FirefoxVersion())
|
||||
FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:" + AnchoredFirefoxVersion + ".0) Gecko/20100101 Firefox/" + AnchoredFirefoxVersion + ".0"
|
||||
SafariUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/" + SafariVersion() + " Safari/605.1.15"
|
||||
// Chromium browsers.
|
||||
AnchoredChromeVersion = ChromeVersion()
|
||||
ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 Safari/537.36"
|
||||
ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome")
|
||||
MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0"
|
||||
MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge")
|
||||
)
|
||||
|
||||
func applyMasqueradedHeaders(header http.Header, browser string, variant string) {
|
||||
// Browser-specific.
|
||||
|
||||
@@ -109,4 +109,4 @@ func (m *TypedSyncMap[K, V]) Swap(key K, value V) (previous V, loaded bool) {
|
||||
previous = anyPrevious.(V)
|
||||
}
|
||||
return previous, loaded
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ResolveSocketPath applies platform-specific transformations to a Unix
|
||||
// socket path, matching the listen-side behaviour in
|
||||
// transport/internet/system_listener.go.
|
||||
//
|
||||
// For abstract sockets (prefix @) on Linux/Android:
|
||||
// - single @ — used as-is (lock-free abstract socket)
|
||||
// - double @@ — stripped to single @ and padded to
|
||||
// syscall.RawSockaddrUnix{}.Path length (HAProxy compat)
|
||||
//
|
||||
// Filesystem paths and abstract sockets on other platforms are returned
|
||||
// unchanged.
|
||||
func ResolveSocketPath(path string) string {
|
||||
if len(path) == 0 || path[0] != '@' {
|
||||
return path
|
||||
}
|
||||
if runtime.GOOS != "linux" && runtime.GOOS != "android" {
|
||||
return path
|
||||
}
|
||||
if len(path) > 1 && path[1] == '@' {
|
||||
fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path))
|
||||
copy(fullAddr, path[1:])
|
||||
return string(fullAddr)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// SplitHTTPUnixURL splits a target into an HTTP URL and an optional Unix
|
||||
// socket path. For regular http(s) URLs the input is returned unchanged
|
||||
// with an empty socketPath. For Unix sockets the format is:
|
||||
//
|
||||
// /path/to/socket.sock[:/http/path]
|
||||
// @abstract[:/http/path]
|
||||
// @@padded[:/http/path]
|
||||
//
|
||||
// The :/ separator delimits the socket path from the HTTP request path.
|
||||
// If omitted, "/" is used.
|
||||
func SplitHTTPUnixURL(raw string) (httpURL, socketPath string) {
|
||||
if len(raw) == 0 || (!filepath.IsAbs(raw) && raw[0] != '@') {
|
||||
return raw, ""
|
||||
}
|
||||
if idx := strings.Index(raw, ":/"); idx >= 0 {
|
||||
return "http://localhost" + raw[idx+1:], raw[:idx]
|
||||
}
|
||||
return "http://localhost/", raw
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"runtime"
|
||||
"sync"
|
||||
"weak"
|
||||
)
|
||||
|
||||
// WeakCacheMap is a map that holds weak references to values.
|
||||
// Use for shared expensive objects and automatic cleanup when no longer used.
|
||||
// This object can be GC and no goroutine is used for cleanup.
|
||||
type WeakCacheMap[K comparable, V any] struct {
|
||||
mu sync.Mutex
|
||||
m map[K]weak.Pointer[V]
|
||||
}
|
||||
|
||||
func NewWeakCacheMap[K comparable, V any]() *WeakCacheMap[K, V] {
|
||||
return &WeakCacheMap[K, V]{
|
||||
m: make(map[K]weak.Pointer[V]),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WeakCacheMap[K, V]) Load(key K) (value *V, ok bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
weakPtr := c.m[key].Value()
|
||||
if weakPtr != nil {
|
||||
return weakPtr, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (c *WeakCacheMap[K, V]) Store(key K, value *V) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
weakPtr := weak.Make(value)
|
||||
c.m[key] = weakPtr
|
||||
runtime.AddCleanup(value, func(struct{}) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.m[key] == weakPtr {
|
||||
delete(c.m, key)
|
||||
}
|
||||
}, struct{}{})
|
||||
}
|
||||
|
||||
func (c *WeakCacheMap[K, V]) Range(f func(K, *V) bool) {
|
||||
c.mu.Lock()
|
||||
snapshot := maps.Clone(c.m)
|
||||
c.mu.Unlock()
|
||||
for k, v := range snapshot {
|
||||
if value := v.Value(); value != nil {
|
||||
if !f(k, value) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -19,8 +19,8 @@ import (
|
||||
|
||||
var (
|
||||
Version_x byte = 26
|
||||
Version_y byte = 5
|
||||
Version_z byte = 9
|
||||
Version_y byte = 6
|
||||
Version_z byte = 22
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -34,7 +34,6 @@ func (ctx *Context) GetSourceIPs() []net.IP {
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// GetSourcePort implements routing.Context.
|
||||
|
||||
@@ -4,7 +4,7 @@ go 1.26
|
||||
|
||||
require (
|
||||
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716
|
||||
github.com/cloudflare/circl v1.6.3
|
||||
github.com/cloudflare/circl v1.6.4
|
||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
|
||||
github.com/golang/mock v1.7.0-rc.1
|
||||
github.com/google/go-cmp v0.7.0
|
||||
@@ -12,6 +12,7 @@ require (
|
||||
github.com/klauspost/cpuid/v2 v2.3.0
|
||||
github.com/miekg/dns v1.1.72
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/pion/stun/v3 v3.1.6
|
||||
github.com/pires/go-proxyproto v0.12.0
|
||||
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
@@ -21,15 +22,15 @@ require (
|
||||
github.com/vishvananda/netlink v1.3.1
|
||||
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.50.0
|
||||
golang.org/x/crypto v0.53.0
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||
golang.org/x/net v0.53.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/sys v0.43.0
|
||||
golang.org/x/net v0.56.0
|
||||
golang.org/x/sync v0.21.0
|
||||
golang.org/x/sys v0.46.0
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
||||
golang.zx2c4.com/wireguard/windows v1.0.1
|
||||
google.golang.org/grpc v1.81.0
|
||||
google.golang.org/grpc v1.81.1
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
|
||||
h12.io/socks v1.0.3
|
||||
@@ -43,13 +44,17 @@ require (
|
||||
github.com/juju/ratelimit v1.0.2 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/pion/dtls/v3 v3.1.4 // indirect
|
||||
github.com/pion/logging v0.2.4 // indirect
|
||||
github.com/pion/transport/v4 v4.0.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
golang.org/x/mod v0.34.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
golang.org/x/time v0.12.0 // indirect
|
||||
golang.org/x/tools v0.43.0 // indirect
|
||||
github.com/wlynxg/anet v0.0.5 // indirect
|
||||
golang.org/x/mod v0.36.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.45.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
@@ -4,8 +4,8 @@ github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716 h1:J1O+xpLuJWkd
|
||||
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/cloudflare/circl v1.6.4 h1:pOXuDTCEYyzydgUpQ0CQz3LsinKjiSk6nNP5Lt5K64U=
|
||||
github.com/cloudflare/circl v1.6.4/go.mod h1:YxarevkLlbaHuWsxG6vmYNWBEsSp4pnp7j+4VljMavY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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=
|
||||
@@ -45,6 +45,14 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||
github.com/pion/dtls/v3 v3.1.4 h1:QhvtMflMfu9Kf0RcDC5BJBle4caPskByrKQR6uuYqpY=
|
||||
github.com/pion/dtls/v3 v3.1.4/go.mod h1:cr/qotLISUw/9C1m83ZPNZtj9WnXkYLpfCptPqbkInc=
|
||||
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
|
||||
github.com/pion/stun/v3 v3.1.6 h1:WnhsD0eHCiwCfKNkVx0VJJwr2Y3eV4Ueih3KJ+dfZy8=
|
||||
github.com/pion/stun/v3 v3.1.6/go.mod h1:zRUghXSQU32Lx5orJsz3uYMkIihweXb3mu5gIns02fs=
|
||||
github.com/pion/transport/v4 v4.0.2 h1:ifYlPqNwsy6aKQ9y8yzxXlHae5431ZrH2avkD/Rn6Tk=
|
||||
github.com/pion/transport/v4 v4.0.2/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
|
||||
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
|
||||
github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -67,6 +75,8 @@ github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW
|
||||
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
||||
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f h1:iy2JRioxmUpoJ3SzbFPyTxHZMbR/rSHP7dOOgYaq1O8=
|
||||
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
@@ -88,22 +98,22 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -111,21 +121,21 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -139,8 +149,8 @@ gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw=
|
||||
google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
|
||||
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -48,4 +48,5 @@ var configLoader = NewJSONConfigLoader(
|
||||
"http": func() interface{} { return new(HTTPResponse) },
|
||||
},
|
||||
"type",
|
||||
"")
|
||||
"",
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ package conf
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -199,7 +200,7 @@ func (v *PortRange) UnmarshalJSON(data []byte) error {
|
||||
if err == nil {
|
||||
v.From = uint32(from)
|
||||
v.To = uint32(to)
|
||||
if v.From > v.To {
|
||||
if v.From > v.To || v.To > math.MaxUint16 {
|
||||
return errors.New("invalid port range ", v.From, " -> ", v.To)
|
||||
}
|
||||
return nil
|
||||
|
||||
+22
-20
@@ -12,8 +12,9 @@ import (
|
||||
|
||||
type DNSOutboundRuleConfig struct {
|
||||
Action string `json:"action"`
|
||||
QType *PortList `json:"qtype"`
|
||||
QType *PortList `json:"qType"`
|
||||
Domain *StringList `json:"domain"`
|
||||
RCode uint32 `json:"rCode"`
|
||||
}
|
||||
|
||||
func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
|
||||
@@ -24,8 +25,8 @@ func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
|
||||
rule.Action = dns.RuleAction_Direct
|
||||
case "drop":
|
||||
rule.Action = dns.RuleAction_Drop
|
||||
case "reject":
|
||||
rule.Action = dns.RuleAction_Reject
|
||||
case "return":
|
||||
rule.Action = dns.RuleAction_Return
|
||||
case "hijack":
|
||||
rule.Action = dns.RuleAction_Hijack
|
||||
default:
|
||||
@@ -34,14 +35,8 @@ func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
|
||||
|
||||
if c.QType != nil {
|
||||
for _, r := range c.QType.Range {
|
||||
if r.From > r.To {
|
||||
return nil, errors.New("invalid qtype range: ", r.String())
|
||||
}
|
||||
if r.To > 65535 {
|
||||
return nil, errors.New("dns rule qtype out of range: ", r.String())
|
||||
}
|
||||
for qtype := r.From; qtype <= r.To; qtype++ {
|
||||
rule.Qtype = append(rule.Qtype, int32(qtype))
|
||||
for qType := r.From; qType <= r.To; qType++ {
|
||||
rule.QType = append(rule.QType, int32(qType))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,6 +49,11 @@ func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
|
||||
rule.Domain = rules
|
||||
}
|
||||
|
||||
if c.RCode > 65535 {
|
||||
return nil, errors.New("rCode out of range: ", c.RCode)
|
||||
}
|
||||
rule.RCode = c.RCode
|
||||
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
@@ -133,28 +133,30 @@ func (c *DNSOutboundConfig) buildLegacyDNSPolicy() ([]*dns.DNSRuleConfig, error)
|
||||
if c.BlockTypes != nil && len(*c.BlockTypes) > 0 {
|
||||
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Drop}
|
||||
if mode == "reject" {
|
||||
rule.Action = dns.RuleAction_Reject
|
||||
rule.Action = dns.RuleAction_Return
|
||||
rule.RCode = 5
|
||||
}
|
||||
for _, qtype := range *c.BlockTypes {
|
||||
if qtype < 0 || qtype > 65535 {
|
||||
return nil, errors.New("legacy blockTypes qtype out of range: ", qtype)
|
||||
for _, qType := range *c.BlockTypes {
|
||||
if qType < 0 || qType > 65535 {
|
||||
return nil, errors.New("legacy blockTypes qType out of range: ", qType)
|
||||
}
|
||||
rule.Qtype = append(rule.Qtype, qtype)
|
||||
rule.QType = append(rule.QType, qType)
|
||||
}
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
|
||||
{
|
||||
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Hijack}
|
||||
rule.Qtype = append(rule.Qtype, 1)
|
||||
rule.Qtype = append(rule.Qtype, 28)
|
||||
rule.QType = append(rule.QType, 1)
|
||||
rule.QType = append(rule.QType, 28)
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
|
||||
{
|
||||
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Reject}
|
||||
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Return}
|
||||
if mode == "reject" {
|
||||
rule.Action = dns.RuleAction_Reject
|
||||
rule.Action = dns.RuleAction_Return
|
||||
rule.RCode = 5
|
||||
} else if mode == "drop" {
|
||||
rule.Action = dns.RuleAction_Drop
|
||||
} else if mode == "skip" {
|
||||
|
||||
@@ -35,10 +35,10 @@ func TestDnsProxyConfig(t *testing.T) {
|
||||
Input: `{
|
||||
"rules": [{
|
||||
"action": "direct",
|
||||
"qtype": "1,3,23-24"
|
||||
"qType": "1,3,23-24"
|
||||
}, {
|
||||
"action": "drop",
|
||||
"qtype": 28,
|
||||
"qType": 28,
|
||||
"domain": ["domain:example.com", "full:example.com"]
|
||||
}]
|
||||
}`,
|
||||
@@ -48,11 +48,11 @@ func TestDnsProxyConfig(t *testing.T) {
|
||||
Rule: []*dns.DNSRuleConfig{
|
||||
{
|
||||
Action: dns.RuleAction_Direct,
|
||||
Qtype: []int32{1, 3, 23, 24},
|
||||
QType: []int32{1, 3, 23, 24},
|
||||
},
|
||||
{
|
||||
Action: dns.RuleAction_Drop,
|
||||
Qtype: []int32{28},
|
||||
QType: []int32{28},
|
||||
Domain: []*geodata.DomainRule{
|
||||
{
|
||||
Value: &geodata.DomainRule_Custom{
|
||||
@@ -78,7 +78,8 @@ func TestDnsProxyConfig(t *testing.T) {
|
||||
{
|
||||
Input: `{
|
||||
"rules": [{
|
||||
"action": "reject",
|
||||
"action": "return",
|
||||
"rCode": 5,
|
||||
"domain": "keyword:example"
|
||||
}]
|
||||
}`,
|
||||
@@ -87,7 +88,8 @@ func TestDnsProxyConfig(t *testing.T) {
|
||||
RewriteServer: &net.Endpoint{},
|
||||
Rule: []*dns.DNSRuleConfig{
|
||||
{
|
||||
Action: dns.RuleAction_Reject,
|
||||
Action: dns.RuleAction_Return,
|
||||
RCode: 5,
|
||||
Domain: []*geodata.DomainRule{
|
||||
{
|
||||
Value: &geodata.DomainRule_Custom{
|
||||
@@ -106,7 +108,7 @@ func TestDnsProxyConfig(t *testing.T) {
|
||||
Input: `{
|
||||
"rules": [{
|
||||
"action": "drop",
|
||||
"qtype": 257
|
||||
"qType": 257
|
||||
}]
|
||||
}`,
|
||||
Parser: loadJSON(creator),
|
||||
@@ -115,7 +117,7 @@ func TestDnsProxyConfig(t *testing.T) {
|
||||
Rule: []*dns.DNSRuleConfig{
|
||||
{
|
||||
Action: dns.RuleAction_Drop,
|
||||
Qtype: []int32{257},
|
||||
QType: []int32{257},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -140,10 +142,11 @@ func TestDnsProxyConfigLegacyCompatibility(t *testing.T) {
|
||||
Rule: []*dns.DNSRuleConfig{
|
||||
{
|
||||
Action: dns.RuleAction_Hijack,
|
||||
Qtype: []int32{1, 28},
|
||||
QType: []int32{1, 28},
|
||||
},
|
||||
{
|
||||
Action: dns.RuleAction_Reject,
|
||||
Action: dns.RuleAction_Return,
|
||||
RCode: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -157,15 +160,17 @@ func TestDnsProxyConfigLegacyCompatibility(t *testing.T) {
|
||||
RewriteServer: &net.Endpoint{},
|
||||
Rule: []*dns.DNSRuleConfig{
|
||||
{
|
||||
Action: dns.RuleAction_Reject,
|
||||
Qtype: []int32{1, 65},
|
||||
Action: dns.RuleAction_Return,
|
||||
QType: []int32{1, 65},
|
||||
RCode: 5,
|
||||
},
|
||||
{
|
||||
Action: dns.RuleAction_Hijack,
|
||||
Qtype: []int32{1, 28},
|
||||
QType: []int32{1, 28},
|
||||
},
|
||||
{
|
||||
Action: dns.RuleAction_Reject,
|
||||
Action: dns.RuleAction_Return,
|
||||
RCode: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -181,11 +186,11 @@ func TestDnsProxyConfigLegacyCompatibility(t *testing.T) {
|
||||
Rule: []*dns.DNSRuleConfig{
|
||||
{
|
||||
Action: dns.RuleAction_Drop,
|
||||
Qtype: []int32{1},
|
||||
QType: []int32{1},
|
||||
},
|
||||
{
|
||||
Action: dns.RuleAction_Hijack,
|
||||
Qtype: []int32{1, 28},
|
||||
QType: []int32{1, 28},
|
||||
},
|
||||
{
|
||||
Action: dns.RuleAction_Drop,
|
||||
@@ -204,11 +209,11 @@ func TestDnsProxyConfigLegacyCompatibility(t *testing.T) {
|
||||
Rule: []*dns.DNSRuleConfig{
|
||||
{
|
||||
Action: dns.RuleAction_Drop,
|
||||
Qtype: []int32{65, 28},
|
||||
QType: []int32{65, 28},
|
||||
},
|
||||
{
|
||||
Action: dns.RuleAction_Hijack,
|
||||
Qtype: []int32{1, 28},
|
||||
QType: []int32{1, 28},
|
||||
},
|
||||
{
|
||||
Action: dns.RuleAction_Direct,
|
||||
@@ -228,7 +233,7 @@ func TestDnsProxyConfigRejectsMixedLegacyAndNewFields(t *testing.T) {
|
||||
_, err := loadJSON(creator)(`{
|
||||
"rules": [{
|
||||
"action": "direct",
|
||||
"qtype": 65
|
||||
"qType": 65
|
||||
}],
|
||||
"blockTypes": [65]
|
||||
}`)
|
||||
|
||||
+12
-2
@@ -1,14 +1,24 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/proxy/loopback"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type LoopbackConfig struct {
|
||||
InboundTag string `json:"inboundTag"`
|
||||
InboundTag string `json:"inboundTag"`
|
||||
Sniffing *SniffingConfig `json:"sniffing"`
|
||||
}
|
||||
|
||||
func (l LoopbackConfig) Build() (proto.Message, error) {
|
||||
return &loopback.Config{InboundTag: l.InboundTag}, nil
|
||||
c := &loopback.Config{InboundTag: l.InboundTag}
|
||||
if l.Sniffing != nil {
|
||||
sc, err := l.Sniffing.Build()
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to build sniffing config").Base(err)
|
||||
}
|
||||
c.Sniffing = sc
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/xtls/xray-core/app/observatory/burst"
|
||||
"github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/infra/conf/cfgcommon/duration"
|
||||
@@ -16,17 +17,14 @@ const (
|
||||
strategyLeastLoad string = "leastload"
|
||||
)
|
||||
|
||||
var (
|
||||
strategyConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||
strategyRandom: func() interface{} { return new(strategyEmptyConfig) },
|
||||
strategyLeastPing: func() interface{} { return new(strategyEmptyConfig) },
|
||||
strategyRoundRobin: func() interface{} { return new(strategyEmptyConfig) },
|
||||
strategyLeastLoad: func() interface{} { return new(strategyLeastLoadConfig) },
|
||||
}, "type", "settings")
|
||||
)
|
||||
var strategyConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||
strategyRandom: func() interface{} { return new(strategyEmptyConfig) },
|
||||
strategyLeastPing: func() interface{} { return new(strategyEmptyConfig) },
|
||||
strategyRoundRobin: func() interface{} { return new(strategyEmptyConfig) },
|
||||
strategyLeastLoad: func() interface{} { return new(strategyLeastLoadConfig) },
|
||||
}, "type", "settings")
|
||||
|
||||
type strategyEmptyConfig struct {
|
||||
}
|
||||
type strategyEmptyConfig struct{}
|
||||
|
||||
func (v *strategyEmptyConfig) Build() (proto.Message, error) {
|
||||
return nil, nil
|
||||
|
||||
@@ -78,18 +78,17 @@ func DecodeJSONConfig(reader io.Reader) (*conf.Config, error) {
|
||||
// byte-by-byte comment stripper and TeeReader, which are significant overhead on
|
||||
// large configs.
|
||||
func DecodeJSONConfigStrict(reader io.Reader) (*conf.Config, error) {
|
||||
data, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to read config file").Base(err)
|
||||
}
|
||||
jsonConfig := &conf.Config{}
|
||||
if err := json.Unmarshal(data, jsonConfig); err != nil {
|
||||
return nil, errors.New("failed to parse remote JSON config").Base(err)
|
||||
}
|
||||
return jsonConfig, nil
|
||||
data, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to read config file").Base(err)
|
||||
}
|
||||
jsonConfig := &conf.Config{}
|
||||
if err := json.Unmarshal(data, jsonConfig); err != nil {
|
||||
return nil, errors.New("failed to parse remote JSON config").Base(err)
|
||||
}
|
||||
return jsonConfig, nil
|
||||
}
|
||||
|
||||
|
||||
func LoadJSONConfig(reader io.Reader) (*core.Config, error) {
|
||||
jsonConfig, err := DecodeJSONConfig(reader)
|
||||
if err != nil {
|
||||
|
||||
+19
-27
@@ -179,26 +179,22 @@ func buildShadowsocks2022(v *ShadowsocksServerConfig) (proto.Message, error) {
|
||||
}
|
||||
|
||||
type ShadowsocksServerTarget struct {
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Level byte `json:"level"`
|
||||
Email string `json:"email"`
|
||||
Cipher string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
UoT bool `json:"uot"`
|
||||
UoTVersion int `json:"uotVersion"`
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Level byte `json:"level"`
|
||||
Email string `json:"email"`
|
||||
Cipher string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type ShadowsocksClientConfig struct {
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Level byte `json:"level"`
|
||||
Email string `json:"email"`
|
||||
Cipher string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
UoT bool `json:"uot"`
|
||||
UoTVersion int `json:"uotVersion"`
|
||||
Servers []*ShadowsocksServerTarget `json:"servers"`
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Level byte `json:"level"`
|
||||
Email string `json:"email"`
|
||||
Cipher string `json:"method"`
|
||||
Password string `json:"password"`
|
||||
Servers []*ShadowsocksServerTarget `json:"servers"`
|
||||
}
|
||||
|
||||
func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
|
||||
@@ -207,14 +203,12 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
|
||||
if v.Address != nil {
|
||||
v.Servers = []*ShadowsocksServerTarget{
|
||||
{
|
||||
Address: v.Address,
|
||||
Port: v.Port,
|
||||
Level: v.Level,
|
||||
Email: v.Email,
|
||||
Cipher: v.Cipher,
|
||||
Password: v.Password,
|
||||
UoT: v.UoT,
|
||||
UoTVersion: v.UoTVersion,
|
||||
Address: v.Address,
|
||||
Port: v.Port,
|
||||
Level: v.Level,
|
||||
Email: v.Email,
|
||||
Cipher: v.Cipher,
|
||||
Password: v.Password,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -240,8 +234,6 @@ func (v *ShadowsocksClientConfig) Build() (proto.Message, error) {
|
||||
config.Port = uint32(server.Port)
|
||||
config.Method = server.Cipher
|
||||
config.Key = server.Password
|
||||
config.UdpOverTcp = server.UoT
|
||||
config.UdpOverTcpVersion = uint32(server.UoTVersion)
|
||||
return config, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,31 +45,31 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
|
||||
Value: []string{utils.ChromeUA},
|
||||
},
|
||||
{
|
||||
Name: "Sec-CH-UA",
|
||||
Name: "Sec-CH-UA",
|
||||
Value: []string{utils.ChromeUACH},
|
||||
},
|
||||
{
|
||||
Name: "Sec-CH-UA-Mobile",
|
||||
Name: "Sec-CH-UA-Mobile",
|
||||
Value: []string{"?0"},
|
||||
},
|
||||
{
|
||||
Name: "Sec-CH-UA-Platform",
|
||||
Name: "Sec-CH-UA-Platform",
|
||||
Value: []string{"Windows"},
|
||||
},
|
||||
{
|
||||
Name: "Sec-Fetch-Mode",
|
||||
Name: "Sec-Fetch-Mode",
|
||||
Value: []string{"no-cors", "cors", "same-origin"},
|
||||
},
|
||||
{
|
||||
Name: "Sec-Fetch-Dest",
|
||||
Name: "Sec-Fetch-Dest",
|
||||
Value: []string{"empty"},
|
||||
},
|
||||
{
|
||||
Name: "Sec-Fetch-Site",
|
||||
Name: "Sec-Fetch-Site",
|
||||
Value: []string{"none"},
|
||||
},
|
||||
{
|
||||
Name: "Sec-Fetch-User",
|
||||
Name: "Sec-Fetch-User",
|
||||
Value: []string{"?1"},
|
||||
},
|
||||
{
|
||||
@@ -96,7 +96,7 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
|
||||
}
|
||||
|
||||
if len(v.Path) > 0 {
|
||||
config.Uri = append([]string(nil), (v.Path)...)
|
||||
config.Uri = append([]string(nil), v.Path...)
|
||||
}
|
||||
|
||||
if len(v.Headers) > 0 {
|
||||
|
||||
+242
-141
@@ -6,6 +6,8 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
@@ -13,7 +15,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
@@ -23,15 +24,11 @@ import (
|
||||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/fragment"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/dns"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/dtls"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/srtp"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/utp"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/wechat"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/wireguard"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/header"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/original"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/noise"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/realm"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/salamander"
|
||||
finalsudoku "github.com/xtls/xray-core/transport/internet/finalmask/sudoku"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/xdns"
|
||||
@@ -48,12 +45,10 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
tcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||
"none": func() interface{} { return new(NoOpConnectionAuthenticator) },
|
||||
"http": func() interface{} { return new(Authenticator) },
|
||||
}, "type", "")
|
||||
)
|
||||
var tcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||
"none": func() interface{} { return new(NoOpConnectionAuthenticator) },
|
||||
"http": func() interface{} { return new(Authenticator) },
|
||||
}, "type", "")
|
||||
|
||||
type KCPConfig struct {
|
||||
Mtu *uint32 `json:"mtu"`
|
||||
@@ -225,8 +220,10 @@ type SplitHTTPConfig struct {
|
||||
XPaddingPlacement string `json:"xPaddingPlacement"`
|
||||
XPaddingMethod string `json:"xPaddingMethod"`
|
||||
UplinkHTTPMethod string `json:"uplinkHTTPMethod"`
|
||||
SessionPlacement string `json:"sessionPlacement"`
|
||||
SessionKey string `json:"sessionKey"`
|
||||
SessionIDPlacement string `json:"sessionIDPlacement"`
|
||||
SessionIDKey string `json:"sessionIDKey"`
|
||||
SessionIDTable string `json:"sessionIDTable"`
|
||||
SessionIDLength Int32Range `json:"sessionIDLength"`
|
||||
SeqPlacement string `json:"seqPlacement"`
|
||||
SeqKey string `json:"seqKey"`
|
||||
UplinkDataPlacement string `json:"uplinkDataPlacement"`
|
||||
@@ -337,12 +334,12 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
return nil, errors.New("uplinkHTTPMethod can be GET only in packet-up mode")
|
||||
}
|
||||
|
||||
switch c.SessionPlacement {
|
||||
switch c.SessionIDPlacement {
|
||||
case "":
|
||||
c.SessionPlacement = "path"
|
||||
c.SessionIDPlacement = "path"
|
||||
case "path", "cookie", "header", "query":
|
||||
default:
|
||||
return nil, errors.New("unsupported session placement: " + c.SessionPlacement)
|
||||
return nil, errors.New("unsupported session placement: " + c.SessionIDPlacement)
|
||||
}
|
||||
|
||||
switch c.SeqPlacement {
|
||||
@@ -353,12 +350,31 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
return nil, errors.New("unsupported seq placement: " + c.SeqPlacement)
|
||||
}
|
||||
|
||||
if c.SessionPlacement != "path" && c.SessionKey == "" {
|
||||
switch c.SessionPlacement {
|
||||
if c.SessionIDPlacement != "path" && c.SessionIDKey == "" {
|
||||
switch c.SessionIDPlacement {
|
||||
case "cookie", "query":
|
||||
c.SessionKey = "x_session"
|
||||
c.SessionIDKey = "x_session"
|
||||
case "header":
|
||||
c.SessionKey = "X-Session"
|
||||
c.SessionIDKey = "X-Session"
|
||||
}
|
||||
}
|
||||
|
||||
if c.SessionIDTable != "" {
|
||||
if predefined, ok := splithttp.PredefinedTable[c.SessionIDTable]; ok {
|
||||
c.SessionIDTable = predefined
|
||||
}
|
||||
room := roomSize(len(c.SessionIDTable), c.SessionIDLength.From, c.SessionIDLength.To)
|
||||
// 2.1B possiblities should be enough
|
||||
if room.Cmp(big.NewInt(2<<30)) < 0 {
|
||||
return nil, errors.New("sessionIDTable or sessionIDLength is too small")
|
||||
}
|
||||
if c.SessionIDLength.From <= 0 {
|
||||
return nil, errors.New("sessionIDLength.from must be greater than 0")
|
||||
}
|
||||
for i := 0; i < len(c.SessionIDTable); i++ {
|
||||
if c.SessionIDTable[i] >= 0x80 {
|
||||
return nil, errors.New("sessionIDTable must contain only ASCII characters")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,9 +424,9 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
XPaddingPlacement: c.XPaddingPlacement,
|
||||
XPaddingMethod: c.XPaddingMethod,
|
||||
UplinkHTTPMethod: c.UplinkHTTPMethod,
|
||||
SessionPlacement: c.SessionPlacement,
|
||||
SessionIDPlacement: c.SessionIDPlacement,
|
||||
SeqPlacement: c.SeqPlacement,
|
||||
SessionKey: c.SessionKey,
|
||||
SessionIDKey: c.SessionIDKey,
|
||||
SeqKey: c.SeqKey,
|
||||
UplinkDataPlacement: c.UplinkDataPlacement,
|
||||
UplinkDataKey: c.UplinkDataKey,
|
||||
@@ -422,6 +438,8 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
ScMaxBufferedPosts: c.ScMaxBufferedPosts,
|
||||
ScStreamUpServerSecs: newRangeConfig(c.ScStreamUpServerSecs),
|
||||
ServerMaxHeaderBytes: c.ServerMaxHeaderBytes,
|
||||
SessionIDTable: c.SessionIDTable,
|
||||
SessionIDLength: newRangeConfig(c.SessionIDLength),
|
||||
Xmux: &splithttp.XmuxConfig{
|
||||
MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency),
|
||||
MaxConnections: newRangeConfig(c.Xmux.MaxConnections),
|
||||
@@ -445,6 +463,17 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func roomSize(tableSize int, min, max int32) *big.Int {
|
||||
base := big.NewInt(int64(tableSize))
|
||||
sum := new(big.Int)
|
||||
term := new(big.Int)
|
||||
for k := min; k <= max; k++ {
|
||||
term.Exp(base, big.NewInt(int64(k)), nil)
|
||||
sum.Add(sum, term)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
const (
|
||||
Byte = 1
|
||||
Kilobyte = 1024 * Byte
|
||||
@@ -656,10 +685,8 @@ type TLSConfig struct {
|
||||
MasterKeyLog string `json:"masterKeyLog"`
|
||||
PinnedPeerCertSha256 string `json:"pinnedPeerCertSha256"`
|
||||
VerifyPeerCertByName string `json:"verifyPeerCertByName"`
|
||||
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
|
||||
ECHServerKeys string `json:"echServerKeys"`
|
||||
ECHConfigList string `json:"echConfigList"`
|
||||
ECHForceQuery string `json:"echForceQuery"`
|
||||
ECHSocketSettings *SocketConfig `json:"echSockopt"`
|
||||
}
|
||||
|
||||
@@ -704,12 +731,7 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
||||
config.MasterKeyLog = c.MasterKeyLog
|
||||
|
||||
if c.AllowInsecure {
|
||||
if time.Now().After(time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)) {
|
||||
return nil, errors.PrintRemovedFeatureError(`"allowInsecure"`, `"pinnedPeerCertSha256"`)
|
||||
} else {
|
||||
errors.LogWarning(context.Background(), `"allowInsecure" will be removed automatically after 2026-06-01, please use "pinnedPeerCertSha256"(pcs) and "verifyPeerCertByName"(vcn) instead, PLEASE CONTACT YOUR SERVICE PROVIDER (AIRPORT)`)
|
||||
config.AllowInsecure = true
|
||||
}
|
||||
return nil, errors.PrintRemovedFeatureError(`"allowInsecure"`, `"pinnedPeerCertSha256"(pcs) and "verifyPeerCertByName"(vcn)`)
|
||||
}
|
||||
if c.PinnedPeerCertSha256 != "" {
|
||||
for v := range strings.SplitSeq(c.PinnedPeerCertSha256, ",") {
|
||||
@@ -728,10 +750,6 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
||||
config.PinnedPeerCertSha256 = append(config.PinnedPeerCertSha256, hashValue)
|
||||
}
|
||||
}
|
||||
|
||||
if c.VerifyPeerCertInNames != nil {
|
||||
return nil, errors.PrintRemovedFeatureError(`"verifyPeerCertInNames"`, `"verifyPeerCertByName"`)
|
||||
}
|
||||
if c.VerifyPeerCertByName != "" {
|
||||
for v := range strings.SplitSeq(c.VerifyPeerCertByName, ",") {
|
||||
v = strings.TrimSpace(v)
|
||||
@@ -749,13 +767,6 @@ func (c *TLSConfig) Build() (proto.Message, error) {
|
||||
}
|
||||
config.EchServerKeys = EchPrivateKey
|
||||
}
|
||||
switch c.ECHForceQuery {
|
||||
case "none", "half", "full", "":
|
||||
config.EchForceQuery = c.ECHForceQuery
|
||||
default:
|
||||
return nil, errors.New(`invalid "echForceQuery": `, c.ECHForceQuery)
|
||||
}
|
||||
config.EchForceQuery = c.ECHForceQuery
|
||||
config.EchConfigList = c.ECHConfigList
|
||||
if c.ECHSocketSettings != nil {
|
||||
ss, err := c.ECHSocketSettings.Build()
|
||||
@@ -1037,7 +1048,7 @@ type HappyEyeballsConfig struct {
|
||||
}
|
||||
|
||||
func (h *HappyEyeballsConfig) UnmarshalJSON(data []byte) error {
|
||||
var innerHappyEyeballsConfig = struct {
|
||||
innerHappyEyeballsConfig := struct {
|
||||
PrioritizeIPv6 bool `json:"prioritizeIPv6"`
|
||||
TryDelayMs uint64 `json:"tryDelayMs"`
|
||||
Interleave uint32 `json:"interleave"`
|
||||
@@ -1165,7 +1176,7 @@ func (c *SocketConfig) Build() (*internet.SocketConfig, error) {
|
||||
return nil, errors.New("unsupported address and port strategy: ", c.AddressPortStrategy)
|
||||
}
|
||||
|
||||
var happyEyeballs = &internet.HappyEyeballsConfig{Interleave: 1, PrioritizeIpv6: false, TryDelayMs: 0, MaxConcurrentTry: 4}
|
||||
happyEyeballs := &internet.HappyEyeballsConfig{Interleave: 1, PrioritizeIpv6: false, TryDelayMs: 0, MaxConcurrentTry: 4}
|
||||
if c.HappyEyeballsSettings != nil {
|
||||
happyEyeballs.PrioritizeIpv6 = c.HappyEyeballsSettings.PrioritizeIPv6
|
||||
happyEyeballs.Interleave = c.HappyEyeballsSettings.Interleave
|
||||
@@ -1241,20 +1252,14 @@ var (
|
||||
}, "type", "settings")
|
||||
|
||||
udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||
"header-custom": func() interface{} { return new(HeaderCustomUDP) },
|
||||
"header-dns": func() interface{} { return new(Dns) },
|
||||
"header-dtls": func() interface{} { return new(Dtls) },
|
||||
"header-srtp": func() interface{} { return new(Srtp) },
|
||||
"header-utp": func() interface{} { return new(Utp) },
|
||||
"header-wechat": func() interface{} { return new(Wechat) },
|
||||
"header-wireguard": func() interface{} { return new(Wireguard) },
|
||||
"mkcp-original": func() interface{} { return new(Original) },
|
||||
"mkcp-aes128gcm": func() interface{} { return new(Aes128Gcm) },
|
||||
"noise": func() interface{} { return new(NoiseMask) },
|
||||
"salamander": func() interface{} { return new(Salamander) },
|
||||
"sudoku": func() interface{} { return new(Sudoku) },
|
||||
"xdns": func() interface{} { return new(Xdns) },
|
||||
"xicmp": func() interface{} { return new(Xicmp) },
|
||||
"header-custom": func() interface{} { return new(HeaderCustomUDP) },
|
||||
"mkcp-legacy": func() interface{} { return new(MkcpLegacy) },
|
||||
"noise": func() interface{} { return new(NoiseMask) },
|
||||
"salamander": func() interface{} { return new(Salamander) },
|
||||
"sudoku": func() interface{} { return new(Sudoku) },
|
||||
"xdns": func() interface{} { return new(Xdns) },
|
||||
"xicmp": func() interface{} { return new(Xicmp) },
|
||||
"realm": func() interface{} { return new(Realm) },
|
||||
}, "type", "settings")
|
||||
)
|
||||
|
||||
@@ -1404,10 +1409,12 @@ func (c *HeaderCustomTCP) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
type FragmentMask struct {
|
||||
Packets string `json:"packets"`
|
||||
Length Int32Range `json:"length"`
|
||||
Delay Int32Range `json:"delay"`
|
||||
MaxSplit Int32Range `json:"maxSplit"`
|
||||
Packets string `json:"packets"`
|
||||
Length Int32Range `json:"length"`
|
||||
Delay Int32Range `json:"delay"`
|
||||
Lengths []Int32Range `json:"lengths"`
|
||||
Delays []Int32Range `json:"delays"`
|
||||
MaxSplit Int32Range `json:"maxSplit"`
|
||||
}
|
||||
|
||||
func (c *FragmentMask) Build() (proto.Message, error) {
|
||||
@@ -1432,14 +1439,29 @@ func (c *FragmentMask) Build() (proto.Message, error) {
|
||||
}
|
||||
}
|
||||
|
||||
config.LengthMin = int64(c.Length.From)
|
||||
config.LengthMax = int64(c.Length.To)
|
||||
if config.LengthMin == 0 {
|
||||
return nil, errors.New("LengthMin can't be 0")
|
||||
if len(c.Lengths) > 0 {
|
||||
for _, r := range c.Lengths {
|
||||
config.LengthsMin = append(config.LengthsMin, int64(r.From))
|
||||
config.LengthsMax = append(config.LengthsMax, int64(r.To))
|
||||
}
|
||||
} else {
|
||||
config.LengthsMin = append(config.LengthsMin, int64(c.Length.From))
|
||||
config.LengthsMax = append(config.LengthsMax, int64(c.Length.To))
|
||||
}
|
||||
|
||||
config.DelayMin = int64(c.Delay.From)
|
||||
config.DelayMax = int64(c.Delay.To)
|
||||
if config.LengthsMin[len(config.LengthsMin)-1] == 0 {
|
||||
return nil, errors.New("last lengths entry min can't be 0")
|
||||
}
|
||||
|
||||
if len(c.Delays) > 0 {
|
||||
for _, r := range c.Delays {
|
||||
config.DelaysMin = append(config.DelaysMin, int64(r.From))
|
||||
config.DelaysMax = append(config.DelaysMax, int64(r.To))
|
||||
}
|
||||
} else {
|
||||
config.DelaysMin = append(config.DelaysMin, int64(c.Delay.From))
|
||||
config.DelaysMax = append(config.DelaysMax, int64(c.Delay.To))
|
||||
}
|
||||
|
||||
config.MaxSplitMin = int64(c.MaxSplit.From)
|
||||
config.MaxSplitMax = int64(c.MaxSplit.To)
|
||||
@@ -1734,82 +1756,73 @@ func (c *HeaderCustomUDP) Build() (proto.Message, error) {
|
||||
})
|
||||
}
|
||||
|
||||
return &custom.UDPConfig{
|
||||
Client: client,
|
||||
Server: server,
|
||||
Mode: c.Mode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Dns struct {
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
func (c *Dns) Build() (proto.Message, error) {
|
||||
config := &dns.Config{}
|
||||
config.Domain = "www.baidu.com"
|
||||
|
||||
if len(c.Domain) > 0 {
|
||||
config.Domain = c.Domain
|
||||
if c.Mode == "standalone" {
|
||||
return &custom.UDPStandaloneConfig{
|
||||
Client: client,
|
||||
Server: server,
|
||||
}, nil
|
||||
} else {
|
||||
return &custom.UDPConfig{
|
||||
Client: client,
|
||||
Server: server,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type Dtls struct{}
|
||||
|
||||
func (c *Dtls) Build() (proto.Message, error) {
|
||||
return &dtls.Config{}, nil
|
||||
type MkcpLegacy struct {
|
||||
Header string `json:"header"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Srtp struct{}
|
||||
|
||||
func (c *Srtp) Build() (proto.Message, error) {
|
||||
return &srtp.Config{}, nil
|
||||
}
|
||||
|
||||
type Utp struct{}
|
||||
|
||||
func (c *Utp) Build() (proto.Message, error) {
|
||||
return &utp.Config{}, nil
|
||||
}
|
||||
|
||||
type Wechat struct{}
|
||||
|
||||
func (c *Wechat) Build() (proto.Message, error) {
|
||||
return &wechat.Config{}, nil
|
||||
}
|
||||
|
||||
type Wireguard struct{}
|
||||
|
||||
func (c *Wireguard) Build() (proto.Message, error) {
|
||||
return &wireguard.Config{}, nil
|
||||
}
|
||||
|
||||
type Original struct{}
|
||||
|
||||
func (c *Original) Build() (proto.Message, error) {
|
||||
return &original.Config{}, nil
|
||||
}
|
||||
|
||||
type Aes128Gcm struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func (c *Aes128Gcm) Build() (proto.Message, error) {
|
||||
return &aes128gcm.Config{
|
||||
Password: c.Password,
|
||||
}, nil
|
||||
func (c *MkcpLegacy) Build() (proto.Message, error) {
|
||||
if len(c.Header) == 0 {
|
||||
if len(c.Value) == 0 {
|
||||
return &original.Config{}, nil
|
||||
} else {
|
||||
return &aes128gcm.Config{Password: c.Value}, nil
|
||||
}
|
||||
}
|
||||
switch strings.ToLower(c.Header) {
|
||||
case "dns":
|
||||
domain := c.Value
|
||||
if len(domain) == 0 {
|
||||
domain = "www.baidu.com"
|
||||
}
|
||||
return &header.Config{ID: 0, Domain: domain}, nil
|
||||
case "dtls":
|
||||
return &header.Config{ID: 1}, nil
|
||||
case "srtp":
|
||||
return &header.Config{ID: 2}, nil
|
||||
case "utp":
|
||||
return &header.Config{ID: 3}, nil
|
||||
case "wechat":
|
||||
return &header.Config{ID: 4}, nil
|
||||
case "wireguard":
|
||||
return &header.Config{ID: 5}, nil
|
||||
default:
|
||||
return nil, errors.New("invalid header ", c.Header)
|
||||
}
|
||||
}
|
||||
|
||||
type Salamander struct {
|
||||
Password string `json:"password"`
|
||||
Password string `json:"password"`
|
||||
PacketSize Int32Range `json:"packetSize"`
|
||||
}
|
||||
|
||||
func (c *Salamander) Build() (proto.Message, error) {
|
||||
config := &salamander.Config{}
|
||||
config.Password = c.Password
|
||||
return config, nil
|
||||
if c.PacketSize.To > 0 {
|
||||
if c.PacketSize.From <= 0 || c.PacketSize.To > 2048 {
|
||||
return nil, errors.New("gecko: invalid min/max packet size")
|
||||
}
|
||||
return &salamander.GeckoConfig{
|
||||
Password: c.Password,
|
||||
MinPacketSize: c.PacketSize.From,
|
||||
MaxPacketSize: c.PacketSize.To,
|
||||
}, nil
|
||||
}
|
||||
return &salamander.Config{
|
||||
Password: c.Password,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Sudoku struct {
|
||||
@@ -1885,23 +1898,111 @@ func (c *Xdns) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
type Xicmp struct {
|
||||
ListenIp string `json:"listenIp"`
|
||||
Id uint16 `json:"id"`
|
||||
DGRAM bool `json:"dgram"`
|
||||
IPs []string `json:"ips"`
|
||||
}
|
||||
|
||||
func (c *Xicmp) Build() (proto.Message, error) {
|
||||
config := &xicmp.Config{
|
||||
Ip: c.ListenIp,
|
||||
Id: int32(c.Id),
|
||||
for _, ip := range c.IPs {
|
||||
if _, err := netip.ParseAddr(ip); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if config.Ip == "" {
|
||||
config.Ip = "0.0.0.0"
|
||||
config := &xicmp.Config{
|
||||
DGRAM: c.DGRAM,
|
||||
IPs: c.IPs,
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
type Realm struct {
|
||||
Url string `json:"url"`
|
||||
StunServers []string `json:"stunServers"`
|
||||
TlsConfig *TLSConfig `json:"tlsConfig"`
|
||||
}
|
||||
|
||||
func (c *Realm) Build() (proto.Message, error) {
|
||||
var scheme, host, port, token, id string
|
||||
var stunServers []string
|
||||
var tlsConfig *tls.Config
|
||||
|
||||
u, err := url.Parse(c.Url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "realm":
|
||||
scheme = "https"
|
||||
case "realm+http":
|
||||
scheme = "http"
|
||||
default:
|
||||
return nil, errors.New("invalid scheme", u.Scheme)
|
||||
}
|
||||
|
||||
host = u.Hostname()
|
||||
if host == "" {
|
||||
return nil, errors.New("invalid host", host)
|
||||
}
|
||||
|
||||
port = u.Port()
|
||||
if port == "" {
|
||||
port = "443"
|
||||
if scheme == "http" {
|
||||
port = "80"
|
||||
}
|
||||
}
|
||||
|
||||
token, err = url.PathUnescape(u.User.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if token == "" {
|
||||
return nil, errors.New("invalid token", token)
|
||||
}
|
||||
|
||||
id, err = url.PathUnescape(strings.TrimPrefix(u.EscapedPath(), "/"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if id == "" {
|
||||
return nil, errors.New("invalid id", id)
|
||||
}
|
||||
|
||||
if len(c.StunServers) == 0 {
|
||||
return nil, errors.New("empty stunServers")
|
||||
}
|
||||
|
||||
for _, s := range c.StunServers {
|
||||
_, _, err = net.SplitHostPort(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
stunServers = c.StunServers
|
||||
|
||||
if c.TlsConfig != nil {
|
||||
tc, err := c.TlsConfig.Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig = tc.(*tls.Config)
|
||||
}
|
||||
|
||||
return &realm.Config{
|
||||
Scheme: scheme,
|
||||
Host: host,
|
||||
Port: port,
|
||||
Token: token,
|
||||
ID: id,
|
||||
StunServers: stunServers,
|
||||
TlsConfig: tlsConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Mask struct {
|
||||
Type string `json:"type"`
|
||||
Settings *json.RawMessage `json:"settings"`
|
||||
|
||||
@@ -195,8 +195,7 @@ func TestHeaderCustomUDPBuild(t *testing.T) {
|
||||
"mode": "standalone"
|
||||
}`,
|
||||
Parser: parser,
|
||||
Output: &finalmaskcustom.UDPConfig{
|
||||
Mode: "standalone",
|
||||
Output: &finalmaskcustom.UDPStandaloneConfig{
|
||||
Client: []*finalmaskcustom.UDPItem{
|
||||
{
|
||||
RandMax: 255,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/xtls/xray-core/app/version"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type VersionConfig struct {
|
||||
|
||||
@@ -3,6 +3,7 @@ package conf
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
@@ -37,8 +38,9 @@ func (c *WireGuardPeerConfig) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
config.Endpoint = c.Endpoint
|
||||
// default 0
|
||||
config.KeepAlive = c.KeepAlive
|
||||
if c.KeepAlive != 0 {
|
||||
config.KeepAlive = strconv.FormatUint(uint64(c.KeepAlive), 10)
|
||||
}
|
||||
if c.AllowedIPs == nil {
|
||||
config.AllowedIps = []string{"0.0.0.0/0", "::0/0"}
|
||||
} else {
|
||||
@@ -56,7 +58,6 @@ type WireGuardConfig struct {
|
||||
Address []string `json:"address"`
|
||||
Peers []*WireGuardPeerConfig `json:"peers"`
|
||||
MTU int32 `json:"mtu"`
|
||||
NumWorkers int32 `json:"workers"`
|
||||
Reserved []byte `json:"reserved"`
|
||||
DomainStrategy string `json:"domainStrategy"`
|
||||
}
|
||||
@@ -93,9 +94,6 @@ func (c *WireGuardConfig) Build() (proto.Message, error) {
|
||||
} else {
|
||||
config.Mtu = c.MTU
|
||||
}
|
||||
// these a fallback code exists in wireguard-go code,
|
||||
// we don't need to process fallback manually
|
||||
config.NumWorkers = c.NumWorkers
|
||||
|
||||
if len(c.Reserved) != 0 && len(c.Reserved) != 3 {
|
||||
return nil, errors.New(`"reserved" should be empty or 3 bytes`)
|
||||
|
||||
@@ -38,12 +38,10 @@ func TestWireGuardConfig(t *testing.T) {
|
||||
// also can read from hex form directly
|
||||
PublicKey: "6e65ce0be17517110c17d77288ad87e7fd5252dcc7d09b95a39d61db03df832a",
|
||||
Endpoint: "127.0.0.1:1234",
|
||||
KeepAlive: 0,
|
||||
AllowedIps: []string{"0.0.0.0/0", "::0/0"},
|
||||
},
|
||||
},
|
||||
Mtu: 1300,
|
||||
NumWorkers: 2,
|
||||
DomainStrategy: wireguard.DeviceConfig_FORCE_IP64,
|
||||
NoKernelTun: false,
|
||||
},
|
||||
|
||||
+1
-2
@@ -265,7 +265,7 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
|
||||
|
||||
if c.SendThrough != nil {
|
||||
address := ParseSendThough(c.SendThrough)
|
||||
//Check if CIDR exists
|
||||
// Check if CIDR exists
|
||||
if strings.Contains(*c.SendThrough, "/") {
|
||||
senderSettings.ViaCidr = strings.Split(*c.SendThrough, "/")[1]
|
||||
} else {
|
||||
@@ -449,7 +449,6 @@ func (c *Config) Override(o *Config, fn string) {
|
||||
c.InboundConfigs = append(c.InboundConfigs, o.InboundConfigs[i])
|
||||
errors.LogInfo(context.Background(), "[", fn, "] appended inbound with tag: ", o.InboundConfigs[i].Tag)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+62
-19
@@ -12,7 +12,16 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var directory = flag.String("pwd", "", "Working directory of Xray vformat.")
|
||||
var (
|
||||
directory = flag.String("pwd", "", "Working directory of Xray vformat.")
|
||||
action = flag.String("mode", "format", "Execution mode. Default is 'format'.\n'format' formatting source files and save changes to files.\n'check' list all paths of improper formatted file.\n'dryrun' formatting source files and shows all diffs, but will not make any changes to files.")
|
||||
)
|
||||
|
||||
var (
|
||||
isCheck bool
|
||||
isDryrun bool
|
||||
isFormat bool
|
||||
)
|
||||
|
||||
// envFile returns the name of the Go environment configuration file.
|
||||
// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166
|
||||
@@ -90,9 +99,10 @@ func Run(binary string, args []string) ([]byte, error) {
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func RunMany(binary string, args, files []string) {
|
||||
fmt.Println("Processing...")
|
||||
func RunMany(binary string, args, files []string) bool {
|
||||
fmt.Println("Processing with", binary, args, "...")
|
||||
|
||||
formatRequired := false
|
||||
maxTasks := make(chan struct{}, runtime.NumCPU())
|
||||
for _, file := range files {
|
||||
maxTasks <- struct{}{}
|
||||
@@ -102,10 +112,12 @@ func RunMany(binary string, args, files []string) {
|
||||
fmt.Println(err)
|
||||
} else if len(output) > 0 {
|
||||
fmt.Println(string(output))
|
||||
formatRequired = true
|
||||
}
|
||||
<-maxTasks
|
||||
}(file)
|
||||
}
|
||||
return formatRequired
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -124,6 +136,19 @@ func main() {
|
||||
*directory = filepath.Join(pwd, *directory)
|
||||
}
|
||||
|
||||
switch *action {
|
||||
case "format":
|
||||
isFormat = true
|
||||
case "check":
|
||||
isCheck = true
|
||||
case "dryrun":
|
||||
isCheck = true
|
||||
isDryrun = true
|
||||
default:
|
||||
fmt.Println("Unrecognized 'mode'. Will format all source files and save changes.")
|
||||
isFormat = true
|
||||
}
|
||||
|
||||
pwd := *directory
|
||||
GOBIN := GetGOBIN()
|
||||
binPath := os.Getenv("PATH")
|
||||
@@ -136,7 +161,6 @@ func main() {
|
||||
suffix = ".exe"
|
||||
}
|
||||
gofmt := "gofumpt" + suffix
|
||||
goimports := "gci" + suffix
|
||||
|
||||
if gofmtPath, err := exec.LookPath(gofmt); err != nil {
|
||||
fmt.Println("Can not find", gofmt, "in system path or current working directory.")
|
||||
@@ -145,13 +169,6 @@ func main() {
|
||||
gofmt = gofmtPath
|
||||
}
|
||||
|
||||
if goimportsPath, err := exec.LookPath(goimports); err != nil {
|
||||
fmt.Println("Can not find", goimports, "in system path or current working directory.")
|
||||
os.Exit(1)
|
||||
} else {
|
||||
goimports = goimportsPath
|
||||
}
|
||||
|
||||
rawFilesSlice := make([]string, 0, 1000)
|
||||
walkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
@@ -179,15 +196,41 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
gofmtArgs := []string{
|
||||
"-s", "-l", "-e", "-w",
|
||||
if isFormat {
|
||||
gofmtArgs := []string{
|
||||
"-l", "-e", "-w",
|
||||
}
|
||||
|
||||
fmt.Println("Formatting Go source files...")
|
||||
RunMany(gofmt, gofmtArgs, rawFilesSlice)
|
||||
fmt.Println("Do NOT forget to commit file changes.")
|
||||
}
|
||||
|
||||
goimportsArgs := []string{
|
||||
"write",
|
||||
}
|
||||
if isCheck {
|
||||
gofmtListArgs := []string{
|
||||
"-l", "-e",
|
||||
}
|
||||
|
||||
RunMany(gofmt, gofmtArgs, rawFilesSlice)
|
||||
RunMany(goimports, goimportsArgs, rawFilesSlice)
|
||||
fmt.Println("Do NOT forget to commit file changes.")
|
||||
fmt.Println("Checking files thar are not properly formatted...")
|
||||
formatRequired := RunMany(gofmt, gofmtListArgs, rawFilesSlice)
|
||||
if formatRequired {
|
||||
fmt.Println("Format problem(s) found.")
|
||||
}
|
||||
|
||||
if isDryrun {
|
||||
if formatRequired {
|
||||
gofmtShowArgs := []string{
|
||||
"-d", "-e",
|
||||
}
|
||||
RunMany(gofmt, gofmtShowArgs, rawFilesSlice)
|
||||
}
|
||||
}
|
||||
|
||||
if formatRequired {
|
||||
fmt.Println("Please run 'go install -v mvdan.cc/gofumpt@latest', then run 'go run ./infra/vformat/main.go' to format the Go source files.")
|
||||
os.Exit(1)
|
||||
} else {
|
||||
fmt.Println("All Go source file format check has been passed.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,6 @@ func executeBalancerInfo(cmd *base.Command, args []string) {
|
||||
}
|
||||
|
||||
showBalancerInfo(resp.Balancer)
|
||||
|
||||
}
|
||||
|
||||
func showBalancerInfo(b *routerService.BalancerMsg) {
|
||||
|
||||
@@ -62,7 +62,8 @@ func addInboundUserAction(ctx context.Context, client handlerService.HandlerServ
|
||||
Operation: cserial.ToTypedMessage(
|
||||
&handlerService.AddUserOperation{
|
||||
User: user,
|
||||
}),
|
||||
},
|
||||
),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -50,7 +50,8 @@ func executeRemoveUsers(cmd *base.Command, args []string) {
|
||||
Operation: cserial.ToTypedMessage(
|
||||
&handlerService.RemoveUserOperation{
|
||||
Email: email,
|
||||
}),
|
||||
},
|
||||
),
|
||||
})
|
||||
if err == nil {
|
||||
success += 1
|
||||
|
||||
@@ -38,9 +38,7 @@ Example:
|
||||
}
|
||||
|
||||
func executeAddRules(cmd *base.Command, args []string) {
|
||||
var (
|
||||
shouldAppend bool
|
||||
)
|
||||
var shouldAppend bool
|
||||
setSharedFlags(cmd)
|
||||
cmd.Flag.BoolVar(&shouldAppend, "append", false, "")
|
||||
cmd.Flag.Parse(args)
|
||||
@@ -96,5 +94,4 @@ func executeAddRules(cmd *base.Command, args []string) {
|
||||
}
|
||||
showJSONResponse(resp)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -56,5 +56,4 @@ func executeRemoveRules(cmd *base.Command, args []string) {
|
||||
}
|
||||
showJSONResponse(resp)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -136,5 +136,4 @@ func executeSourceIpBlock(cmd *base.Command, args []string) {
|
||||
base.Fatalf("failed to perform AddRule: %s", err)
|
||||
}
|
||||
showJSONResponse(resp)
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ Examples:
|
||||
}
|
||||
|
||||
func executeTypedMessageToJson(cmd *base.Command, args []string) {
|
||||
|
||||
var injectTypeInfo bool
|
||||
cmd.Flag.BoolVar(&injectTypeInfo, "t", false, "")
|
||||
cmd.Flag.BoolVar(&injectTypeInfo, "type", false, "")
|
||||
|
||||
@@ -41,7 +41,6 @@ Examples:
|
||||
}
|
||||
|
||||
func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
|
||||
|
||||
var optFile string
|
||||
var optDump bool
|
||||
var optType bool
|
||||
@@ -60,7 +59,7 @@ func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
|
||||
}
|
||||
|
||||
if len(optFile) > 0 {
|
||||
switch core.GetFormat(optFile){
|
||||
switch core.GetFormat(optFile) {
|
||||
case "protobuf", "":
|
||||
fmt.Println("Output ProtoBuf file is ", optFile)
|
||||
default:
|
||||
|
||||
@@ -34,8 +34,10 @@ func init() {
|
||||
var input_echServerKeys = cmdECH.Flag.String("i", "", "ECHServerKeys (base64.StdEncoding)")
|
||||
|
||||
// var input_pqSignatureSchemesEnabled = cmdECH.Flag.Bool("pqSignatureSchemesEnabled", false, "")
|
||||
var input_serverName = cmdECH.Flag.String("serverName", "cloudflare-ech.com", "")
|
||||
var input_pem = cmdECH.Flag.Bool("pem", false, "True == turn on pem output")
|
||||
var (
|
||||
input_serverName = cmdECH.Flag.String("serverName", "cloudflare-ech.com", "")
|
||||
input_pem = cmdECH.Flag.Bool("pem", false, "True == turn on pem output")
|
||||
)
|
||||
|
||||
func executeECH(cmd *base.Command, args []string) {
|
||||
var kem uint16
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user