Compare commits

...
Author SHA1 Message Date
Fangliding bbcf2c4e45 Rename 2026-06-09 20:12:54 +08:00
风扇滑翔翼andGitHub 3f4a4004f9 Reorganize predefined character sets in config.go 2026-06-09 04:29:57 +08:00
Fangliding 87c08d0d7a More table 2026-06-06 06:11:54 +08:00
Fangliding 0e6ab63b8a nil safe 2026-06-03 22:42:37 +08:00
Fangliding 2f121bd6a2 Custom sessionID 2026-06-03 22:42:20 +08:00
fdb9b616fc README.md: Add Hey to HarmonyOS in GUI Clients (#6244)
https://github.com/popsiclelmlm/Hey/blob/main/entry/src/main/cpp/README.md

---------

Co-authored-by: liumin01 <liumin01@roborock.com>
2026-06-02 23:58:56 +00:00
𐲓𐳛𐳪𐳂𐳐 𐲀𐳢𐳦𐳫𐳢 𐲥𐳔𐳛𐳪𐳌𐳑𐳖𐳇andGitHub d792fba59c GitHub Actions CI: Builders run only once in pull requests in the same repository (#6222)
https://github.com/XTLS/Xray-core/pull/6221#issuecomment-4588919827

Completes https://github.com/XTLS/Xray-core/pull/6090#issuecomment-4572094191
2026-06-02 23:40:17 +00:00
55956f8d70 TLS config: Remove some deprecated fields (#6226)
https://t.me/projectXtls/1490

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-06-02 23:36:42 +00:00
RPRXandGitHub 94ffd50060 Xray-core v26.6.1
Sponsor & Donation & NFTs: https://github.com/XTLS/Xray-core/issues/3668
Project X Channel: https://t.me/projectXtls

Announcement of NFTs by Project X: https://github.com/XTLS/Xray-core/discussions/3633
Project X NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/1

VLESS Post-Quantum Encryption: https://github.com/XTLS/Xray-core/pull/5067
VLESS NFT: https://opensea.io/collection/vless

XHTTP: Beyond REALITY: https://github.com/XTLS/Xray-core/discussions/4113
REALITY NFT: https://opensea.io/assets/ethereum/0x5ee362866001613093361eb8569d59c4141b76d1/2
2026-06-01 02:11:09 +00:00
风扇滑翔翼andGitHub c4dfcd4c1a Burst observatory: Fix init check (#6221)
https://github.com/XTLS/Xray-core/pull/6106#issuecomment-4587975865
2026-06-01 01:30:40 +00:00
cb8cd048c1 DNS outbound: Replace "reject" with "return" (rCode is 0 by default) (#6214)
https://github.com/XTLS/Xray-core/pull/6214#issuecomment-4587988752

Example: https://github.com/XTLS/Xray-core/pull/6214#issue-4553786283

---------

Co-authored-by: Meo597 <197331664+Meo597@users.noreply.github.com>
2026-06-01 01:25:47 +00:00
DavoyanandGitHub 455f6bc2d5 uTLS: Update ModernFingerprints map and OtherFingerprints map (#6181)
Paying attention to https://github.com/XTLS/Xray-core/pull/6181#issuecomment-4567373533

And a real issue: https://github.com/XTLS/Xray-core/pull/6181#issuecomment-4583018017
2026-05-30 17:29:33 +00:00
LjhAUMEMandGitHub ba538619eb Realm finalmask: Fix client punch peers (#6213)
Fixes https://github.com/XTLS/Xray-core/pull/6137
2026-05-30 09:30:23 +00:00
𐲓𐳛𐳪𐳂𐳐 𐲀𐳢𐳦𐳫𐳢 𐲥𐳔𐳛𐳪𐳌𐳑𐳖𐳇andGitHub d43a808ea5 GitHub Action CI: Add Go source file format check (#6090)
https://github.com/XTLS/Xray-core/pull/6057#issuecomment-4364819830

And https://github.com/XTLS/Xray-core/pull/6149#issuecomment-4546876261
2026-05-29 15:04:59 +00:00
ca4b156b57 Burst observatory: Fix time compare, cancel pending ping on instance close or new schedule started (#6106)
https://github.com/XTLS/Xray-core/pull/6106#issuecomment-4448117773

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-05-29 11:59:24 +00:00
LjhAUMEMandRPRX aba22722a6 Finalmask: Add mkcp-legacy (UDP) to replace mkcp-* and legacy header-* (#6201)
https://github.com/XTLS/Xray-core/pull/6193#issuecomment-4543737699

https://github.com/XTLS/Xray-core/pull/6201#issuecomment-4574213827

Example: https://github.com/XTLS/Xray-core/pull/6201#issue-4525483699
2026-05-29 11:57:32 +00:00
569459c54c Sudoku finalmask: Harden UDP ReadFrom() against invalid packets (#6185)
https://github.com/XTLS/Xray-core/issues/6184#issuecomment-4573949756

Fixes https://github.com/XTLS/Xray-core/issues/6184

---------

Co-authored-by: LjhAUMEM <llnu14702@gmail.com>
2026-05-29 10:47:58 +00:00
LjhAUMEMandGitHub 2b42699623 XICMP finalmask: Refactor & Speed up; Add multi ips and dgram mode (client) (#6168)
https://github.com/XTLS/Xray-core/pull/5872#issuecomment-4198514120
https://github.com/XTLS/Xray-core/pull/6168#issuecomment-4571606666
https://github.com/XTLS/Xray-core/pull/6168#issuecomment-4573294637

Example: https://github.com/XTLS/Xray-core/pull/6168#issue-4484980927

Closes https://github.com/XTLS/Xray-core/discussions/5879
2026-05-29 09:44:06 +00:00
LjhAUMEMandRPRX 66a8100737 Salamander finalmask: Support packetSize (Gecko in Hysteria v2.9.2) (#6198)
And some other refinements

https://github.com/XTLS/Xray-core/pull/6198#issuecomment-4567438813

Example: https://github.com/XTLS/Xray-core/pull/6198#issue-4522226670
2026-05-29 08:51:41 +00:00
LjhAUMEMandGitHub 36303694d1 Finalmask: Add Realm (UDP hole punching in Hysteria v2.9.1) (#6137)
https://github.com/XTLS/Xray-core/pull/5657#issuecomment-4446406536

https://github.com/XTLS/Xray-core/pull/6137#issuecomment-4469822775

Example: https://github.com/XTLS/Xray-core/pull/6137#issue-4454013510
2026-05-29 08:29:53 +00:00
Jason FoxandGitHub 4dcf802ae3 README.md: Add flutter_vless to Xray Wrapper in Others (#6197)
https://pub.dev/packages/flutter_vless
2026-05-28 20:15:38 +00:00
MeowandGitHub a2cec2e580 DNS: Avoid panic on domain too long (#6207)
Fixes https://github.com/XTLS/Xray-core/issues/6204
2026-05-28 20:06:32 +00:00
LjhAUMEMandGitHub 1cd7d25fec header-custom finalmask: Remove headerConnMode headerReadAddrAware interface (#6193)
https://github.com/XTLS/Xray-core/pull/5920#issuecomment-4253108964

https://github.com/XTLS/Xray-core/pull/6193#issuecomment-4531875088
2026-05-28 19:44:16 +00:00
LjhAUMEMandGitHub 09002ab763 noise finalmask: Better reset (#6188)
https://github.com/XTLS/Xray-core/pull/6170

Explanation: https://github.com/XTLS/Xray-core/pull/5657#issue-3901983829
2026-05-28 19:24:22 +00:00
cb206ddc74 DNS: Avoid passing domain to WriteTo func (#6163)
https://github.com/XTLS/Xray-core/pull/6163#issuecomment-4528771758

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-05-28 18:27:08 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
fa466f8174 Bump golang.org/x/net from 0.54.0 to 0.55.0 (#6192)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.54.0 to 0.55.0.
- [Commits](https://github.com/golang/net/compare/v0.54.0...v0.55.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.55.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-28 18:20:27 +00:00
e26f5e9548 Socks5 server: More standard UDP ASSOCIATE (RFC 1928) (#6149)
https://github.com/XTLS/Xray-core/pull/6149#issuecomment-4529069218

Fixes https://github.com/XTLS/Xray-core/issues/6145#issuecomment-4467482623

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-05-28 14:22:57 +00:00
风扇滑翔翼andRPRX 787aa7677b Hysteria server: tls.WithNextProto("h3") by default (#6186)
Client: https://github.com/XTLS/Xray-core/pull/6186#issuecomment-4528041838
2026-05-24 14:35:12 +00:00
风扇滑翔翼andGitHub d878fc83f8 SS2022 inbound: Fix potential panic when dispatching (#6162)
https://github.com/XTLS/Xray-core/pull/6162#issuecomment-4496738737
2026-05-24 12:50:02 +00:00
风扇滑翔翼andGitHub ee2b2c5ab6 leastLoad balancer: Implement missing tolerance (#6156)
Closes https://github.com/XTLS/Xray-core/issues/6154
2026-05-24 12:27:12 +00:00
Whale ChoiandGitHub 81d993f49d README.md: Remove Xray4Magisk; Add AsteriskNG to Android in GUI Clients (#6153)
https://github.com/XTLS/Xray-core/pull/6153#issuecomment-4522913987
2026-05-24 12:19:00 +00:00
风扇滑翔翼andGitHub 4c3842711d XHTTP client: Fix packet-up OpenUsage counting and H3 keepAlivePeriod parameter (#6140)
Fixes https://github.com/XTLS/Xray-core/pull/6140#issuecomment-4525409315
2026-05-24 12:05:40 +00:00
MeowandGitHub ab69985fcc Config: Warn when sockopt.trustedXForwardedFor is not set for XHTTP/WS/HU inbounds (#6159)
https://github.com/XTLS/Xray-core/pull/6110#issuecomment-4470157219

Usage: https://github.com/XTLS/Xray-core/pull/5331#issue-3655317949
2026-05-23 13:50:01 +00:00
风扇滑翔翼andGitHub 56bb63668c Geodata: Cleanup unneeded shared matchers (weakly referenced in map) (#6139)
https://github.com/XTLS/Xray-core/pull/6139#issuecomment-4525195265
2026-05-23 12:23:52 +00:00
bytecategoryandGitHub 359a28f876 XDNS finalmask: Support AAAA & A (#6123)
https://github.com/XTLS/Xray-core/pull/6123#issuecomment-4439994266
https://github.com/XTLS/Xray-core/pull/6123#issuecomment-4441819696
https://github.com/XTLS/Xray-core/pull/6123#issuecomment-4522352460

Example: https://github.com/XTLS/Xray-core/pull/6123#issue-4436283689

Document: https://xtls.github.io/config/transports/finalmask.html#xdns
2026-05-23 09:24:37 +00:00
Yury KastovandGitHub da9ba693cb Config: Support abstract unix sockets in remote loader (#6111)
Replaces https://github.com/XTLS/Xray-core/pull/5200
2026-05-22 19:49:19 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
5488b86c67 Bump google.golang.org/grpc from 1.81.0 to 1.81.1 (#6133)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.81.0 to 1.81.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.81.0...v1.81.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.81.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-22 19:37:44 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
a3c054a54c Bump golang.org/x/net from 0.53.0 to 0.54.0 (#6113)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.53.0 to 0.54.0.
- [Commits](https://github.com/golang/net/compare/v0.53.0...v0.54.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.54.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-22 19:37:25 +00:00
233 changed files with 5525 additions and 3652 deletions
+2
View File
@@ -11,6 +11,7 @@ on:
jobs: jobs:
check-assets: check-assets:
runs-on: ubuntu-latest 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: steps:
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
@@ -75,6 +76,7 @@ jobs:
fail-fast: false fail-fast: false
runs-on: ubuntu-latest 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: env:
GOOS: ${{ matrix.goos}} GOOS: ${{ matrix.goos}}
GOARCH: ${{ matrix.goarch }} GOARCH: ${{ matrix.goarch }}
+5
View File
@@ -11,6 +11,7 @@ on:
jobs: jobs:
check-assets: check-assets:
runs-on: ubuntu-latest 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: steps:
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
@@ -161,6 +162,7 @@ jobs:
fail-fast: false fail-fast: false
runs-on: ubuntu-latest 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: env:
GOOS: ${{ matrix.goos }} GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }} GOARCH: ${{ matrix.goarch }}
@@ -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 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. # 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 # 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 else
echo 'Building Xray...' echo 'Building Xray...'
if [[ ${GOARCH} == 'mips' || ${GOARCH} == 'mipsle' ]]; then if [[ ${GOARCH} == 'mips' || ${GOARCH} == 'mipsle' ]]; then
+23 -1
View File
@@ -1,4 +1,4 @@
name: Test name: Tests and Checkings
on: on:
push: push:
@@ -8,6 +8,7 @@ on:
jobs: jobs:
check-assets: check-assets:
runs-on: ubuntu-latest 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: steps:
- name: Restore Geodat Cache - name: Restore Geodat Cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
@@ -36,6 +37,7 @@ jobs:
check-proto: check-proto:
runs-on: ubuntu-latest 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: steps:
- name: Checkout codebase - name: Checkout codebase
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -50,8 +52,28 @@ jobs:
fi fi
done 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@v6
- 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: test:
needs: check-assets 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: permissions:
contents: read contents: read
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
+11 -1
View File
@@ -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) - [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) - [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
- Magisk - Magisk
- [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk)
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk) - [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
- Homebrew - Homebrew
- `brew install xray` - `brew install xray`
@@ -120,6 +119,7 @@
- [XrayFA](https://github.com/Q7DF1/XrayFA) - [XrayFA](https://github.com/Q7DF1/XrayFA)
- [AnyPortal](https://github.com/AnyPortal/AnyPortal) - [AnyPortal](https://github.com/AnyPortal/AnyPortal)
- [OneXray](https://github.com/OneXray/OneXray) - [OneXray](https://github.com/OneXray/OneXray)
- [AsteriskNG](https://github.com/Asterisk4Magisk/AsteriskNG)
- iOS & macOS arm64 & tvOS - 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) - [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) - [Streisand](https://apps.apple.com/app/streisand/id6450534064)
@@ -145,6 +145,8 @@
- [v2rayN](https://github.com/2dust/v2rayN) - [v2rayN](https://github.com/2dust/v2rayN)
- [GenyConnect](https://github.com/genyleap/GenyConnect) - [GenyConnect](https://github.com/genyleap/GenyConnect)
- [OneXray](https://github.com/OneXray/OneXray) - [OneXray](https://github.com/OneXray/OneXray)
- HarmonyOS
- [Hey](https://github.com/popsiclelmlm/Hey)
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX... ## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
@@ -162,6 +164,7 @@
- [xtls-sdk](https://github.com/remnawave/xtls-sdk) - [xtls-sdk](https://github.com/remnawave/xtls-sdk)
- [xtlsapi](https://github.com/hiddify/xtlsapi) - [xtlsapi](https://github.com/hiddify/xtlsapi)
- [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite) - [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-core-python](https://github.com/LorenEteval/Xray-core-python)
- [xray-api](https://github.com/XVGuardian/xray-api) - [xray-api](https://github.com/XVGuardian/xray-api)
- [XrayR](https://github.com/XrayR-project/XrayR) - [XrayR](https://github.com/XrayR-project/XrayR)
@@ -206,6 +209,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 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: If you are compiling a 32-bit MIPS/MIPSLE target, use this command instead:
```bash ```bash
+5 -6
View File
@@ -3,15 +3,15 @@ package commander
import ( import (
"context" "context"
"net" "net"
"strings"
"sync" "sync"
"strings"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/signal/done" "github.com/xtls/xray-core/common/signal/done"
core "github.com/xtls/xray-core/core" core "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/outbound" "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" "google.golang.org/grpc"
) )
@@ -69,16 +69,15 @@ func (c *Commander) Start() error {
} }
c.Unlock() c.Unlock()
var listen = func(listener net.Listener) { listen := func(listener net.Listener) {
if err := c.server.Serve(listener); err != nil { if err := c.server.Serve(listener); err != nil {
errors.LogErrorInner(context.Background(), err, "failed to start grpc server") errors.LogErrorInner(context.Background(), err, "failed to start grpc server")
} }
} }
if len(c.listen) > 0 { if len(c.listen) > 0 {
var addr net.Addr var addr net.Addr
if strings.HasPrefix(c.listen, "/") || strings.HasPrefix(c.listen, "@") { if strings.HasPrefix(c.listen, "/") || strings.HasPrefix(c.listen, "@") {
addr = &net.UnixAddr{Name: c.listen, Net: "unix"} addr = &net.UnixAddr{Name: c.listen, Net: "unix"}
} else { } else {
@@ -89,7 +88,7 @@ func (c *Commander) Start() error {
} }
addr = tcpAddr addr = tcpAddr
} }
l, err := internet.ListenSystem(context.Background(), addr, nil) l, err := internet.ListenSystem(context.Background(), addr, nil)
if err != nil { if err != nil {
errors.LogErrorInner(context.Background(), err, "API server failed to listen on ", c.listen) errors.LogErrorInner(context.Background(), err, "API server failed to listen on ", c.listen)
return err return err
+4 -3
View File
@@ -139,7 +139,8 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
if lenAfter == 0 { if lenAfter == 0 {
if c.highWatermark >= minSizeForEmptyRebuild { if c.highWatermark >= minSizeForEmptyRebuild {
errors.LogDebug(context.Background(), c.name, errors.LogDebug(
context.Background(), c.name,
" rebuilding empty cache map to reclaim memory.", " rebuilding empty cache map to reclaim memory.",
" size_before_cleanup=", lenBefore, " size_before_cleanup=", lenBefore,
" peak_size_before_rebuild=", c.highWatermark, " peak_size_before_rebuild=", c.highWatermark,
@@ -153,7 +154,8 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
if reductionFromPeak := c.highWatermark - lenAfter; reductionFromPeak > shrinkAbsoluteThreshold && if reductionFromPeak := c.highWatermark - lenAfter; reductionFromPeak > shrinkAbsoluteThreshold &&
float64(reductionFromPeak) > float64(c.highWatermark)*shrinkRatioThreshold { float64(reductionFromPeak) > float64(c.highWatermark)*shrinkRatioThreshold {
errors.LogDebug(context.Background(), c.name, errors.LogDebug(
context.Background(), c.name,
" shrinking cache map to reclaim memory.", " shrinking cache map to reclaim memory.",
" new_size=", lenAfter, " new_size=", lenAfter,
" peak_size_before_shrink=", c.highWatermark, " peak_size_before_shrink=", c.highWatermark,
@@ -165,7 +167,6 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
c.highWatermark = lenAfter c.highWatermark = lenAfter
go c.migrate() go c.migrate()
} }
} }
type migrationEntry struct { type migrationEntry struct {
+2 -2
View File
@@ -86,7 +86,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
return nil, errors.New("failed to create hosts").Base(err) return nil, errors.New("failed to create hosts").Base(err)
} }
var defaultTag = config.Tag defaultTag := config.Tag
if len(config.Tag) == 0 { if len(config.Tag) == 0 {
defaultTag = generateRandomTag() defaultTag = generateRandomTag()
} }
@@ -139,7 +139,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
serveExpiredTTL = *ns.ServeExpiredTTL serveExpiredTTL = *ns.ServeExpiredTTL
} }
var tag = defaultTag tag := defaultTag
if len(ns.Tag) > 0 { if len(ns.Tag) > 0 {
tag = ns.Tag tag = ns.Tag
} }
+9 -4
View File
@@ -127,15 +127,20 @@ func genEDNS0Options(clientIP net.IP, padding int) *dnsmessage.Resource {
return opt 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{ qA := dnsmessage.Question{
Name: dnsmessage.MustNewName(domain), Name: name,
Type: dnsmessage.TypeA, Type: dnsmessage.TypeA,
Class: dnsmessage.ClassINET, Class: dnsmessage.ClassINET,
} }
qAAAA := dnsmessage.Question{ qAAAA := dnsmessage.Question{
Name: dnsmessage.MustNewName(domain), Name: name,
Type: dnsmessage.TypeAAAA, Type: dnsmessage.TypeAAAA,
Class: dnsmessage.ClassINET, 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 // parseResponse parses DNS answers from the returned payload
+11 -3
View File
@@ -2,6 +2,7 @@ package dns
import ( import (
"math/rand" "math/rand"
"strings"
"testing" "testing"
"time" "time"
@@ -24,7 +25,8 @@ func Test_parseResponse(t *testing.T) {
ans = new(dns.Msg) ans = new(dns.Msg)
ans.Id = 1 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 m.test.google.com")),
common.Must2(dns.NewRR("google.com. IN CNAME fake.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")), 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 = new(dns.Msg)
ans.Id = 2 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 m.test.google.com")),
common.Must2(dns.NewRR("google.com. IN CNAME fake.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")), common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
@@ -131,10 +134,15 @@ func Test_buildReqMsgs(t *testing.T) {
IPv6Enable: false, IPv6Enable: false,
FakeEnable: false, FakeEnable: false,
}, nil}, 0}, }, 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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) t.Errorf("buildReqMsgs() = %v, want %v", got, tt.want)
} }
}) })
+10 -9
View File
@@ -129,15 +129,16 @@ func TestFakeDnsHolderCreateMappingAndRollOver(t *testing.T) {
} }
func TestFakeDNSMulti(t *testing.T) { func TestFakeDNSMulti(t *testing.T) {
fakeMulti, err := NewFakeDNSHolderMulti(&FakeDnsPoolMulti{ fakeMulti, err := NewFakeDNSHolderMulti(
Pools: []*FakeDnsPool{{ &FakeDnsPoolMulti{
IpPool: "240.0.0.0/12", Pools: []*FakeDnsPool{{
LruSize: 256, IpPool: "240.0.0.0/12",
}, { LruSize: 256,
IpPool: "fddd:c5b4:ff5f:f4f0::/64", }, {
LruSize: 256, IpPool: "fddd:c5b4:ff5f:f4f0::/64",
}}, LruSize: 256,
}, }},
},
) )
common.Must(err) common.Must(err)
+1 -1
View File
@@ -135,7 +135,7 @@ func NewClient(
} }
} }
var timeoutMs = 4000 * time.Millisecond timeoutMs := 4000 * time.Millisecond
if ns.TimeoutMs > 0 { if ns.TimeoutMs > 0 {
timeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond timeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond
} }
+20 -2
View File
@@ -137,14 +137,32 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- er
if s.Name()+"." == "DOH//"+fqdn { if s.Name()+"." == "DOH//"+fqdn {
errors.LogError(ctx, s.Name(), " tries to resolve itself! Use IP or set \"hosts\" instead") errors.LogError(ctx, s.Name(), " tries to resolve itself! Use IP or set \"hosts\" instead")
if noResponseErrCh != nil { 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 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 // 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 // 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 var deadline time.Time
if d, ok := ctx.Deadline(); ok { if d, ok := ctx.Deadline(); ok {
-1
View File
@@ -18,7 +18,6 @@ type LocalNameServer struct {
// QueryIP implements Server. // QueryIP implements Server.
func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, option dns.IPOption) (ips []net.IP, ttl uint32, err error) { func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, option dns.IPOption) (ips []net.IP, ttl uint32, err error) {
start := time.Now() start := time.Now()
ips, ttl, err = s.client.LookupIP(domain, option) ips, ttl, err = s.client.LookupIP(domain, option)
+13 -1
View File
@@ -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) { func (s *QUICNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
errors.LogInfo(ctx, s.Name(), " querying: ", fqdn) 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 var deadline time.Time
if d, ok := ctx.Deadline(); ok { if d, ok := ctx.Deadline(); ok {
+13 -1
View File
@@ -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) { 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) 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 var deadline time.Time
if d, ok := ctx.Deadline(); ok { if d, ok := ctx.Deadline(); ok {
+13 -5
View File
@@ -131,8 +131,6 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
newReq.msg = &newMsg newReq.msg = &newMsg
s.addPendingRequest(&newReq) s.addPendingRequest(&newReq)
b, _ := dns.PackMessage(newReq.msg) b, _ := dns.PackMessage(newReq.msg)
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
b.UDP = &copyDest
s.udpServer.Dispatch(toDnsContext(newReq.ctx, s.address.String()), *s.address, b) s.udpServer.Dispatch(toDnsContext(newReq.ctx, s.address.String()), *s.address, b)
return 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) { 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) 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 { for _, req := range reqs {
udpReq := &udpDnsRequest{ udpReq := &udpDnsRequest{
@@ -179,8 +189,6 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<
} }
return return
} }
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
b.UDP = &copyDest
s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b) s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)
} }
} }
-1
View File
@@ -85,7 +85,6 @@ func (p *MetricsHandler) Type() interface{} {
} }
func (p *MetricsHandler) Start() error { func (p *MetricsHandler) Start() error {
// direct listen a port if listen is set // direct listen a port if listen is set
if p.listen != "" { if p.listen != "" {
TCPlistener, err := net.Listen("tcp", p.listen) TCPlistener, err := net.Listen("tcp", p.listen)
-2
View File
@@ -2,7 +2,6 @@ package burst
import ( import (
"context" "context"
"sync" "sync"
"github.com/xtls/xray-core/app/observatory" "github.com/xtls/xray-core/app/observatory"
@@ -72,7 +71,6 @@ func (o *Observer) Start() error {
o.hp.StartScheduler(func() ([]string, error) { o.hp.StartScheduler(func() ([]string, error) {
hs, ok := o.ohm.(outbound.HandlerSelector) hs, ok := o.ohm.(outbound.HandlerSelector)
if !ok { if !ok {
return nil, errors.New("outbound.Manager is not a HandlerSelector") return nil, errors.New("outbound.Manager is not a HandlerSelector")
} }
+42 -24
View File
@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/xtls/xray-core/common/dice" "github.com/xtls/xray-core/common/dice"
@@ -24,11 +25,12 @@ type HealthPingSettings struct {
// HealthPing is the health checker for balancers // HealthPing is the health checker for balancers
type HealthPing struct { type HealthPing struct {
ctx context.Context ctx context.Context
dispatcher routing.Dispatcher cancelCtx context.CancelFunc
access sync.Mutex cancelPending atomic.Pointer[context.CancelFunc]
ticker *time.Ticker dispatcher routing.Dispatcher
tickerClose chan struct{} access sync.Mutex
ticker *time.Ticker
Settings *HealthPingSettings Settings *HealthPingSettings
Results map[string]*HealthPingRTTS 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" settings.Destination = "https://connectivitycheck.gstatic.com/generate_204"
} }
if settings.Interval == 0 { if settings.Interval == 0 {
settings.Interval = time.Duration(1) * time.Minute settings.Interval = 1 * time.Minute
} else if settings.Interval < 10 { } else if settings.Interval < 10*time.Second {
errors.LogWarning(ctx, "health check interval is too small, 10s is applied") 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 { if settings.SamplingCount <= 0 {
settings.SamplingCount = 10 settings.SamplingCount = 10
@@ -73,10 +75,12 @@ func NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *H
if settings.Timeout <= 0 { if settings.Timeout <= 0 {
// results are saved after all health pings finish, // results are saved after all health pings finish,
// a larger timeout could possibly makes checks run longer // 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{ return &HealthPing{
ctx: ctx, ctx: ctx,
cancelCtx: cancel,
dispatcher: dispatcher, dispatcher: dispatcher,
Settings: settings, Settings: settings,
Results: nil, Results: nil,
@@ -90,9 +94,9 @@ func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
} }
interval := h.Settings.Interval * time.Duration(h.Settings.SamplingCount) interval := h.Settings.Interval * time.Duration(h.Settings.SamplingCount)
ticker := time.NewTicker(interval) ticker := time.NewTicker(interval)
tickerClose := make(chan struct{})
h.ticker = ticker h.ticker = ticker
h.tickerClose = tickerClose
// init run to get a fast check result
go func() { go func() {
tags, err := selector() tags, err := selector()
if err != nil { 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) errors.LogWarning(h.ctx, "error select outbounds for scheduled health check: ", err)
return 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) h.Cleanup(tags)
}() }()
select { select {
case <-ticker.C: case <-ticker.C:
continue continue
case <-tickerClose: case <-h.ctx.Done():
return return
} }
} }
@@ -130,8 +141,7 @@ func (h *HealthPing) StopScheduler() {
} }
h.ticker.Stop() h.ticker.Stop()
h.ticker = nil h.ticker = nil
close(h.tickerClose) h.cancelCtx()
h.tickerClose = nil
} }
// Check implements the HealthChecker // Check implements the HealthChecker
@@ -140,7 +150,7 @@ func (h *HealthPing) Check(tags []string) error {
return nil return nil
} }
errors.LogInfo(h.ctx, "perform one-time health check for tags ", tags) 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 return nil
} }
@@ -151,13 +161,14 @@ type rtt struct {
// doCheck performs the 'rounds' amount checks in given 'duration'. You should make // doCheck performs the 'rounds' amount checks in given 'duration'. You should make
// sure all tags are valid for current balancer // 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 count := len(tags) * rounds
if count == 0 { if count == 0 {
return return
} }
ch := make(chan *rtt, count) ch := make(chan *rtt, count)
timers := make([]*time.Timer, 0, count)
for _, tag := range tags { for _, tag := range tags {
handler := tag handler := tag
client := newPingClient( client := newPingClient(
@@ -172,7 +183,7 @@ func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int)
if duration > 0 { if duration > 0 {
delay = time.Duration(dice.RollInt63n(int64(duration))) delay = time.Duration(dice.RollInt63n(int64(duration)))
} }
time.AfterFunc(delay, func() { timers = append(timers, time.AfterFunc(delay, func() {
errors.LogDebug(h.ctx, "checking ", handler) errors.LogDebug(h.ctx, "checking ", handler)
delay, err := client.MeasureDelay(h.Settings.HttpMethod) delay, err := client.MeasureDelay(h.Settings.HttpMethod)
if err == nil { if err == nil {
@@ -200,14 +211,21 @@ func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int)
handler: handler, handler: handler,
value: rttFailed, value: rttFailed,
} }
}) }))
} }
} }
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
rtt := <-ch select {
if rtt.value > 0 { case rtt := <-ch:
// should not put results when network is down if rtt.value > 0 {
h.PutResult(rtt.handler, rtt.value) // should not put results when network is down
h.PutResult(rtt.handler, rtt.value)
}
case <-ctx.Done():
for _, timer := range timers {
timer.Stop()
}
return
} }
} }
} }
+2 -2
View File
@@ -59,7 +59,7 @@ func (h *HealthPingRTTS) Put(d time.Duration) {
if h.rtts == nil { if h.rtts == nil {
h.rtts = make([]*pingRTT, h.cap) h.rtts = make([]*pingRTT, h.cap)
for i := 0; i < h.cap; i++ { for i := 0; i < h.cap; i++ {
h.rtts[i] = &pingRTT{} h.rtts[i] = &pingRTT{value: rttUntested}
} }
h.idx = -1 h.idx = -1
} }
@@ -88,7 +88,7 @@ func (h *HealthPingRTTS) getStatistics() *HealthPingStats {
validRTTs := make([]time.Duration, 0) validRTTs := make([]time.Duration, 0)
for _, rtt := range h.rtts { for _, rtt := range h.rtts {
switch { switch {
case rtt.value == 0 || time.Since(rtt.time) > h.validity: case rtt.value == rttUntested || time.Since(rtt.time) > h.validity:
continue continue
case rtt.value == rttFailed: case rtt.value == rttFailed:
stats.Fail++ stats.Fail++
+1 -1
View File
@@ -186,7 +186,7 @@ func (o *Observer) probe(outbound string) ProbeResult {
return nil return nil
}) })
if err != 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) errors.LogInfoInner(o.ctx, errorCollectorForRequest.UnderlyingError(), errorMessage)
return ProbeResult{Alive: false, LastErrorReason: errorMessage} return ProbeResult{Alive: false, LastErrorReason: errorMessage}
} }
+1 -1
View File
@@ -138,7 +138,7 @@ func (s *handlerServer) GetInboundUsers(ctx context.Context, request *GetInbound
if len(request.Email) > 0 { if len(request.Email) > 0 {
return &GetInboundUserResponse{Users: []*protocol.User{protocol.ToProtoUser(um.GetUser(ctx, request.Email))}}, nil 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) users := um.GetUsers(ctx)
for _, u := range users { for _, u := range users {
result = append(result, protocol.ToProtoUser(u)) result = append(result, protocol.ToProtoUser(u))
+3 -3
View File
@@ -16,10 +16,10 @@ import (
// Manager manages all inbound handlers. // Manager manages all inbound handlers.
type Manager struct { type Manager struct {
access sync.RWMutex access sync.RWMutex
untaggedHandlers []inbound.Handler untaggedHandlers []inbound.Handler
taggedHandlers map[string]inbound.Handler taggedHandlers map[string]inbound.Handler
running bool running bool
} }
// New returns a new Manager for inbound handlers. // New returns a new Manager for inbound handlers.
+1 -2
View File
@@ -348,7 +348,7 @@ func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound)
errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String()) errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String())
} }
} }
//case addr.Family().IsDomain(): // case addr.Family().IsDomain():
default: default:
ob.Gateway = addr ob.Gateway = addr
@@ -396,7 +396,6 @@ func (h *Handler) ProxySettings() *serial.TypedMessage {
} }
func ParseRandomIP(addr net.Address, prefix string) net.Address { func ParseRandomIP(addr net.Address, prefix string) net.Address {
_, ipnet, _ := net.ParseCIDR(addr.IP().String() + "/" + prefix) _, ipnet, _ := net.ParseCIDR(addr.IP().String() + "/" + prefix)
ones, bits := ipnet.Mask.Size() ones, bits := ipnet.Mask.Size()
+4 -5
View File
@@ -22,8 +22,8 @@ import (
) )
func TestInterfaces(t *testing.T) { func TestInterfaces(t *testing.T) {
_ = (outbound.Handler)(new(Handler)) _ = outbound.Handler(new(Handler))
_ = (outbound.Manager)(new(Manager)) _ = outbound.Manager(new(Manager))
} }
const xrayKey core.XrayKey = 1 const xrayKey core.XrayKey = 1
@@ -43,7 +43,7 @@ func TestOutboundWithoutStatCounter(t *testing.T) {
} }
v, _ := core.New(config) v, _ := core.New(config)
v.AddFeature((outbound.Manager)(new(Manager))) v.AddFeature(outbound.Manager(new(Manager)))
ctx := context.WithValue(context.Background(), xrayKey, v) ctx := context.WithValue(context.Background(), xrayKey, v)
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}}) ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{ h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
@@ -73,7 +73,7 @@ func TestOutboundWithStatCounter(t *testing.T) {
} }
v, _ := core.New(config) v, _ := core.New(config)
v.AddFeature((outbound.Manager)(new(Manager))) v.AddFeature(outbound.Manager(new(Manager)))
ctx := context.WithValue(context.Background(), xrayKey, v) ctx := context.WithValue(context.Background(), xrayKey, v)
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}}) ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{ h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
@@ -88,7 +88,6 @@ func TestOutboundWithStatCounter(t *testing.T) {
} }
func TestTagsCache(t *testing.T) { func TestTagsCache(t *testing.T) {
test_duration := 10 * time.Second test_duration := 10 * time.Second
threads_num := 50 threads_num := 50
delay := 10 * time.Millisecond delay := 10 * time.Millisecond
-1
View File
@@ -162,7 +162,6 @@ func (m *Manager) ListHandlers(ctx context.Context) []outbound.Handler {
// Select implements outbound.HandlerSelector. // Select implements outbound.HandlerSelector.
func (m *Manager) Select(selectors []string) []string { func (m *Manager) Select(selectors []string) []string {
key := strings.Join(selectors, ",") key := strings.Join(selectors, ",")
if cache, ok := m.tagsCache.Load(key); ok { if cache, ok := m.tagsCache.Load(key); ok {
return cache.([]string) return cache.([]string)
-1
View File
@@ -58,7 +58,6 @@ func (s *routingServer) AddRule(ctx context.Context, request *AddRuleRequest) (*
return &AddRuleResponse{}, bo.AddRule(request.Config, request.ShouldAppend) return &AddRuleResponse{}, bo.AddRule(request.Config, request.ShouldAppend)
} }
return nil, errors.New("unsupported router implementation") return nil, errors.New("unsupported router implementation")
} }
func (s *routingServer) RemoveRule(ctx context.Context, request *RemoveRuleRequest) (*RemoveRuleResponse, error) { func (s *routingServer) RemoveRule(ctx context.Context, request *RemoveRuleRequest) (*RemoveRuleResponse, error) {
-2
View File
@@ -110,7 +110,6 @@ func (r *Router) PickRoute(ctx routing.Context) (routing.Route, error) {
// AddRule implements routing.Router. // AddRule implements routing.Router.
func (r *Router) AddRule(config *serial.TypedMessage, shouldAppend bool) error { func (r *Router) AddRule(config *serial.TypedMessage, shouldAppend bool) error {
inst, err := config.GetInstance() inst, err := config.GetInstance()
if err != nil { if err != nil {
return err return err
@@ -227,7 +226,6 @@ func (r *Router) RemoveRule(tag string) error {
return nil return nil
} }
return errors.New("empty tag name!") return errors.New("empty tag name!")
} }
// ListRule implements routing.Router // ListRule implements routing.Router
+22 -6
View File
@@ -3,6 +3,7 @@ package router
import ( import (
"context" "context"
"math" "math"
"slices"
"sort" "sort"
"time" "time"
@@ -77,7 +78,7 @@ func (s *LeastLoadStrategy) PickOutbound(candidates []string) string {
} }
func (s *LeastLoadStrategy) pickOutbounds(candidates []string) []*node { func (s *LeastLoadStrategy) pickOutbounds(candidates []string) []*node {
qualified := s.getNodes(candidates, time.Duration(s.settings.MaxRTT)) qualified := s.getNodes(candidates)
selects := s.selectLeastLoad(qualified) selects := s.selectLeastLoad(qualified)
return selects return selects
} }
@@ -138,7 +139,7 @@ func (s *LeastLoadStrategy) selectLeastLoad(nodes []*node) []*node {
return nodes[:count] return nodes[:count]
} }
func (s *LeastLoadStrategy) getNodes(candidates []string, maxRTT time.Duration) []*node { func (s *LeastLoadStrategy) getNodes(candidates []string) []*node {
if s.observer == nil { if s.observer == nil {
errors.LogError(s.ctx, "observer is nil") errors.LogError(s.ctx, "observer is nil")
return make([]*node, 0) return make([]*node, 0)
@@ -151,12 +152,10 @@ func (s *LeastLoadStrategy) getNodes(candidates []string, maxRTT time.Duration)
results := observeResult.(*observatory.ObservationResult) results := observeResult.(*observatory.ObservationResult)
outboundlist := outboundList(candidates)
var ret []*node var ret []*node
for _, v := range results.Status { 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{ record := &node{
Tag: v.OutboundTag, Tag: v.OutboundTag,
CountAll: 1, 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.RTTDeviationCost = time.Duration(s.costs.Apply(v.OutboundTag, float64(v.HealthPing.Deviation)))
record.CountAll = int(v.HealthPing.All) record.CountAll = int(v.HealthPing.All)
record.CountFail = int(v.HealthPing.Fail) record.CountFail = int(v.HealthPing.Fail)
} }
ret = append(ret, record) ret = append(ret, record)
} }
} }
@@ -182,6 +181,23 @@ func (s *LeastLoadStrategy) getNodes(candidates []string, maxRTT time.Duration)
return ret 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) { func leastloadSort(nodes []*node) {
sort.Slice(nodes, func(i, j int) bool { sort.Slice(nodes, func(i, j int) bool {
left := nodes[i] left := nodes[i]
+5
View File
@@ -85,6 +85,7 @@ func TestSelectLeastExpected(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns)) t.Errorf("expected: %v, actual: %v", expected, len(ns))
} }
} }
func TestSelectLeastExpected2(t *testing.T) { func TestSelectLeastExpected2(t *testing.T) {
strategy := &LeastLoadStrategy{ strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{ settings: &StrategyLeastLoadConfig{
@@ -102,6 +103,7 @@ func TestSelectLeastExpected2(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns)) t.Errorf("expected: %v, actual: %v", expected, len(ns))
} }
} }
func TestSelectLeastExpectedAndBaselines(t *testing.T) { func TestSelectLeastExpectedAndBaselines(t *testing.T) {
strategy := &LeastLoadStrategy{ strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{ settings: &StrategyLeastLoadConfig{
@@ -122,6 +124,7 @@ func TestSelectLeastExpectedAndBaselines(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns)) t.Errorf("expected: %v, actual: %v", expected, len(ns))
} }
} }
func TestSelectLeastExpectedAndBaselines2(t *testing.T) { func TestSelectLeastExpectedAndBaselines2(t *testing.T) {
strategy := &LeastLoadStrategy{ strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{ settings: &StrategyLeastLoadConfig{
@@ -142,6 +145,7 @@ func TestSelectLeastExpectedAndBaselines2(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns)) t.Errorf("expected: %v, actual: %v", expected, len(ns))
} }
} }
func TestSelectLeastLoadBaselines(t *testing.T) { func TestSelectLeastLoadBaselines(t *testing.T) {
strategy := &LeastLoadStrategy{ strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{ settings: &StrategyLeastLoadConfig{
@@ -160,6 +164,7 @@ func TestSelectLeastLoadBaselines(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns)) t.Errorf("expected: %v, actual: %v", expected, len(ns))
} }
} }
func TestSelectLeastLoadBaselinesNoQualified(t *testing.T) { func TestSelectLeastLoadBaselinesNoQualified(t *testing.T) {
strategy := &LeastLoadStrategy{ strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{ settings: &StrategyLeastLoadConfig{
+3 -49
View File
@@ -7,61 +7,15 @@ import (
"io" "io"
"net" "net"
"net/http" "net/http"
"path/filepath"
"runtime"
"strings"
"sync" "sync"
"syscall"
"time" "time"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
routing_session "github.com/xtls/xray-core/features/routing/session" 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 } func ptr[T any](v T) *T { return &v }
type event struct { type event struct {
@@ -96,7 +50,7 @@ func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
return nil, nil return nil, nil
} }
httpURL, socketPath := parseURL(cfg.Url) httpURL, socketPath := utils.SplitHTTPUnixURL(cfg.Url)
h := &WebhookNotifier{ h := &WebhookNotifier{
url: httpURL, url: httpURL,
deduplication: cfg.Deduplication, deduplication: cfg.Deduplication,
@@ -107,7 +61,7 @@ func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
} }
if socketPath != "" { if socketPath != "" {
dialAddr := resolveSocketPath(socketPath) dialAddr := utils.ResolveSocketPath(socketPath)
h.client.Transport = &http.Transport{ h.client.Transport = &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
var d net.Dialer var d net.Dialer
+1 -1
View File
@@ -11,7 +11,7 @@ import (
) )
func TestInterface(t *testing.T) { func TestInterface(t *testing.T) {
_ = (stats.Manager)(new(Manager)) _ = stats.Manager(new(Manager))
} }
func TestStatsChannelRunnable(t *testing.T) { func TestStatsChannelRunnable(t *testing.T) {
+3 -2
View File
@@ -2,10 +2,11 @@ package version
import ( import (
"context" "context"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"strconv" "strconv"
"strings" "strings"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
) )
type Version struct { type Version struct {
+2 -2
View File
@@ -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. // MergeBytes merges the given bytes into MultiBuffer and return the new address of the merged MultiBuffer.
func MergeBytes(dest MultiBuffer, src []byte) MultiBuffer { func MergeBytes(dest MultiBuffer, src []byte) MultiBuffer {
n := len(dest) n := len(dest)
if n > 0 && !(dest)[n-1].IsFull() { if n > 0 && !dest[n-1].IsFull() {
nBytes, _ := (dest)[n-1].Write(src) nBytes, _ := dest[n-1].Write(src)
src = src[nBytes:] src = src[nBytes:]
} }
+6 -6
View File
@@ -121,11 +121,11 @@ func TestPacketReader_ReadMultiBuffer(t *testing.T) {
} }
func TestReaderInterface(t *testing.T) { func TestReaderInterface(t *testing.T) {
_ = (io.Reader)(new(ReadVReader)) _ = io.Reader(new(ReadVReader))
_ = (Reader)(new(ReadVReader)) _ = Reader(new(ReadVReader))
_ = (Reader)(new(BufferedReader)) _ = Reader(new(BufferedReader))
_ = (io.Reader)(new(BufferedReader)) _ = io.Reader(new(BufferedReader))
_ = (io.ByteReader)(new(BufferedReader)) _ = io.ByteReader(new(BufferedReader))
_ = (io.WriterTo)(new(BufferedReader)) _ = io.WriterTo(new(BufferedReader))
} }
+1 -1
View File
@@ -19,7 +19,7 @@ func (r *posixReader) Init(bs []*Buffer) {
} }
for idx, b := range bs { for idx, b := range bs {
iovecs = append(iovecs, syscall.Iovec{ iovecs = append(iovecs, syscall.Iovec{
Base: &(b.v[0]), Base: &b.v[0],
}) })
iovecs[idx].SetLen(int(Size)) iovecs[idx].SetLen(int(Size))
} }
+9 -8
View File
@@ -8,6 +8,7 @@ import (
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/geodata/strmatcher" "github.com/xtls/xray-core/common/geodata/strmatcher"
"github.com/xtls/xray-core/common/utils"
) )
type DomainMatcher interface { type DomainMatcher interface {
@@ -25,7 +26,7 @@ type DomainMatcherFactory interface {
type MphDomainMatcherFactory struct { type MphDomainMatcherFactory struct {
sync.Mutex sync.Mutex
shared map[string]strmatcher.MatcherGroup // TODO: cleanup shared *utils.WeakCacheMap[string, strmatcher.MphValueMatcher]
} }
func buildDomainRulesKey(rules []*DomainRule) string { func buildDomainRulesKey(rules []*DomainRule) string {
@@ -65,7 +66,7 @@ func (f *MphDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatch
if key != "" { if key != "" {
f.Lock() f.Lock()
defer f.Unlock() 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") errors.LogDebug(context.Background(), "geodata mph domain matcher cache HIT for ", len(rules), " rules")
return g, nil return g, nil
} }
@@ -102,14 +103,14 @@ func (f *MphDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatch
return nil, err return nil, err
} }
if key != "" { if key != "" {
f.shared[key] = g f.shared.Store(key, g)
} }
return g, nil return g, nil
} }
type CompactDomainMatcherFactory struct { type CompactDomainMatcherFactory struct {
sync.Mutex sync.Mutex
shared map[string]strmatcher.MatcherSet // TODO: cleanup shared *utils.WeakCacheMap[string, strmatcher.LinearAnyMatcher]
} }
func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmatcher.MatcherSet, error) { func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmatcher.MatcherSet, error) {
@@ -118,7 +119,7 @@ func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmat
f.Lock() f.Lock()
defer f.Unlock() 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) errors.LogDebug(context.Background(), "geodata geosite matcher cache HIT ", key)
return s, nil return s, nil
} }
@@ -138,7 +139,7 @@ func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmat
} }
s.Add(m) s.Add(m)
} }
f.shared[key] = s f.shared.Store(key, s)
return s, err return s, err
} }
@@ -230,8 +231,8 @@ func parseDomain(d *Domain) (strmatcher.Matcher, error) {
func newDomainMatcherFactory() DomainMatcherFactory { func newDomainMatcherFactory() DomainMatcherFactory {
switch runtime.GOOS { switch runtime.GOOS {
case "ios", "android": case "ios", "android":
return &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)} return &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()}
default: default:
return &MphDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)} return &MphDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.MphValueMatcher]()}
} }
} }
+8 -6
View File
@@ -7,10 +7,11 @@ import (
"testing" "testing"
"github.com/xtls/xray-core/common/geodata/strmatcher" "github.com/xtls/xray-core/common/geodata/strmatcher"
"github.com/xtls/xray-core/common/utils"
) )
func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) { 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{ matcher, err := factory.BuildMatcher([]*DomainRule{
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}}, {Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, 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) { func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources")) 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{ matcher, err := factory.BuildMatcher([]*DomainRule{
{Value: &DomainRule_Geosite{Geosite: &GeoSiteRule{File: DefaultGeoSiteDat, Code: "CN"}}}, {Value: &DomainRule_Geosite{Geosite: &GeoSiteRule{File: DefaultGeoSiteDat, Code: "CN"}}},
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "163.com"}}}, {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) { func TestMphDomainMatcher_MatchReturnsDetachedSlice(t *testing.T) {
matcher, err := (&MphDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}).BuildMatcher([]*DomainRule{ matcher, err := (&MphDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.MphValueMatcher]()}).
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}}, BuildMatcher([]*DomainRule{
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}}, {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 { if err != nil {
t.Fatalf("BuildMatcher() failed: %v", err) t.Fatalf("BuildMatcher() failed: %v", err)
} }
+5 -4
View File
@@ -11,6 +11,7 @@ import (
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/utils"
"go4.org/netipx" "go4.org/netipx"
) )
@@ -806,7 +807,7 @@ func (mm *HeuristicMultiIPMatcher) SetReverse(reverse bool) {
type IPSetFactory struct { type IPSetFactory struct {
sync.Mutex sync.Mutex
shared map[string]*IPSet // TODO: cleanup shared *utils.WeakCacheMap[string, IPSet]
} }
func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, error) { func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, error) {
@@ -815,7 +816,7 @@ func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, er
f.Lock() f.Lock()
defer f.Unlock() 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) errors.LogDebug(context.Background(), "geodata geoip matcher cache HIT ", key)
return ipset, nil return ipset, nil
} }
@@ -835,7 +836,7 @@ func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, er
return nil return nil
}) })
if err == nil { if err == nil {
f.shared[key] = ipset f.shared.Store(key, ipset)
} }
return ipset, err return ipset, err
} }
@@ -1018,5 +1019,5 @@ func buildOptimizedIPMatcher(f *IPSetFactory, rules []*IPRule) (IPMatcher, error
} }
func newIPSetFactory() *IPSetFactory { func newIPSetFactory() *IPSetFactory {
return &IPSetFactory{shared: make(map[string]*IPSet)} return &IPSetFactory{shared: utils.NewWeakCacheMap[string, IPSet]()}
} }
+3 -3
View File
@@ -315,7 +315,7 @@ func TestIPMatcherAnyMatchAndMatches(t *testing.T) {
} }
if !matcher.AnyMatch([]net.IP{ if !matcher.AnyMatch([]net.IP{
net.IP{}, {},
ip("1.1.1.1"), ip("1.1.1.1"),
ip("8.8.8.8"), ip("8.8.8.8"),
}) { }) {
@@ -345,7 +345,7 @@ func TestIPMatcherAnyMatchAndMatches(t *testing.T) {
if matcher.Matches([]net.IP{ if matcher.Matches([]net.IP{
ip("8.8.8.8"), ip("8.8.8.8"),
net.IP{}, {},
}) { }) {
t.Fatal("expect Matches to be false when any IP is invalid") 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{ matched, unmatched := matcher.FilterIPs([]net.IP{
net.IP{}, {},
ip("8.8.8.8"), ip("8.8.8.8"),
ip("91.108.255.254"), ip("91.108.255.254"),
ip("1.1.1.1"), ip("1.1.1.1"),
-1
View File
@@ -98,7 +98,6 @@ func (l *generalLogger) run() {
} }
func (l *generalLogger) Handle(msg Message) { func (l *generalLogger) Handle(msg Message) {
select { select {
case l.buffer <- msg: case l.buffer <- msg:
default: default:
+3 -2
View File
@@ -36,9 +36,10 @@ func TestNormalizeEnvName(t *testing.T) {
} }
func TestEnvFlag(t *testing.T) { func TestEnvFlag(t *testing.T) {
if v := (EnvFlag{ v := EnvFlag{
Name: "xxxxx.y", Name: "xxxxx.y",
}.GetValueAsInt(10)); v != 10 { }.GetValueAsInt(10)
if v != 10 {
t.Error("env value: ", v) t.Error("env value: ", v)
} }
} }
+1 -1
View File
@@ -19,7 +19,7 @@ type ID struct {
// Equals returns true if this ID equals to the other one. // Equals returns true if this ID equals to the other one.
func (id *ID) Equals(another *ID) bool { func (id *ID) Equals(another *ID) bool {
return id.uuid.Equals(&(another.uuid)) return id.uuid.Equals(&another.uuid)
} }
func (id *ID) Bytes() []byte { func (id *ID) Bytes() []byte {
+1 -1
View File
@@ -229,7 +229,7 @@ func TestSniffQUICComplex(t *testing.T) {
t.Errorf("SniffQUIC() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("SniffQUIC() error = %v, wantErr %v", err, tt.wantErr)
return 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) t.Errorf("SniffQUIC() error = %v, expectsNoClue %v", err, tt.needsMoreData)
return return
} }
+2 -2
View File
@@ -5,8 +5,8 @@ import (
) )
type ServerSpec struct { type ServerSpec struct {
Destination net.Destination Destination net.Destination
User *MemoryUser User *MemoryUser
} }
func NewServerSpec(dest net.Destination, user *MemoryUser) *ServerSpec { func NewServerSpec(dest net.Destination, user *MemoryUser) *ServerSpec {
-1
View File
@@ -228,7 +228,6 @@ func isValueKind(kind reflect.Kind) bool {
} }
func marshalInterface(v interface{}, ignoreNullValue bool, insertTypeInfo bool) interface{} { func marshalInterface(v interface{}, ignoreNullValue bool, insertTypeInfo bool) interface{} {
if r, ok := marshalKnownType(v, ignoreNullValue, insertTypeInfo); ok { if r, ok := marshalKnownType(v, ignoreNullValue, insertTypeInfo); ok {
return r return r
} }
+1 -3
View File
@@ -27,7 +27,6 @@ func TestMashalAccount(t *testing.T) {
j, ok := MarshalToJson(user, false) j, ok := MarshalToJson(user, false)
if !ok || strings.Contains(j, "_TypedMessage_") { if !ok || strings.Contains(j, "_TypedMessage_") {
t.Error("marshal account failed") t.Error("marshal account failed")
} }
@@ -79,13 +78,12 @@ func TestMashalStruct(t *testing.T) {
v := (*f2.Arr)[0]["foo"]["hello"] 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") t.Error("f1 not equal to f2")
} }
} }
func TestMarshalConfigJson(t *testing.T) { func TestMarshalConfigJson(t *testing.T) {
buf := bytes.NewBufferString(getConfig()) buf := bytes.NewBufferString(getConfig())
config, err := iserial.DecodeJSONConfig(buf) config, err := iserial.DecodeJSONConfig(buf)
if err != nil { if err != nil {
-29
View File
@@ -4,7 +4,6 @@ import (
"testing" "testing"
. "github.com/xtls/xray-core/common/serial" . "github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
) )
func TestGetInstance(t *testing.T) { func TestGetInstance(t *testing.T) {
@@ -23,31 +22,3 @@ func TestConvertingNilMessage(t *testing.T) {
t.Error("expect nil, but actually not") 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())
}
}
+5 -4
View File
@@ -3,6 +3,7 @@ package singbridge
import ( import (
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "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 // IsFqdn() implicitly checks if the domain name is valid
if socksaddr.IsFqdn() { if socksaddr.IsFqdn() {
return net.Destination{ return net.Destination{
Network: network, Network: network,
Address: net.DomainAddress(socksaddr.Fqdn), Address: net.DomainAddress(socksaddr.Fqdn),
Port: net.Port(socksaddr.Port), Port: net.Port(socksaddr.Port),
} }, nil
} }
// IsIP() implicitly checks if the IP address is valid // 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, Network: network,
Address: net.IPAddress(socksaddr.Addr.AsSlice()), Address: net.IPAddress(socksaddr.Addr.AsSlice()),
Port: net.Port(socksaddr.Port), 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 { func ToSocksaddr(destination net.Destination) M.Socksaddr {
+10 -2
View File
@@ -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) { 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) { 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) { 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) outbounds := session.OutboundsFromContext(ctx)
if len(outbounds) == 0 { if len(outbounds) == 0 {
outbounds = []*session.Outbound{{}} outbounds = []*session.Outbound{{}}
ctx = session.ContextWithOutbounds(ctx, outbounds) ctx = session.ContextWithOutbounds(ctx, outbounds)
} }
ob := outbounds[len(outbounds)-1] ob := outbounds[len(outbounds)-1]
ob.Target = ToDestination(destination, ToNetwork(network)) ob.Target = dest
opts := []pipe.Option{pipe.WithSizeLimit(64 * 1024)} opts := []pipe.Option{pipe.WithSizeLimit(64 * 1024)}
uplinkReader, uplinkWriter := pipe.New(opts...) uplinkReader, uplinkWriter := pipe.New(opts...)
+10 -2
View File
@@ -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 { 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) 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, Reader: xConn,
Writer: xConn, Writer: xConn,
}) })
} }
func (d *Dispatcher) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error { 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)), Reader: buf.NewPacketReader(conn.(io.Reader)),
Writer: buf.NewWriter(conn.(io.Writer)), Writer: buf.NewWriter(conn.(io.Writer)),
}) })
+31 -30
View File
@@ -10,15 +10,21 @@ import (
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
) )
func CopyPacketConn(ctx context.Context, inboundConn net.Conn, link *transport.Link, destination net.Destination, serverConn net.PacketConn) error { 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{ conn := &PacketConnWrapper{
Reader: link.Reader, Reader: link.Reader,
Writer: link.Writer, Writer: link.Writer,
Dest: destination, Dest: destination,
Conn: inboundConn, Conn: inboundConn,
T: signal.CancelAfterInactivity(ctx, cancel, 300*time.Second),
} }
return ReturnError(bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(serverConn))) return ReturnError(bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(serverConn)))
} }
@@ -29,11 +35,19 @@ type PacketConnWrapper struct {
net.Conn net.Conn
Dest net.Destination Dest net.Destination
cached buf.MultiBuffer 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() func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (addr M.Socksaddr, err error) {
// as a temporarily solution w.T.Update()
func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) { defer func() {
if err != nil {
// uplinkonly
w.T.SetTimeout(2 * time.Second)
}
}()
if w.cached != nil { if w.cached != nil {
mb, bb := buf.SplitFirst(w.cached) mb, bb := buf.SplitFirst(w.cached)
if bb == nil { if bb == nil {
@@ -51,30 +65,7 @@ func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) {
return ToSocksaddr(destination), nil return ToSocksaddr(destination), nil
} }
} }
mb, err := w.ReadMultiBuffer()
// 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
}
nb, bb := buf.SplitFirst(mb) nb, bb := buf.SplitFirst(mb)
if bb == nil { if bb == nil {
return M.Socksaddr{}, 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 := buf.New()
vBuf.Write(buffer.Bytes()) vBuf.Write(buffer.Bytes())
endpoint := ToDestination(destination, net.Network_UDP)
vBuf.UDP = &endpoint vBuf.UDP = &endpoint
return w.Writer.WriteMultiBuffer(buf.MultiBuffer{vBuf}) return w.WriteMultiBuffer(buf.MultiBuffer{vBuf})
} }
func (w *PacketConnWrapper) Close() error { func (w *PacketConnWrapper) Close() error {
+18 -18
View File
@@ -9,6 +9,7 @@ import (
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
) )
@@ -22,6 +23,11 @@ func CopyConn(ctx context.Context, inboundConn net.Conn, link *transport.Link, s
} else { } else {
conn.R = &buf.BufferedReader{Reader: link.Reader} 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)) return ReturnError(bufio.CopyConn(ctx, conn, serverConn))
} }
@@ -29,35 +35,27 @@ type PipeConnWrapper struct {
R io.Reader R io.Reader
W buf.Writer W buf.Writer
net.Conn 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 { func (w *PipeConnWrapper) Close() error {
return nil 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) { func (w *PipeConnWrapper) Read(b []byte) (n int, err error) {
type readResult struct { w.T.Update()
n int n, err = w.R.Read(b)
err error if err != nil {
} // uplinkonly
c := make(chan readResult, 1) w.T.SetTimeout(2 * time.Second)
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
} }
return
} }
func (w *PipeConnWrapper) Write(p []byte) (n int, err error) { func (w *PipeConnWrapper) Write(p []byte) (n int, err error) {
w.T.Update()
n = len(p) n = len(p)
var mb buf.MultiBuffer var mb buf.MultiBuffer
pLen := len(p) pLen := len(p)
@@ -76,6 +74,8 @@ func (w *PipeConnWrapper) Write(p []byte) (n int, err error) {
if err != nil { if err != nil {
n = 0 n = 0
buf.ReleaseMulti(mb) buf.ReleaseMulti(mb)
// downlinkonly
w.T.SetTimeout(5 * time.Second)
} }
return return
} }
+14 -7
View File
@@ -10,37 +10,44 @@ func TestByteSizes(t *testing.T) {
size := units.ByteSize(0) size := units.ByteSize(0)
assertSizeString(t, size, "0") assertSizeString(t, size, "0")
size++ size++
assertSizeValue(t, assertSizeValue(
t,
assertSizeString(t, size, "1.00B"), assertSizeString(t, size, "1.00B"),
size, size,
) )
size <<= 10 size <<= 10
assertSizeValue(t, assertSizeValue(
t,
assertSizeString(t, size, "1.00KB"), assertSizeString(t, size, "1.00KB"),
size, size,
) )
size <<= 10 size <<= 10
assertSizeValue(t, assertSizeValue(
t,
assertSizeString(t, size, "1.00MB"), assertSizeString(t, size, "1.00MB"),
size, size,
) )
size <<= 10 size <<= 10
assertSizeValue(t, assertSizeValue(
t,
assertSizeString(t, size, "1.00GB"), assertSizeString(t, size, "1.00GB"),
size, size,
) )
size <<= 10 size <<= 10
assertSizeValue(t, assertSizeValue(
t,
assertSizeString(t, size, "1.00TB"), assertSizeString(t, size, "1.00TB"),
size, size,
) )
size <<= 10 size <<= 10
assertSizeValue(t, assertSizeValue(
t,
assertSizeString(t, size, "1.00PB"), assertSizeString(t, size, "1.00PB"),
size, size,
) )
size <<= 10 size <<= 10
assertSizeValue(t, assertSizeValue(
t,
assertSizeString(t, size, "1.00EB"), assertSizeString(t, size, "1.00EB"),
size, size,
) )
+77 -43
View File
@@ -4,10 +4,10 @@ import (
"hash/fnv" "hash/fnv"
"math" "math"
"math/rand" "math/rand"
"strconv"
"time"
"net/http" "net/http"
"strconv"
"strings" "strings"
"time"
"github.com/klauspost/cpuid/v2" "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))) 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()))) return rand.New(rand.NewSource(int64(fnvHash.Sum64())))
} }
var globalRng *rand.Rand = GetRandomizer() var globalRng *rand.Rand = GetRandomizer()
// The Chrome version generator will suffer from deviation of a normal distribution. // The Chrome version generator will suffer from deviation of a normal distribution.
@@ -26,83 +27,114 @@ func ChromeVersion() int {
var startVersion int = 144 var startVersion int = 144
var timeStart int64 = time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC).Unix() / 86400 var timeStart int64 = time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC).Unix() / 86400
var timeCurrent int64 = time.Now().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. 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, 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. // The following version generators use deterministic generators, but with the distribution scaled by a curve.
func CurlVersion() string { func CurlVersion() string {
// curl 8.0.0 was released on 20/03/2023. // curl 8.0.0 was released on 20/03/2023.
var timeCurrent int64 = time.Now().Unix() / 86400 var timeCurrent int64 = time.Now().Unix() / 86400
var timeStart int64 = time.Date(2023, 3, 20, 0, 0, 0, 0, time.UTC).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. var minorValue int = int(timeDiff / 57) // The release cadence is actually 56.67 days.
return "8." + strconv.Itoa(minorValue) + ".0" return "8." + strconv.Itoa(minorValue) + ".0"
} }
func FirefoxVersion() int { func FirefoxVersion() int {
// Firefox 128 ESR was released on 09/07/2023. // Firefox 128 ESR was released on 09/07/2023.
var timeCurrent int64 = time.Now().Unix() / 86400 var timeCurrent int64 = time.Now().Unix() / 86400
var timeStart int64 = time.Date(2024, 7, 29, 0, 0, 0, 0, time.UTC).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)) timeDiff := timeCurrent - timeStart - 25 - int64(math.Floor(math.Pow(globalRng.Float64(), 2)*50))
return int(timeDiff / 30) + 128 return int(timeDiff/30) + 128
} }
func SafariVersion() string { func SafariVersion() string {
var anchoredTime time.Time = time.Now() var anchoredTime time.Time = time.Now()
var releaseYear int = anchoredTime.Year() var releaseYear int = anchoredTime.Year()
var splitPoint time.Time = time.Date(releaseYear, 9, 23, 0, 0, 0, 0, time.UTC) 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) splitPoint = splitPoint.AddDate(0, 0, delayedDays)
if (anchoredTime.Compare(splitPoint) < 0) { if anchoredTime.Compare(splitPoint) < 0 {
releaseYear -- releaseYear--
splitPoint = time.Date(releaseYear, 9, 23, 0, 0, 0, 0, time.UTC) splitPoint = time.Date(releaseYear, 9, 23, 0, 0, 0, 0, time.UTC)
splitPoint = splitPoint.AddDate(0, 0, delayedDays) splitPoint = splitPoint.AddDate(0, 0, delayedDays)
} }
var minorVersion = safariMinorMap[(anchoredTime.Unix() - splitPoint.Unix()) / 1296000] minorVersion := safariMinorMap[(anchoredTime.Unix()-splitPoint.Unix())/1296000]
return strconv.Itoa(releaseYear - 1999) + "." + strconv.Itoa(minorVersion) return strconv.Itoa(releaseYear-1999) + "." + strconv.Itoa(minorVersion)
} }
// The full Chromium brand GREASE implementation // The full Chromium brand GREASE implementation
var clientHintGreaseNA = []string{" ", "(", ":", "-", ".", "/", ")", ";", "=", "?", "_"} var (
var clientHintVersionNA = []string{"8", "99", "24"} clientHintGreaseNA = []string{" ", "(", ":", "-", ".", "/", ")", ";", "=", "?", "_"}
var clientHintShuffle3 = [][3]int{{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}} clientHintVersionNA = []string{"8", "99", "24"}
var clientHintShuffle4 = [][4]int{ clientHintShuffle3 = [][3]int{{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}}
{0, 1, 2, 3}, {0, 1, 3, 2}, {0, 2, 1, 3}, {0, 2, 3, 1}, {0, 3, 1, 2}, {0, 3, 2, 1}, clientHintShuffle4 = [][4]int{
{1, 0, 2, 3}, {1, 0, 3, 2}, {1, 2, 0, 3}, {1, 2, 3, 0}, {1, 3, 0, 2}, {1, 3, 2, 0}, {0, 1, 2, 3},
{2, 0, 1, 3}, {2, 0, 3, 1}, {2, 1, 0, 3}, {2, 1, 3, 0}, {2, 3, 0, 1}, {2, 3, 1, 0}, {0, 1, 3, 2},
{3, 0, 1, 2}, {3, 0, 2, 1}, {3, 1, 0, 2}, {3, 1, 2, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}} {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 { 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 { func getGreasedChOrder(brandLength int, seed int) []int {
switch brandLength { switch brandLength {
case 1: case 1:
return []int{0} return []int{0}
case 2: case 2:
return []int{seed % brandLength, (seed + 1) % brandLength} return []int{seed % brandLength, (seed + 1) % brandLength}
case 3: case 3:
return clientHintShuffle3[seed % len(clientHintShuffle3)][:] return clientHintShuffle3[seed%len(clientHintShuffle3)][:]
default: default:
return clientHintShuffle4[seed % len(clientHintShuffle4)][:] return clientHintShuffle4[seed%len(clientHintShuffle4)][:]
} }
//return []int{} //return []int{}
} }
func getUngreasedChUa(majorVersion int, forkName string) []string { func getUngreasedChUa(majorVersion int, forkName string) []string {
// Set the capacity to 4, the maximum allowed brand size, so Go will never allocate memory twice // Set the capacity to 4, the maximum allowed brand size, so Go will never allocate memory twice
baseChUa := make([]string, 0, 4) baseChUa := make([]string, 0, 4)
baseChUa = append(baseChUa, getGreasedChInvalidBrand(majorVersion), baseChUa = append(baseChUa, getGreasedChInvalidBrand(majorVersion),
"\"Chromium\";v=\"" + strconv.Itoa(majorVersion) + "\"") "\"Chromium\";v=\""+strconv.Itoa(majorVersion)+"\"")
switch forkName { switch forkName {
case "chrome": case "chrome":
baseChUa = append(baseChUa, "\"Google Chrome\";v=\"" + strconv.Itoa(majorVersion) + "\"") baseChUa = append(baseChUa, "\"Google Chrome\";v=\""+strconv.Itoa(majorVersion)+"\"")
case "edge": case "edge":
baseChUa = append(baseChUa, "\"Microsoft Edge\";v=\"" + strconv.Itoa(majorVersion) + "\"") baseChUa = append(baseChUa, "\"Microsoft Edge\";v=\""+strconv.Itoa(majorVersion)+"\"")
} }
return baseChUa return baseChUa
} }
func getGreasedChUa(majorVersion int, forkName string) string { func getGreasedChUa(majorVersion int, forkName string) string {
ungreasedCh := getUngreasedChUa(majorVersion, forkName) ungreasedCh := getUngreasedChUa(majorVersion, forkName)
shuffleMap := getGreasedChOrder(len(ungreasedCh), majorVersion) 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. // The code below provides a coherent default browser user agent string based on a CPU-seeded PRNG.
var CurlUA = "curl/" + CurlVersion() var (
var AnchoredFirefoxVersion = strconv.Itoa(FirefoxVersion()) CurlUA = "curl/" + CurlVersion()
var FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:" + AnchoredFirefoxVersion + ".0) Gecko/20100101 Firefox/" + AnchoredFirefoxVersion + ".0" AnchoredFirefoxVersion = strconv.Itoa(FirefoxVersion())
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" FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:" + AnchoredFirefoxVersion + ".0) Gecko/20100101 Firefox/" + AnchoredFirefoxVersion + ".0"
// Chromium browsers. 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"
var AnchoredChromeVersion = ChromeVersion() // Chromium browsers.
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" AnchoredChromeVersion = ChromeVersion()
var ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome") 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 MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0" ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome")
var MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge") MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0"
MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge")
)
func applyMasqueradedHeaders(header http.Header, browser string, variant string) { func applyMasqueradedHeaders(header http.Header, browser string, variant string) {
// Browser-specific. // Browser-specific.
+1 -1
View File
@@ -109,4 +109,4 @@ func (m *TypedSyncMap[K, V]) Swap(key K, value V) (previous V, loaded bool) {
previous = anyPrevious.(V) previous = anyPrevious.(V)
} }
return previous, loaded return previous, loaded
} }
+54
View File
@@ -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
}
+45
View File
@@ -0,0 +1,45 @@
package utils
import (
"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{}{})
}
+2 -2
View File
@@ -19,8 +19,8 @@ import (
var ( var (
Version_x byte = 26 Version_x byte = 26
Version_y byte = 5 Version_y byte = 6
Version_z byte = 9 Version_z byte = 1
) )
var ( var (
-1
View File
@@ -34,7 +34,6 @@ func (ctx *Context) GetSourceIPs() []net.IP {
} }
return nil return nil
} }
// GetSourcePort implements routing.Context. // GetSourcePort implements routing.Context.
+12 -7
View File
@@ -12,6 +12,7 @@ require (
github.com/klauspost/cpuid/v2 v2.3.0 github.com/klauspost/cpuid/v2 v2.3.0
github.com/miekg/dns v1.1.72 github.com/miekg/dns v1.1.72
github.com/pelletier/go-toml v1.9.5 github.com/pelletier/go-toml v1.9.5
github.com/pion/stun/v3 v3.1.2
github.com/pires/go-proxyproto v0.12.0 github.com/pires/go-proxyproto v0.12.0
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
@@ -21,15 +22,15 @@ require (
github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netlink v1.3.1
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.50.0 golang.org/x/crypto v0.51.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.53.0 golang.org/x/net v0.55.0
golang.org/x/sync v0.20.0 golang.org/x/sync v0.20.0
golang.org/x/sys v0.43.0 golang.org/x/sys v0.45.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/windows v1.0.1 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 google.golang.org/protobuf v1.36.11
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
h12.io/socks v1.0.3 h12.io/socks v1.0.3
@@ -43,13 +44,17 @@ require (
github.com/juju/ratelimit v1.0.2 // indirect github.com/juju/ratelimit v1.0.2 // indirect
github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/compress v1.17.4 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/pion/dtls/v3 v3.1.2 // indirect
github.com/pion/logging v0.2.4 // indirect
github.com/pion/transport/v4 v4.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect
github.com/vishvananda/netns v0.0.5 // indirect github.com/vishvananda/netns v0.0.5 // indirect
golang.org/x/mod v0.34.0 // indirect github.com/wlynxg/anet v0.0.5 // indirect
golang.org/x/text v0.36.0 // indirect golang.org/x/mod v0.35.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.12.0 // indirect golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.43.0 // indirect golang.org/x/tools v0.44.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
+24 -14
View File
@@ -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/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 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc=
github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo=
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.2 h1:86IhD8wFn6IDW4b1/0QzoQS+f5PeA8OHHRn8UZW5ErY=
github.com/pion/stun/v3 v3.1.2/go.mod h1:H7gDic7nNwlUL05pbs6T1dtaBehh/KjupxfWw3ZI7cA=
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM= 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/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/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 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= 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 h1:iy2JRioxmUpoJ3SzbFPyTxHZMbR/rSHP7dOOgYaq1O8=
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI= 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= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
@@ -88,18 +98,18 @@ 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= 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-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.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= 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/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.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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-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.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.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 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
@@ -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.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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.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.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.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.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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= 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/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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.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.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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= 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 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= 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.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= 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 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 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= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+2 -1
View File
@@ -48,4 +48,5 @@ var configLoader = NewJSONConfigLoader(
"http": func() interface{} { return new(HTTPResponse) }, "http": func() interface{} { return new(HTTPResponse) },
}, },
"type", "type",
"") "",
)
+2 -1
View File
@@ -3,6 +3,7 @@ package conf
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"math"
"strconv" "strconv"
"strings" "strings"
@@ -199,7 +200,7 @@ func (v *PortRange) UnmarshalJSON(data []byte) error {
if err == nil { if err == nil {
v.From = uint32(from) v.From = uint32(from)
v.To = uint32(to) 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 errors.New("invalid port range ", v.From, " -> ", v.To)
} }
return nil return nil
+22 -20
View File
@@ -12,8 +12,9 @@ import (
type DNSOutboundRuleConfig struct { type DNSOutboundRuleConfig struct {
Action string `json:"action"` Action string `json:"action"`
QType *PortList `json:"qtype"` QType *PortList `json:"qType"`
Domain *StringList `json:"domain"` Domain *StringList `json:"domain"`
RCode uint32 `json:"rCode"`
} }
func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) { func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
@@ -24,8 +25,8 @@ func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
rule.Action = dns.RuleAction_Direct rule.Action = dns.RuleAction_Direct
case "drop": case "drop":
rule.Action = dns.RuleAction_Drop rule.Action = dns.RuleAction_Drop
case "reject": case "return":
rule.Action = dns.RuleAction_Reject rule.Action = dns.RuleAction_Return
case "hijack": case "hijack":
rule.Action = dns.RuleAction_Hijack rule.Action = dns.RuleAction_Hijack
default: default:
@@ -34,14 +35,8 @@ func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
if c.QType != nil { if c.QType != nil {
for _, r := range c.QType.Range { for _, r := range c.QType.Range {
if r.From > r.To { for qType := r.From; qType <= r.To; qType++ {
return nil, errors.New("invalid qtype range: ", r.String()) rule.QType = append(rule.QType, int32(qType))
}
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))
} }
} }
} }
@@ -54,6 +49,11 @@ func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
rule.Domain = rules rule.Domain = rules
} }
if c.RCode > 65535 {
return nil, errors.New("rCode out of range: ", c.RCode)
}
rule.RCode = c.RCode
return rule, nil return rule, nil
} }
@@ -133,28 +133,30 @@ func (c *DNSOutboundConfig) buildLegacyDNSPolicy() ([]*dns.DNSRuleConfig, error)
if c.BlockTypes != nil && len(*c.BlockTypes) > 0 { if c.BlockTypes != nil && len(*c.BlockTypes) > 0 {
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Drop} rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Drop}
if mode == "reject" { if mode == "reject" {
rule.Action = dns.RuleAction_Reject rule.Action = dns.RuleAction_Return
rule.RCode = 5
} }
for _, qtype := range *c.BlockTypes { for _, qType := range *c.BlockTypes {
if qtype < 0 || qtype > 65535 { if qType < 0 || qType > 65535 {
return nil, errors.New("legacy blockTypes qtype out of range: ", qtype) 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) rules = append(rules, rule)
} }
{ {
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Hijack} rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Hijack}
rule.Qtype = append(rule.Qtype, 1) rule.QType = append(rule.QType, 1)
rule.Qtype = append(rule.Qtype, 28) rule.QType = append(rule.QType, 28)
rules = append(rules, rule) rules = append(rules, rule)
} }
{ {
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Reject} rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Return}
if mode == "reject" { if mode == "reject" {
rule.Action = dns.RuleAction_Reject rule.Action = dns.RuleAction_Return
rule.RCode = 5
} else if mode == "drop" { } else if mode == "drop" {
rule.Action = dns.RuleAction_Drop rule.Action = dns.RuleAction_Drop
} else if mode == "skip" { } else if mode == "skip" {
+24 -19
View File
@@ -35,10 +35,10 @@ func TestDnsProxyConfig(t *testing.T) {
Input: `{ Input: `{
"rules": [{ "rules": [{
"action": "direct", "action": "direct",
"qtype": "1,3,23-24" "qType": "1,3,23-24"
}, { }, {
"action": "drop", "action": "drop",
"qtype": 28, "qType": 28,
"domain": ["domain:example.com", "full:example.com"] "domain": ["domain:example.com", "full:example.com"]
}] }]
}`, }`,
@@ -48,11 +48,11 @@ func TestDnsProxyConfig(t *testing.T) {
Rule: []*dns.DNSRuleConfig{ Rule: []*dns.DNSRuleConfig{
{ {
Action: dns.RuleAction_Direct, Action: dns.RuleAction_Direct,
Qtype: []int32{1, 3, 23, 24}, QType: []int32{1, 3, 23, 24},
}, },
{ {
Action: dns.RuleAction_Drop, Action: dns.RuleAction_Drop,
Qtype: []int32{28}, QType: []int32{28},
Domain: []*geodata.DomainRule{ Domain: []*geodata.DomainRule{
{ {
Value: &geodata.DomainRule_Custom{ Value: &geodata.DomainRule_Custom{
@@ -78,7 +78,8 @@ func TestDnsProxyConfig(t *testing.T) {
{ {
Input: `{ Input: `{
"rules": [{ "rules": [{
"action": "reject", "action": "return",
"rCode": 5,
"domain": "keyword:example" "domain": "keyword:example"
}] }]
}`, }`,
@@ -87,7 +88,8 @@ func TestDnsProxyConfig(t *testing.T) {
RewriteServer: &net.Endpoint{}, RewriteServer: &net.Endpoint{},
Rule: []*dns.DNSRuleConfig{ Rule: []*dns.DNSRuleConfig{
{ {
Action: dns.RuleAction_Reject, Action: dns.RuleAction_Return,
RCode: 5,
Domain: []*geodata.DomainRule{ Domain: []*geodata.DomainRule{
{ {
Value: &geodata.DomainRule_Custom{ Value: &geodata.DomainRule_Custom{
@@ -106,7 +108,7 @@ func TestDnsProxyConfig(t *testing.T) {
Input: `{ Input: `{
"rules": [{ "rules": [{
"action": "drop", "action": "drop",
"qtype": 257 "qType": 257
}] }]
}`, }`,
Parser: loadJSON(creator), Parser: loadJSON(creator),
@@ -115,7 +117,7 @@ func TestDnsProxyConfig(t *testing.T) {
Rule: []*dns.DNSRuleConfig{ Rule: []*dns.DNSRuleConfig{
{ {
Action: dns.RuleAction_Drop, Action: dns.RuleAction_Drop,
Qtype: []int32{257}, QType: []int32{257},
}, },
}, },
}, },
@@ -140,10 +142,11 @@ func TestDnsProxyConfigLegacyCompatibility(t *testing.T) {
Rule: []*dns.DNSRuleConfig{ Rule: []*dns.DNSRuleConfig{
{ {
Action: dns.RuleAction_Hijack, 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{}, RewriteServer: &net.Endpoint{},
Rule: []*dns.DNSRuleConfig{ Rule: []*dns.DNSRuleConfig{
{ {
Action: dns.RuleAction_Reject, Action: dns.RuleAction_Return,
Qtype: []int32{1, 65}, QType: []int32{1, 65},
RCode: 5,
}, },
{ {
Action: dns.RuleAction_Hijack, 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{ Rule: []*dns.DNSRuleConfig{
{ {
Action: dns.RuleAction_Drop, Action: dns.RuleAction_Drop,
Qtype: []int32{1}, QType: []int32{1},
}, },
{ {
Action: dns.RuleAction_Hijack, Action: dns.RuleAction_Hijack,
Qtype: []int32{1, 28}, QType: []int32{1, 28},
}, },
{ {
Action: dns.RuleAction_Drop, Action: dns.RuleAction_Drop,
@@ -204,11 +209,11 @@ func TestDnsProxyConfigLegacyCompatibility(t *testing.T) {
Rule: []*dns.DNSRuleConfig{ Rule: []*dns.DNSRuleConfig{
{ {
Action: dns.RuleAction_Drop, Action: dns.RuleAction_Drop,
Qtype: []int32{65, 28}, QType: []int32{65, 28},
}, },
{ {
Action: dns.RuleAction_Hijack, Action: dns.RuleAction_Hijack,
Qtype: []int32{1, 28}, QType: []int32{1, 28},
}, },
{ {
Action: dns.RuleAction_Direct, Action: dns.RuleAction_Direct,
@@ -228,7 +233,7 @@ func TestDnsProxyConfigRejectsMixedLegacyAndNewFields(t *testing.T) {
_, err := loadJSON(creator)(`{ _, err := loadJSON(creator)(`{
"rules": [{ "rules": [{
"action": "direct", "action": "direct",
"qtype": 65 "qType": 65
}], }],
"blockTypes": [65] "blockTypes": [65]
}`) }`)
+9 -11
View File
@@ -1,9 +1,10 @@
package conf package conf
import ( import (
"google.golang.org/protobuf/proto"
"strings" "strings"
"google.golang.org/protobuf/proto"
"github.com/xtls/xray-core/app/observatory/burst" "github.com/xtls/xray-core/app/observatory/burst"
"github.com/xtls/xray-core/app/router" "github.com/xtls/xray-core/app/router"
"github.com/xtls/xray-core/infra/conf/cfgcommon/duration" "github.com/xtls/xray-core/infra/conf/cfgcommon/duration"
@@ -16,17 +17,14 @@ const (
strategyLeastLoad string = "leastload" strategyLeastLoad string = "leastload"
) )
var ( var strategyConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{
strategyConfigLoader = NewJSONConfigLoader(ConfigCreatorCache{ strategyRandom: func() interface{} { return new(strategyEmptyConfig) },
strategyRandom: func() interface{} { return new(strategyEmptyConfig) }, strategyLeastPing: func() interface{} { return new(strategyEmptyConfig) },
strategyLeastPing: func() interface{} { return new(strategyEmptyConfig) }, strategyRoundRobin: func() interface{} { return new(strategyEmptyConfig) },
strategyRoundRobin: func() interface{} { return new(strategyEmptyConfig) }, strategyLeastLoad: func() interface{} { return new(strategyLeastLoadConfig) },
strategyLeastLoad: func() interface{} { return new(strategyLeastLoadConfig) }, }, "type", "settings")
}, "type", "settings")
)
type strategyEmptyConfig struct { type strategyEmptyConfig struct{}
}
func (v *strategyEmptyConfig) Build() (proto.Message, error) { func (v *strategyEmptyConfig) Build() (proto.Message, error) {
return nil, nil return nil, nil
+9 -10
View File
@@ -78,18 +78,17 @@ func DecodeJSONConfig(reader io.Reader) (*conf.Config, error) {
// byte-by-byte comment stripper and TeeReader, which are significant overhead on // byte-by-byte comment stripper and TeeReader, which are significant overhead on
// large configs. // large configs.
func DecodeJSONConfigStrict(reader io.Reader) (*conf.Config, error) { func DecodeJSONConfigStrict(reader io.Reader) (*conf.Config, error) {
data, err := io.ReadAll(reader) data, err := io.ReadAll(reader)
if err != nil { if err != nil {
return nil, errors.New("failed to read config file").Base(err) return nil, errors.New("failed to read config file").Base(err)
} }
jsonConfig := &conf.Config{} jsonConfig := &conf.Config{}
if err := json.Unmarshal(data, jsonConfig); err != nil { if err := json.Unmarshal(data, jsonConfig); err != nil {
return nil, errors.New("failed to parse remote JSON config").Base(err) return nil, errors.New("failed to parse remote JSON config").Base(err)
} }
return jsonConfig, nil return jsonConfig, nil
} }
func LoadJSONConfig(reader io.Reader) (*core.Config, error) { func LoadJSONConfig(reader io.Reader) (*core.Config, error) {
jsonConfig, err := DecodeJSONConfig(reader) jsonConfig, err := DecodeJSONConfig(reader)
if err != nil { if err != nil {
+8 -8
View File
@@ -45,31 +45,31 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
Value: []string{utils.ChromeUA}, Value: []string{utils.ChromeUA},
}, },
{ {
Name: "Sec-CH-UA", Name: "Sec-CH-UA",
Value: []string{utils.ChromeUACH}, Value: []string{utils.ChromeUACH},
}, },
{ {
Name: "Sec-CH-UA-Mobile", Name: "Sec-CH-UA-Mobile",
Value: []string{"?0"}, Value: []string{"?0"},
}, },
{ {
Name: "Sec-CH-UA-Platform", Name: "Sec-CH-UA-Platform",
Value: []string{"Windows"}, Value: []string{"Windows"},
}, },
{ {
Name: "Sec-Fetch-Mode", Name: "Sec-Fetch-Mode",
Value: []string{"no-cors", "cors", "same-origin"}, Value: []string{"no-cors", "cors", "same-origin"},
}, },
{ {
Name: "Sec-Fetch-Dest", Name: "Sec-Fetch-Dest",
Value: []string{"empty"}, Value: []string{"empty"},
}, },
{ {
Name: "Sec-Fetch-Site", Name: "Sec-Fetch-Site",
Value: []string{"none"}, Value: []string{"none"},
}, },
{ {
Name: "Sec-Fetch-User", Name: "Sec-Fetch-User",
Value: []string{"?1"}, Value: []string{"?1"},
}, },
{ {
@@ -96,7 +96,7 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
} }
if len(v.Path) > 0 { if len(v.Path) > 0 {
config.Uri = append([]string(nil), (v.Path)...) config.Uri = append([]string(nil), v.Path...)
} }
if len(v.Headers) > 0 { if len(v.Headers) > 0 {
+212 -131
View File
@@ -6,6 +6,8 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"math" "math"
"math/big"
"net/netip"
"net/url" "net/url"
"os" "os"
"regexp" "regexp"
@@ -13,7 +15,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"time"
"github.com/xtls/xray-core/common" "github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors" "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"
"github.com/xtls/xray-core/transport/internet/finalmask/fragment" "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/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/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/mkcp/original"
"github.com/xtls/xray-core/transport/internet/finalmask/noise" "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" "github.com/xtls/xray-core/transport/internet/finalmask/salamander"
finalsudoku "github.com/xtls/xray-core/transport/internet/finalmask/sudoku" finalsudoku "github.com/xtls/xray-core/transport/internet/finalmask/sudoku"
"github.com/xtls/xray-core/transport/internet/finalmask/xdns" "github.com/xtls/xray-core/transport/internet/finalmask/xdns"
@@ -48,12 +45,10 @@ import (
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
var ( var tcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{
tcpHeaderLoader = NewJSONConfigLoader(ConfigCreatorCache{ "none": func() interface{} { return new(NoOpConnectionAuthenticator) },
"none": func() interface{} { return new(NoOpConnectionAuthenticator) }, "http": func() interface{} { return new(Authenticator) },
"http": func() interface{} { return new(Authenticator) }, }, "type", "")
}, "type", "")
)
type KCPConfig struct { type KCPConfig struct {
Mtu *uint32 `json:"mtu"` Mtu *uint32 `json:"mtu"`
@@ -225,8 +220,10 @@ type SplitHTTPConfig struct {
XPaddingPlacement string `json:"xPaddingPlacement"` XPaddingPlacement string `json:"xPaddingPlacement"`
XPaddingMethod string `json:"xPaddingMethod"` XPaddingMethod string `json:"xPaddingMethod"`
UplinkHTTPMethod string `json:"uplinkHTTPMethod"` UplinkHTTPMethod string `json:"uplinkHTTPMethod"`
SessionPlacement string `json:"sessionPlacement"` SessionIDPlacement string `json:"sessionIDPlacement"`
SessionKey string `json:"sessionKey"` SessionIDKey string `json:"sessionIDKey"`
SessionIDTable string `json:"sessionIDTable"`
SessionIDLength Int32Range `json:"sessionIDLength"`
SeqPlacement string `json:"seqPlacement"` SeqPlacement string `json:"seqPlacement"`
SeqKey string `json:"seqKey"` SeqKey string `json:"seqKey"`
UplinkDataPlacement string `json:"uplinkDataPlacement"` 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") return nil, errors.New("uplinkHTTPMethod can be GET only in packet-up mode")
} }
switch c.SessionPlacement { switch c.SessionIDPlacement {
case "": case "":
c.SessionPlacement = "path" c.SessionIDPlacement = "path"
case "path", "cookie", "header", "query": case "path", "cookie", "header", "query":
default: default:
return nil, errors.New("unsupported session placement: " + c.SessionPlacement) return nil, errors.New("unsupported session placement: " + c.SessionIDPlacement)
} }
switch c.SeqPlacement { switch c.SeqPlacement {
@@ -353,12 +350,31 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
return nil, errors.New("unsupported seq placement: " + c.SeqPlacement) return nil, errors.New("unsupported seq placement: " + c.SeqPlacement)
} }
if c.SessionPlacement != "path" && c.SessionKey == "" { if c.SessionIDPlacement != "path" && c.SessionIDKey == "" {
switch c.SessionPlacement { switch c.SessionIDPlacement {
case "cookie", "query": case "cookie", "query":
c.SessionKey = "x_session" c.SessionIDKey = "x_session"
case "header": 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, XPaddingPlacement: c.XPaddingPlacement,
XPaddingMethod: c.XPaddingMethod, XPaddingMethod: c.XPaddingMethod,
UplinkHTTPMethod: c.UplinkHTTPMethod, UplinkHTTPMethod: c.UplinkHTTPMethod,
SessionPlacement: c.SessionPlacement, SessionIDPlacement: c.SessionIDPlacement,
SeqPlacement: c.SeqPlacement, SeqPlacement: c.SeqPlacement,
SessionKey: c.SessionKey, SessionIDKey: c.SessionIDKey,
SeqKey: c.SeqKey, SeqKey: c.SeqKey,
UplinkDataPlacement: c.UplinkDataPlacement, UplinkDataPlacement: c.UplinkDataPlacement,
UplinkDataKey: c.UplinkDataKey, UplinkDataKey: c.UplinkDataKey,
@@ -422,6 +438,8 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
ScMaxBufferedPosts: c.ScMaxBufferedPosts, ScMaxBufferedPosts: c.ScMaxBufferedPosts,
ScStreamUpServerSecs: newRangeConfig(c.ScStreamUpServerSecs), ScStreamUpServerSecs: newRangeConfig(c.ScStreamUpServerSecs),
ServerMaxHeaderBytes: c.ServerMaxHeaderBytes, ServerMaxHeaderBytes: c.ServerMaxHeaderBytes,
SessionIDTable: c.SessionIDTable,
SessionIDLength: newRangeConfig(c.SessionIDLength),
Xmux: &splithttp.XmuxConfig{ Xmux: &splithttp.XmuxConfig{
MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency), MaxConcurrency: newRangeConfig(c.Xmux.MaxConcurrency),
MaxConnections: newRangeConfig(c.Xmux.MaxConnections), MaxConnections: newRangeConfig(c.Xmux.MaxConnections),
@@ -445,6 +463,17 @@ func (c *SplitHTTPConfig) Build() (proto.Message, error) {
return config, nil 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 ( const (
Byte = 1 Byte = 1
Kilobyte = 1024 * Byte Kilobyte = 1024 * Byte
@@ -656,10 +685,8 @@ type TLSConfig struct {
MasterKeyLog string `json:"masterKeyLog"` MasterKeyLog string `json:"masterKeyLog"`
PinnedPeerCertSha256 string `json:"pinnedPeerCertSha256"` PinnedPeerCertSha256 string `json:"pinnedPeerCertSha256"`
VerifyPeerCertByName string `json:"verifyPeerCertByName"` VerifyPeerCertByName string `json:"verifyPeerCertByName"`
VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"`
ECHServerKeys string `json:"echServerKeys"` ECHServerKeys string `json:"echServerKeys"`
ECHConfigList string `json:"echConfigList"` ECHConfigList string `json:"echConfigList"`
ECHForceQuery string `json:"echForceQuery"`
ECHSocketSettings *SocketConfig `json:"echSockopt"` ECHSocketSettings *SocketConfig `json:"echSockopt"`
} }
@@ -704,12 +731,7 @@ func (c *TLSConfig) Build() (proto.Message, error) {
config.MasterKeyLog = c.MasterKeyLog config.MasterKeyLog = c.MasterKeyLog
if c.AllowInsecure { if c.AllowInsecure {
if time.Now().After(time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)) { return nil, errors.PrintRemovedFeatureError(`"allowInsecure"`, `"pinnedPeerCertSha256"(pcs) and "verifyPeerCertByName"(vcn)`)
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
}
} }
if c.PinnedPeerCertSha256 != "" { if c.PinnedPeerCertSha256 != "" {
for v := range strings.SplitSeq(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) config.PinnedPeerCertSha256 = append(config.PinnedPeerCertSha256, hashValue)
} }
} }
if c.VerifyPeerCertInNames != nil {
return nil, errors.PrintRemovedFeatureError(`"verifyPeerCertInNames"`, `"verifyPeerCertByName"`)
}
if c.VerifyPeerCertByName != "" { if c.VerifyPeerCertByName != "" {
for v := range strings.SplitSeq(c.VerifyPeerCertByName, ",") { for v := range strings.SplitSeq(c.VerifyPeerCertByName, ",") {
v = strings.TrimSpace(v) v = strings.TrimSpace(v)
@@ -749,13 +767,6 @@ func (c *TLSConfig) Build() (proto.Message, error) {
} }
config.EchServerKeys = EchPrivateKey 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 config.EchConfigList = c.ECHConfigList
if c.ECHSocketSettings != nil { if c.ECHSocketSettings != nil {
ss, err := c.ECHSocketSettings.Build() ss, err := c.ECHSocketSettings.Build()
@@ -1037,7 +1048,7 @@ type HappyEyeballsConfig struct {
} }
func (h *HappyEyeballsConfig) UnmarshalJSON(data []byte) error { func (h *HappyEyeballsConfig) UnmarshalJSON(data []byte) error {
var innerHappyEyeballsConfig = struct { innerHappyEyeballsConfig := struct {
PrioritizeIPv6 bool `json:"prioritizeIPv6"` PrioritizeIPv6 bool `json:"prioritizeIPv6"`
TryDelayMs uint64 `json:"tryDelayMs"` TryDelayMs uint64 `json:"tryDelayMs"`
Interleave uint32 `json:"interleave"` 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) 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 { if c.HappyEyeballsSettings != nil {
happyEyeballs.PrioritizeIpv6 = c.HappyEyeballsSettings.PrioritizeIPv6 happyEyeballs.PrioritizeIpv6 = c.HappyEyeballsSettings.PrioritizeIPv6
happyEyeballs.Interleave = c.HappyEyeballsSettings.Interleave happyEyeballs.Interleave = c.HappyEyeballsSettings.Interleave
@@ -1241,20 +1252,14 @@ var (
}, "type", "settings") }, "type", "settings")
udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{ udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
"header-custom": func() interface{} { return new(HeaderCustomUDP) }, "header-custom": func() interface{} { return new(HeaderCustomUDP) },
"header-dns": func() interface{} { return new(Dns) }, "mkcp-legacy": func() interface{} { return new(MkcpLegacy) },
"header-dtls": func() interface{} { return new(Dtls) }, "noise": func() interface{} { return new(NoiseMask) },
"header-srtp": func() interface{} { return new(Srtp) }, "salamander": func() interface{} { return new(Salamander) },
"header-utp": func() interface{} { return new(Utp) }, "sudoku": func() interface{} { return new(Sudoku) },
"header-wechat": func() interface{} { return new(Wechat) }, "xdns": func() interface{} { return new(Xdns) },
"header-wireguard": func() interface{} { return new(Wireguard) }, "xicmp": func() interface{} { return new(Xicmp) },
"mkcp-original": func() interface{} { return new(Original) }, "realm": func() interface{} { return new(Realm) },
"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) },
}, "type", "settings") }, "type", "settings")
) )
@@ -1734,82 +1739,70 @@ func (c *HeaderCustomUDP) Build() (proto.Message, error) {
}) })
} }
return &custom.UDPConfig{ if c.Mode == "standalone" {
Client: client, return &custom.UDPStandaloneConfig{
Server: server, Client: client,
Mode: c.Mode, Server: server,
}, nil }, nil
} } else {
return &custom.UDPConfig{
type Dns struct { Client: client,
Domain string `json:"domain"` Server: server,
} }, nil
func (c *Dns) Build() (proto.Message, error) {
config := &dns.Config{}
config.Domain = "www.baidu.com"
if len(c.Domain) > 0 {
config.Domain = c.Domain
} }
return config, nil
} }
type Dtls struct{} type MkcpLegacy struct {
Header string `json:"header"`
func (c *Dtls) Build() (proto.Message, error) { Value string `json:"value"`
return &dtls.Config{}, nil
} }
type Srtp struct{} func (c *MkcpLegacy) Build() (proto.Message, error) {
if len(c.Header) == 0 {
func (c *Srtp) Build() (proto.Message, error) { if len(c.Value) == 0 {
return &srtp.Config{}, nil return &original.Config{}, nil
} } else {
return &aes128gcm.Config{Password: c.Value}, nil
type Utp struct{} }
}
func (c *Utp) Build() (proto.Message, error) { switch strings.ToLower(c.Header) {
return &utp.Config{}, nil case "dns":
} domain := c.Value
if len(domain) == 0 {
type Wechat struct{} domain = "www.baidu.com"
}
func (c *Wechat) Build() (proto.Message, error) { return &header.Config{ID: 0, Domain: domain}, nil
return &wechat.Config{}, nil case "dtls":
} return &header.Config{ID: 1}, nil
case "srtp":
type Wireguard struct{} return &header.Config{ID: 2}, nil
case "utp":
func (c *Wireguard) Build() (proto.Message, error) { return &header.Config{ID: 3}, nil
return &wireguard.Config{}, nil case "wechat":
} return &header.Config{ID: 4}, nil
case "wireguard":
type Original struct{} return &header.Config{ID: 5}, nil
default:
func (c *Original) Build() (proto.Message, error) { return nil, errors.New("invalid header ", c.Header)
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
} }
type Salamander struct { type Salamander struct {
Password string `json:"password"` Password string `json:"password"`
PacketSize *Int32Range `json:"packetSize"`
} }
func (c *Salamander) Build() (proto.Message, error) { func (c *Salamander) Build() (proto.Message, error) {
config := &salamander.Config{} if c.PacketSize != nil {
config.Password = c.Password return &salamander.GeckoConfig{
return config, nil Password: c.Password,
MinPacketSize: c.PacketSize.From,
MaxPacketSize: c.PacketSize.To,
}, nil
}
return &salamander.Config{
Password: c.Password,
}, nil
} }
type Sudoku struct { type Sudoku struct {
@@ -1885,23 +1878,111 @@ func (c *Xdns) Build() (proto.Message, error) {
} }
type Xicmp struct { type Xicmp struct {
ListenIp string `json:"listenIp"` DGRAM bool `json:"dgram"`
Id uint16 `json:"id"` IPs []string `json:"ips"`
} }
func (c *Xicmp) Build() (proto.Message, error) { func (c *Xicmp) Build() (proto.Message, error) {
config := &xicmp.Config{ for _, ip := range c.IPs {
Ip: c.ListenIp, if _, err := netip.ParseAddr(ip); err != nil {
Id: int32(c.Id), return nil, err
}
} }
if config.Ip == "" { config := &xicmp.Config{
config.Ip = "0.0.0.0" DGRAM: c.DGRAM,
IPs: c.IPs,
} }
return config, nil 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 Mask struct {
Type string `json:"type"` Type string `json:"type"`
Settings *json.RawMessage `json:"settings"` Settings *json.RawMessage `json:"settings"`
+1 -2
View File
@@ -195,8 +195,7 @@ func TestHeaderCustomUDPBuild(t *testing.T) {
"mode": "standalone" "mode": "standalone"
}`, }`,
Parser: parser, Parser: parser,
Output: &finalmaskcustom.UDPConfig{ Output: &finalmaskcustom.UDPStandaloneConfig{
Mode: "standalone",
Client: []*finalmaskcustom.UDPItem{ Client: []*finalmaskcustom.UDPItem{
{ {
RandMax: 255, RandMax: 255,
+2 -1
View File
@@ -1,9 +1,10 @@
package conf package conf
import ( import (
"strconv"
"github.com/xtls/xray-core/app/version" "github.com/xtls/xray-core/app/version"
"github.com/xtls/xray-core/core" "github.com/xtls/xray-core/core"
"strconv"
) )
type VersionConfig struct { type VersionConfig struct {
+22 -2
View File
@@ -173,6 +173,27 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
return nil, err return nil, err
} }
receiverSettings.StreamSettings = ss receiverSettings.StreamSettings = ss
// TODO: Actually implement this breaking change
protocol := ss.GetEffectiveProtocol()
if (protocol == "websocket" || protocol == "httpupgrade" || protocol == "splithttp") &&
(c.StreamSetting.SocketSettings == nil || len(c.StreamSetting.SocketSettings.TrustedXForwardedFor) == 0) {
errors.LogWarning(
context.Background(),
`====== SECURITY WARNING ======`,
"\n",
`inbound "`, c.Tag, `" using `, protocol, ` has not configured "sockopt.trustedXForwardedFor".`,
"\n",
`THIS IS VERY INSECURE!!!`,
"\n",
`For compatibility, Xray still allows this for now and still trusts X-Forwarded-For implicitly.`,
"\n",
`Please configure "sockopt.trustedXForwardedFor" immediately.`,
"\n",
`In future versions, this option must be explicitly set.`,
"\n",
`====== SECURITY WARNING ======`,
)
}
if strings.Contains(ss.SecurityType, "reality") && (receiverSettings.PortList == nil || if strings.Contains(ss.SecurityType, "reality") && (receiverSettings.PortList == nil ||
len(receiverSettings.PortList.Ports()) != 1 || receiverSettings.PortList.Ports()[0] != 443) { len(receiverSettings.PortList.Ports()) != 1 || receiverSettings.PortList.Ports()[0] != 443) {
errors.LogWarning(context.Background(), `REALITY: Listening on non-443 ports may get your IP blocked by the GFW`) errors.LogWarning(context.Background(), `REALITY: Listening on non-443 ports may get your IP blocked by the GFW`)
@@ -265,7 +286,7 @@ func (c *OutboundDetourConfig) Build() (*core.OutboundHandlerConfig, error) {
if c.SendThrough != nil { if c.SendThrough != nil {
address := ParseSendThough(c.SendThrough) address := ParseSendThough(c.SendThrough)
//Check if CIDR exists // Check if CIDR exists
if strings.Contains(*c.SendThrough, "/") { if strings.Contains(*c.SendThrough, "/") {
senderSettings.ViaCidr = strings.Split(*c.SendThrough, "/")[1] senderSettings.ViaCidr = strings.Split(*c.SendThrough, "/")[1]
} else { } else {
@@ -449,7 +470,6 @@ func (c *Config) Override(o *Config, fn string) {
c.InboundConfigs = append(c.InboundConfigs, o.InboundConfigs[i]) c.InboundConfigs = append(c.InboundConfigs, o.InboundConfigs[i])
errors.LogInfo(context.Background(), "[", fn, "] appended inbound with tag: ", o.InboundConfigs[i].Tag) errors.LogInfo(context.Background(), "[", fn, "] appended inbound with tag: ", o.InboundConfigs[i].Tag)
} }
} }
} }
+62 -19
View File
@@ -12,7 +12,16 @@ import (
"strings" "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. // 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 // 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 return output, nil
} }
func RunMany(binary string, args, files []string) { func RunMany(binary string, args, files []string) bool {
fmt.Println("Processing...") fmt.Println("Processing with", binary, args, "...")
formatRequired := false
maxTasks := make(chan struct{}, runtime.NumCPU()) maxTasks := make(chan struct{}, runtime.NumCPU())
for _, file := range files { for _, file := range files {
maxTasks <- struct{}{} maxTasks <- struct{}{}
@@ -102,10 +112,12 @@ func RunMany(binary string, args, files []string) {
fmt.Println(err) fmt.Println(err)
} else if len(output) > 0 { } else if len(output) > 0 {
fmt.Println(string(output)) fmt.Println(string(output))
formatRequired = true
} }
<-maxTasks <-maxTasks
}(file) }(file)
} }
return formatRequired
} }
func main() { func main() {
@@ -124,6 +136,19 @@ func main() {
*directory = filepath.Join(pwd, *directory) *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 pwd := *directory
GOBIN := GetGOBIN() GOBIN := GetGOBIN()
binPath := os.Getenv("PATH") binPath := os.Getenv("PATH")
@@ -136,7 +161,6 @@ func main() {
suffix = ".exe" suffix = ".exe"
} }
gofmt := "gofumpt" + suffix gofmt := "gofumpt" + suffix
goimports := "gci" + suffix
if gofmtPath, err := exec.LookPath(gofmt); err != nil { if gofmtPath, err := exec.LookPath(gofmt); err != nil {
fmt.Println("Can not find", gofmt, "in system path or current working directory.") fmt.Println("Can not find", gofmt, "in system path or current working directory.")
@@ -145,13 +169,6 @@ func main() {
gofmt = gofmtPath 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) rawFilesSlice := make([]string, 0, 1000)
walkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error { walkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
@@ -179,15 +196,41 @@ func main() {
os.Exit(1) os.Exit(1)
} }
gofmtArgs := []string{ if isFormat {
"-s", "-l", "-e", "-w", 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{ if isCheck {
"write", gofmtListArgs := []string{
} "-l", "-e",
}
RunMany(gofmt, gofmtArgs, rawFilesSlice) fmt.Println("Checking files thar are not properly formatted...")
RunMany(goimports, goimportsArgs, rawFilesSlice) formatRequired := RunMany(gofmt, gofmtListArgs, rawFilesSlice)
fmt.Println("Do NOT forget to commit file changes.") 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.")
}
}
} }
-1
View File
@@ -59,7 +59,6 @@ func executeBalancerInfo(cmd *base.Command, args []string) {
} }
showBalancerInfo(resp.Balancer) showBalancerInfo(resp.Balancer)
} }
func showBalancerInfo(b *routerService.BalancerMsg) { func showBalancerInfo(b *routerService.BalancerMsg) {
+2 -1
View File
@@ -62,7 +62,8 @@ func addInboundUserAction(ctx context.Context, client handlerService.HandlerServ
Operation: cserial.ToTypedMessage( Operation: cserial.ToTypedMessage(
&handlerService.AddUserOperation{ &handlerService.AddUserOperation{
User: user, User: user,
}), },
),
}) })
return err return err
} }
+2 -1
View File
@@ -50,7 +50,8 @@ func executeRemoveUsers(cmd *base.Command, args []string) {
Operation: cserial.ToTypedMessage( Operation: cserial.ToTypedMessage(
&handlerService.RemoveUserOperation{ &handlerService.RemoveUserOperation{
Email: email, Email: email,
}), },
),
}) })
if err == nil { if err == nil {
success += 1 success += 1
+1 -4
View File
@@ -38,9 +38,7 @@ Example:
} }
func executeAddRules(cmd *base.Command, args []string) { func executeAddRules(cmd *base.Command, args []string) {
var ( var shouldAppend bool
shouldAppend bool
)
setSharedFlags(cmd) setSharedFlags(cmd)
cmd.Flag.BoolVar(&shouldAppend, "append", false, "") cmd.Flag.BoolVar(&shouldAppend, "append", false, "")
cmd.Flag.Parse(args) cmd.Flag.Parse(args)
@@ -96,5 +94,4 @@ func executeAddRules(cmd *base.Command, args []string) {
} }
showJSONResponse(resp) showJSONResponse(resp)
} }
} }
-1
View File
@@ -56,5 +56,4 @@ func executeRemoveRules(cmd *base.Command, args []string) {
} }
showJSONResponse(resp) showJSONResponse(resp)
} }
} }
-1
View File
@@ -136,5 +136,4 @@ func executeSourceIpBlock(cmd *base.Command, args []string) {
base.Fatalf("failed to perform AddRule: %s", err) base.Fatalf("failed to perform AddRule: %s", err)
} }
showJSONResponse(resp) showJSONResponse(resp)
} }
-1
View File
@@ -38,7 +38,6 @@ Examples:
} }
func executeTypedMessageToJson(cmd *base.Command, args []string) { func executeTypedMessageToJson(cmd *base.Command, args []string) {
var injectTypeInfo bool var injectTypeInfo bool
cmd.Flag.BoolVar(&injectTypeInfo, "t", false, "") cmd.Flag.BoolVar(&injectTypeInfo, "t", false, "")
cmd.Flag.BoolVar(&injectTypeInfo, "type", false, "") cmd.Flag.BoolVar(&injectTypeInfo, "type", false, "")
+1 -2
View File
@@ -41,7 +41,6 @@ Examples:
} }
func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) { func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
var optFile string var optFile string
var optDump bool var optDump bool
var optType bool var optType bool
@@ -60,7 +59,7 @@ func executeConvertConfigsToProtobuf(cmd *base.Command, args []string) {
} }
if len(optFile) > 0 { if len(optFile) > 0 {
switch core.GetFormat(optFile){ switch core.GetFormat(optFile) {
case "protobuf", "": case "protobuf", "":
fmt.Println("Output ProtoBuf file is ", optFile) fmt.Println("Output ProtoBuf file is ", optFile)
default: default:
+4 -2
View File
@@ -34,8 +34,10 @@ func init() {
var input_echServerKeys = cmdECH.Flag.String("i", "", "ECHServerKeys (base64.StdEncoding)") var input_echServerKeys = cmdECH.Flag.String("i", "", "ECHServerKeys (base64.StdEncoding)")
// var input_pqSignatureSchemesEnabled = cmdECH.Flag.Bool("pqSignatureSchemesEnabled", false, "") // var input_pqSignatureSchemesEnabled = cmdECH.Flag.Bool("pqSignatureSchemesEnabled", false, "")
var input_serverName = cmdECH.Flag.String("serverName", "cloudflare-ech.com", "") var (
var input_pem = cmdECH.Flag.Bool("pem", false, "True == turn on pem output") 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) { func executeECH(cmd *base.Command, args []string) {
var kem uint16 var kem uint16
+4 -2
View File
@@ -21,8 +21,10 @@ func init() {
cmdX25519.Run = executeX25519 // break init loop cmdX25519.Run = executeX25519 // break init loop
} }
var input_stdEncoding = cmdX25519.Flag.Bool("std-encoding", false, "") var (
var input_x25519 = cmdX25519.Flag.String("i", "", "") input_stdEncoding = cmdX25519.Flag.Bool("std-encoding", false, "")
input_x25519 = cmdX25519.Flag.String("i", "", "")
)
func executeX25519(cmd *base.Command, args []string) { func executeX25519(cmd *base.Command, args []string) {
Curve25519Genkey(false, *input_x25519) Curve25519Genkey(false, *input_x25519)
+1 -3
View File
@@ -12,9 +12,7 @@ type (
configFileLoader func(string) (io.Reader, error) configFileLoader func(string) (io.Reader, error)
) )
var ( var EffectiveConfigFileLoader configFileLoader
EffectiveConfigFileLoader configFileLoader
)
// LoadConfig reads from a path/url/stdin // LoadConfig reads from a path/url/stdin
// actual work is in external module // actual work is in external module
+61 -54
View File
@@ -3,8 +3,8 @@ package external
import ( import (
"bytes" "bytes"
"context" "context"
"net"
"io" "io"
"net"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@@ -13,6 +13,7 @@ import (
"github.com/xtls/xray-core/common/buf" "github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/main/confloader" "github.com/xtls/xray-core/main/confloader"
) )
@@ -20,9 +21,10 @@ func ConfigLoader(arg string) (out io.Reader, err error) {
var data []byte var data []byte
switch { switch {
case strings.HasPrefix(arg, "http+unix://"): case strings.HasPrefix(arg, "http+unix://"):
data, err = FetchUnixSocketHTTPContent(arg) errors.PrintDeprecatedFeatureWarning(`"http+unix://" prefix`, `direct Unix socket path (e.g. /path/socket.sock:/api or @abstract:/api)`)
data, err = FetchHTTPContent(httpUnixToCanonical(arg))
case strings.HasPrefix(arg, "http://"), strings.HasPrefix(arg, "https://"): case isRemoteSource(arg):
data, err = FetchHTTPContent(arg) data, err = FetchHTTPContent(arg)
case arg == "stdin:": case arg == "stdin:":
@@ -39,19 +41,38 @@ func ConfigLoader(arg string) (out io.Reader, err error) {
return return
} }
// FetchHTTPContent issues an HTTP GET against either a regular HTTP(S) URL
// or a Unix socket HTTP endpoint.
//
// http(s)://host/api regular HTTP(S)
// /path/to/socket.sock[:/api] filesystem socket
// @abstract[:/api] abstract socket (Linux/Android)
// @@padded[:/api] padded abstract socket (HAProxy compat)
//
// When the ":/" separator is omitted on a socket target, the request is
// made to "/".
func FetchHTTPContent(target string) ([]byte, error) { func FetchHTTPContent(target string) ([]byte, error) {
parsedTarget, err := url.Parse(target) httpURL, socketPath := utils.SplitHTTPUnixURL(target)
parsedTarget, err := url.Parse(httpURL)
if err != nil { if err != nil {
return nil, errors.New("invalid URL: ", target).Base(err) return nil, errors.New("invalid URL: ", target).Base(err)
} }
if s := strings.ToLower(parsedTarget.Scheme); s != "http" && s != "https" {
return nil, errors.New("invalid scheme: ", parsedTarget.Scheme)
}
client := &http.Client{ client := &http.Client{
Timeout: 30 * time.Second, Timeout: 30 * time.Second,
} }
if socketPath != "" {
dialAddr := utils.ResolveSocketPath(socketPath)
client.Transport = &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, "unix", dialAddr)
},
}
}
resp, err := client.Do(&http.Request{ resp, err := client.Do(&http.Request{
Method: "GET", Method: "GET",
URL: parsedTarget, URL: parsedTarget,
@@ -74,58 +95,44 @@ func FetchHTTPContent(target string) ([]byte, error) {
return content, nil return content, nil
} }
// Format: http+unix:///path/to/socket.sock/api/endpoint // isRemoteSource reports whether arg should be fetched via HTTP (regular
func FetchUnixSocketHTTPContent(target string) ([]byte, error) { // network or Unix socket) rather than read from the local filesystem.
path := strings.TrimPrefix(target, "http+unix://") // Recognized forms:
//
if !strings.HasPrefix(path, "/") { // - http(s)://... regular HTTP(S)
return nil, errors.New("unix socket path must be absolute") // - @abstract[:/api] abstract socket (Linux/Android)
// - /abs/path:/api filesystem socket, explicit HTTP path
// - /abs/path filesystem socket detected via os.ModeSocket
func isRemoteSource(arg string) bool {
if arg == "" {
return false
} }
if strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") {
var socketPath, httpPath string return true
sockIdx := strings.Index(path, ".sock")
if sockIdx != -1 {
socketPath = path[:sockIdx+5]
httpPath = path[sockIdx+5:]
if httpPath == "" {
httpPath = "/"
}
} else {
return nil, errors.New("cannot determine socket path, socket file should have .sock extension")
} }
if arg[0] == '@' {
if _, err := os.Stat(socketPath); err != nil { return true
return nil, errors.New("socket file not found: ", socketPath).Base(err)
} }
if arg[0] != '/' {
client := &http.Client{ return false
Timeout: 30 * time.Second,
Transport: &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, "unix", socketPath)
},
},
} }
defer client.CloseIdleConnections() if strings.Contains(arg, ":/") {
return true
resp, err := client.Get("http://localhost" + httpPath)
if err != nil {
return nil, errors.New("failed to fetch from unix socket: ", socketPath).Base(err)
} }
defer resp.Body.Close() info, err := os.Stat(arg)
return err == nil && info.Mode()&os.ModeSocket != 0
if resp.StatusCode != 200 { }
return nil, errors.New("unexpected HTTP status code: ", resp.StatusCode)
// httpUnixToCanonical converts the deprecated http+unix:///path/to/socket.sock/api
// URL into the canonical /path/to/socket.sock:/api form by inserting ":"
// between the ".sock" extension and the HTTP path. Inputs without a path
// after ".sock" are returned with just the "http+unix://" prefix stripped.
func httpUnixToCanonical(target string) string {
raw := strings.TrimPrefix(target, "http+unix://")
if i := strings.Index(raw, ".sock/"); i >= 0 {
raw = raw[:i+5] + ":" + raw[i+5:]
} }
return raw
content, err := buf.ReadAllToBytes(resp.Body)
if err != nil {
return nil, errors.New("failed to read response").Base(err)
}
return content, nil
} }
func init() { func init() {
+20 -11
View File
@@ -28,7 +28,7 @@ type RuleAction int32
const ( const (
RuleAction_Direct RuleAction = 0 RuleAction_Direct RuleAction = 0
RuleAction_Drop RuleAction = 1 RuleAction_Drop RuleAction = 1
RuleAction_Reject RuleAction = 2 RuleAction_Return RuleAction = 2
RuleAction_Hijack RuleAction = 3 RuleAction_Hijack RuleAction = 3
) )
@@ -37,13 +37,13 @@ var (
RuleAction_name = map[int32]string{ RuleAction_name = map[int32]string{
0: "Direct", 0: "Direct",
1: "Drop", 1: "Drop",
2: "Reject", 2: "Return",
3: "Hijack", 3: "Hijack",
} }
RuleAction_value = map[string]int32{ RuleAction_value = map[string]int32{
"Direct": 0, "Direct": 0,
"Drop": 1, "Drop": 1,
"Reject": 2, "Return": 2,
"Hijack": 3, "Hijack": 3,
} }
) )
@@ -78,8 +78,9 @@ func (RuleAction) EnumDescriptor() ([]byte, []int) {
type DNSRuleConfig struct { type DNSRuleConfig struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
Action RuleAction `protobuf:"varint,1,opt,name=action,proto3,enum=xray.proxy.dns.RuleAction" json:"action,omitempty"` Action RuleAction `protobuf:"varint,1,opt,name=action,proto3,enum=xray.proxy.dns.RuleAction" json:"action,omitempty"`
Qtype []int32 `protobuf:"varint,2,rep,packed,name=qtype,proto3" json:"qtype,omitempty"` QType []int32 `protobuf:"varint,2,rep,packed,name=q_type,json=qType,proto3" json:"q_type,omitempty"`
Domain []*geodata.DomainRule `protobuf:"bytes,3,rep,name=domain,proto3" json:"domain,omitempty"` Domain []*geodata.DomainRule `protobuf:"bytes,3,rep,name=domain,proto3" json:"domain,omitempty"`
RCode uint32 `protobuf:"varint,4,opt,name=r_code,json=rCode,proto3" json:"r_code,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -121,9 +122,9 @@ func (x *DNSRuleConfig) GetAction() RuleAction {
return RuleAction_Direct return RuleAction_Direct
} }
func (x *DNSRuleConfig) GetQtype() []int32 { func (x *DNSRuleConfig) GetQType() []int32 {
if x != nil { if x != nil {
return x.Qtype return x.QType
} }
return nil return nil
} }
@@ -135,6 +136,13 @@ func (x *DNSRuleConfig) GetDomain() []*geodata.DomainRule {
return nil return nil
} }
func (x *DNSRuleConfig) GetRCode() uint32 {
if x != nil {
return x.RCode
}
return 0
}
type Config struct { type Config struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
UserLevel uint32 `protobuf:"varint,1,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"` UserLevel uint32 `protobuf:"varint,1,opt,name=user_level,json=userLevel,proto3" json:"user_level,omitempty"`
@@ -199,11 +207,12 @@ var File_proxy_dns_config_proto protoreflect.FileDescriptor
const file_proxy_dns_config_proto_rawDesc = "" + const file_proxy_dns_config_proto_rawDesc = "" +
"\n" + "\n" +
"\x16proxy/dns/config.proto\x12\x0exray.proxy.dns\x1a\x1ccommon/net/destination.proto\x1a\x1bcommon/geodata/geodat.proto\"\x92\x01\n" + "\x16proxy/dns/config.proto\x12\x0exray.proxy.dns\x1a\x1ccommon/net/destination.proto\x1a\x1bcommon/geodata/geodat.proto\"\xaa\x01\n" +
"\rDNSRuleConfig\x122\n" + "\rDNSRuleConfig\x122\n" +
"\x06action\x18\x01 \x01(\x0e2\x1a.xray.proxy.dns.RuleActionR\x06action\x12\x14\n" + "\x06action\x18\x01 \x01(\x0e2\x1a.xray.proxy.dns.RuleActionR\x06action\x12\x15\n" +
"\x05qtype\x18\x02 \x03(\x05R\x05qtype\x127\n" + "\x06q_type\x18\x02 \x03(\x05R\x05qType\x127\n" +
"\x06domain\x18\x03 \x03(\v2\x1f.xray.common.geodata.DomainRuleR\x06domain\"\x9c\x01\n" + "\x06domain\x18\x03 \x03(\v2\x1f.xray.common.geodata.DomainRuleR\x06domain\x12\x15\n" +
"\x06r_code\x18\x04 \x01(\rR\x05rCode\"\x9c\x01\n" +
"\x06Config\x12\x1d\n" + "\x06Config\x12\x1d\n" +
"\n" + "\n" +
"user_level\x18\x01 \x01(\rR\tuserLevel\x121\n" + "user_level\x18\x01 \x01(\rR\tuserLevel\x121\n" +
@@ -215,7 +224,7 @@ const file_proxy_dns_config_proto_rawDesc = "" +
"\x06Direct\x10\x00\x12\b\n" + "\x06Direct\x10\x00\x12\b\n" +
"\x04Drop\x10\x01\x12\n" + "\x04Drop\x10\x01\x12\n" +
"\n" + "\n" +
"\x06Reject\x10\x02\x12\n" + "\x06Return\x10\x02\x12\n" +
"\n" + "\n" +
"\x06Hijack\x10\x03BL\n" + "\x06Hijack\x10\x03BL\n" +
"\x12com.xray.proxy.dnsP\x01Z#github.com/xtls/xray-core/proxy/dns\xaa\x02\x0eXray.Proxy.Dnsb\x06proto3" "\x12com.xray.proxy.dnsP\x01Z#github.com/xtls/xray-core/proxy/dns\xaa\x02\x0eXray.Proxy.Dnsb\x06proto3"
+3 -2
View File
@@ -12,14 +12,15 @@ import "common/geodata/geodat.proto";
enum RuleAction { enum RuleAction {
Direct = 0; Direct = 0;
Drop = 1; Drop = 1;
Reject = 2; Return = 2;
Hijack = 3; Hijack = 3;
} }
message DNSRuleConfig { message DNSRuleConfig {
RuleAction action = 1; RuleAction action = 1;
repeated int32 qtype = 2; repeated int32 q_type = 2;
repeated xray.common.geodata.DomainRule domain = 3; repeated xray.common.geodata.DomainRule domain = 3;
uint32 r_code = 4;
} }
message Config { message Config {
+41 -43
View File
@@ -45,6 +45,7 @@ type DNSRule struct {
action RuleAction action RuleAction
qTypes []uint16 qTypes []uint16
domains geodata.DomainMatcher domains geodata.DomainMatcher
rCode dnsmessage.RCode
} }
func (r *DNSRule) matchQType(qType uint16) bool { func (r *DNSRule) matchQType(qType uint16) bool {
@@ -95,9 +96,10 @@ func (h *Handler) Init(config *Config, dnsClient dns.Client, policyManager polic
for _, r := range config.Rule { for _, r := range config.Rule {
rule := &DNSRule{ rule := &DNSRule{
action: r.Action, action: r.Action,
qTypes: make([]uint16, 0, len(r.Qtype)), qTypes: make([]uint16, 0, len(r.QType)),
rCode: dnsmessage.RCode(r.RCode),
} }
for _, t := range r.Qtype { for _, t := range r.QType {
rule.qTypes = append(rule.qTypes, uint16(t)) rule.qTypes = append(rule.qTypes, uint16(t))
} }
if len(r.Domain) > 0 { if len(r.Domain) > 0 {
@@ -136,17 +138,17 @@ func parseQuery(b []byte) (id uint16, qType dnsmessage.Type, domain string, ok b
return return
} }
func (h *Handler) applyRules(qType dnsmessage.Type, domain string) RuleAction { func (h *Handler) applyRules(qType dnsmessage.Type, domain string) (RuleAction, dnsmessage.RCode) {
qCode := uint16(qType) qCode := uint16(qType)
for _, r := range h.rules { for _, r := range h.rules {
if r.Apply(qCode, domain) { if r.Apply(qCode, domain) {
return r.action return r.action, r.rCode
} }
} }
if qType == dnsmessage.TypeA || qType == dnsmessage.TypeAAAA { if qType == dnsmessage.TypeA || qType == dnsmessage.TypeAAAA {
return RuleAction_Hijack return RuleAction_Hijack, dnsmessage.RCodeSuccess
} }
return RuleAction_Reject return RuleAction_Return, dnsmessage.RCodeSuccess
} }
// Process implements proxy.Outbound. // Process implements proxy.Outbound.
@@ -213,7 +215,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
} }
if session.TimeoutOnlyFromContext(ctx) { if session.TimeoutOnlyFromContext(ctx) {
ctx, _ = context.WithCancel(context.Background()) ctx = context.Background()
} }
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
@@ -250,21 +252,22 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
continue continue
} }
switch h.applyRules(qType, domain) { action, rCode := h.applyRules(qType, domain)
switch action {
case RuleAction_Drop: case RuleAction_Drop:
b.Release() b.Release()
errors.LogInfo(ctx, "blocked type ", qType, " query for domain ", domain) errors.LogInfo(ctx, "blocked type ", qType, " query for domain ", domain)
case RuleAction_Reject: case RuleAction_Return:
b.Release() b.Release()
errors.LogInfo(ctx, "rejected type ", qType, " query for domain ", domain) errors.LogInfo(ctx, "rejected type ", qType, " query for domain ", domain)
if err := h.rejectNonIPQuery(id, qType, domain, writer); err != nil { if err := h.rejectNonIPQuery(id, qType, domain, writer, rCode); err != nil {
return err return err
} }
case RuleAction_Hijack: case RuleAction_Hijack:
b.Release() b.Release()
if qType != dnsmessage.TypeA && qType != dnsmessage.TypeAAAA { if qType != dnsmessage.TypeA && qType != dnsmessage.TypeAAAA {
errors.LogError(ctx, "can only hijack A/AAAA records") errors.LogError(ctx, "can only hijack A/AAAA records")
if err := h.rejectNonIPQuery(id, qType, domain, writer); err != nil { if err := h.rejectNonIPQuery(id, qType, domain, writer, rCode); err != nil {
return err return err
} }
} else { } else {
@@ -309,48 +312,35 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter, timer *signal.ActivityTimer) { func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter, timer *signal.ActivityTimer) {
var ips []net.IP var ips []net.IP
var ttl uint32
var err error var err error
var ttl4 uint32
var ttl6 uint32
switch qType { switch qType {
case dnsmessage.TypeA: case dnsmessage.TypeA:
ips, ttl4, err = h.client.LookupIP(domain, dns.IPOption{ ips, ttl, err = h.client.LookupIP(domain, dns.IPOption{
IPv4Enable: true, IPv4Enable: true,
IPv6Enable: false, IPv6Enable: false,
FakeEnable: true, FakeEnable: true,
}) })
case dnsmessage.TypeAAAA: case dnsmessage.TypeAAAA:
ips, ttl6, err = h.client.LookupIP(domain, dns.IPOption{ ips, ttl, err = h.client.LookupIP(domain, dns.IPOption{
IPv4Enable: false, IPv4Enable: false,
IPv6Enable: true, IPv6Enable: true,
FakeEnable: true, FakeEnable: true,
}) })
} }
rcode := dns.RCodeFromError(err) rCode := dns.RCodeFromError(err)
if rcode == 0 && len(ips) == 0 && !go_errors.Is(err, dns.ErrEmptyResponse) { if rCode == 0 && len(ips) == 0 && !go_errors.Is(err, dns.ErrEmptyResponse) {
errors.LogInfoInner(context.Background(), err, "ip query") errors.LogInfoInner(context.Background(), err, "ip query")
return return
} }
switch qType {
case dnsmessage.TypeA:
for i, ip := range ips {
ips[i] = ip.To4()
}
case dnsmessage.TypeAAAA:
for i, ip := range ips {
ips[i] = ip.To16()
}
}
b := buf.New() b := buf.New()
rawBytes := b.Extend(buf.Size) rawBytes := b.Extend(buf.Size)
builder := dnsmessage.NewBuilder(rawBytes[:0], dnsmessage.Header{ builder := dnsmessage.NewBuilder(rawBytes[:0], dnsmessage.Header{
ID: id, ID: id,
RCode: dnsmessage.RCode(rcode), RCode: dnsmessage.RCode(rCode),
RecursionAvailable: true, RecursionAvailable: true,
RecursionDesired: true, RecursionDesired: true,
Response: true, Response: true,
@@ -365,17 +355,25 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
})) }))
common.Must(builder.StartAnswers()) common.Must(builder.StartAnswers())
rHeader4 := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl4} rHeader := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl}
rHeader6 := dnsmessage.ResourceHeader{Name: dnsmessage.MustNewName(domain), Class: dnsmessage.ClassINET, TTL: ttl6} switch qType {
for _, ip := range ips { case dnsmessage.TypeA:
if len(ip) == net.IPv4len { for _, ip := range ips {
var r dnsmessage.AResource ip = ip.To4()
copy(r.A[:], ip) if len(ip) == net.IPv4len {
common.Must(builder.AResource(rHeader4, r)) var r dnsmessage.AResource
} else { copy(r.A[:], ip)
var r dnsmessage.AAAAResource common.Must(builder.AResource(rHeader, r))
copy(r.AAAA[:], ip) }
common.Must(builder.AAAAResource(rHeader6, r)) }
case dnsmessage.TypeAAAA:
for _, ip := range ips {
ip = ip.To16()
if len(ip) == net.IPv6len {
var r dnsmessage.AAAAResource
copy(r.AAAA[:], ip)
common.Must(builder.AAAAResource(rHeader, r))
}
} }
} }
msgBytes, err := builder.Finish() msgBytes, err := builder.Finish()
@@ -392,7 +390,7 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
} }
} }
func (h *Handler) rejectNonIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter) error { func (h *Handler) rejectNonIPQuery(id uint16, qType dnsmessage.Type, domain string, writer dns_proto.MessageWriter, rCode dnsmessage.RCode) error {
domainT := strings.TrimSuffix(domain, ".") domainT := strings.TrimSuffix(domain, ".")
if domainT == "" { if domainT == "" {
return errors.New("empty domain name") return errors.New("empty domain name")
@@ -401,7 +399,7 @@ func (h *Handler) rejectNonIPQuery(id uint16, qType dnsmessage.Type, domain stri
rawBytes := b.Extend(buf.Size) rawBytes := b.Extend(buf.Size)
builder := dnsmessage.NewBuilder(rawBytes[:0], dnsmessage.Header{ builder := dnsmessage.NewBuilder(rawBytes[:0], dnsmessage.Header{
ID: id, ID: id,
RCode: dnsmessage.RCodeRefused, RCode: rCode,
RecursionAvailable: true, RecursionAvailable: true,
RecursionDesired: true, RecursionDesired: true,
Response: true, Response: true,
+4 -3
View File
@@ -424,7 +424,7 @@ func TestDNSRules(t *testing.T) {
ProxySettings: serial.ToTypedMessage(&dns_proxy.Config{ ProxySettings: serial.ToTypedMessage(&dns_proxy.Config{
Rule: []*dns_proxy.DNSRuleConfig{ Rule: []*dns_proxy.DNSRuleConfig{
{ {
Qtype: []int32{int32(dns.TypeA)}, QType: []int32{int32(dns.TypeA)},
Domain: []*geodata.DomainRule{ Domain: []*geodata.DomainRule{
{ {
Value: &geodata.DomainRule_Custom{ Value: &geodata.DomainRule_Custom{
@@ -438,7 +438,7 @@ func TestDNSRules(t *testing.T) {
Action: dns_proxy.RuleAction_Direct, Action: dns_proxy.RuleAction_Direct,
}, },
{ {
Qtype: []int32{int32(dns.TypeA)}, QType: []int32{int32(dns.TypeA)},
Domain: []*geodata.DomainRule{ Domain: []*geodata.DomainRule{
{ {
Value: &geodata.DomainRule_Custom{ Value: &geodata.DomainRule_Custom{
@@ -449,7 +449,8 @@ func TestDNSRules(t *testing.T) {
}, },
}, },
}, },
Action: dns_proxy.RuleAction_Reject, Action: dns_proxy.RuleAction_Return,
RCode: 5,
}, },
}, },
}), }),
+5 -3
View File
@@ -190,9 +190,11 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
} }
} }
if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{ if err := dispatcher.DispatchLink(
Reader: reader, ctx, dest, &transport.Link{
Writer: writer}, Reader: reader,
Writer: writer,
},
); err != nil { ); err != nil {
return errors.New("failed to dispatch request").Base(err) return errors.New("failed to dispatch request").Base(err)
} }
+9 -7
View File
@@ -30,10 +30,12 @@ import (
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
) )
var useSplice bool var (
var allNetworks [8]bool useSplice bool
var defaultBlockPrivateRule *FinalRule allNetworks [8]bool
var defaultBlockAllRule *FinalRule defaultBlockPrivateRule *FinalRule
defaultBlockAllRule *FinalRule
)
func init() { func init() {
common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
@@ -677,7 +679,7 @@ type NoisePacketWriter struct {
func (w *NoisePacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error { func (w *NoisePacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
if w.firstWrite { if w.firstWrite {
w.firstWrite = false w.firstWrite = false
//Do not send Noise for dns requests(just to be safe) // Do not send Noise for dns requests(just to be safe)
if w.UDPOverride.Port == 53 { if w.UDPOverride.Port == 53 {
return w.Writer.WriteMultiBuffer(mb) return w.Writer.WriteMultiBuffer(mb)
} }
@@ -700,11 +702,11 @@ func (w *NoisePacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
default: default:
panic("unreachable, applyTo is ip/ipv4/ipv6") panic("unreachable, applyTo is ip/ipv4/ipv6")
} }
//User input string or base64 encoded string or hex string // User input string or base64 encoded string or hex string
if n.Packet != nil { if n.Packet != nil {
noise = n.Packet noise = n.Packet
} else { } else {
//Random noise // Random noise
noise, err = GenerateRandomBytes(crypto.RandBetween(int64(n.LengthMin), noise, err = GenerateRandomBytes(crypto.RandBetween(int64(n.LengthMin),
int64(n.LengthMax))) int64(n.LengthMax)))
} }
+5 -3
View File
@@ -192,9 +192,11 @@ func (s *Server) handleConnect(ctx context.Context, _ *http.Request, buffer *buf
if inbound.CanSpliceCopy == 2 { if inbound.CanSpliceCopy == 2 {
inbound.CanSpliceCopy = 1 inbound.CanSpliceCopy = 1
} }
if err := dispatcher.DispatchLink(ctx, dest, &transport.Link{ if err := dispatcher.DispatchLink(
Reader: reader, ctx, dest, &transport.Link{
Writer: buf.NewWriter(conn)}, Reader: reader,
Writer: buf.NewWriter(conn),
},
); err != nil { ); err != nil {
return errors.New("failed to dispatch request").Base(err) return errors.New("failed to dispatch request").Base(err)
} }
+1 -1
View File
@@ -113,7 +113,7 @@ func (v *Validator) GetAll() []*protocol.MemoryUser {
v.mutex.Lock() v.mutex.Lock()
defer v.mutex.Unlock() defer v.mutex.Unlock()
var users = make([]*protocol.MemoryUser, 0, len(v.users)) users := make([]*protocol.MemoryUser, 0, len(v.users))
for _, user := range v.users { for _, user := range v.users {
users = append(users, user) users = append(users, user)
} }
+3
View File
@@ -253,6 +253,9 @@ func FragUDPMessage(m *UDPMessage, maxSize int) []UDPMessage {
} }
fullPayload := m.Data fullPayload := m.Data
maxPayloadSize := maxSize - m.HeaderSize() maxPayloadSize := maxSize - m.HeaderSize()
if maxPayloadSize <= 0 {
return nil
}
off := 0 off := 0
fragID := uint8(0) fragID := uint8(0)
fragCount := uint8((len(fullPayload) + maxPayloadSize - 1) / maxPayloadSize) // round up fragCount := uint8((len(fullPayload) + maxPayloadSize - 1) / maxPayloadSize) // round up
+1 -1
View File
@@ -743,7 +743,7 @@ func CopyRawConnIfExist(ctx context.Context, readerConn net.Conn, writerConn net
for { for {
inbound := session.InboundFromContext(ctx) inbound := session.InboundFromContext(ctx)
outbounds := session.OutboundsFromContext(ctx) outbounds := session.OutboundsFromContext(ctx)
var splice = inbound.CanSpliceCopy == 1 splice := inbound.CanSpliceCopy == 1
for _, ob := range outbounds { for _, ob := range outbounds {
if ob.CanSpliceCopy != 1 { if ob.CanSpliceCopy != 1 {
splice = false splice = false
+14 -2
View File
@@ -2,6 +2,7 @@ package shadowsocks_2022
import ( import (
"context" "context"
"time"
shadowsocks "github.com/sagernet/sing-shadowsocks" shadowsocks "github.com/sagernet/sing-shadowsocks"
"github.com/sagernet/sing-shadowsocks/shadowaead_2022" "github.com/sagernet/sing-shadowsocks/shadowaead_2022"
@@ -18,6 +19,7 @@ import (
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/common/singbridge" "github.com/xtls/xray-core/common/singbridge"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/transport/internet/stat" "github.com/xtls/xray-core/transport/internet/stat"
@@ -115,7 +117,11 @@ func (i *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata M.M
}) })
errors.LogInfo(ctx, "tunnelling request to tcp:", metadata.Destination) errors.LogInfo(ctx, "tunnelling request to tcp:", metadata.Destination)
dispatcher := session.DispatcherFromContext(ctx) dispatcher := session.DispatcherFromContext(ctx)
link, err := dispatcher.Dispatch(ctx, singbridge.ToDestination(metadata.Destination, net.Network_TCP)) destination, err := singbridge.ToDestination(metadata.Destination, net.Network_TCP)
if err != nil {
return err
}
link, err := dispatcher.Dispatch(ctx, destination)
if err != nil { if err != nil {
return err return err
} }
@@ -136,7 +142,10 @@ func (i *Inbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, me
}) })
errors.LogInfo(ctx, "tunnelling request to udp:", metadata.Destination) errors.LogInfo(ctx, "tunnelling request to udp:", metadata.Destination)
dispatcher := session.DispatcherFromContext(ctx) dispatcher := session.DispatcherFromContext(ctx)
destination := singbridge.ToDestination(metadata.Destination, net.Network_UDP) destination, err := singbridge.ToDestination(metadata.Destination, net.Network_UDP)
if err != nil {
return err
}
link, err := dispatcher.Dispatch(ctx, destination) link, err := dispatcher.Dispatch(ctx, destination)
if err != nil { if err != nil {
return err return err
@@ -145,6 +154,9 @@ func (i *Inbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, me
Reader: link.Reader, Reader: link.Reader,
Writer: link.Writer, Writer: link.Writer,
Dest: destination, Dest: destination,
T: signal.CancelAfterInactivity(ctx, func() {
common.Interrupt(link.Reader)
}, 300*time.Second),
} }
return bufio.CopyPacketConn(ctx, conn, outConn) return bufio.CopyPacketConn(ctx, conn, outConn)
} }
+12 -5
View File
@@ -6,6 +6,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time"
"github.com/sagernet/sing-shadowsocks/shadowaead_2022" "github.com/sagernet/sing-shadowsocks/shadowaead_2022"
C "github.com/sagernet/sing/common" C "github.com/sagernet/sing/common"
@@ -22,6 +23,7 @@ import (
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/common/singbridge" "github.com/xtls/xray-core/common/singbridge"
"github.com/xtls/xray-core/common/uuid" "github.com/xtls/xray-core/common/uuid"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
@@ -237,11 +239,10 @@ func (i *MultiUserInbound) NewConnection(ctx context.Context, conn net.Conn, met
}) })
errors.LogInfo(ctx, "tunnelling request to tcp:", metadata.Destination) errors.LogInfo(ctx, "tunnelling request to tcp:", metadata.Destination)
dispatcher := session.DispatcherFromContext(ctx) dispatcher := session.DispatcherFromContext(ctx)
destination := singbridge.ToDestination(metadata.Destination, net.Network_TCP) destination, err := singbridge.ToDestination(metadata.Destination, net.Network_TCP)
if !destination.IsValid() { if err != nil {
return errors.New("invalid destination") return err
} }
link, err := dispatcher.Dispatch(ctx, destination) link, err := dispatcher.Dispatch(ctx, destination)
if err != nil { if err != nil {
return err return err
@@ -262,7 +263,10 @@ func (i *MultiUserInbound) NewPacketConnection(ctx context.Context, conn N.Packe
}) })
errors.LogInfo(ctx, "tunnelling request to udp:", metadata.Destination) errors.LogInfo(ctx, "tunnelling request to udp:", metadata.Destination)
dispatcher := session.DispatcherFromContext(ctx) dispatcher := session.DispatcherFromContext(ctx)
destination := singbridge.ToDestination(metadata.Destination, net.Network_UDP) destination, err := singbridge.ToDestination(metadata.Destination, net.Network_UDP)
if err != nil {
return err
}
link, err := dispatcher.Dispatch(ctx, destination) link, err := dispatcher.Dispatch(ctx, destination)
if err != nil { if err != nil {
return err return err
@@ -271,6 +275,9 @@ func (i *MultiUserInbound) NewPacketConnection(ctx context.Context, conn N.Packe
Reader: link.Reader, Reader: link.Reader,
Writer: link.Writer, Writer: link.Writer,
Dest: destination, Dest: destination,
T: signal.CancelAfterInactivity(ctx, func() {
common.Interrupt(link.Reader)
}, 300*time.Second),
} }
return bufio.CopyPacketConn(ctx, conn, outConn) return bufio.CopyPacketConn(ctx, conn, outConn)
} }
+14 -2
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/sagernet/sing-shadowsocks/shadowaead_2022" "github.com/sagernet/sing-shadowsocks/shadowaead_2022"
C "github.com/sagernet/sing/common" C "github.com/sagernet/sing/common"
@@ -20,6 +21,7 @@ import (
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/protocol" "github.com/xtls/xray-core/common/protocol"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/common/singbridge" "github.com/xtls/xray-core/common/singbridge"
"github.com/xtls/xray-core/common/uuid" "github.com/xtls/xray-core/common/uuid"
"github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/routing"
@@ -138,7 +140,11 @@ func (i *RelayInbound) NewConnection(ctx context.Context, conn net.Conn, metadat
}) })
errors.LogInfo(ctx, "tunnelling request to tcp:", metadata.Destination) errors.LogInfo(ctx, "tunnelling request to tcp:", metadata.Destination)
dispatcher := session.DispatcherFromContext(ctx) dispatcher := session.DispatcherFromContext(ctx)
link, err := dispatcher.Dispatch(ctx, singbridge.ToDestination(metadata.Destination, net.Network_TCP)) destination, err := singbridge.ToDestination(metadata.Destination, net.Network_TCP)
if err != nil {
return err
}
link, err := dispatcher.Dispatch(ctx, destination)
if err != nil { if err != nil {
return err return err
} }
@@ -161,7 +167,10 @@ func (i *RelayInbound) NewPacketConnection(ctx context.Context, conn N.PacketCon
}) })
errors.LogInfo(ctx, "tunnelling request to udp:", metadata.Destination) errors.LogInfo(ctx, "tunnelling request to udp:", metadata.Destination)
dispatcher := session.DispatcherFromContext(ctx) dispatcher := session.DispatcherFromContext(ctx)
destination := singbridge.ToDestination(metadata.Destination, net.Network_UDP) destination, err := singbridge.ToDestination(metadata.Destination, net.Network_UDP)
if err != nil {
return err
}
link, err := dispatcher.Dispatch(ctx, destination) link, err := dispatcher.Dispatch(ctx, destination)
if err != nil { if err != nil {
return err return err
@@ -170,6 +179,9 @@ func (i *RelayInbound) NewPacketConnection(ctx context.Context, conn N.PacketCon
Reader: link.Reader, Reader: link.Reader,
Writer: link.Writer, Writer: link.Writer,
Dest: destination, Dest: destination,
T: signal.CancelAfterInactivity(ctx, func() {
common.Interrupt(link.Reader)
}, 300*time.Second),
} }
return bufio.CopyPacketConn(ctx, conn, outConn) return bufio.CopyPacketConn(ctx, conn, outConn)
} }
+4
View File
@@ -16,6 +16,7 @@ import (
"github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/session" "github.com/xtls/xray-core/common/session"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/common/singbridge" "github.com/xtls/xray-core/common/singbridge"
"github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet"
@@ -142,6 +143,9 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int
Writer: link.Writer, Writer: link.Writer,
Conn: inboundConn, Conn: inboundConn,
Dest: destination, Dest: destination,
T: signal.CancelAfterInactivity(ctx, func() {
common.Interrupt(link.Reader)
}, 300*time.Second),
} }
} }

Some files were not shown because too many files have changed in this diff Show More