Compare commits

..
109 Commits
Author SHA1 Message Date
patterniha a9d6ded31b Fix typo "Syetem" -> "System" 2026-08-11 15:35:18 +03:30
patterniha 89aedc7d04 Sockopt: Fix indentation of the CustomSockopt block
https://github.com/XTLS/Xray-core/issues/6564#issuecomment-5150960771

Closes https://github.com/XTLS/Xray-core/issues/6564
2026-08-01 14:50:26 +03:30
RPRXandGitHub 5ca6f4b7d4 Xray-core v26.7.28
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-07-28 07:59:48 +00:00
RPRX 18e283909c XHTTP client: Reduce default maxConnections from 6 to 3 for anti-TSPU
https://github.com/XTLS/Xray-core/issues/6376#issuecomment-5101210849

Replaces https://github.com/XTLS/Xray-core/commit/18b85adb4e288f49a7894351c6e0f2428c0beef6
2026-07-28 07:57:55 +00:00
6ab123bf8f XMC finalmask: Add default directional padding/keep-alive packets, matching Minecraft 26.1.2 login/join/etc traffic shapes (#6487)
https://github.com/XTLS/Xray-core/pull/6487#issuecomment-5092235757

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-07-28 06:45:36 +00:00
4aba687dd3 XHTTP & gRPC servers: Get accurate localAddr (#6526)
Fixes https://github.com/XTLS/Xray-core/pull/6476

---------

Co-authored-by: echoowall <echoowall@gmail.com>
2026-07-27 13:11:25 +00:00
yiguodevandRPRX 5b1b41058e Routing: Exclude iOS from Darwin for process (#6524)
Fixes https://github.com/XTLS/Xray-core/pull/6434#issuecomment-5016175948
2026-07-27 12:40:18 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
6e3322d219 Bump actions/setup-go from 6 to 7 (#6511)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6 to 7.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-17 22:28:51 +00:00
Jasper344612andGitHub 1d8eb81d70 TUN inbound: Add desc config for Windows ("Wintun" by default) (#6486)
https://github.com/XTLS/Xray-docs-next/pull/872#issuecomment-4936055488
2026-07-17 22:21:33 +00:00
e78d8ef184 REALITY server: Refine warnings in parsing configuration (#6508)
https://github.com/XTLS/Xray-core/pull/6508#issuecomment-4995787310
https://github.com/XTLS/Xray-core/pull/6507#issuecomment-4995466980

---------

Co-authored-by: Enle <42325154+EnleStars@users.noreply.github.com>
Co-authored-by: Aleksey <fraybyl@gmail.com>
2026-07-17 21:51:29 +00:00
Jasper344612andGitHub 6ce924ad56 TUN inbound: Change default name to "utunN" (10~1024) (#6485)
https://github.com/XTLS/Xray-docs-next/pull/872#issuecomment-4936853064
2026-07-16 19:53:04 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
035d438979 Bump google.golang.org/grpc from 1.82.0 to 1.82.1 (#6502)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.82.0 to 1.82.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.82.0...v1.82.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.82.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-07-16 18:04:03 +00:00
RPRXandGitHub 50231eaff9 Xray-core v26.7.11
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-07-11 15:51:51 +00:00
1f74c480d6 Docker: Add pre-release tag (#6470)
https://github.com/XTLS/Xray-core/pull/6470#issuecomment-4947190027

---------

Co-authored-by: KobeArthurScofield <26771058+KobeArthurScofield@users.noreply.github.com>
Co-authored-by: Meow <197331664+Meo597@users.noreply.github.com>
2026-07-11 15:45:59 +00:00
RPRXandGitHub af7eb68028 REALITY server: Set default "minClientVer": "26.3.27" (change it at your own risk)
https://github.com/XTLS/Xray-core/pull/6181#issuecomment-4567373533
https://t.me/projectXtls/3373
https://t.me/projectXtls/3378
2026-07-11 14:30:54 +00:00
Exclude0122andGitHub 35387572e0 Finalmask: Add XMC (TCP, Minecraft) (#6210)
https://github.com/XTLS/Xray-core/pull/6210#issuecomment-4573680059
https://github.com/XTLS/Xray-core/pull/6210#issuecomment-4582554889
https://github.com/XTLS/Xray-core/pull/6210#issuecomment-4937008835
2026-07-11 13:59:24 +00:00
风扇滑翔翼andGitHub 8f15190c23 QUIC sniffer: Fix potential panic on malformed QUIC packets again (#6471)
Fixes https://github.com/XTLS/Xray-core/security/advisories/GHSA-xqmr-94vq-cxvx
Fixes https://github.com/XTLS/Xray-core/security/advisories/GHSA-9h6x-9r9m-5qw2
2026-07-10 21:40:50 +00:00
风扇滑翔翼andGitHub 64fada32b5 TLS client: Pinning CA must have serverName (or higher-priority verifyPeerCertByName or outbound's address) for pinnedPeerCertSha256 (#6472)
Fixes https://github.com/XTLS/Xray-core/security/advisories/GHSA-5wf9-h793-w73c
2026-07-10 21:03:35 +00:00
0bafca9486 Stats: Fix GetOrRegister*() races (#6468)
Fixes https://github.com/XTLS/Xray-core/pull/6467#issue-4856851059

---------

Co-authored-by: Joshua Su <i@joshua.su>
2026-07-10 20:34:27 +00:00
yiguodevandGitHub d5bc58dc6b Root config: Add env config (#6400)
https://github.com/XTLS/Xray-core/pull/6400#issuecomment-4880198100
https://github.com/XTLS/Xray-core/pull/6400#issuecomment-4916892425
https://github.com/XTLS/Xray-core/pull/6400#issuecomment-4929136670
2026-07-10 02:56:49 +00:00
patternihaandGitHub c18b39ed80 TLS client: Support more cipherSuites for "unsafe" (golang) fingerprint for anti-NIN (#6450)
https://github.com/XTLS/Xray-core/pull/6450#issuecomment-4929495504
2026-07-10 02:02:31 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
e2ad0acf60 Bump github.com/pires/go-proxyproto from 0.14.0 to 0.15.0 (#6464)
Bumps [github.com/pires/go-proxyproto](https://github.com/pires/go-proxyproto) from 0.14.0 to 0.15.0.
- [Release notes](https://github.com/pires/go-proxyproto/releases)
- [Commits](https://github.com/pires/go-proxyproto/compare/v0.14.0...v0.15.0)

---
updated-dependencies:
- dependency-name: github.com/pires/go-proxyproto
  dependency-version: 0.15.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-07-10 01:56:16 +00:00
c320e89108 HTTPUpgrade server: Keep accepting after transient errors as RAW does; Add handshake read deadline as WS does (#6463)
https://github.com/XTLS/Xray-core/pull/6463#issuecomment-4929183257

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-07-09 20:55:40 +00:00
LjhAUMEMandGitHub 412898fed7 WireGuard outbound: Fix a race condition (#6461)
Fixes https://github.com/XTLS/Xray-core/pull/6321#issuecomment-4881463309
2026-07-09 20:40:02 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
5c62d50d43 Bump golang.org/x/net from 0.56.0 to 0.57.0 (#6455)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.56.0 to 0.57.0.
- [Commits](https://github.com/golang/net/compare/v0.56.0...v0.57.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.57.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-07-09 14:11:38 +00:00
ivolframandGitHub 1aabe7ea78 XHTTP transport: Do not force trailing path / when both sessionID and seq are placed elsewhere (#6307)
https://github.com/XTLS/Xray-core/discussions/6385#discussioncomment-17454120

https://github.com/XTLS/Xray-core/pull/6307#issuecomment-4917498294

Closes https://github.com/XTLS/Xray-core/issues/6410
2026-07-08 17:59:19 +00:00
e4e7614c62 TLS ECH: Update ECH configuration parsing logic to be more robust (#6441)
https://github.com/XTLS/Xray-core/pull/6441#issuecomment-4903624643

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
2026-07-08 11:12:37 +00:00
yiguodevandGitHub 987290ba48 Routing: process supports macOS as well (#6447)
https://github.com/XTLS/Xray-core/pull/6434#issuecomment-4903384018
2026-07-08 11:03:24 +00:00
d7fa2076c3 Xray-core: Forbid unencrypted outbounds on public Internet for VLESS and Trojan; Remove "none/zero/plain" for VMess and Shadowsocks (#6303)
https://github.com/XTLS/Xray-core/pull/5640#issuecomment-4611416512
https://github.com/XTLS/Xray-core/pull/6303#issuecomment-4668055274

---------

Co-authored-by: Meow <197331664+Meo597@users.noreply.github.com>
2026-07-07 02:01:53 +00:00
fb548f54d2 infra/conf/transport_internet.go: Split into multiple files; Rename network to method (#6426)
https://github.com/XTLS/Xray-core/issues/6376#issuecomment-4817591444
https://github.com/XTLS/Xray-core/pull/6426#issuecomment-4899048255

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-07-07 01:26:15 +00:00
yiguodevandGitHub 0495b17650 TUN inbound: Refine gateway and autoSystemRoutingTable on macOS (#6434)
https://github.com/XTLS/Xray-core/pull/6434#issuecomment-4885521586
2026-07-07 00:28:50 +00:00
yiguodevandGitHub 65f6f0a43b TUN inbound: Refine gateway and autoSystemRoutingTable on Linux (#6398)
https://github.com/XTLS/Xray-core/pull/6398#issuecomment-4880261591
2026-07-04 02:25:15 +00:00
Jasper344612andGitHub 3263ae9255 TUN inbound: Fix autoOutboundsInterface on Linux (#6413)
Fixes https://github.com/XTLS/Xray-core/issues/6412
2026-07-04 01:38:25 +00:00
LjhAUMEMandGitHub 3dc8bf3d8b Hysteria inbound: Fix dynamic UUID not accepted (#6395)
Fixes https://github.com/XTLS/Xray-core/pull/6375#issuecomment-4825923934
2026-07-04 01:28:21 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
695e68ef9e Bump github.com/pires/go-proxyproto from 0.12.0 to 0.14.0 (#6420)
Bumps [github.com/pires/go-proxyproto](https://github.com/pires/go-proxyproto) from 0.12.0 to 0.14.0.
- [Release notes](https://github.com/pires/go-proxyproto/releases)
- [Commits](https://github.com/pires/go-proxyproto/compare/v0.12.0...v0.14.0)

---
updated-dependencies:
- dependency-name: github.com/pires/go-proxyproto
  dependency-version: 0.14.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-07-03 17:53:15 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
dfdbcf86cc Bump github.com/klauspost/cpuid/v2 from 2.3.0 to 2.4.0 (#6417)
Bumps [github.com/klauspost/cpuid/v2](https://github.com/klauspost/cpuid) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/klauspost/cpuid/releases)
- [Commits](https://github.com/klauspost/cpuid/compare/v2.3.0...v2.4.0)

---
updated-dependencies:
- dependency-name: github.com/klauspost/cpuid/v2
  dependency-version: 2.4.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-07-03 17:45:51 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2b828b7bc2 Bump google.golang.org/grpc from 1.81.1 to 1.82.0 (#6415)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.81.1 to 1.82.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.81.1...v1.82.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-version: 1.82.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-07-03 17:45:47 +00:00
RPRXandGitHub 45cf2898ab Xray-core v26.6.27
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-27 13:18:03 +00:00
RPRXandGitHub 18b85adb4e XHTTP client: Change default maxConnections to 6 for anti-RKN
"xmux": {
    "maxConcurrency": 0,
    "maxConnections": "6",
    "cMaxReuseTimes": 0,
    "hMaxRequestTimes": "600-900",
    "hMaxReusableSecs": "1800-3000",
    "hKeepAlivePeriod": 0
}

Replaces https://github.com/XTLS/Xray-core/commit/9cc7907234a8297a87a0ff77fc40db373b74a0f2 and https://github.com/XTLS/Xray-core/commit/4ce65fc74c4c50919b10b3faff9725f75bba5d73

Closes https://github.com/XTLS/Xray-core/issues/6376#issuecomment-4817033592
2026-06-27 12:41:39 +00:00
452b719504 Hysteria inbound: Support routing's vlessRoute as well (#6375)
https://github.com/XTLS/Xray-core/pull/6375#issuecomment-4795522284

---------

Co-authored-by: LjhAUMEM <llnu14702@gmail.com>
2026-06-27 12:04:58 +00:00
345c76f9a8 WireGuard inbound: Support dynamic peer management (#6360)
https://github.com/XTLS/Xray-core/pull/6360#issuecomment-4780311547

Closes https://github.com/XTLS/Xray-core/issues/6314

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: LjhAUMEM <llnu14702@gmail.com>
2026-06-27 11:41:22 +00:00
f496437b84 XHTTP server: Refactor upload_queue.go (#6372)
https://github.com/XTLS/Xray-core/pull/6372#issuecomment-4801395378

---------

Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-06-27 10:40:58 +00:00
b12bc504c8 README.md: Add Magic_V2Ray to Magisk in Installation (#6355)
https://github.com/XTLS/Xray-core/pull/6355#issuecomment-4793603321

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-06-24 21:06:24 +00:00
yiguodevandGitHub dda2b10c9d TUN inbound: Add traffic counters; Metrics: Rely on instance (#6349)
https://github.com/XTLS/Xray-core/pull/6349#issuecomment-4775121300
2026-06-24 12:00:22 +00:00
Jasper344612andGitHub 241aa38ac0 TUN inbound: Support autoSystemRoutingTable and autoOutboundsInterface on macOS and Linux as well (#6366)
https://github.com/XTLS/Xray-core/pull/6366#issuecomment-4788510365
2026-06-24 11:06:00 +00:00
风扇滑翔翼andRPRX 7e7e820763 Geodata: Apply uTLS Chrome fingerprint when downloading (#6371)
Closes https://github.com/XTLS/Xray-core/issues/6369
2026-06-24 10:52:28 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
f9eb1597ad Bump actions/cache from 5 to 6 (#6368)
Bumps [actions/cache](https://github.com/actions/cache) from 5 to 6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-24 10:48:21 +00:00
风扇滑翔翼andGitHub ac04c445bd DNS: Fix unexpected TTL clamp (#6363)
Fixes https://github.com/XTLS/Xray-core/issues/6359
2026-06-23 11:48:17 +00:00
e7e9254630 TUN inbound: Avoid panic on nil RemoteAddr due to quickly closed connection (#6365)
Fixes https://github.com/XTLS/Xray-core/issues/6364#issuecomment-4778262075

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-06-23 11:37:21 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
fab4bcc1ed Bump github.com/cloudflare/circl from 1.6.3 to 1.6.4 (#6362)
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.6.3 to 1.6.4.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.6.3...v1.6.4)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-version: 1.6.4
  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-06-23 10:34:07 +00:00
RPRXandGitHub b99c3e5657 Xray-core v26.6.22
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-22 18:55:10 +00:00
OmoebaandGitHub 583bb4a63f XHTTP server: Fix scStreamUpServerSecs when xPaddingObfsMode is true (#6343)
https://github.com/XTLS/Xray-core/pull/6343#issuecomment-4771666966
2026-06-22 18:51:47 +00:00
Жора ЗмейкинandRPRX 9cd9382e3d TUN inbound: Support env XRAY_TUN_FD on Linux as well (#6338)
https://github.com/XTLS/Xray-core/pull/6338#issuecomment-4770945163
2026-06-22 17:38:36 +00:00
patternihaRPRXcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
567500c4af Fragment finalmask: Add lengths and delays (#6334)
Usage: https://github.com/XTLS/Xray-core/pull/6334#issue-4685556394

Behavior: https://github.com/XTLS/Xray-core/pull/6334#issuecomment-4751547750

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-22 17:38:12 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
5aefcb41fb Bump github.com/pion/stun/v3 from 3.1.5 to 3.1.6 (#6357)
Bumps [github.com/pion/stun/v3](https://github.com/pion/stun) from 3.1.5 to 3.1.6.
- [Release notes](https://github.com/pion/stun/releases)
- [Commits](https://github.com/pion/stun/compare/v3.1.5...v3.1.6)

---
updated-dependencies:
- dependency-name: github.com/pion/stun/v3
  dependency-version: 3.1.6
  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-06-22 16:05:47 +00:00
MeowandGitHub be8009c625 Geodata: Cleanup unneeded matchers & domain: ignore case (#6342)
Completes https://github.com/XTLS/Xray-core/pull/6139
2026-06-19 12:02:27 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
8734774e4a Bump actions/checkout from 6 to 7 (#6344)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-19 11:23:41 +00:00
风扇滑翔翼andGitHub 1e036ce1c5 XHTTP/3 client: Actively close underlying QUIC & UDP (#6332)
Fixes https://github.com/XTLS/Xray-core/issues/6328#issuecomment-4730379021
2026-06-18 23:55:11 +00:00
j2rong4cnandGitHub c815c2f2df Loopback outbound: Add sniffing (#6326)
Example: https://github.com/XTLS/Xray-core/pull/6326#issue-4659701786
2026-06-18 23:17:01 +00:00
bytecategoryandGitHub 986c512e0f XHTTP client: Avoid panic when host is invalid (#6316)
Fixes https://github.com/XTLS/Xray-core/issues/6315
2026-06-18 22:55:18 +00:00
MeowandGitHub 711aea4e34 XHTTP & WS & HU & gRPC servers: Require sockopt.trustedXForwardedFor (#6309)
https://github.com/XTLS/Xray-core/pull/6258#issuecomment-4663652131

Behavior: https://github.com/XTLS/Xray-core/pull/6258#issuecomment-4746598275

Replaces https://github.com/XTLS/Xray-core/pull/6159
2026-06-18 22:31:21 +00:00
风扇滑翔翼andRPRX 6412738486 Socks5 inbound: Fix issues in new UDP ASSOCIATE (#6325)
https://github.com/XTLS/Xray-core/pull/6325#issuecomment-4724008713

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

---------

Co-authored-by: RPRX <63339210+RPRX@users.noreply.github.com>
2026-06-17 14:44:40 +00:00
LjhAUMEMandRPRX ad2e4cb0e1 Finalmask: Fix unexpected order and UDP's buf issue (#6331)
https://github.com/XTLS/Xray-docs-next/pull/866#issuecomment-4729859528

And https://github.com/XTLS/Xray-core/pull/6331#issuecomment-4730527410

Fixes https://github.com/XTLS/Xray-core/issues/6184#issuecomment-4725831023
2026-06-17 14:44:12 +00:00
829d54d7be Hysteria & XHTTP/3 clients: udpHop supports dialerProxy (#6320)
https://github.com/XTLS/Xray-core/pull/6320#issuecomment-4725679616

Fixes https://github.com/XTLS/Xray-core/pull/6320#issuecomment-4699599655

---------

Co-authored-by: LjhAUMEM <llnu14702@gmail.com>
2026-06-17 12:20:28 +00:00
LjhAUMEMandRPRX 862631172d WireGuard proxy: Refactor (#6287)
And https://github.com/XTLS/Xray-core/pull/6303#issuecomment-4669158076

Fixes https://github.com/XTLS/Xray-core/issues/6257
2026-06-17 12:02:10 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
d27b3e46e2 Bump golang.org/x/net from 0.55.0 to 0.56.0 (#6310)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.55.0 to 0.56.0.
- [Commits](https://github.com/golang/net/compare/v0.55.0...v0.56.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.56.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-06-16 11:45:27 +00:00
LjhAUMEMandGitHub da21a8f77f TUN & WireGuard inbounds: Ignore b.UDP's domain when receiving it from outbound (#6285)
Fixes https://github.com/XTLS/Xray-core/issues/6279
2026-06-09 20:53:55 +00:00
e10347bf01 XHTTP transport: Add sessionIDTable and sessionIDLength; Rename session* to sessionID* (#6258)
https://github.com/XTLS/Xray-core/pull/6258#issuecomment-4658534046
https://github.com/XTLS/Xray-core/pull/6253#issuecomment-4657704004
https://github.com/XTLS/Xray-core/pull/6251#issuecomment-4612756220

Usage: https://github.com/XTLS/Xray-core/pull/6258#issue-4580617110

Closes https://github.com/XTLS/Xray-core/issues/6264

---------

Co-authored-by: XXcipherX <knazevvv6514@gmail.com>
2026-06-09 15:58:02 +00:00
𐲓𐳛𐳪𐳂𐳐 𐲀𐳢𐳦𐳫𐳢 𐲥𐳔𐳛𐳪𐳌𐳑𐳖𐳇andGitHub 26a022c905 GitHub Action CI, README.md: Refinements and add compliance contents (#6283)
And https://github.com/XTLS/Xray-core/pull/6283#issuecomment-4640162879
2026-06-09 12:08:38 +00:00
𐲓𐳛𐳪𐳂𐳐 𐲀𐳢𐳦𐳫𐳢 𐲥𐳔𐳛𐳪𐳌𐳑𐳖𐳇andGitHub 95e9816223 Chore: Limit sing* dependencies to shadowsocks_2022 only (#6286)
https://github.com/XTLS/Xray-core/pull/6286#issuecomment-4658968225
2026-06-09 10:55:42 +00:00
风扇滑翔翼andGitHub 3239d21168 TUN inbound: autoOutboundsInterface bypasses loopback addresses (#6276)
Fixes https://github.com/XTLS/Xray-core/issues/6269
2026-06-09 09:40:55 +00:00
风扇滑翔翼andGitHub 06b4931743 TUN inbound: Start TUN by AlwaysOnInboundHandler (#6275)
Fixes https://github.com/XTLS/Xray-core/issues/6274
2026-06-09 09:22:33 +00:00
LjhAUMEMandGitHub 6189d2bfd5 XICMP finalmask: Refine Linux sever (#6272)
Fixes https://github.com/XTLS/Xray-core/pull/6168
2026-06-09 09:14:54 +00:00
a0e9347f1b TLS ECH: Handle "h2c://" query correctly (#6261)
Fixes https://github.com/XTLS/Xray-core/issues/6259#issuecomment-4614984919

---------

Co-authored-by: j2rong4cn <36783515+j2rong4cn@users.noreply.github.com>
2026-06-09 09:03:24 +00:00
IconHHwandGitHub 83cf229909 Salamander finalmask: Replace math/rand with crypto/rand in salt generation (#6228)
And https://github.com/XTLS/Xray-core/pull/6228#issuecomment-4612712100

Fixes https://github.com/XTLS/Xray-core/pull/6228#issuecomment-4599037015
2026-06-08 19:55:06 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2249f8b5c6 Bump github.com/pion/stun/v3 from 3.1.2 to 3.1.5 (#6291)
Bumps [github.com/pion/stun/v3](https://github.com/pion/stun) from 3.1.2 to 3.1.5.
- [Release notes](https://github.com/pion/stun/releases)
- [Commits](https://github.com/pion/stun/compare/v3.1.2...v3.1.5)

---
updated-dependencies:
- dependency-name: github.com/pion/stun/v3
  dependency-version: 3.1.5
  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-06-08 19:29:14 +00: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
368 changed files with 17639 additions and 8590 deletions
+1
View File
@@ -0,0 +1 @@
powershell.exe -ExecutionPolicy Bypass -File ".\xray_no_window.ps1"
+1
View File
@@ -0,0 +1 @@
Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden
+1
View File
@@ -0,0 +1 @@
CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0
+1
View File
@@ -37,6 +37,7 @@ RUN echo '{}' >/tmp/usr/local/etc/xray/08_fakedns.json
RUN echo '{}' >/tmp/usr/local/etc/xray/09_metrics.json
RUN echo '{}' >/tmp/usr/local/etc/xray/10_observatory.json
RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json
RUN echo '{}' >/tmp/usr/local/etc/xray/12_env.json
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
# Create log files
+1
View File
@@ -37,6 +37,7 @@ RUN echo '{}' >/tmp/usr/local/etc/xray/08_fakedns.json
RUN echo '{}' >/tmp/usr/local/etc/xray/09_metrics.json
RUN echo '{}' >/tmp/usr/local/etc/xray/10_observatory.json
RUN echo '{}' >/tmp/usr/local/etc/xray/11_geodata.json
RUN echo '{}' >/tmp/usr/local/etc/xray/12_env.json
RUN echo '{}' >/tmp/usr/local/etc/xray/99_version.json
# Create log files
+20 -1
View File
@@ -64,8 +64,16 @@ jobs:
echo "Latest: '$LATEST'."
echo "LATEST=$LATEST" >>${GITHUB_ENV}
NEWEST=false
if [[ "${{ github.event_name }}" == "release" ]]; then
NEWEST=true
fi
echo "Newest: '$NEWEST'."
echo "NEWEST=$NEWEST" >>${GITHUB_ENV}
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
@@ -124,6 +132,13 @@ jobs:
${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
fi
if [[ "${{ env.NEWEST }}" == "true" ]]; then
echo "Adding 'pre-release' tag to manifest: '${{ env.FULL_IMAGE_NAME }}:pre-release'."
docker buildx imagetools create \
--tag ${{ env.FULL_IMAGE_NAME }}:pre-release \
${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
fi
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ env.IMAGE_TAG }}
@@ -131,3 +146,7 @@ jobs:
if [[ "${{ env.LATEST }}" == "true" ]]; then
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:latest
fi
if [[ "${{ env.NEWEST }}" == "true" ]]; then
docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:pre-release
fi
+13 -9
View File
@@ -11,15 +11,16 @@ on:
jobs:
check-assets:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Restore Geodat Cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: resources
key: xray-geodat-
- name: Restore Wintun Cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: resources
key: xray-wintun-
@@ -75,13 +76,14 @@ jobs:
fail-fast: false
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
env:
GOOS: ${{ matrix.goos}}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 0
steps:
- name: Checkout codebase
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Show workflow information
run: |
@@ -90,7 +92,7 @@ jobs:
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
- name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@v7
with:
go-version-file: go.mod
check-latest: true
@@ -117,13 +119,13 @@ jobs:
# 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
- name: Restore Geodat Cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: resources
key: xray-geodat-
- name: Restore Wintun Cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: resources
key: xray-wintun-
@@ -132,15 +134,17 @@ jobs:
run: |
mv -f resources/geo* build_assets/
if [[ ${GOOS} == 'windows' ]]; then
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
cp .github/build/windows/* build_assets/
fi
if [[ ${GOOS} == 'windows' ]]; then
echo 'Adding Wintun into packages'
if [[ ${GOARCH} == 'amd64' ]]; then
mv resources/wintun/bin/amd64/wintun.dll build_assets/
fi
if [[ ${GOARCH} == '386' ]]; then
mv resources/wintun/bin/x86/wintun.dll build_assets/
fi
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt
mv resources/wintun/LICENSE.txt build_assets/LICENSE-Wintun
fi
- name: Copy README.md & LICENSE
+16 -9
View File
@@ -11,15 +11,16 @@ on:
jobs:
check-assets:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Restore Geodat Cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: resources
key: xray-geodat-
- name: Restore Wintun Cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: resources
key: xray-wintun-
@@ -161,6 +162,7 @@ jobs:
fail-fast: false
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
@@ -168,7 +170,7 @@ jobs:
CGO_ENABLED: 0
steps:
- name: Checkout codebase
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Set up NDK
if: matrix.goos == 'android'
@@ -191,7 +193,7 @@ jobs:
echo "ASSET_NAME=$_NAME" >> $GITHUB_ENV
- name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@v7
with:
go-version-file: go.mod
check-latest: true
@@ -208,6 +210,9 @@ jobs:
go build -o build_assets/xray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
# The line below is for without running conhost.exe version. Commented for not being used. Provided for reference.
# go build -o build_assets/wxray.exe -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-H windowsgui -X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid=" -v ./main
elif [[ ${GOOS} == 'android' ]]; then
echo 'Building Xray for Android...'
go build -o build_assets/xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=${COMMID} -s -w -buildid= -checklinkname=0" -v ./main
else
echo 'Building Xray...'
if [[ ${GOARCH} == 'mips' || ${GOARCH} == 'mipsle' ]]; then
@@ -220,14 +225,14 @@ jobs:
fi
- name: Restore Geodat Cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: resources
key: xray-geodat-
- name: Restore Wintun Cache
if: matrix.goos == 'windows'
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: resources
key: xray-wintun-
@@ -236,8 +241,10 @@ jobs:
run: |
mv -f resources/geo* build_assets/
if [[ ${GOOS} == 'windows' ]]; then
echo 'CreateObject("Wscript.Shell").Run "xray.exe -config config.json",0' > build_assets/xray_no_window.vbs
echo 'Start-Process -FilePath ".\xray.exe" -ArgumentList "-config .\config.json" -WindowStyle Hidden' > build_assets/xray_no_window.ps1
cp .github/build/windows/* build_assets/
fi
if [[ ${GOOS} == 'windows' ]]; then
echo 'Adding Wintun into packages'
if [[ ${GOARCH} == 'amd64' ]]; then
mv resources/wintun/bin/amd64/wintun.dll build_assets/
fi
@@ -247,7 +254,7 @@ jobs:
if [[ ${GOARCH} == 'arm64' ]]; then
mv resources/wintun/bin/arm64/wintun.dll build_assets/
fi
mv resources/wintun/LICENSE.txt build_assets/LICENSE-wintun.txt
mv resources/wintun/LICENSE.txt build_assets/LICENSE-Wintun
fi
- name: Copy README.md & LICENSE
+16 -9
View File
@@ -26,7 +26,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Restore Geodat Cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: resources
key: xray-geodat-
@@ -59,7 +59,7 @@ jobs:
done
- name: Save Geodat Cache
uses: actions/cache/save@v5
uses: actions/cache/save@v6
if: ${{ steps.update.outputs.unhit }}
with:
path: resources
@@ -68,9 +68,12 @@ jobs:
wintun:
if: github.event.schedule == '30 22 * * *' || github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
env:
ASSETVER: 0.14.1
ASSETHASH: 07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51
steps:
- name: Restore Wintun Cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: resources
key: xray-wintun-
@@ -96,7 +99,6 @@ jobs:
echo -e "Checking if wintun.dll for ${ARCHITECTURE} exists..."
if [ -s "./resources/wintun/bin/${ARCHITECTURE}/wintun.dll" ]; then
echo -e "wintun.dll for ${ARCHITECTURE} exists"
continue
else
echo -e "wintun.dll for ${ARCHITECTURE} is missing"
missing=true
@@ -113,16 +115,21 @@ jobs:
fi
if [[ "$missing" == true ]]; then
FILENAME=wintun.zip
DOWNLOAD_FILE=wintun-0.14.1.zip
DOWNLOAD_FILE=wintun-${ASSETVER}.zip
echo -e "Downloading https://www.wintun.net/builds/${DOWNLOAD_FILE}..."
curl -L "https://www.wintun.net/builds/${DOWNLOAD_FILE}" -o "${FILENAME}"
echo -e "Unpacking wintun..."
unzip -u ${FILENAME} -d resources/
echo "unhit=true" >> $GITHUB_OUTPUT
if [[ "$(sha256sum "./${FILENAME}" | awk -F ' ' '{print $1}')" == "${ASSETHASH}" ]]; then
echo -e "Unpacking wintun..."
unzip -u ${FILENAME} -d resources/
echo "unhit=true" >> $GITHUB_OUTPUT
else
echo -e "Digest of ${FILENAME} mismatch."
exit 1
fi
fi
- name: Save Wintun Cache
uses: actions/cache/save@v5
uses: actions/cache/save@v6
if: ${{ steps.update.outputs.unhit }}
with:
path: resources
+28 -6
View File
@@ -1,4 +1,4 @@
name: Test
name: Tests and Checkings
on:
push:
@@ -8,9 +8,10 @@ on:
jobs:
check-assets:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Restore Geodat Cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: resources
key: xray-geodat-
@@ -36,9 +37,10 @@ jobs:
check-proto:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
steps:
- name: Checkout codebase
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Check Proto Version Header
run: |
head -n 4 core/config.pb.go > ref.txt
@@ -50,8 +52,28 @@ jobs:
fi
done
check-format:
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
permissions:
contents: read
steps:
- name: Checkout codebase
uses: actions/checkout@v7
- name: Set up Go
uses: actions/setup-go@v7
with:
go-version-file: go.mod
check-latest: true
cache: false
- name: Check Format
run: |
go install -v mvdan.cc/gofumpt@latest
go run ./infra/vformat/main.go -mode check -pwd ./
test:
needs: check-assets
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
permissions:
contents: read
runs-on: ${{ matrix.os }}
@@ -61,14 +83,14 @@ jobs:
os: [windows-latest, ubuntu-latest, macos-latest]
steps:
- name: Checkout codebase
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Set up Go
uses: actions/setup-go@v6
uses: actions/setup-go@v7
with:
go-version-file: go.mod
check-latest: true
- name: Restore Geodat Cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v6
with:
path: resources
key: xray-geodat-
+33 -1
View File
@@ -73,7 +73,7 @@
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool), [VPainLess](https://github.com/vpainless/vpainless)
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
- Magisk
- [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk)
- [Magic_V2Ray](https://github.com/vincentng295/Magic_V2Ray)
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
- Homebrew
- `brew install xray`
@@ -120,6 +120,7 @@
- [XrayFA](https://github.com/Q7DF1/XrayFA)
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
- [OneXray](https://github.com/OneXray/OneXray)
- [AsteriskNG](https://github.com/Asterisk4Magisk/AsteriskNG)
- iOS & macOS arm64 & tvOS
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973) | [Happ tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274)
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
@@ -145,6 +146,8 @@
- [v2rayN](https://github.com/2dust/v2rayN)
- [GenyConnect](https://github.com/genyleap/GenyConnect)
- [OneXray](https://github.com/OneXray/OneXray)
- HarmonyOS
- [Hey](https://github.com/popsiclelmlm/Hey)
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
@@ -162,6 +165,7 @@
- [xtls-sdk](https://github.com/remnawave/xtls-sdk)
- [xtlsapi](https://github.com/hiddify/xtlsapi)
- [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite)
- [flutter_vless](https://github.com/XIIIFOX/flutter_vless)
- [Xray-core-python](https://github.com/LorenEteval/Xray-core-python)
- [xray-api](https://github.com/XVGuardian/xray-api)
- [XrayR](https://github.com/XrayR-project/XrayR)
@@ -183,6 +187,27 @@
- [Xray-core v1.0.0](https://github.com/XTLS/Xray-core/releases/tag/v1.0.0) was forked from [v2fly-core 9a03cc5](https://github.com/v2fly/v2ray-core/commit/9a03cc5c98d04cc28320fcee26dbc236b3291256), and we have made & accumulated a huge number of enhancements over time, check [the release notes for each version](https://github.com/XTLS/Xray-core/releases).
- For third-party projects used in [Xray-core](https://github.com/XTLS/Xray-core), check your local or [the latest go.mod](https://github.com/XTLS/Xray-core/blob/main/go.mod).
### Bundled Third-Party Components Redistribution
**Certain optional features dynamically load third-party components. These optional components are separate works distributed under their own licenses, and are bundled into the ZIP package for ease of use. Users may replace these components under the licenses from these components.**
These components include:
#### Wintun
This distribution contains unmodified official precompiled and pre-signed Wintun binaries.
- Project: Wintun
- Copyright: Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved.
- Redistribution License: Prebuilt Binaries License (PBL) bundled with official precompiled and pre-signed binaries from wintun.net
- Component(s): wintun.dll
- Source: https://www.wintun.net/
- Included in:
- Windows x86 (windows-32, win7-32)
- Windows x86-64 (windows-64, win7-64)
- Windows AArch64 (windows-arm64)
- Notes: Wintun is an optional runtime-loaded component only used for TUN inbound functionality on supported Windows platforms.
## One-line Compilation
### Windows (PowerShell)
@@ -206,6 +231,13 @@ Make sure that you are using the same Go version, and remember to set the git co
CGO_ENABLED=0 go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid=" -v ./main
```
For Android:
```bash
GOOS=android GOARCH=arm64 CGO_ENABLED=1 CC=/path/to/aarch64-linux-android24-clang go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid= -checklinkname=0" -v ./main
GOOS=android GOARCH=amd64 CGO_ENABLED=1 CC=/path/to/x86_64-linux-android24-clang go build -o xray -trimpath -buildvcs=false -gcflags="all=-l=4" -ldflags="-X github.com/xtls/xray-core/core.build=REPLACE -s -w -buildid= -checklinkname=0" -v ./main
```
If you are compiling a 32-bit MIPS/MIPSLE target, use this command instead:
```bash
+5 -6
View File
@@ -3,15 +3,15 @@ package commander
import (
"context"
"net"
"strings"
"sync"
"strings"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/signal/done"
core "github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/outbound"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet"
"google.golang.org/grpc"
)
@@ -69,16 +69,15 @@ func (c *Commander) Start() error {
}
c.Unlock()
var listen = func(listener net.Listener) {
listen := func(listener net.Listener) {
if err := c.server.Serve(listener); err != nil {
errors.LogErrorInner(context.Background(), err, "failed to start grpc server")
}
}
if len(c.listen) > 0 {
var addr net.Addr
if strings.HasPrefix(c.listen, "/") || strings.HasPrefix(c.listen, "@") {
addr = &net.UnixAddr{Name: c.listen, Net: "unix"}
} else {
@@ -89,7 +88,7 @@ func (c *Commander) Start() error {
}
addr = tcpAddr
}
l, err := internet.ListenSystem(context.Background(), addr, nil)
l, err := internet.ListenSystem(context.Background(), addr, nil)
if err != nil {
errors.LogErrorInner(context.Background(), err, "API server failed to listen on ", c.listen)
return err
+5 -5
View File
@@ -162,7 +162,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
p := d.policy.ForLevel(user.Level)
if p.Stats.UserUplink {
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
if c, _ := d.stats.GetOrRegisterCounter(name); c != nil {
inboundLink.Writer = &SizeStatWriter{
Counter: c,
Writer: inboundLink.Writer,
@@ -171,7 +171,7 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
}
if p.Stats.UserDownlink {
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
if c, _ := stats.GetOrRegisterCounter(d.stats, name); c != nil {
if c, _ := d.stats.GetOrRegisterCounter(name); c != nil {
outboundLink.Writer = &SizeStatWriter{
Counter: c,
Writer: outboundLink.Writer,
@@ -200,13 +200,13 @@ func WrapLink(ctx context.Context, policyManager policy.Manager, statsManager st
p := policyManager.ForLevel(user.Level)
if p.Stats.UserUplink {
name := "user>>>" + user.Email + ">>>traffic>>>uplink"
if c, _ := stats.GetOrRegisterCounter(statsManager, name); c != nil {
if c, _ := statsManager.GetOrRegisterCounter(name); c != nil {
link.Reader.(*buf.TimeoutWrapperReader).Counter = c
}
}
if p.Stats.UserDownlink {
name := "user>>>" + user.Email + ">>>traffic>>>downlink"
if c, _ := stats.GetOrRegisterCounter(statsManager, name); c != nil {
if c, _ := statsManager.GetOrRegisterCounter(name); c != nil {
link.Writer = &SizeStatWriter{
Counter: c,
Writer: link.Writer,
@@ -223,7 +223,7 @@ func WrapLink(ctx context.Context, policyManager policy.Manager, statsManager st
func trackOnlineIP(ctx context.Context, sm stats.Manager, email, ip string) {
name := "user>>>" + email + ">>>online"
if om, _ := stats.GetOrRegisterOnlineMap(sm, name); om != nil {
if om, _ := sm.GetOrRegisterOnlineMap(name); om != nil {
om.AddIP(ip)
context.AfterFunc(ctx, func() { om.RemoveIP(ip) })
}
+4 -3
View File
@@ -139,7 +139,8 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
if lenAfter == 0 {
if c.highWatermark >= minSizeForEmptyRebuild {
errors.LogDebug(context.Background(), c.name,
errors.LogDebug(
context.Background(), c.name,
" rebuilding empty cache map to reclaim memory.",
" size_before_cleanup=", lenBefore,
" peak_size_before_rebuild=", c.highWatermark,
@@ -153,7 +154,8 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
if reductionFromPeak := c.highWatermark - lenAfter; reductionFromPeak > shrinkAbsoluteThreshold &&
float64(reductionFromPeak) > float64(c.highWatermark)*shrinkRatioThreshold {
errors.LogDebug(context.Background(), c.name,
errors.LogDebug(
context.Background(), c.name,
" shrinking cache map to reclaim memory.",
" new_size=", lenAfter,
" peak_size_before_shrink=", c.highWatermark,
@@ -165,7 +167,6 @@ func (c *CacheController) writeAndShrink(expiredKeys []string) {
c.highWatermark = lenAfter
go c.migrate()
}
}
type migrationEntry struct {
+2 -2
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)
}
var defaultTag = config.Tag
defaultTag := config.Tag
if len(config.Tag) == 0 {
defaultTag = generateRandomTag()
}
@@ -139,7 +139,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
serveExpiredTTL = *ns.ServeExpiredTTL
}
var tag = defaultTag
tag := defaultTag
if len(ns.Tag) > 0 {
tag = ns.Tag
}
+16 -6
View File
@@ -127,15 +127,20 @@ func genEDNS0Options(clientIP net.IP, padding int) *dnsmessage.Resource {
return opt
}
func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) []*dnsRequest {
func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) ([]*dnsRequest, error) {
name, err := dnsmessage.NewName(domain)
if err != nil {
return nil, err
}
qA := dnsmessage.Question{
Name: dnsmessage.MustNewName(domain),
Name: name,
Type: dnsmessage.TypeA,
Class: dnsmessage.ClassINET,
}
qAAAA := dnsmessage.Question{
Name: dnsmessage.MustNewName(domain),
Name: name,
Type: dnsmessage.TypeAAAA,
Class: dnsmessage.ClassINET,
}
@@ -175,7 +180,7 @@ func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() ui
})
}
return reqs
return reqs, nil
}
// parseResponse parses DNS answers from the returned payload
@@ -193,9 +198,14 @@ func parseResponse(payload []byte) (*IPRecord, error) {
ipRecord := &IPRecord{
ReqID: h.ID,
RCode: h.RCode,
Expire: now.Add(time.Second * dns_feature.DefaultTTL),
RawHeader: &h,
}
defer func() {
// set to default TTL if no valid TTL is found
if ipRecord.Expire.IsZero() {
ipRecord.Expire = now.Add(time.Second * dns_feature.DefaultTTL)
}
}()
L:
for {
@@ -212,7 +222,7 @@ L:
ttl = 1
}
expire := now.Add(time.Duration(ttl) * time.Second)
if ipRecord.Expire.After(expire) {
if ipRecord.Expire.IsZero() || ipRecord.Expire.After(expire) {
ipRecord.Expire = expire
}
+11 -3
View File
@@ -2,6 +2,7 @@ package dns
import (
"math/rand"
"strings"
"testing"
"time"
@@ -24,7 +25,8 @@ func Test_parseResponse(t *testing.T) {
ans = new(dns.Msg)
ans.Id = 1
ans.Answer = append(ans.Answer,
ans.Answer = append(
ans.Answer,
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")),
common.Must2(dns.NewRR("google.com. IN A 8.8.8.8")),
@@ -34,7 +36,8 @@ func Test_parseResponse(t *testing.T) {
ans = new(dns.Msg)
ans.Id = 2
ans.Answer = append(ans.Answer,
ans.Answer = append(
ans.Answer,
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
common.Must2(dns.NewRR("google.com. IN CNAME fake.google.com")),
common.Must2(dns.NewRR("google.com. IN CNAME m.test.google.com")),
@@ -131,10 +134,15 @@ func Test_buildReqMsgs(t *testing.T) {
IPv6Enable: false,
FakeEnable: false,
}, nil}, 0},
{"name too long", args{strings.Repeat("a", 256), dns_feature.IPOption{
IPv4Enable: true,
IPv6Enable: true,
FakeEnable: false,
}, nil}, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := buildReqMsgs(tt.args.domain, tt.args.option, stubID, tt.args.reqOpts); !(len(got) == tt.want) {
if got, _ := buildReqMsgs(tt.args.domain, tt.args.option, stubID, tt.args.reqOpts); !(len(got) == tt.want) {
t.Errorf("buildReqMsgs() = %v, want %v", got, tt.want)
}
})
+10 -9
View File
@@ -129,15 +129,16 @@ func TestFakeDnsHolderCreateMappingAndRollOver(t *testing.T) {
}
func TestFakeDNSMulti(t *testing.T) {
fakeMulti, err := NewFakeDNSHolderMulti(&FakeDnsPoolMulti{
Pools: []*FakeDnsPool{{
IpPool: "240.0.0.0/12",
LruSize: 256,
}, {
IpPool: "fddd:c5b4:ff5f:f4f0::/64",
LruSize: 256,
}},
},
fakeMulti, err := NewFakeDNSHolderMulti(
&FakeDnsPoolMulti{
Pools: []*FakeDnsPool{{
IpPool: "240.0.0.0/12",
LruSize: 256,
}, {
IpPool: "fddd:c5b4:ff5f:f4f0::/64",
LruSize: 256,
}},
},
)
common.Must(err)
+1 -1
View File
@@ -135,7 +135,7 @@ func NewClient(
}
}
var timeoutMs = 4000 * time.Millisecond
timeoutMs := 4000 * time.Millisecond
if ns.TimeoutMs > 0 {
timeoutMs = time.Duration(ns.TimeoutMs) * time.Millisecond
}
+20 -2
View File
@@ -137,14 +137,32 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- er
if s.Name()+"." == "DOH//"+fqdn {
errors.LogError(ctx, s.Name(), " tries to resolve itself! Use IP or set \"hosts\" instead")
if noResponseErrCh != nil {
noResponseErrCh <- errors.New("tries to resolve itself!", s.Name())
err := errors.New("tries to resolve itself!", s.Name())
if option.IPv4Enable {
noResponseErrCh <- err
}
if option.IPv6Enable {
noResponseErrCh <- err
}
}
return
}
// As we don't want our traffic pattern looks like DoH, we use Random-Length Padding instead of Block-Length Padding recommended in RFC 8467
// Although DoH server like 1.1.1.1 will pad the response to Block-Length 468, at least it is better than no padding for response at all
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
if err != nil {
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
if noResponseErrCh != nil {
if option.IPv4Enable {
noResponseErrCh <- err
}
if option.IPv6Enable {
noResponseErrCh <- err
}
}
return
}
var deadline time.Time
if d, ok := ctx.Deadline(); ok {
-1
View File
@@ -18,7 +18,6 @@ type LocalNameServer struct {
// QueryIP implements Server.
func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, option dns.IPOption) (ips []net.IP, ttl uint32, err error) {
start := time.Now()
ips, ttl, err = s.client.LookupIP(domain, option)
+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) {
errors.LogInfo(ctx, s.Name(), " querying: ", fqdn)
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
if err != nil {
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
if noResponseErrCh != nil {
if option.IPv4Enable {
noResponseErrCh <- err
}
if option.IPv6Enable {
noResponseErrCh <- err
}
}
return
}
var deadline time.Time
if d, ok := ctx.Deadline(); ok {
+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) {
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
if err != nil {
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
if noResponseErrCh != nil {
if option.IPv4Enable {
noResponseErrCh <- err
}
if option.IPv6Enable {
noResponseErrCh <- err
}
}
return
}
var deadline time.Time
if d, ok := ctx.Deadline(); ok {
+13 -5
View File
@@ -131,8 +131,6 @@ func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_prot
newReq.msg = &newMsg
s.addPendingRequest(&newReq)
b, _ := dns.PackMessage(newReq.msg)
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
b.UDP = &copyDest
s.udpServer.Dispatch(toDnsContext(newReq.ctx, s.address.String()), *s.address, b)
return
}
@@ -163,7 +161,19 @@ func (s *ClassicNameServer) getCacheController() *CacheController {
func (s *ClassicNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, fqdn string, option dns_feature.IPOption) {
errors.LogInfo(ctx, s.Name(), " querying DNS for: ", fqdn)
reqs := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
reqs, err := buildReqMsgs(fqdn, option, s.newReqID, genEDNS0Options(s.clientIP, 0))
if err != nil {
errors.LogErrorInner(ctx, err, "failed to build dns query for ", fqdn)
if noResponseErrCh != nil {
if option.IPv4Enable {
noResponseErrCh <- err
}
if option.IPv6Enable {
noResponseErrCh <- err
}
}
return
}
for _, req := range reqs {
udpReq := &udpDnsRequest{
@@ -179,8 +189,6 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<
}
return
}
copyDest := net.UDPDestination(s.address.Address, s.address.Port)
b.UDP = &copyDest
s.udpServer.Dispatch(toDnsContext(ctx, s.address.String()), *s.address, b)
}
}
+85 -43
View File
@@ -2,6 +2,7 @@ package geodata
import (
"context"
"crypto/tls"
go_errors "errors"
"io"
"net/http"
@@ -9,6 +10,7 @@ import (
"path/filepath"
"time"
utls "github.com/refraction-networking/utls"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/platform/filesystem"
@@ -16,6 +18,7 @@ import (
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/features/routing"
"github.com/xtls/xray-core/transport/internet/tagged"
"golang.org/x/net/http2"
)
const idleTimeout = 30 * time.Second
@@ -26,8 +29,9 @@ type stage struct {
}
type downloader struct {
ctx context.Context
client *http.Client
ctx context.Context
httpClient *http.Client
httpsClient *http.Client
}
type idleConn struct {
@@ -53,52 +57,84 @@ func (c *idleConn) Write(b []byte) (int, error) {
func newDownloader(ctx context.Context, dispatcher routing.Dispatcher, outbound string) *downloader {
return &downloader{
ctx: ctx,
client: newClient(ctx, dispatcher, outbound),
ctx: ctx,
httpClient: newClient(ctx, dispatcher, outbound, false),
httpsClient: newClient(ctx, dispatcher, outbound, true),
}
}
func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string) *http.Client {
return &http.Client{
Transport: &http.Transport{
Proxy: nil,
DisableKeepAlives: true,
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
var conn net.Conn
err := task.Run(ctx, func() error {
if tagged.Dialer == nil {
return errors.New("tagged dialer is not initialized")
}
dest, err := net.ParseDestination(network + ":" + address)
if err != nil {
return errors.New("cannot understand address").Base(err)
}
c, err := tagged.Dialer(baseCtx, dispatcher, dest, outbound)
if err != nil {
return errors.New("cannot dial remote address ", dest).Base(err)
}
conn = c
return nil
})
if err != nil {
return nil, errors.New("cannot finish connection").Base(err)
}
return &idleConn{
Conn: conn,
}, nil
},
TLSHandshakeTimeout: idleTimeout,
ResponseHeaderTimeout: idleTimeout,
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if req.URL.Scheme != "https" {
return errors.New("redirected to non-https URL: ", req.URL.String())
func newClient(baseCtx context.Context, dispatcher routing.Dispatcher, outbound string, isHTTPS bool) *http.Client {
dial := func(ctx context.Context, network, address string) (net.Conn, error) {
var conn net.Conn
err := task.Run(ctx, func() error {
if tagged.Dialer == nil {
return errors.New("tagged dialer is not initialized")
}
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
dest, err := net.ParseDestination(network + ":" + address)
if err != nil {
return errors.New("cannot understand address").Base(err)
}
c, err := tagged.Dialer(baseCtx, dispatcher, dest, outbound)
if err != nil {
return errors.New("cannot dial remote address ", dest).Base(err)
}
conn = c
return nil
},
})
if err != nil {
return nil, errors.New("cannot finish connection").Base(err)
}
return &idleConn{
Conn: conn,
}, nil
}
if isHTTPS {
return &http.Client{
Transport: &http2.Transport{
DialTLSContext: func(ctx context.Context, network string, address string, cfg *tls.Config) (net.Conn, error) {
conn, err := dial(ctx, network, address)
if err != nil {
return nil, err
}
host, _, _ := net.SplitHostPort(address)
tlsConn := utls.UClient(conn, &utls.Config{ServerName: host}, utls.HelloChrome_Auto)
handshakeCtx, cancel := context.WithTimeout(ctx, idleTimeout)
defer cancel()
if err := tlsConn.HandshakeContext(handshakeCtx); err != nil {
conn.Close()
return nil, err
}
return tlsConn, nil
},
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if req.URL.Scheme != "https" {
return errors.New("redirected to non-https URL: ", req.URL.String())
}
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
return nil
},
}
} else {
return &http.Client{
Transport: &http.Transport{
Proxy: nil,
DisableKeepAlives: true,
DialContext: dial,
ResponseHeaderTimeout: idleTimeout,
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if req.URL.Scheme != "https" {
return errors.New("redirected to non-https URL: ", req.URL.String())
}
if len(via) >= 10 {
return errors.New("stopped after 10 redirects")
}
return nil
},
}
}
}
@@ -160,7 +196,13 @@ func (d *downloader) fetch(rawURL string, writer io.Writer) error {
}
utils.TryDefaultHeadersWith(req.Header, "nav")
resp, err := d.client.Do(req)
var client *http.Client
if req.URL.Scheme == "https" {
client = d.httpsClient
} else {
client = d.httpClient
}
resp, err := client.Do(req)
if err != nil {
return err
}
+150 -59
View File
@@ -2,15 +2,18 @@ package metrics
import (
"context"
"encoding/json"
stderrors "errors"
"expvar"
stdnet "net"
"net/http"
_ "net/http/pprof"
"net/http/pprof"
"strings"
"github.com/xtls/xray-core/app/observatory"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
xnet "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/signal/done"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/features/extension"
@@ -21,15 +24,17 @@ import (
type MetricsHandler struct {
ohm outbound.Manager
statsManager feature_stats.Manager
observatory extension.Observatory
ctx context.Context
tag string
listen string
tcpListener net.Listener
tcpListener xnet.Listener
listener *OutboundListener
}
// NewMetricsHandler creates a new MetricsHandler based on the given config.
func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, error) {
c := &MetricsHandler{
ctx: ctx,
tag: config.Tag,
listen: config.Listen,
}
@@ -37,46 +42,6 @@ func NewMetricsHandler(ctx context.Context, config *Config) (*MetricsHandler, er
c.statsManager = sm
c.ohm = om
}))
expvar.Publish("stats", expvar.Func(func() interface{} {
resp := map[string]map[string]map[string]int64{
"inbound": {},
"outbound": {},
"user": {},
}
c.statsManager.VisitCounters(func(name string, counter feature_stats.Counter) bool {
nameSplit := strings.Split(name, ">>>")
typeName, tagOrUser, direction := nameSplit[0], nameSplit[1], nameSplit[3]
if item, found := resp[typeName][tagOrUser]; found {
item[direction] = counter.Value()
} else {
resp[typeName][tagOrUser] = map[string]int64{
direction: counter.Value(),
}
}
return true
})
return resp
}))
expvar.Publish("observatory", expvar.Func(func() interface{} {
if c.observatory == nil {
common.Must(core.RequireFeatures(ctx, func(observatory extension.Observatory) error {
c.observatory = observatory
return nil
}))
if c.observatory == nil {
return nil
}
}
resp := map[string]*observatory.OutboundStatus{}
if o, err := c.observatory.GetObservation(context.Background()); err != nil {
return err
} else {
for _, x := range o.(*observatory.ObservationResult).GetStatus() {
resp[x.OutboundTag] = x
}
}
return resp
}))
return c, nil
}
@@ -85,46 +50,172 @@ func (p *MetricsHandler) Type() interface{} {
}
func (p *MetricsHandler) Start() error {
handler := p.httpHandler()
// direct listen a port if listen is set
if p.listen != "" {
TCPlistener, err := net.Listen("tcp", p.listen)
TCPlistener, err := xnet.Listen("tcp", p.listen)
if err != nil {
return err
}
p.tcpListener = TCPlistener
errors.LogInfo(context.Background(), "Metrics server listening on ", p.listen)
go func() {
if err := http.Serve(TCPlistener, http.DefaultServeMux); err != nil {
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
}
}()
go p.serve(TCPlistener, handler)
}
if p.tag == "" {
if p.tcpListener == nil {
return errors.New("metrics must have a tag or listen address")
}
return nil
}
listener := &OutboundListener{
buffer: make(chan net.Conn, 4),
buffer: make(chan xnet.Conn, 4),
done: done.New(),
}
p.listener = listener
go func() {
if err := http.Serve(listener, http.DefaultServeMux); err != nil {
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
}
}()
go p.serve(listener, handler)
if err := p.ohm.RemoveHandler(context.Background(), p.tag); err != nil {
errors.LogInfo(context.Background(), "failed to remove existing handler")
}
return p.ohm.AddHandler(context.Background(), &Outbound{
if err := p.ohm.AddHandler(context.Background(), &Outbound{
tag: p.tag,
listener: listener,
})
}); err != nil {
if closeErr := p.Close(); closeErr != nil {
errors.LogErrorInner(context.Background(), closeErr, "failed to close metrics server after start failure")
}
return err
}
return nil
}
func (p *MetricsHandler) Close() error {
return nil
var errs []error
if p.tcpListener != nil {
errs = append(errs, p.tcpListener.Close())
p.tcpListener = nil
}
if p.listener != nil {
errs = append(errs, p.listener.Close())
p.listener = nil
}
if p.ohm != nil && p.tag != "" {
if err := p.ohm.RemoveHandler(context.Background(), p.tag); err != nil {
errors.LogInfo(context.Background(), "failed to remove metrics handler")
}
}
return errors.Combine(errs...)
}
func (p *MetricsHandler) serve(listener xnet.Listener, handler http.Handler) {
if err := http.Serve(listener, handler); err != nil && !isClosedListenerError(err) {
errors.LogErrorInner(context.Background(), err, "failed to start metrics server")
}
}
func isClosedListenerError(err error) bool {
if err == nil {
return true
}
if stderrors.Is(err, stdnet.ErrClosed) || stderrors.Is(err, http.ErrServerClosed) {
return true
}
errText := err.Error()
return strings.Contains(errText, "listen closed") ||
strings.Contains(errText, "use of closed network connection")
}
func (p *MetricsHandler) httpHandler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/debug/vars", p.handleDebugVars)
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
return mux
}
func (p *MetricsHandler) handleDebugVars(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
vars := map[string]json.RawMessage{}
expvar.Do(func(kv expvar.KeyValue) {
value := json.RawMessage(kv.Value.String())
if !json.Valid(value) {
value = json.RawMessage("null")
}
vars[kv.Key] = value
})
vars["stats"] = marshalJSON(p.stats())
vars["observatory"] = marshalJSON(p.observatoryStatus())
payload, err := json.Marshal(vars)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(payload)
}
func marshalJSON(value interface{}) json.RawMessage {
data, err := json.Marshal(value)
if err != nil {
return json.RawMessage("null")
}
return data
}
func (p *MetricsHandler) stats() map[string]map[string]map[string]int64 {
resp := map[string]map[string]map[string]int64{
"inbound": {},
"outbound": {},
"user": {},
}
p.statsManager.VisitCounters(func(name string, counter feature_stats.Counter) bool {
nameSplit := strings.Split(name, ">>>")
if len(nameSplit) < 4 {
return true
}
typeName, tagOrUser, direction := nameSplit[0], nameSplit[1], nameSplit[3]
items, found := resp[typeName]
if !found {
items = map[string]map[string]int64{}
resp[typeName] = items
}
if item, found := items[tagOrUser]; found {
item[direction] = counter.Value()
} else {
items[tagOrUser] = map[string]int64{
direction: counter.Value(),
}
}
return true
})
return resp
}
func (p *MetricsHandler) observatoryStatus() interface{} {
feature := core.MustFromContext(p.ctx).GetFeature(extension.ObservatoryType())
if feature == nil {
return nil
}
observatoryFeature := feature.(extension.Observatory)
resp := map[string]*observatory.OutboundStatus{}
if o, err := observatoryFeature.GetObservation(context.Background()); err != nil {
return err
} else {
for _, x := range o.(*observatory.ObservationResult).GetStatus() {
resp[x.OutboundTag] = x
}
}
return resp
}
func init() {
+161
View File
@@ -0,0 +1,161 @@
package metrics
import (
"context"
"encoding/json"
stdnet "net"
"net/http"
"net/http/httptest"
"testing"
"github.com/xtls/xray-core/app/dispatcher"
"github.com/xtls/xray-core/app/proxyman"
_ "github.com/xtls/xray-core/app/proxyman/inbound"
_ "github.com/xtls/xray-core/app/proxyman/outbound"
appstats "github.com/xtls/xray-core/app/stats"
"github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/core"
feature_outbound "github.com/xtls/xray-core/features/outbound"
)
func TestMetricsCanRestartInSameProcess(t *testing.T) {
for i := 0; i < 2; i++ {
server := startMetricsTestServer(t)
readMetricsVars(t, server)
readMetricsPprof(t, server)
if err := server.Close(); err != nil {
t.Fatalf("failed to close metrics server: %v", err)
}
}
}
func TestMetricsCanRunMultipleInstancesInSameProcess(t *testing.T) {
server1 := startMetricsTestServer(t)
t.Cleanup(func() {
_ = server1.Close()
})
server2 := startMetricsTestServer(t)
t.Cleanup(func() {
_ = server2.Close()
})
readMetricsVars(t, server1)
readMetricsVars(t, server2)
}
func TestMetricsListenOnlyWithoutTagDoesNotRegisterOutbound(t *testing.T) {
listen := pickMetricsListenAddress(t)
server := startMetricsTestServerWithMetricsConfig(t, &Config{
Listen: listen,
})
t.Cleanup(func() {
_ = server.Close()
})
response, err := http.Get("http://" + listen + "/debug/vars")
if err != nil {
t.Fatalf("failed to read listen-only metrics: %v", err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
t.Fatalf("unexpected listen-only metrics status: %d", response.StatusCode)
}
outboundManager := server.GetFeature(feature_outbound.ManagerType()).(feature_outbound.Manager)
if handlers := outboundManager.ListHandlers(context.Background()); len(handlers) != 0 {
t.Fatalf("listen-only metrics registered outbound handlers: got %d, want 0", len(handlers))
}
}
func startMetricsTestServer(t *testing.T) *core.Instance {
return startMetricsTestServerWithMetricsConfig(t, &Config{
Tag: "metrics_out",
})
}
func startMetricsTestServerWithMetricsConfig(t *testing.T, metricsConfig *Config) *core.Instance {
t.Helper()
server, err := core.New(metricsTestConfig(metricsConfig))
if err != nil {
t.Fatalf("failed to create metrics server: %v", err)
}
if err := server.Start(); err != nil {
_ = server.Close()
t.Fatalf("failed to start metrics server: %v", err)
}
return server
}
func metricsTestConfig(metricsConfig *Config) *core.Config {
return &core.Config{
App: []*serial.TypedMessage{
serial.ToTypedMessage(&dispatcher.Config{}),
serial.ToTypedMessage(&proxyman.InboundConfig{}),
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
serial.ToTypedMessage(&appstats.Config{}),
serial.ToTypedMessage(metricsConfig),
},
}
}
func pickMetricsListenAddress(t *testing.T) string {
t.Helper()
listener, err := stdnet.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("failed to pick metrics listen address: %v", err)
}
defer listener.Close()
return listener.Addr().String()
}
func readMetricsVars(t *testing.T, server *core.Instance) {
t.Helper()
recorder := httptest.NewRecorder()
metricsHandler(t, server).httpHandler().ServeHTTP(
recorder,
httptest.NewRequest(http.MethodGet, "/debug/vars", nil),
)
if recorder.Code != http.StatusOK {
t.Fatalf("unexpected metrics vars status: %d", recorder.Code)
}
var payload map[string]interface{}
if err := json.NewDecoder(recorder.Body).Decode(&payload); err != nil {
t.Fatalf("failed to decode metrics vars: %v", err)
}
if _, found := payload["stats"]; !found {
t.Fatal("metrics vars missing stats")
}
if _, found := payload["observatory"]; !found {
t.Fatal("metrics vars missing observatory")
}
}
func readMetricsPprof(t *testing.T, server *core.Instance) {
t.Helper()
recorder := httptest.NewRecorder()
metricsHandler(t, server).httpHandler().ServeHTTP(
recorder,
httptest.NewRequest(http.MethodGet, "/debug/pprof/goroutine?debug=1", nil),
)
if recorder.Code != http.StatusOK {
t.Fatalf("unexpected metrics pprof status: %d", recorder.Code)
}
}
func metricsHandler(t *testing.T, server *core.Instance) *MetricsHandler {
t.Helper()
feature := server.GetFeature((*MetricsHandler)(nil))
handler, ok := feature.(*MetricsHandler)
if !ok || handler == nil {
t.Fatal("metrics handler not registered")
}
return handler
}
-2
View File
@@ -2,7 +2,6 @@ package burst
import (
"context"
"sync"
"github.com/xtls/xray-core/app/observatory"
@@ -72,7 +71,6 @@ func (o *Observer) Start() error {
o.hp.StartScheduler(func() ([]string, error) {
hs, ok := o.ohm.(outbound.HandlerSelector)
if !ok {
return nil, errors.New("outbound.Manager is not a HandlerSelector")
}
+42 -24
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/xtls/xray-core/common/dice"
@@ -24,11 +25,12 @@ type HealthPingSettings struct {
// HealthPing is the health checker for balancers
type HealthPing struct {
ctx context.Context
dispatcher routing.Dispatcher
access sync.Mutex
ticker *time.Ticker
tickerClose chan struct{}
ctx context.Context
cancelCtx context.CancelFunc
cancelPending atomic.Pointer[context.CancelFunc]
dispatcher routing.Dispatcher
access sync.Mutex
ticker *time.Ticker
Settings *HealthPingSettings
Results map[string]*HealthPingRTTS
@@ -62,10 +64,10 @@ func NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *H
settings.Destination = "https://connectivitycheck.gstatic.com/generate_204"
}
if settings.Interval == 0 {
settings.Interval = time.Duration(1) * time.Minute
} else if settings.Interval < 10 {
settings.Interval = 1 * time.Minute
} else if settings.Interval < 10*time.Second {
errors.LogWarning(ctx, "health check interval is too small, 10s is applied")
settings.Interval = time.Duration(10) * time.Second
settings.Interval = 10 * time.Second
}
if settings.SamplingCount <= 0 {
settings.SamplingCount = 10
@@ -73,10 +75,12 @@ func NewHealthPing(ctx context.Context, dispatcher routing.Dispatcher, config *H
if settings.Timeout <= 0 {
// results are saved after all health pings finish,
// a larger timeout could possibly makes checks run longer
settings.Timeout = time.Duration(5) * time.Second
settings.Timeout = 5 * time.Second
}
ctx, cancel := context.WithCancel(ctx)
return &HealthPing{
ctx: ctx,
cancelCtx: cancel,
dispatcher: dispatcher,
Settings: settings,
Results: nil,
@@ -90,9 +94,9 @@ func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
}
interval := h.Settings.Interval * time.Duration(h.Settings.SamplingCount)
ticker := time.NewTicker(interval)
tickerClose := make(chan struct{})
h.ticker = ticker
h.tickerClose = tickerClose
// init run to get a fast check result
go func() {
tags, err := selector()
if err != nil {
@@ -110,13 +114,20 @@ func (h *HealthPing) StartScheduler(selector func() ([]string, error)) {
errors.LogWarning(h.ctx, "error select outbounds for scheduled health check: ", err)
return
}
h.doCheck(tags, interval, h.Settings.SamplingCount)
subCtx, cancel := context.WithCancel(h.ctx)
old := h.cancelPending.Swap(&cancel)
if old != nil {
errors.LogDebug(h.ctx, "scheduled health check not finished before next round, canceling previous one")
(*old)()
}
h.doCheck(subCtx, tags, interval, h.Settings.SamplingCount)
h.cancelPending.CompareAndSwap(&cancel, nil)
h.Cleanup(tags)
}()
select {
case <-ticker.C:
continue
case <-tickerClose:
case <-h.ctx.Done():
return
}
}
@@ -130,8 +141,7 @@ func (h *HealthPing) StopScheduler() {
}
h.ticker.Stop()
h.ticker = nil
close(h.tickerClose)
h.tickerClose = nil
h.cancelCtx()
}
// Check implements the HealthChecker
@@ -140,7 +150,7 @@ func (h *HealthPing) Check(tags []string) error {
return nil
}
errors.LogInfo(h.ctx, "perform one-time health check for tags ", tags)
h.doCheck(tags, 0, 1)
h.doCheck(h.ctx, tags, 0, 1)
return nil
}
@@ -151,13 +161,14 @@ type rtt struct {
// doCheck performs the 'rounds' amount checks in given 'duration'. You should make
// sure all tags are valid for current balancer
func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int) {
// cancel ctx will stop all pending checks
func (h *HealthPing) doCheck(ctx context.Context, tags []string, duration time.Duration, rounds int) {
count := len(tags) * rounds
if count == 0 {
return
}
ch := make(chan *rtt, count)
timers := make([]*time.Timer, 0, count)
for _, tag := range tags {
handler := tag
client := newPingClient(
@@ -172,7 +183,7 @@ func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int)
if duration > 0 {
delay = time.Duration(dice.RollInt63n(int64(duration)))
}
time.AfterFunc(delay, func() {
timers = append(timers, time.AfterFunc(delay, func() {
errors.LogDebug(h.ctx, "checking ", handler)
delay, err := client.MeasureDelay(h.Settings.HttpMethod)
if err == nil {
@@ -200,14 +211,21 @@ func (h *HealthPing) doCheck(tags []string, duration time.Duration, rounds int)
handler: handler,
value: rttFailed,
}
})
}))
}
}
for i := 0; i < count; i++ {
rtt := <-ch
if rtt.value > 0 {
// should not put results when network is down
h.PutResult(rtt.handler, rtt.value)
select {
case rtt := <-ch:
if rtt.value > 0 {
// should not put results when network is down
h.PutResult(rtt.handler, rtt.value)
}
case <-ctx.Done():
for _, timer := range timers {
timer.Stop()
}
return
}
}
}
+2 -2
View File
@@ -59,7 +59,7 @@ func (h *HealthPingRTTS) Put(d time.Duration) {
if h.rtts == nil {
h.rtts = make([]*pingRTT, h.cap)
for i := 0; i < h.cap; i++ {
h.rtts[i] = &pingRTT{}
h.rtts[i] = &pingRTT{value: rttUntested}
}
h.idx = -1
}
@@ -88,7 +88,7 @@ func (h *HealthPingRTTS) getStatistics() *HealthPingStats {
validRTTs := make([]time.Duration, 0)
for _, rtt := range h.rtts {
switch {
case rtt.value == 0 || time.Since(rtt.time) > h.validity:
case rtt.value == rttUntested || time.Since(rtt.time) > h.validity:
continue
case rtt.value == rttFailed:
stats.Fail++
+1 -1
View File
@@ -186,7 +186,7 @@ func (o *Observer) probe(outbound string) ProbeResult {
return nil
})
if err != nil {
var errorMessage = "the outbound " + outbound + " is dead: GET request failed:" + err.Error() + "with outbound handler report underlying connection failed"
errorMessage := "the outbound " + outbound + " is dead: GET request failed:" + err.Error() + "with outbound handler report underlying connection failed"
errors.LogInfoInner(o.ctx, errorCollectorForRequest.UnderlyingError(), errorMessage)
return ProbeResult{Alive: false, LastErrorReason: errorMessage}
}
+1 -1
View File
@@ -138,7 +138,7 @@ func (s *handlerServer) GetInboundUsers(ctx context.Context, request *GetInbound
if len(request.Email) > 0 {
return &GetInboundUserResponse{Users: []*protocol.User{protocol.ToProtoUser(um.GetUser(ctx, request.Email))}}, nil
}
var result = make([]*protocol.User, 0, 100)
result := make([]*protocol.User, 0, 100)
users := um.GetUsers(ctx)
for _, u := range users {
result = append(result, protocol.ToProtoUser(u))
+24 -16
View File
@@ -26,7 +26,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
if len(tag) > 0 && policy.ForSystem().Stats.InboundUplink {
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
name := "inbound>>>" + tag + ">>>traffic>>>uplink"
c, _ := stats.GetOrRegisterCounter(statsManager, name)
c, _ := statsManager.GetOrRegisterCounter(name)
if c != nil {
uplinkCounter = c
}
@@ -34,7 +34,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
if len(tag) > 0 && policy.ForSystem().Stats.InboundDownlink {
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
name := "inbound>>>" + tag + ">>>traffic>>>downlink"
c, _ := stats.GetOrRegisterCounter(statsManager, name)
c, _ := statsManager.GetOrRegisterCounter(name)
if c != nil {
downlinkCounter = c
}
@@ -57,16 +57,23 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
if err != nil {
return nil, err
}
// Set tag and sniffing config in context before creating proxy
// This allows proxies like TUN to access these settings
ctx = session.ContextWithInbound(ctx, &session.Inbound{Tag: tag})
if receiverConfig.SniffingSettings != nil {
ctx = session.ContextWithContent(ctx, &session.Content{
SniffingRequest: sniffingRequest,
})
src := net.TCPDestination(net.AnyIP, 0)
if receiverConfig.Listen != nil {
src.Address = receiverConfig.Listen.AsAddress()
}
rawProxy, err := common.CreateObject(ctx, proxyConfig)
if receiverConfig.PortList != nil && len(receiverConfig.PortList.Range) > 0 {
src.Port = net.Port(receiverConfig.PortList.Range[0].From)
}
mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings)
if err != nil {
return nil, errors.New("failed to parse stream config").Base(err).AtWarning()
}
newCtx := session.ContextWithInbound(ctx, &session.Inbound{Tag: tag, Source: src})
newCtx = session.ContextWithContent(newCtx, &session.Content{SniffingRequest: sniffingRequest})
newCtx = session.ContextWithStreamSettings(newCtx, mss)
rawProxy, err := common.CreateObject(newCtx, proxyConfig)
if err != nil {
return nil, err
}
@@ -92,11 +99,6 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
address = net.AnyIP
}
mss, err := internet.ToMemoryStreamConfig(receiverConfig.StreamSettings)
if err != nil {
return nil, errors.New("failed to parse stream config").Base(err).AtWarning()
}
if receiverConfig.ReceiveOriginalDestination {
if mss.SocketSettings == nil {
mss.SocketSettings = &internet.SocketConfig{}
@@ -170,6 +172,12 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
// Start implements common.Runnable.
func (h *AlwaysOnInboundHandler) Start() error {
// for inbound without worker (TUN)
if run, ok := h.proxy.(common.Runnable); ok {
if err := run.Start(); err != nil {
return errors.New("failed to start proxy").Base(err)
}
}
for _, worker := range h.workers {
if err := worker.Start(); err != nil {
return err
+3 -3
View File
@@ -16,10 +16,10 @@ import (
// Manager manages all inbound handlers.
type Manager struct {
access sync.RWMutex
access sync.RWMutex
untaggedHandlers []inbound.Handler
taggedHandlers map[string]inbound.Handler
running bool
taggedHandlers map[string]inbound.Handler
running bool
}
// New returns a new Manager for inbound handlers.
+6 -11
View File
@@ -6,7 +6,6 @@ import (
goerrors "errors"
"io"
"math/big"
"os"
"github.com/xtls/xray-core/common/dice"
@@ -40,7 +39,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
if len(tag) > 0 && policy.ForSystem().Stats.OutboundUplink {
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
name := "outbound>>>" + tag + ">>>traffic>>>uplink"
c, _ := stats.GetOrRegisterCounter(statsManager, name)
c, _ := statsManager.GetOrRegisterCounter(name)
if c != nil {
uplinkCounter = c
}
@@ -48,7 +47,7 @@ func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter)
if len(tag) > 0 && policy.ForSystem().Stats.OutboundDownlink {
statsManager := v.GetFeature(stats.ManagerType()).(stats.Manager)
name := "outbound>>>" + tag + ">>>traffic>>>downlink"
c, _ := stats.GetOrRegisterCounter(statsManager, name)
c, _ := statsManager.GetOrRegisterCounter(name)
if c != nil {
downlinkCounter = c
}
@@ -109,7 +108,9 @@ func NewHandler(ctx context.Context, config *core.OutboundHandlerConfig) (outbou
ctx = session.ContextWithFullHandler(ctx, h)
rawProxyHandler, err := common.CreateObject(ctx, proxyConfig)
newCtx := session.ContextWithStreamSettings(ctx, h.streamSettings)
rawProxyHandler, err := common.CreateObject(newCtx, proxyConfig)
if err != nil {
return nil, err
}
@@ -306,11 +307,6 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (stat.Connecti
ob := outbounds[len(outbounds)-1]
h.SetOutboundGateway(ctx, ob)
}
}
if conn, err := h.getUoTConnection(ctx, dest); err != os.ErrInvalid {
return conn, err
}
conn, err := internet.Dial(ctx, dest, h.streamSettings)
@@ -348,7 +344,7 @@ func (h *Handler) SetOutboundGateway(ctx context.Context, ob *session.Outbound)
errors.LogDebug(ctx, "use inbound source ip as sendthrough: ", inbound.Source.Address.String())
}
}
//case addr.Family().IsDomain():
// case addr.Family().IsDomain():
default:
ob.Gateway = addr
@@ -396,7 +392,6 @@ func (h *Handler) ProxySettings() *serial.TypedMessage {
}
func ParseRandomIP(addr net.Address, prefix string) net.Address {
_, ipnet, _ := net.ParseCIDR(addr.IP().String() + "/" + prefix)
ones, bits := ipnet.Mask.Size()
+4 -5
View File
@@ -22,8 +22,8 @@ import (
)
func TestInterfaces(t *testing.T) {
_ = (outbound.Handler)(new(Handler))
_ = (outbound.Manager)(new(Manager))
_ = outbound.Handler(new(Handler))
_ = outbound.Manager(new(Manager))
}
const xrayKey core.XrayKey = 1
@@ -43,7 +43,7 @@ func TestOutboundWithoutStatCounter(t *testing.T) {
}
v, _ := core.New(config)
v.AddFeature((outbound.Manager)(new(Manager)))
v.AddFeature(outbound.Manager(new(Manager)))
ctx := context.WithValue(context.Background(), xrayKey, v)
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
@@ -73,7 +73,7 @@ func TestOutboundWithStatCounter(t *testing.T) {
}
v, _ := core.New(config)
v.AddFeature((outbound.Manager)(new(Manager)))
v.AddFeature(outbound.Manager(new(Manager)))
ctx := context.WithValue(context.Background(), xrayKey, v)
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{}})
h, _ := NewHandler(ctx, &core.OutboundHandlerConfig{
@@ -88,7 +88,6 @@ func TestOutboundWithStatCounter(t *testing.T) {
}
func TestTagsCache(t *testing.T) {
test_duration := 10 * time.Second
threads_num := 50
delay := 10 * time.Millisecond
-1
View File
@@ -162,7 +162,6 @@ func (m *Manager) ListHandlers(ctx context.Context) []outbound.Handler {
// Select implements outbound.HandlerSelector.
func (m *Manager) Select(selectors []string) []string {
key := strings.Join(selectors, ",")
if cache, ok := m.tagsCache.Load(key); ok {
return cache.([]string)
-35
View File
@@ -1,35 +0,0 @@
package outbound
import (
"context"
"os"
"github.com/sagernet/sing/common/uot"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/stat"
)
func (h *Handler) getUoTConnection(ctx context.Context, dest net.Destination) (stat.Connection, error) {
if dest.Address == nil {
return nil, errors.New("nil destination address")
}
if !dest.Address.Family().IsDomain() {
return nil, os.ErrInvalid
}
var uotVersion int
if dest.Address.Domain() == uot.MagicAddress {
uotVersion = uot.Version
} else if dest.Address.Domain() == uot.LegacyMagicAddress {
uotVersion = uot.LegacyVersion
} else {
return nil, os.ErrInvalid
}
packetConn, err := internet.ListenSystemPacket(ctx, &net.UDPAddr{IP: net.AnyIP.IP(), Port: 0}, h.streamSettings.SocketSettings)
if err != nil {
return nil, errors.New("unable to listen socket").Base(err)
}
conn := uot.NewServerConn(packetConn, uotVersion)
return h.getStatCouterConnection(conn), nil
}
-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 nil, errors.New("unsupported router implementation")
}
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.
func (r *Router) AddRule(config *serial.TypedMessage, shouldAppend bool) error {
inst, err := config.GetInstance()
if err != nil {
return err
@@ -227,7 +226,6 @@ func (r *Router) RemoveRule(tag string) error {
return nil
}
return errors.New("empty tag name!")
}
// ListRule implements routing.Router
+5
View File
@@ -85,6 +85,7 @@ func TestSelectLeastExpected(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns))
}
}
func TestSelectLeastExpected2(t *testing.T) {
strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{
@@ -102,6 +103,7 @@ func TestSelectLeastExpected2(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns))
}
}
func TestSelectLeastExpectedAndBaselines(t *testing.T) {
strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{
@@ -122,6 +124,7 @@ func TestSelectLeastExpectedAndBaselines(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns))
}
}
func TestSelectLeastExpectedAndBaselines2(t *testing.T) {
strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{
@@ -142,6 +145,7 @@ func TestSelectLeastExpectedAndBaselines2(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns))
}
}
func TestSelectLeastLoadBaselines(t *testing.T) {
strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{
@@ -160,6 +164,7 @@ func TestSelectLeastLoadBaselines(t *testing.T) {
t.Errorf("expected: %v, actual: %v", expected, len(ns))
}
}
func TestSelectLeastLoadBaselinesNoQualified(t *testing.T) {
strategy := &LeastLoadStrategy{
settings: &StrategyLeastLoadConfig{
+3 -49
View File
@@ -7,61 +7,15 @@ import (
"io"
"net"
"net/http"
"path/filepath"
"runtime"
"strings"
"sync"
"syscall"
"time"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/features/routing"
routing_session "github.com/xtls/xray-core/features/routing/session"
)
// parseURL splits a webhook URL into an HTTP URL and an optional Unix socket
// path. For regular http/https URLs the input is returned unchanged with an
// empty socketPath. For Unix sockets the format is:
//
// /path/to/socket.sock:/http/path
// @abstract:/http/path
// @@padded:/http/path
//
// The :/ separator after the socket path delimits the HTTP request path.
// If omitted, "/" is used.
func parseURL(raw string) (httpURL, socketPath string) {
if len(raw) == 0 || (!filepath.IsAbs(raw) && raw[0] != '@') {
return raw, ""
}
if idx := strings.Index(raw, ":/"); idx >= 0 {
return "http://localhost" + raw[idx+1:], raw[:idx]
}
return "http://localhost/", raw
}
// resolveSocketPath applies platform-specific transformations to a Unix
// socket path, matching the behaviour of the listen side in
// transport/internet/system_listener.go.
//
// For abstract sockets (prefix @) on Linux/Android:
// - single @ — used as-is (lock-free abstract socket)
// - double @@ — stripped to single @ and padded to
// syscall.RawSockaddrUnix{}.Path length (HAProxy compat)
func resolveSocketPath(path string) string {
if len(path) == 0 || path[0] != '@' {
return path
}
if runtime.GOOS != "linux" && runtime.GOOS != "android" {
return path
}
if len(path) > 1 && path[1] == '@' {
fullAddr := make([]byte, len(syscall.RawSockaddrUnix{}.Path))
copy(fullAddr, path[1:])
return string(fullAddr)
}
return path
}
func ptr[T any](v T) *T { return &v }
type event struct {
@@ -96,7 +50,7 @@ func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
return nil, nil
}
httpURL, socketPath := parseURL(cfg.Url)
httpURL, socketPath := utils.SplitHTTPUnixURL(cfg.Url)
h := &WebhookNotifier{
url: httpURL,
deduplication: cfg.Deduplication,
@@ -107,7 +61,7 @@ func NewWebhookNotifier(cfg *WebhookConfig) (*WebhookNotifier, error) {
}
if socketPath != "" {
dialAddr := resolveSocketPath(socketPath)
dialAddr := utils.ResolveSocketPath(socketPath)
h.client.Transport = &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
var d net.Dialer
+48
View File
@@ -48,6 +48,20 @@ func (m *Manager) RegisterCounter(name string) (stats.Counter, error) {
return c, nil
}
// GetOrRegisterCounter implements stats.Manager.
func (m *Manager) GetOrRegisterCounter(name string) (stats.Counter, error) {
m.access.Lock()
defer m.access.Unlock()
if c, found := m.counters[name]; found {
return c, nil
}
errors.LogDebug(context.Background(), "create new counter ", name)
c := new(Counter)
m.counters[name] = c
return c, nil
}
// UnregisterCounter implements stats.Manager.
func (m *Manager) UnregisterCounter(name string) error {
m.access.Lock()
@@ -97,6 +111,20 @@ func (m *Manager) RegisterOnlineMap(name string) (stats.OnlineMap, error) {
return om, nil
}
// GetOrRegisterOnlineMap implements stats.Manager.
func (m *Manager) GetOrRegisterOnlineMap(name string) (stats.OnlineMap, error) {
m.access.Lock()
defer m.access.Unlock()
if om, found := m.onlineMaps[name]; found {
return om, nil
}
errors.LogDebug(context.Background(), "create new OnlineMap ", name)
om := NewOnlineMap()
m.onlineMaps[name] = om
return om, nil
}
// UnregisterOnlineMap implements stats.Manager.
func (m *Manager) UnregisterOnlineMap(name string) error {
m.access.Lock()
@@ -149,6 +177,26 @@ func (m *Manager) RegisterChannel(name string) (stats.Channel, error) {
return c, nil
}
// GetOrRegisterChannel implements stats.Manager.
func (m *Manager) GetOrRegisterChannel(name string) (stats.Channel, error) {
m.access.Lock()
defer m.access.Unlock()
if c, found := m.channels[name]; found {
return c, nil
}
errors.LogDebug(context.Background(), "create new channel ", name)
c := NewChannel(&ChannelConfig{BufferSize: 64, Blocking: false})
if m.running {
// Start before publishing so no goroutine can observe an unstarted channel.
if err := c.Start(); err != nil {
return nil, err
}
}
m.channels[name] = c
return c, nil
}
// UnregisterChannel implements stats.Manager.
func (m *Manager) UnregisterChannel(name string) error {
m.access.Lock()
+1 -1
View File
@@ -11,7 +11,7 @@ import (
)
func TestInterface(t *testing.T) {
_ = (stats.Manager)(new(Manager))
_ = stats.Manager(new(Manager))
}
func TestStatsChannelRunnable(t *testing.T) {
+3 -2
View File
@@ -2,10 +2,11 @@ package version
import (
"context"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
"strconv"
"strings"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/errors"
)
type Version struct {
+1 -1
View File
@@ -122,7 +122,7 @@ func NewReader(reader io.Reader) Reader {
}
_, isFile := reader.(*os.File)
if !isFile && useReadv {
if !isFile && useReadV() {
if sc, ok := reader.(syscall.Conn); ok {
rawConn, err := sc.SyscallConn()
if err != nil {
+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.
func MergeBytes(dest MultiBuffer, src []byte) MultiBuffer {
n := len(dest)
if n > 0 && !(dest)[n-1].IsFull() {
nBytes, _ := (dest)[n-1].Write(src)
if n > 0 && !dest[n-1].IsFull() {
nBytes, _ := dest[n-1].Write(src)
src = src[nBytes:]
}
+6 -6
View File
@@ -121,11 +121,11 @@ func TestPacketReader_ReadMultiBuffer(t *testing.T) {
}
func TestReaderInterface(t *testing.T) {
_ = (io.Reader)(new(ReadVReader))
_ = (Reader)(new(ReadVReader))
_ = io.Reader(new(ReadVReader))
_ = Reader(new(ReadVReader))
_ = (Reader)(new(BufferedReader))
_ = (io.Reader)(new(BufferedReader))
_ = (io.ByteReader)(new(BufferedReader))
_ = (io.WriterTo)(new(BufferedReader))
_ = Reader(new(BufferedReader))
_ = io.Reader(new(BufferedReader))
_ = io.ByteReader(new(BufferedReader))
_ = io.WriterTo(new(BufferedReader))
}
+1 -1
View File
@@ -19,7 +19,7 @@ func (r *posixReader) Init(bs []*Buffer) {
}
for idx, b := range bs {
iovecs = append(iovecs, syscall.Iovec{
Base: &(b.v[0]),
Base: &b.v[0],
})
iovecs[idx].SetLen(int(Size))
}
+15 -3
View File
@@ -5,6 +5,7 @@ package buf
import (
"io"
"sync/atomic"
"syscall"
"github.com/xtls/xray-core/common/platform"
@@ -143,13 +144,24 @@ func (r *ReadVReader) ReadMultiBuffer() (MultiBuffer, error) {
return mb, nil
}
var useReadv bool
var useReadv atomic.Bool
func init() {
func useReadV() bool {
return useReadv.Load()
}
func reloadEnvSettings() error {
const defaultFlagValue = "NOT_DEFINED_AT_ALL"
value := platform.NewEnvFlag(platform.UseReadV).GetValue(func() string { return defaultFlagValue })
enabled := false
switch value {
case defaultFlagValue, "auto", "enable":
useReadv = true
enabled = true
}
useReadv.Store(enabled)
return nil
}
func init() {
platform.RegisterEnvReload(reloadEnvSettings)
}
+3 -1
View File
@@ -10,7 +10,9 @@ import (
"github.com/xtls/xray-core/features/stats"
)
const useReadv = false
func useReadV() bool {
return false
}
func NewReadVReader(reader io.Reader, rawConn syscall.RawConn, counter stats.Counter) Reader {
panic("not implemented")
+49
View File
@@ -0,0 +1,49 @@
package geodata
import (
"sync"
"github.com/xtls/xray-core/common"
)
var privateIPMatcher = sync.OnceValue(func() IPMatcher {
return common.Must2(IPReg.BuildIPMatcher(common.Must2(ParseIPRules([]string{
"0.0.0.0/8",
"10.0.0.0/8",
"100.64.0.0/10",
"127.0.0.0/8",
"169.254.0.0/16",
"172.16.0.0/12",
"192.0.0.0/24",
"192.0.2.0/24",
"192.88.99.0/24",
"192.168.0.0/16",
"198.18.0.0/15",
"198.51.100.0/24",
"203.0.113.0/24",
"224.0.0.0/3",
"::/127",
"fc00::/7",
"fe80::/10",
"ff00::/8",
}))))
})
func GetPrivateIPMatcher() IPMatcher { return privateIPMatcher() }
var privateDomainMatcher = sync.OnceValue(func() DomainMatcher {
return common.Must2(DomainReg.BuildDomainMatcher(common.Must2(ParseDomainRules([]string{
"lan",
"localdomain",
"example",
"invalid",
"localhost",
"test",
"local",
"home.arpa",
"internal",
"regexp:^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$", // Dotless domains
}, Domain_Domain))))
})
func GetPrivateDomainMatcher() DomainMatcher { return privateDomainMatcher() }
+10 -9
View File
@@ -8,6 +8,7 @@ import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/geodata/strmatcher"
"github.com/xtls/xray-core/common/utils"
)
type DomainMatcher interface {
@@ -25,7 +26,7 @@ type DomainMatcherFactory interface {
type MphDomainMatcherFactory struct {
sync.Mutex
shared map[string]strmatcher.MatcherGroup // TODO: cleanup
shared *utils.WeakCacheMap[string, strmatcher.MphValueMatcher]
}
func buildDomainRulesKey(rules []*DomainRule) string {
@@ -65,7 +66,7 @@ func (f *MphDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatch
if key != "" {
f.Lock()
defer f.Unlock()
if g := f.shared[key]; g != nil {
if g, ok := f.shared.Load(key); ok {
errors.LogDebug(context.Background(), "geodata mph domain matcher cache HIT for ", len(rules), " rules")
return g, nil
}
@@ -102,14 +103,14 @@ func (f *MphDomainMatcherFactory) BuildMatcher(rules []*DomainRule) (DomainMatch
return nil, err
}
if key != "" {
f.shared[key] = g
f.shared.Store(key, g)
}
return g, nil
}
type CompactDomainMatcherFactory struct {
sync.Mutex
shared map[string]strmatcher.MatcherSet // TODO: cleanup
shared *utils.WeakCacheMap[string, strmatcher.LinearAnyMatcher]
}
func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmatcher.MatcherSet, error) {
@@ -118,7 +119,7 @@ func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmat
f.Lock()
defer f.Unlock()
if s := f.shared[key]; s != nil {
if s, ok := f.shared.Load(key); ok {
errors.LogDebug(context.Background(), "geodata geosite matcher cache HIT ", key)
return s, nil
}
@@ -138,7 +139,7 @@ func (f *CompactDomainMatcherFactory) getOrCreateFrom(rule *GeoSiteRule) (strmat
}
s.Add(m)
}
f.shared[key] = s
f.shared.Store(key, s)
return s, err
}
@@ -219,7 +220,7 @@ func parseDomain(d *Domain) (strmatcher.Matcher, error) {
case Domain_Regex:
return strmatcher.Regex.New(d.Value)
case Domain_Domain:
return strmatcher.Domain.New(d.Value)
return strmatcher.Domain.New(strings.ToLower(d.Value))
case Domain_Full:
return strmatcher.Full.New(strings.ToLower(d.Value))
default:
@@ -230,8 +231,8 @@ func parseDomain(d *Domain) (strmatcher.Matcher, error) {
func newDomainMatcherFactory() DomainMatcherFactory {
switch runtime.GOOS {
case "ios", "android":
return &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
return &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()}
default:
return &MphDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}
return &MphDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.MphValueMatcher]()}
}
}
+8 -6
View File
@@ -7,10 +7,11 @@ import (
"testing"
"github.com/xtls/xray-core/common/geodata/strmatcher"
"github.com/xtls/xray-core/common/utils"
)
func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) {
factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
factory := &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()}
matcher, err := factory.BuildMatcher([]*DomainRule{
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
@@ -31,7 +32,7 @@ func TestCompactDomainMatcher_PreservesCustomRuleIndices(t *testing.T) {
func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
factory := &CompactDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherSet)}
factory := &CompactDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.LinearAnyMatcher]()}
matcher, err := factory.BuildMatcher([]*DomainRule{
{Value: &DomainRule_Geosite{Geosite: &GeoSiteRule{File: DefaultGeoSiteDat, Code: "CN"}}},
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "163.com"}}},
@@ -50,10 +51,11 @@ func TestCompactDomainMatcher_PreservesMixedRuleIndices(t *testing.T) {
}
func TestMphDomainMatcher_MatchReturnsDetachedSlice(t *testing.T) {
matcher, err := (&MphDomainMatcherFactory{shared: make(map[string]strmatcher.MatcherGroup)}).BuildMatcher([]*DomainRule{
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
})
matcher, err := (&MphDomainMatcherFactory{shared: utils.NewWeakCacheMap[string, strmatcher.MphValueMatcher]()}).
BuildMatcher([]*DomainRule{
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Full, Value: "example.com"}}},
{Value: &DomainRule_Custom{Custom: &Domain{Type: Domain_Domain, Value: "example.com"}}},
})
if err != nil {
t.Fatalf("BuildMatcher() failed: %v", err)
}
+15 -7
View File
@@ -6,12 +6,14 @@ import (
"sync/atomic"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/common/uuid"
)
type DomainRegistry struct {
mu sync.Mutex
factory DomainMatcherFactory
matchers []*DynamicDomainMatcher
matchers *utils.WeakCacheMap[uuid.UUID, DynamicDomainMatcher]
}
func (r *DomainRegistry) BuildDomainMatcher(rules []*DomainRule) (DomainMatcher, error) {
@@ -24,7 +26,7 @@ func (r *DomainRegistry) BuildDomainMatcher(rules []*DomainRule) (DomainMatcher,
}
d := NewDynamicDomainMatcher(rules, m)
r.matchers = append(r.matchers, d)
r.matchers.Store(uuid.New(), d)
return d, nil
}
@@ -32,15 +34,20 @@ func (r *DomainRegistry) Reload() error {
r.mu.Lock()
defer r.mu.Unlock()
errors.LogInfo(context.Background(), "reloading GeoSite data for ", len(r.matchers), " domain matcher(s)")
var matchers []*DynamicDomainMatcher
r.matchers.Range(func(_ uuid.UUID, matcher *DynamicDomainMatcher) bool {
matchers = append(matchers, matcher)
return true
})
errors.LogInfo(context.Background(), "reloading GeoSite data for ", len(matchers), " domain matcher(s)")
factory := newDomainMatcherFactory()
type reloadEntry struct {
dynamic *DynamicDomainMatcher
matcher DomainMatcher
}
reloaded := make([]reloadEntry, len(r.matchers))
for i, d := range r.matchers {
reloaded := make([]reloadEntry, len(matchers))
for i, d := range matchers {
m, err := factory.BuildMatcher(d.rules)
if err != nil {
errors.LogErrorInner(context.Background(), err, "failed to reload GeoSite data for domain matcher ", i)
@@ -52,13 +59,14 @@ func (r *DomainRegistry) Reload() error {
entry.dynamic.Reload(entry.matcher)
}
r.factory = factory
errors.LogInfo(context.Background(), "reloaded GeoSite data for ", len(r.matchers), " domain matcher(s)")
errors.LogInfo(context.Background(), "reloaded GeoSite data for ", len(matchers), " domain matcher(s)")
return nil
}
func newDomainRegistry() *DomainRegistry {
return &DomainRegistry{
factory: newDomainMatcherFactory(),
factory: newDomainMatcherFactory(),
matchers: utils.NewWeakCacheMap[uuid.UUID, DynamicDomainMatcher](),
}
}
+5 -4
View File
@@ -11,6 +11,7 @@ import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/utils"
"go4.org/netipx"
)
@@ -806,7 +807,7 @@ func (mm *HeuristicMultiIPMatcher) SetReverse(reverse bool) {
type IPSetFactory struct {
sync.Mutex
shared map[string]*IPSet // TODO: cleanup
shared *utils.WeakCacheMap[string, IPSet]
}
func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, error) {
@@ -815,7 +816,7 @@ func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, er
f.Lock()
defer f.Unlock()
if ipset := f.shared[key]; ipset != nil {
if ipset, ok := f.shared.Load(key); ok {
errors.LogDebug(context.Background(), "geodata geoip matcher cache HIT ", key)
return ipset, nil
}
@@ -835,7 +836,7 @@ func (f *IPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*IPSet, er
return nil
})
if err == nil {
f.shared[key] = ipset
f.shared.Store(key, ipset)
}
return ipset, err
}
@@ -1018,5 +1019,5 @@ func buildOptimizedIPMatcher(f *IPSetFactory, rules []*IPRule) (IPMatcher, error
}
func newIPSetFactory() *IPSetFactory {
return &IPSetFactory{shared: make(map[string]*IPSet)}
return &IPSetFactory{shared: utils.NewWeakCacheMap[string, IPSet]()}
}
+3 -3
View File
@@ -315,7 +315,7 @@ func TestIPMatcherAnyMatchAndMatches(t *testing.T) {
}
if !matcher.AnyMatch([]net.IP{
net.IP{},
{},
ip("1.1.1.1"),
ip("8.8.8.8"),
}) {
@@ -345,7 +345,7 @@ func TestIPMatcherAnyMatchAndMatches(t *testing.T) {
if matcher.Matches([]net.IP{
ip("8.8.8.8"),
net.IP{},
{},
}) {
t.Fatal("expect Matches to be false when any IP is invalid")
}
@@ -362,7 +362,7 @@ func TestIPMatcherFilterIPs(t *testing.T) {
}
matched, unmatched := matcher.FilterIPs([]net.IP{
net.IP{},
{},
ip("8.8.8.8"),
ip("91.108.255.254"),
ip("1.1.1.1"),
+19 -11
View File
@@ -7,25 +7,27 @@ import (
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/common/uuid"
)
type IPRegistry struct {
mu sync.Mutex
ipsetFactory *IPSetFactory
matchers []*DynamicIPMatcher
mu sync.Mutex
factory *IPSetFactory
matchers *utils.WeakCacheMap[uuid.UUID, DynamicIPMatcher]
}
func (r *IPRegistry) BuildIPMatcher(rules []*IPRule) (IPMatcher, error) {
r.mu.Lock()
defer r.mu.Unlock()
m, err := buildOptimizedIPMatcher(r.ipsetFactory, rules)
m, err := buildOptimizedIPMatcher(r.factory, rules)
if err != nil {
return nil, err
}
d := NewDynamicIPMatcher(rules, m)
r.matchers = append(r.matchers, d)
r.matchers.Store(uuid.New(), d)
return d, nil
}
@@ -33,15 +35,20 @@ func (r *IPRegistry) Reload() error {
r.mu.Lock()
defer r.mu.Unlock()
errors.LogInfo(context.Background(), "reloading GeoIP data for ", len(r.matchers), " IP matcher(s)")
var matchers []*DynamicIPMatcher
r.matchers.Range(func(_ uuid.UUID, matcher *DynamicIPMatcher) bool {
matchers = append(matchers, matcher)
return true
})
errors.LogInfo(context.Background(), "reloading GeoIP data for ", len(matchers), " IP matcher(s)")
factory := newIPSetFactory()
type reloadEntry struct {
dynamic *DynamicIPMatcher
matcher IPMatcher
}
reloaded := make([]reloadEntry, len(r.matchers))
for i, d := range r.matchers {
reloaded := make([]reloadEntry, len(matchers))
for i, d := range matchers {
m, err := buildOptimizedIPMatcher(factory, d.rules)
if err != nil {
errors.LogErrorInner(context.Background(), err, "failed to reload GeoIP data for IP matcher ", i)
@@ -52,14 +59,15 @@ func (r *IPRegistry) Reload() error {
for _, entry := range reloaded {
entry.dynamic.Reload(entry.matcher)
}
r.ipsetFactory = factory
errors.LogInfo(context.Background(), "reloaded GeoIP data for ", len(r.matchers), " IP matcher(s)")
r.factory = factory
errors.LogInfo(context.Background(), "reloaded GeoIP data for ", len(matchers), " IP matcher(s)")
return nil
}
func newIPRegistry() *IPRegistry {
return &IPRegistry{
ipsetFactory: newIPSetFactory(),
factory: newIPSetFactory(),
matchers: utils.NewWeakCacheMap[uuid.UUID, DynamicIPMatcher](),
}
}
+2 -2
View File
@@ -138,7 +138,7 @@ func ParseDomainRule(r string, defaultType Domain_Type) (*DomainRule, error) {
}
prefix := 0
for _, ext := range [...]string{"ext:", "ext-domain:"} {
for _, ext := range [...]string{"ext:", "ext-domain:", "ext-site:"} {
if strings.HasPrefix(r, ext) {
prefix = len(ext)
break
@@ -167,7 +167,7 @@ func ParseDomainRules(rules []string, defaultType Domain_Type) ([]*DomainRule, e
}
prefix := 0
for _, ext := range [...]string{"ext:", "ext-domain:"} {
for _, ext := range [...]string{"ext:", "ext-domain:", "ext-site:"} {
if strings.HasPrefix(r, ext) {
prefix = len(ext)
break
-1
View File
@@ -98,7 +98,6 @@ func (l *generalLogger) run() {
}
func (l *generalLogger) Handle(msg Message) {
select {
case l.buffer <- msg:
default:
+348
View File
@@ -0,0 +1,348 @@
//go:build darwin && !ios
package net
import (
"bytes"
"net"
"net/netip"
"path/filepath"
"strings"
"syscall"
"unsafe"
"golang.org/x/sys/unix"
"github.com/xtls/xray-core/common/errors"
)
const (
darwinProcPIDListFDs = 1
darwinProcPIDFDSocketInfo = 3
darwinProcFDTypeSocket = 2
darwinProcFDInfoSize = 8
darwinSocketFDInfoSize = 792
darwinSocketFDInfoPSIOff = 24
darwinSocketInfoProtoOff = darwinSocketFDInfoPSIOff + 156
darwinSocketInfoFamilyOff = darwinSocketFDInfoPSIOff + 160
darwinSocketInfoKindOff = darwinSocketFDInfoPSIOff + 232
darwinSocketInfoInSockOff = darwinSocketFDInfoPSIOff + 240
darwinInSockInfoFPortOff = darwinSocketInfoInSockOff
darwinInSockInfoLPortOff = darwinSocketInfoInSockOff + 4
darwinInSockInfoVFlagOff = darwinSocketInfoInSockOff + 24
darwinInSockInfoFAddrOff = darwinSocketInfoInSockOff + 32
darwinInSockInfoLAddrOff = darwinSocketInfoInSockOff + 48
darwinInSockInfoSize = 80
darwinInSockInfoIPv4 = 0x1
darwinInSockInfoIPv6 = 0x2
darwinSockInfoIN = 1
darwinSockInfoTCP = 2
)
type darwinSocketMatchLevel int
const (
darwinSocketNoMatch darwinSocketMatchLevel = iota
darwinSocketPortMatch
darwinSocketRemoteMatch
darwinSocketLocalMatch
darwinSocketExactMatch
)
func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort uint16) (PID int, Name string, AbsolutePath string, err error) {
isLocal, err := IsLocal(net.ParseIP(srcIP))
if err != nil {
return 0, "", "", errors.New("failed to determine if address is local: ", err)
}
if !isLocal {
return 0, "", "", ErrNotLocal
}
if network != "tcp" && network != "udp" {
panic("Unsupported network type for process lookup.")
}
srcAddr, err := netip.ParseAddr(srcIP)
if err != nil {
return 0, "", "", errors.New("invalid source IP address: ", srcIP)
}
srcAddr = srcAddr.Unmap()
var dstAddr netip.Addr
hasDstAddr := false
if destIP != "" && destPort != 0 {
dstAddr, err = netip.ParseAddr(destIP)
if err != nil {
return 0, "", "", errors.New("invalid destination IP address: ", destIP)
}
dstAddr = dstAddr.Unmap()
hasDstAddr = true
}
processes, err := unix.SysctlKinfoProcSlice("kern.proc.all")
if err != nil {
return 0, "", "", errors.New("failed to list processes").Base(err)
}
var bestPID int32
bestLevel := darwinSocketNoMatch
ambiguousBest := false
for _, process := range processes {
pid := process.Proc.P_pid
if pid <= 0 {
continue
}
matchLevel, err := darwinProcessSocketMatchLevel(pid, network, srcAddr, srcPort, dstAddr, destPort, hasDstAddr)
if err != nil || matchLevel == darwinSocketNoMatch {
continue
}
if matchLevel == darwinSocketExactMatch {
bestPID = pid
bestLevel = matchLevel
ambiguousBest = false
break
}
if matchLevel > bestLevel {
bestPID = pid
bestLevel = matchLevel
ambiguousBest = false
continue
}
if matchLevel == bestLevel {
ambiguousBest = true
}
}
if bestLevel == darwinSocketNoMatch {
return 0, "", "", errors.New("process not found for ", network, " connection from ", srcIP, ":", srcPort, " to ", destIP, ":", destPort)
}
if ambiguousBest {
return 0, "", "", errors.New("ambiguous process match for ", network, " connection from ", srcIP, ":", srcPort, " to ", destIP, ":", destPort)
}
absPath, err := darwinProcessPath(bestPID)
if err != nil {
return 0, "", "", errors.New("could not get process path for PID ", bestPID, ": ", err)
}
absPath = filepath.ToSlash(absPath)
return int(bestPID), filepath.Base(absPath), absPath, nil
}
func darwinProcessSocketMatchLevel(pid int32, network string, srcAddr netip.Addr, srcPort uint16, dstAddr netip.Addr, dstPort uint16, hasDstAddr bool) (darwinSocketMatchLevel, error) {
fds, err := darwinProcessFDs(pid)
if err != nil {
return darwinSocketNoMatch, err
}
bestLevel := darwinSocketNoMatch
info := make([]byte, darwinSocketFDInfoSize)
for fd := 0; fd+darwinProcFDInfoSize <= len(fds); fd += darwinProcFDInfoSize {
fdNumber := int32(darwinReadNativeUint32(fds[fd : fd+4]))
fdType := darwinReadNativeUint32(fds[fd+4 : fd+8])
if fdType != darwinProcFDTypeSocket {
continue
}
n, err := darwinProcPIDFDInfo(pid, fdNumber, darwinProcPIDFDSocketInfo, info)
if err != nil || n < darwinSocketInfoInSockOff+darwinInSockInfoSize {
continue
}
level := darwinSocketInfoMatchLevel(info[:n], network, srcAddr, srcPort, dstAddr, dstPort, hasDstAddr)
if level == darwinSocketExactMatch {
return level, nil
}
if level > bestLevel {
bestLevel = level
}
}
return bestLevel, nil
}
func darwinProcessFDs(pid int32) ([]byte, error) {
n, err := darwinProcPIDInfo(pid, darwinProcPIDListFDs, 0, nil)
if err != nil {
return nil, err
}
if n <= 0 {
return nil, nil
}
buf := make([]byte, n)
n, err = darwinProcPIDInfo(pid, darwinProcPIDListFDs, 0, buf)
if err != nil {
return nil, err
}
return buf[:n], nil
}
func darwinSocketInfoMatchLevel(info []byte, network string, srcAddr netip.Addr, srcPort uint16, dstAddr netip.Addr, dstPort uint16, hasDstAddr bool) darwinSocketMatchLevel {
protocol := int(darwinReadNativeUint32(info[darwinSocketInfoProtoOff : darwinSocketInfoProtoOff+4]))
family := int(darwinReadNativeUint32(info[darwinSocketInfoFamilyOff : darwinSocketInfoFamilyOff+4]))
kind := int(darwinReadNativeUint32(info[darwinSocketInfoKindOff : darwinSocketInfoKindOff+4]))
switch network {
case "tcp":
if protocol != unix.IPPROTO_TCP || kind != darwinSockInfoTCP {
return darwinSocketNoMatch
}
case "udp":
if protocol != unix.IPPROTO_UDP || kind != darwinSockInfoIN {
return darwinSocketNoMatch
}
default:
return darwinSocketNoMatch
}
vflag := info[darwinInSockInfoVFlagOff]
if srcAddr.Is4() {
if family != unix.AF_INET || vflag&darwinInSockInfoIPv4 == 0 {
return darwinSocketNoMatch
}
} else {
if family != unix.AF_INET6 || vflag&darwinInSockInfoIPv6 == 0 {
return darwinSocketNoMatch
}
}
localPort := int32(darwinReadNativeUint32(info[darwinInSockInfoLPortOff : darwinInSockInfoLPortOff+4]))
if !darwinPortMatches(localPort, srcPort) {
return darwinSocketNoMatch
}
localAddrMatches := darwinAddrMatchesOrUnspecified(info[darwinInSockInfoLAddrOff:darwinInSockInfoLAddrOff+16], srcAddr)
foreignAddrRaw := info[darwinInSockInfoFAddrOff : darwinInSockInfoFAddrOff+16]
foreignPort := int32(darwinReadNativeUint32(info[darwinInSockInfoFPortOff : darwinInSockInfoFPortOff+4]))
if !hasDstAddr {
if localAddrMatches {
return darwinSocketExactMatch
}
return darwinSocketNoMatch
}
remoteMatches := darwinPortMatches(foreignPort, dstPort) && darwinAddrMatches(foreignAddrRaw, dstAddr)
if network == "udp" && darwinEndpointIsZero(foreignAddrRaw, foreignPort) && localAddrMatches {
return darwinSocketExactMatch
}
switch {
case localAddrMatches && remoteMatches:
return darwinSocketExactMatch
case localAddrMatches:
return darwinSocketLocalMatch
case remoteMatches:
return darwinSocketRemoteMatch
default:
return darwinSocketPortMatch
}
}
func darwinPortMatches(value int32, port uint16) bool {
raw := uint16(value)
return raw == port || darwinNtohs(raw) == port
}
func darwinNtohs(value uint16) uint16 {
return value<<8 | value>>8
}
func darwinAddrMatches(raw []byte, addr netip.Addr) bool {
if addr.Is4() {
ip := addr.As4()
return bytes.Equal(raw[12:16], ip[:])
}
ip := addr.As16()
return bytes.Equal(raw, ip[:])
}
func darwinAddrMatchesOrUnspecified(raw []byte, addr netip.Addr) bool {
if darwinAddrMatches(raw, addr) {
return true
}
if addr.Is4() {
return darwinBytesAreZero(raw[12:16])
}
return darwinBytesAreZero(raw)
}
func darwinEndpointIsZero(rawAddr []byte, port int32) bool {
return uint32(port) == 0 && darwinBytesAreZero(rawAddr)
}
func darwinBytesAreZero(raw []byte) bool {
for _, value := range raw {
if value != 0 {
return false
}
}
return true
}
func darwinReadNativeUint32(b []byte) uint32 {
return *(*uint32)(unsafe.Pointer(&b[0]))
}
func darwinProcessPath(pid int32) (string, error) {
buf := make([]byte, unix.PathMax)
n, err := darwinProcPIDPath(pid, buf)
if err != nil {
return "", err
}
if n <= 0 {
return "", errors.New("empty process path")
}
return strings.TrimRight(string(buf[:n]), "\x00"), nil
}
func darwinProcPIDInfo(pid int32, flavor int, arg uint64, buf []byte) (int, error) {
var ptr unsafe.Pointer
if len(buf) > 0 {
ptr = unsafe.Pointer(&buf[0])
}
r0, _, errno := syscall_syscall6(libc_proc_pidinfo_trampoline_addr, uintptr(pid), uintptr(flavor), uintptr(arg), uintptr(ptr), uintptr(len(buf)), 0)
if errno != 0 {
return 0, errno
}
return int(r0), nil
}
func darwinProcPIDFDInfo(pid int32, fd int32, flavor int, buf []byte) (int, error) {
var ptr unsafe.Pointer
if len(buf) > 0 {
ptr = unsafe.Pointer(&buf[0])
}
r0, _, errno := syscall_syscall6(libc_proc_pidfdinfo_trampoline_addr, uintptr(pid), uintptr(fd), uintptr(flavor), uintptr(ptr), uintptr(len(buf)), 0)
if errno != 0 {
return 0, errno
}
return int(r0), nil
}
func darwinProcPIDPath(pid int32, buf []byte) (int, error) {
r0, _, errno := syscall_syscall6(libc_proc_pidpath_trampoline_addr, uintptr(pid), uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)), 0, 0, 0)
if errno != 0 {
return 0, errno
}
return int(r0), nil
}
var libc_proc_pidinfo_trampoline_addr uintptr
//go:cgo_import_dynamic libc_proc_pidinfo proc_pidinfo "/usr/lib/libproc.dylib"
var libc_proc_pidfdinfo_trampoline_addr uintptr
//go:cgo_import_dynamic libc_proc_pidfdinfo proc_pidfdinfo "/usr/lib/libproc.dylib"
var libc_proc_pidpath_trampoline_addr uintptr
//go:cgo_import_dynamic libc_proc_pidpath proc_pidpath "/usr/lib/libproc.dylib"
// Implemented in the runtime package (runtime/sys_darwin.go).
func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)
//go:linkname syscall_syscall6 syscall.syscall6
+18
View File
@@ -0,0 +1,18 @@
//go:build darwin && !ios
#include "textflag.h"
TEXT libc_proc_pidinfo_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_proc_pidinfo(SB)
GLOBL ·libc_proc_pidinfo_trampoline_addr(SB), RODATA, $8
DATA ·libc_proc_pidinfo_trampoline_addr(SB)/8, $libc_proc_pidinfo_trampoline<>(SB)
TEXT libc_proc_pidfdinfo_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_proc_pidfdinfo(SB)
GLOBL ·libc_proc_pidfdinfo_trampoline_addr(SB), RODATA, $8
DATA ·libc_proc_pidfdinfo_trampoline_addr(SB)/8, $libc_proc_pidfdinfo_trampoline<>(SB)
TEXT libc_proc_pidpath_trampoline<>(SB),NOSPLIT,$0-0
JMP libc_proc_pidpath(SB)
GLOBL ·libc_proc_pidpath_trampoline_addr(SB), RODATA, $8
DATA ·libc_proc_pidpath_trampoline_addr(SB)/8, $libc_proc_pidpath_trampoline<>(SB)
+293
View File
@@ -0,0 +1,293 @@
//go:build darwin && !ios
package net
import (
stdnet "net"
"net/netip"
"os"
"testing"
"time"
"unsafe"
"golang.org/x/sys/unix"
)
func TestFindProcessDarwinTCP(t *testing.T) {
listener, err := stdnet.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer listener.Close()
accepted := make(chan stdnet.Conn, 1)
go func() {
conn, err := listener.Accept()
if err == nil {
accepted <- conn
return
}
close(accepted)
}()
conn, err := stdnet.Dial("tcp", listener.Addr().String())
if err != nil {
t.Fatal(err)
}
defer conn.Close()
serverConn := <-accepted
if serverConn == nil {
t.Fatal("server did not accept tcp connection")
}
defer serverConn.Close()
local := conn.LocalAddr().(*stdnet.TCPAddr)
remote := conn.RemoteAddr().(*stdnet.TCPAddr)
pid, name, path, err := FindProcess("tcp", local.IP.String(), uint16(local.Port), remote.IP.String(), uint16(remote.Port))
if err != nil {
t.Fatal(err)
}
assertCurrentProcess(t, pid, name, path)
}
func TestFindProcessDarwinUDP(t *testing.T) {
conn, err := stdnet.ListenUDP("udp", &stdnet.UDPAddr{IP: stdnet.ParseIP("127.0.0.1")})
if err != nil {
t.Fatal(err)
}
defer conn.Close()
local := conn.LocalAddr().(*stdnet.UDPAddr)
pid, name, path, err := FindProcess("udp", local.IP.String(), uint16(local.Port), "", 0)
if err != nil {
t.Fatal(err)
}
assertCurrentProcess(t, pid, name, path)
}
func TestFindProcessDarwinNonLocal(t *testing.T) {
_, _, _, err := FindProcess("tcp", "203.0.113.1", 80, "", 0)
if err != ErrNotLocal {
t.Fatalf("expected ErrNotLocal, got %v", err)
}
}
func TestFindProcessDarwinUnsupportedNetwork(t *testing.T) {
defer func() {
if recover() == nil {
t.Fatal("expected panic")
}
}()
_, _, _, _ = FindProcess("icmp", "127.0.0.1", 0, "", 0)
}
func assertCurrentProcess(t *testing.T, pid int, name string, path string) {
t.Helper()
if pid != os.Getpid() {
t.Fatalf("expected pid %d, got %d (%s, %s)", os.Getpid(), pid, name, path)
}
executable, err := os.Executable()
if err != nil {
t.Fatal(err)
}
if path == "" || name == "" {
t.Fatalf("expected process path and name, got name=%q path=%q", name, path)
}
if sameFile(executable, path) {
return
}
t.Fatalf("expected executable %q, got %q", executable, path)
}
func sameFile(left string, right string) bool {
leftInfo, leftErr := os.Stat(left)
rightInfo, rightErr := os.Stat(right)
if leftErr != nil || rightErr != nil {
return false
}
return os.SameFile(leftInfo, rightInfo)
}
func TestFindProcessDarwinTCPWithoutDestination(t *testing.T) {
listener, err := stdnet.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer listener.Close()
accepted := make(chan stdnet.Conn, 1)
go func() {
conn, err := listener.Accept()
if err == nil {
accepted <- conn
return
}
close(accepted)
}()
conn, err := stdnet.DialTimeout("tcp", listener.Addr().String(), time.Second)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
serverConn := <-accepted
if serverConn == nil {
t.Fatal("server did not accept tcp connection")
}
defer serverConn.Close()
local := conn.LocalAddr().(*stdnet.TCPAddr)
pid, name, path, err := FindProcess("tcp", local.IP.String(), uint16(local.Port), "", 0)
if err != nil {
t.Fatal(err)
}
assertCurrentProcess(t, pid, name, path)
}
func TestFindProcessDarwinTCPWithDifferentDestination(t *testing.T) {
listener, err := stdnet.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer listener.Close()
accepted := make(chan stdnet.Conn, 1)
go func() {
conn, err := listener.Accept()
if err == nil {
accepted <- conn
return
}
close(accepted)
}()
conn, err := stdnet.DialTimeout("tcp", listener.Addr().String(), time.Second)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
serverConn := <-accepted
if serverConn == nil {
t.Fatal("server did not accept tcp connection")
}
defer serverConn.Close()
local := conn.LocalAddr().(*stdnet.TCPAddr)
pid, name, path, err := FindProcess("tcp", local.IP.String(), uint16(local.Port), "203.0.113.10", 443)
if err != nil {
t.Fatal(err)
}
assertCurrentProcess(t, pid, name, path)
}
func TestDarwinSocketInfoMatchLevelFallbacks(t *testing.T) {
src := netip.MustParseAddr("198.18.0.2")
dst := netip.MustParseAddr("203.0.113.10")
otherLocal := netip.MustParseAddr("192.168.1.10")
otherRemote := netip.MustParseAddr("198.51.100.10")
unspecifiedLocal := netip.MustParseAddr("0.0.0.0")
tests := []struct {
name string
local netip.Addr
remote netip.Addr
hasDst bool
wantLevel darwinSocketMatchLevel
}{
{
name: "exact",
local: src,
remote: dst,
hasDst: true,
wantLevel: darwinSocketExactMatch,
},
{
name: "unspecified local with matching remote",
local: unspecifiedLocal,
remote: dst,
hasDst: true,
wantLevel: darwinSocketExactMatch,
},
{
name: "unspecified local without destination",
local: unspecifiedLocal,
remote: otherRemote,
hasDst: false,
wantLevel: darwinSocketExactMatch,
},
{
name: "local match with different remote",
local: src,
remote: otherRemote,
hasDst: true,
wantLevel: darwinSocketLocalMatch,
},
{
name: "remote match with different local",
local: otherLocal,
remote: dst,
hasDst: true,
wantLevel: darwinSocketRemoteMatch,
},
{
name: "port only with destination",
local: otherLocal,
remote: otherRemote,
hasDst: true,
wantLevel: darwinSocketPortMatch,
},
{
name: "different local without destination",
local: otherLocal,
remote: otherRemote,
hasDst: false,
wantLevel: darwinSocketNoMatch,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
info := newDarwinSocketInfo("tcp", test.local, 12345, test.remote, 443)
level := darwinSocketInfoMatchLevel(info, "tcp", src, 12345, dst, 443, test.hasDst)
if level != test.wantLevel {
t.Fatalf("unexpected match level: got %d, want %d", level, test.wantLevel)
}
})
}
}
func newDarwinSocketInfo(network string, local netip.Addr, localPort uint16, remote netip.Addr, remotePort uint16) []byte {
info := make([]byte, darwinSocketFDInfoSize)
switch network {
case "tcp":
writeDarwinNativeUint32(info, darwinSocketInfoProtoOff, uint32(unix.IPPROTO_TCP))
writeDarwinNativeUint32(info, darwinSocketInfoKindOff, uint32(darwinSockInfoTCP))
case "udp":
writeDarwinNativeUint32(info, darwinSocketInfoProtoOff, uint32(unix.IPPROTO_UDP))
writeDarwinNativeUint32(info, darwinSocketInfoKindOff, uint32(darwinSockInfoIN))
}
writeDarwinNativeUint32(info, darwinSocketInfoFamilyOff, uint32(unix.AF_INET))
info[darwinInSockInfoVFlagOff] = darwinInSockInfoIPv4
writeDarwinNativeUint32(info, darwinInSockInfoLPortOff, uint32(localPort))
writeDarwinNativeUint32(info, darwinInSockInfoFPortOff, uint32(remotePort))
copyDarwinIPv4(info[darwinInSockInfoLAddrOff:darwinInSockInfoLAddrOff+16], local)
copyDarwinIPv4(info[darwinInSockInfoFAddrOff:darwinInSockInfoFAddrOff+16], remote)
return info
}
func writeDarwinNativeUint32(b []byte, offset int, value uint32) {
*(*uint32)(unsafe.Pointer(&b[offset])) = value
}
func copyDarwinIPv4(dst []byte, addr netip.Addr) {
ip := addr.As4()
copy(dst[12:16], ip[:])
}
+11
View File
@@ -0,0 +1,11 @@
//go:build ios
package net
import (
"github.com/xtls/xray-core/common/errors"
)
func FindProcess(network, srcIP string, srcPort uint16, destIP string, destPort uint16) (int, string, string, error) {
return 0, "", "", errors.New("process lookup is not supported on this platform")
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !windows && !linux && !android
//go:build !windows && !linux && !android && !darwin
package net
+40
View File
@@ -0,0 +1,40 @@
package platform
import (
"errors"
"sync"
)
var envReloadRegistry = struct {
sync.RWMutex
handlers []func() error
}{}
// RegisterEnvReload registers an environment reload handler and runs it once
// immediately so package defaults keep the same behavior as init-time reads.
func RegisterEnvReload(handler func() error) {
if handler == nil {
return
}
envReloadRegistry.Lock()
envReloadRegistry.handlers = append(envReloadRegistry.handlers, handler)
envReloadRegistry.Unlock()
if err := handler(); err != nil {
panic(err)
}
}
// ReloadEnvSettings refreshes all registered environment-backed package state.
func ReloadEnvSettings() error {
envReloadRegistry.RLock()
handlers := append([]func() error{}, envReloadRegistry.handlers...)
envReloadRegistry.RUnlock()
var errs []error
for _, handler := range handlers {
if err := handler(); err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
}
+3 -2
View File
@@ -36,9 +36,10 @@ func TestNormalizeEnvName(t *testing.T) {
}
func TestEnvFlag(t *testing.T) {
if v := (EnvFlag{
v := EnvFlag{
Name: "xxxxx.y",
}.GetValueAsInt(10)); v != 10 {
}.GetValueAsInt(10)
if v != 10 {
t.Error("env value: ", v)
}
}
+2 -10
View File
@@ -28,8 +28,6 @@ const (
SecurityType_AUTO SecurityType = 2
SecurityType_AES128_GCM SecurityType = 3
SecurityType_CHACHA20_POLY1305 SecurityType = 4
SecurityType_NONE SecurityType = 5 // [DEPRECATED 2023-06]
SecurityType_ZERO SecurityType = 6
)
// Enum value maps for SecurityType.
@@ -39,16 +37,12 @@ var (
2: "AUTO",
3: "AES128_GCM",
4: "CHACHA20_POLY1305",
5: "NONE",
6: "ZERO",
}
SecurityType_value = map[string]int32{
"UNKNOWN": 0,
"AUTO": 2,
"AES128_GCM": 3,
"CHACHA20_POLY1305": 4,
"NONE": 5,
"ZERO": 6,
}
)
@@ -129,15 +123,13 @@ const file_common_protocol_headers_proto_rawDesc = "" +
"\n" +
"\x1dcommon/protocol/headers.proto\x12\x14xray.common.protocol\"H\n" +
"\x0eSecurityConfig\x126\n" +
"\x04type\x18\x01 \x01(\x0e2\".xray.common.protocol.SecurityTypeR\x04type*`\n" +
"\x04type\x18\x01 \x01(\x0e2\".xray.common.protocol.SecurityTypeR\x04type*L\n" +
"\fSecurityType\x12\v\n" +
"\aUNKNOWN\x10\x00\x12\b\n" +
"\x04AUTO\x10\x02\x12\x0e\n" +
"\n" +
"AES128_GCM\x10\x03\x12\x15\n" +
"\x11CHACHA20_POLY1305\x10\x04\x12\b\n" +
"\x04NONE\x10\x05\x12\b\n" +
"\x04ZERO\x10\x06B^\n" +
"\x11CHACHA20_POLY1305\x10\x04B^\n" +
"\x18com.xray.common.protocolP\x01Z)github.com/xtls/xray-core/common/protocol\xaa\x02\x14Xray.Common.Protocolb\x06proto3"
var (
-2
View File
@@ -11,8 +11,6 @@ enum SecurityType {
AUTO = 2;
AES128_GCM = 3;
CHACHA20_POLY1305 = 4;
NONE = 5; // [DEPRECATED 2023-06]
ZERO = 6;
}
message SecurityConfig {
+26 -10
View File
@@ -1,25 +1,41 @@
package http
import (
"context"
"net/http"
"strconv"
"strings"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
)
// ParseXForwardedFor parses X-Forwarded-For header in http headers, and return the IP list in it.
func ParseXForwardedFor(header http.Header) []net.Address {
xff := header.Get("X-Forwarded-For")
if xff == "" {
return nil
// ApplyTrustedXForwardedFor returns remoteAddr overridden by X-Forwarded-For only when a configured trusted header is present.
func ApplyTrustedXForwardedFor(header http.Header, trusted []string, remoteAddr net.Addr) net.Addr {
value := header.Get("X-Forwarded-For")
if value == "" {
return remoteAddr
}
list := strings.Split(xff, ",")
addrs := make([]net.Address, 0, len(list))
for _, proxy := range list {
addrs = append(addrs, net.ParseAddress(proxy))
for _, t := range trusted {
if len(header.Values(t)) > 0 {
if idx := strings.IndexByte(value, ','); idx >= 0 {
value = value[:idx]
}
if addr := net.ParseAddress(value); addr.Family().IsIP() {
return &net.TCPAddr{
IP: addr.IP(),
Port: 0,
}
}
return remoteAddr
}
}
return addrs
if len(trusted) == 0 {
errors.LogWarning(context.Background(), `received "X-Forwarded-For" from `, remoteAddr, ` but "sockopt.trustedXForwardedFor" is not configured; ignoring it and using the real remote address`)
} else {
errors.LogError(context.Background(), `ignored potentially forged "X-Forwarded-For" from `, remoteAddr, `: `, value)
}
return remoteAddr
}
// RemoveHopByHopHeaders removes hop by hop headers in http header list.
+33 -8
View File
@@ -2,23 +2,48 @@ package http_test
import (
"bufio"
gonet "net"
"net/http"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/net"
. "github.com/xtls/xray-core/common/protocol/http"
)
func TestParseXForwardedFor(t *testing.T) {
header := http.Header{}
header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103")
addrs := ParseXForwardedFor(header)
if r := cmp.Diff(addrs, []net.Address{net.ParseAddress("129.78.138.66"), net.ParseAddress("129.78.64.103")}); r != "" {
t.Error(r)
}
func TestApplyTrustedXForwardedFor(t *testing.T) {
remoteAddr := &gonet.TCPAddr{IP: gonet.ParseIP("127.0.0.1"), Port: 12345}
t.Run("ignore X-Forwarded-For without trusted header", func(t *testing.T) {
header := http.Header{}
header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103")
if addr := ApplyTrustedXForwardedFor(header, nil, remoteAddr); addr != remoteAddr {
t.Fatalf("unexpected remote address: %v", addr)
}
})
t.Run("trust X-Forwarded-For", func(t *testing.T) {
header := http.Header{}
header.Add("X-Forwarded-For", "129.78.138.66, 129.78.64.103")
header.Add("X-Trusted-CDN", "")
addr := ApplyTrustedXForwardedFor(header, []string{"X-Trusted-CDN"}, remoteAddr)
if addr.String() != "129.78.138.66:0" {
t.Fatalf("unexpected remote address: %v", addr)
}
})
t.Run("ignore non-IP X-Forwarded-For", func(t *testing.T) {
header := http.Header{}
header.Add("X-Forwarded-For", "example.com")
header.Add("X-Trusted-CDN", "")
if addr := ApplyTrustedXForwardedFor(header, []string{"X-Trusted-CDN"}, remoteAddr); addr != remoteAddr {
t.Fatalf("unexpected remote address: %v", addr)
}
})
}
func TestHopByHopHeadersRemoving(t *testing.T) {
+1 -1
View File
@@ -19,7 +19,7 @@ type ID struct {
// Equals returns true if this ID equals to the other one.
func (id *ID) Equals(another *ID) bool {
return id.uuid.Equals(&(another.uuid))
return id.uuid.Equals(&another.uuid)
}
func (id *ID) Bytes() []byte {
+3 -1
View File
@@ -143,7 +143,9 @@ func SniffQUIC(b []byte) (*SniffHeader, error) {
if err != nil {
return nil, err
}
if len(b) < hdrLen+4+block.BlockSize() {
return nil, errNotQuic
}
cache.Clear()
mask := cache.Extend(int32(block.BlockSize()))
block.Encrypt(mask, b[hdrLen+4:hdrLen+4+len(mask)])
+1 -1
View File
@@ -229,7 +229,7 @@ func TestSniffQUICComplex(t *testing.T) {
t.Errorf("SniffQUIC() error = %v, wantErr %v", err, tt.wantErr)
return
}
if (errors.Is(err, protocol.ErrProtoNeedMoreData)) != tt.needsMoreData {
if errors.Is(err, protocol.ErrProtoNeedMoreData) != tt.needsMoreData {
t.Errorf("SniffQUIC() error = %v, expectsNoClue %v", err, tt.needsMoreData)
return
}
+2 -2
View File
@@ -5,8 +5,8 @@ import (
)
type ServerSpec struct {
Destination net.Destination
User *MemoryUser
Destination net.Destination
User *MemoryUser
}
func NewServerSpec(dest net.Destination, user *MemoryUser) *ServerSpec {
-1
View File
@@ -228,7 +228,6 @@ func isValueKind(kind reflect.Kind) bool {
}
func marshalInterface(v interface{}, ignoreNullValue bool, insertTypeInfo bool) interface{} {
if r, ok := marshalKnownType(v, ignoreNullValue, insertTypeInfo); ok {
return r
}
+1 -3
View File
@@ -27,7 +27,6 @@ func TestMashalAccount(t *testing.T) {
j, ok := MarshalToJson(user, false)
if !ok || strings.Contains(j, "_TypedMessage_") {
t.Error("marshal account failed")
}
@@ -79,13 +78,12 @@ func TestMashalStruct(t *testing.T) {
v := (*f2.Arr)[0]["foo"]["hello"]
if f1.N != f2.N || *(f1.Np) != *(f2.Np) || f1.S != f2.S || v != "world" {
if f1.N != f2.N || *f1.Np != *f2.Np || f1.S != f2.S || v != "world" {
t.Error("f1 not equal to f2")
}
}
func TestMarshalConfigJson(t *testing.T) {
buf := bytes.NewBufferString(getConfig())
config, err := iserial.DecodeJSONConfig(buf)
if err != nil {
-29
View File
@@ -4,7 +4,6 @@ import (
"testing"
. "github.com/xtls/xray-core/common/serial"
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
)
func TestGetInstance(t *testing.T) {
@@ -23,31 +22,3 @@ func TestConvertingNilMessage(t *testing.T) {
t.Error("expect nil, but actually not")
}
}
func TestTypedMessageRoundTripPreservesFinalmaskCustomUDPMode(t *testing.T) {
msg := &custom.UDPConfig{
Mode: "standalone",
Client: []*custom.UDPItem{
{Rand: 12, Save: "txid"},
},
}
tm := ToTypedMessage(msg)
if tm == nil {
t.Fatal("expected typed message")
}
roundTrip, err := tm.GetInstance()
if err != nil {
t.Fatalf("GetInstance() failed: %v", err)
}
udp, ok := roundTrip.(*custom.UDPConfig)
if !ok {
t.Fatalf("unexpected round-trip type: %T", roundTrip)
}
if udp.GetMode() != "standalone" {
t.Fatalf("mode lost during typed message round-trip: got %q", udp.GetMode())
}
}
+10
View File
@@ -26,6 +26,8 @@ const (
fullHandlerKey ctx.SessionKey = 10 // outbound gets full handler
mitmAlpn11Key ctx.SessionKey = 11 // used by TLS dialer
mitmServerNameKey ctx.SessionKey = 12 // used by TLS dialer
streamSettingsKey ctx.SessionKey = 13
)
func ContextWithInbound(ctx context.Context, inbound *Inbound) context.Context {
@@ -192,3 +194,11 @@ func MitmServerNameFromContext(ctx context.Context) string {
}
return ""
}
func ContextWithStreamSettings(ctx context.Context, streamSettings any) context.Context {
return context.WithValue(ctx, streamSettingsKey, streamSettings)
}
func StreamSettingsFromContext(ctx context.Context) any {
return ctx.Value(streamSettingsKey)
}
+5 -4
View File
@@ -3,6 +3,7 @@ package singbridge
import (
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
)
@@ -17,14 +18,14 @@ func ToNetwork(network string) net.Network {
}
}
func ToDestination(socksaddr M.Socksaddr, network net.Network) net.Destination {
func ToDestination(socksaddr M.Socksaddr, network net.Network) (net.Destination, error) {
// IsFqdn() implicitly checks if the domain name is valid
if socksaddr.IsFqdn() {
return net.Destination{
Network: network,
Address: net.DomainAddress(socksaddr.Fqdn),
Port: net.Port(socksaddr.Port),
}
}, nil
}
// IsIP() implicitly checks if the IP address is valid
@@ -33,10 +34,10 @@ func ToDestination(socksaddr M.Socksaddr, network net.Network) net.Destination {
Network: network,
Address: net.IPAddress(socksaddr.Addr.AsSlice()),
Port: net.Port(socksaddr.Port),
}
}, nil
}
return net.Destination{}
return net.Destination{}, errors.New("invalid socks address: ", socksaddr)
}
func ToSocksaddr(destination net.Destination) M.Socksaddr {
+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) {
return d.Dialer.Dial(ctx, ToDestination(destination, ToNetwork(network)))
dest, err := ToDestination(destination, ToNetwork(network))
if err != nil {
return nil, err
}
return d.Dialer.Dial(ctx, dest)
}
func (d *XrayDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
@@ -43,13 +47,17 @@ func NewOutboundDialer(outbound proxy.Outbound, dialer internet.Dialer) *XrayOut
}
func (d *XrayOutboundDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
dest, err := ToDestination(destination, ToNetwork(network))
if err != nil {
return nil, err
}
outbounds := session.OutboundsFromContext(ctx)
if len(outbounds) == 0 {
outbounds = []*session.Outbound{{}}
ctx = session.ContextWithOutbounds(ctx, outbounds)
}
ob := outbounds[len(outbounds)-1]
ob.Target = ToDestination(destination, ToNetwork(network))
ob.Target = dest
opts := []pipe.Option{pipe.WithSizeLimit(64 * 1024)}
uplinkReader, uplinkWriter := pipe.New(opts...)
+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 {
dest, err := ToDestination(metadata.Destination, net.Network_TCP)
if err != nil {
return err
}
xConn := NewConn(conn)
return d.upstream.DispatchLink(ctx, ToDestination(metadata.Destination, net.Network_TCP), &transport.Link{
return d.upstream.DispatchLink(ctx, dest, &transport.Link{
Reader: xConn,
Writer: xConn,
})
}
func (d *Dispatcher) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
return d.upstream.DispatchLink(ctx, ToDestination(metadata.Destination, net.Network_UDP), &transport.Link{
dest, err := ToDestination(metadata.Destination, net.Network_UDP)
if err != nil {
return err
}
return d.upstream.DispatchLink(ctx, dest, &transport.Link{
Reader: buf.NewPacketReader(conn.(io.Reader)),
Writer: buf.NewWriter(conn.(io.Writer)),
})
+31 -30
View File
@@ -10,15 +10,21 @@ import (
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/transport"
)
func CopyPacketConn(ctx context.Context, inboundConn net.Conn, link *transport.Link, destination net.Destination, serverConn net.PacketConn) error {
cancel := func() {
common.Interrupt(link.Reader)
common.Interrupt(serverConn)
}
conn := &PacketConnWrapper{
Reader: link.Reader,
Writer: link.Writer,
Dest: destination,
Conn: inboundConn,
T: signal.CancelAfterInactivity(ctx, cancel, 300*time.Second),
}
return ReturnError(bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(serverConn)))
}
@@ -29,11 +35,19 @@ type PacketConnWrapper struct {
net.Conn
Dest net.Destination
cached buf.MultiBuffer
// A simple patch to avoid goroutine leak since sing infra cannot awake read block by write err
T *signal.ActivityTimer
}
// This ReadPacket implemented a timeout to avoid goroutine leak like PipeConnWrapper.Read()
// as a temporarily solution
func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) {
func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (addr M.Socksaddr, err error) {
w.T.Update()
defer func() {
if err != nil {
// uplinkonly
w.T.SetTimeout(2 * time.Second)
}
}()
if w.cached != nil {
mb, bb := buf.SplitFirst(w.cached)
if bb == nil {
@@ -51,30 +65,7 @@ func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) {
return ToSocksaddr(destination), nil
}
}
// timeout
type readResult struct {
mb buf.MultiBuffer
err error
}
c := make(chan readResult, 1)
go func() {
mb, err := w.ReadMultiBuffer()
c <- readResult{mb: mb, err: err}
}()
var mb buf.MultiBuffer
select {
case <-time.After(60 * time.Second):
common.Close(w.Reader)
common.Interrupt(w.Reader)
return M.Socksaddr{}, buf.ErrReadTimeout
case result := <-c:
if result.err != nil {
return M.Socksaddr{}, result.err
}
mb = result.mb
}
mb, err := w.ReadMultiBuffer()
nb, bb := buf.SplitFirst(mb)
if bb == nil {
return M.Socksaddr{}, nil
@@ -92,12 +83,22 @@ func (w *PacketConnWrapper) ReadPacket(buffer *B.Buffer) (M.Socksaddr, error) {
}
}
func (w *PacketConnWrapper) WritePacket(buffer *B.Buffer, destination M.Socksaddr) error {
func (w *PacketConnWrapper) WritePacket(buffer *B.Buffer, destination M.Socksaddr) (err error) {
w.T.Update()
defer func() {
if err != nil {
// downlinkonly
w.T.SetTimeout(5 * time.Second)
}
}()
endpoint, err := ToDestination(destination, net.Network_UDP)
if err != nil {
return err
}
vBuf := buf.New()
vBuf.Write(buffer.Bytes())
endpoint := ToDestination(destination, net.Network_UDP)
vBuf.UDP = &endpoint
return w.Writer.WriteMultiBuffer(buf.MultiBuffer{vBuf})
return w.WriteMultiBuffer(buf.MultiBuffer{vBuf})
}
func (w *PacketConnWrapper) Close() error {
+18 -18
View File
@@ -9,6 +9,7 @@ import (
"github.com/sagernet/sing/common/bufio"
"github.com/xtls/xray-core/common"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/signal"
"github.com/xtls/xray-core/transport"
)
@@ -22,6 +23,11 @@ func CopyConn(ctx context.Context, inboundConn net.Conn, link *transport.Link, s
} else {
conn.R = &buf.BufferedReader{Reader: link.Reader}
}
cancel := func() {
common.Interrupt(link.Reader)
common.Interrupt(serverConn)
}
conn.T = signal.CancelAfterInactivity(ctx, cancel, 300*time.Second)
return ReturnError(bufio.CopyConn(ctx, conn, serverConn))
}
@@ -29,35 +35,27 @@ type PipeConnWrapper struct {
R io.Reader
W buf.Writer
net.Conn
// A simple patch to avoid goroutine leak since sing infra cannot awake read block by write err
T *signal.ActivityTimer
}
func (w *PipeConnWrapper) Close() error {
return nil
}
// This Read implemented a timeout to avoid goroutine leak.
// as a temporarily solution
func (w *PipeConnWrapper) Read(b []byte) (n int, err error) {
type readResult struct {
n int
err error
}
c := make(chan readResult, 1)
go func() {
n, err := w.R.Read(b)
c <- readResult{n: n, err: err}
}()
select {
case result := <-c:
return result.n, result.err
case <-time.After(300 * time.Second):
common.Close(w.R)
common.Interrupt(w.R)
return 0, buf.ErrReadTimeout
w.T.Update()
n, err = w.R.Read(b)
if err != nil {
// uplinkonly
w.T.SetTimeout(2 * time.Second)
}
return
}
func (w *PipeConnWrapper) Write(p []byte) (n int, err error) {
w.T.Update()
n = len(p)
var mb buf.MultiBuffer
pLen := len(p)
@@ -76,6 +74,8 @@ func (w *PipeConnWrapper) Write(p []byte) (n int, err error) {
if err != nil {
n = 0
buf.ReleaseMulti(mb)
// downlinkonly
w.T.SetTimeout(5 * time.Second)
}
return
}
+14 -7
View File
@@ -10,37 +10,44 @@ func TestByteSizes(t *testing.T) {
size := units.ByteSize(0)
assertSizeString(t, size, "0")
size++
assertSizeValue(t,
assertSizeValue(
t,
assertSizeString(t, size, "1.00B"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeValue(
t,
assertSizeString(t, size, "1.00KB"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeValue(
t,
assertSizeString(t, size, "1.00MB"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeValue(
t,
assertSizeString(t, size, "1.00GB"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeValue(
t,
assertSizeString(t, size, "1.00TB"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeValue(
t,
assertSizeString(t, size, "1.00PB"),
size,
)
size <<= 10
assertSizeValue(t,
assertSizeValue(
t,
assertSizeString(t, size, "1.00EB"),
size,
)
+77 -43
View File
@@ -4,10 +4,10 @@ import (
"hash/fnv"
"math"
"math/rand"
"strconv"
"time"
"net/http"
"strconv"
"strings"
"time"
"github.com/klauspost/cpuid/v2"
)
@@ -18,6 +18,7 @@ func GetRandomizer() *rand.Rand {
fnvHash.Write([]byte(strconv.Itoa(cpuid.CPU.Family) + strconv.Itoa(cpuid.CPU.Model) + strconv.Itoa(cpuid.CPU.PhysicalCores) + strconv.Itoa(cpuid.CPU.LogicalCores) + strconv.Itoa(cpuid.CPU.CacheLine) + strconv.Itoa(cpuid.CPU.ThreadsPerCore)))
return rand.New(rand.NewSource(int64(fnvHash.Sum64())))
}
var globalRng *rand.Rand = GetRandomizer()
// The Chrome version generator will suffer from deviation of a normal distribution.
@@ -26,83 +27,114 @@ func ChromeVersion() int {
var startVersion int = 144
var timeStart int64 = time.Date(2026, 1, 13, 0, 0, 0, 0, time.UTC).Unix() / 86400
var timeCurrent int64 = time.Now().Unix() / 86400
var timeDiff int = int((timeCurrent - timeStart - 35)) - int(math.Floor(math.Pow(globalRng.Float64(), 2) * 105))
var timeDiff int = int((timeCurrent - timeStart - 35)) - int(math.Floor(math.Pow(globalRng.Float64(), 2)*105))
return startVersion + (timeDiff / 35) // It's 31.15 currently.
}
var safariMinorMap [25]int = [25]int{0, 0, 0, 1, 1,
var safariMinorMap [25]int = [25]int{
0, 0, 0, 1, 1,
1, 2, 2, 2, 2, 3, 3, 3, 4, 4,
4, 5, 5, 5, 5, 5, 6, 6, 6, 6}
4, 5, 5, 5, 5, 5, 6, 6, 6, 6,
}
// The following version generators use deterministic generators, but with the distribution scaled by a curve.
func CurlVersion() string {
// curl 8.0.0 was released on 20/03/2023.
var timeCurrent int64 = time.Now().Unix() / 86400
var timeStart int64 = time.Date(2023, 3, 20, 0, 0, 0, 0, time.UTC).Unix() / 86400
var timeDiff int = int((timeCurrent - timeStart - 60)) - int(math.Floor(math.Pow(globalRng.Float64(), 2) * 165))
var timeDiff int = int((timeCurrent - timeStart - 60)) - int(math.Floor(math.Pow(globalRng.Float64(), 2)*165))
var minorValue int = int(timeDiff / 57) // The release cadence is actually 56.67 days.
return "8." + strconv.Itoa(minorValue) + ".0"
}
func FirefoxVersion() int {
// Firefox 128 ESR was released on 09/07/2023.
var timeCurrent int64 = time.Now().Unix() / 86400
var timeStart int64 = time.Date(2024, 7, 29, 0, 0, 0, 0, time.UTC).Unix() / 86400
var timeDiff = timeCurrent - timeStart - 25 - int64(math.Floor(math.Pow(globalRng.Float64(), 2) * 50))
return int(timeDiff / 30) + 128
timeDiff := timeCurrent - timeStart - 25 - int64(math.Floor(math.Pow(globalRng.Float64(), 2)*50))
return int(timeDiff/30) + 128
}
func SafariVersion() string {
var anchoredTime time.Time = time.Now()
var releaseYear int = anchoredTime.Year()
var splitPoint time.Time = time.Date(releaseYear, 9, 23, 0, 0, 0, 0, time.UTC)
var delayedDays = int(math.Floor(math.Pow(globalRng.Float64(), 3) * 75))
delayedDays := int(math.Floor(math.Pow(globalRng.Float64(), 3) * 75))
splitPoint = splitPoint.AddDate(0, 0, delayedDays)
if (anchoredTime.Compare(splitPoint) < 0) {
releaseYear --
if anchoredTime.Compare(splitPoint) < 0 {
releaseYear--
splitPoint = time.Date(releaseYear, 9, 23, 0, 0, 0, 0, time.UTC)
splitPoint = splitPoint.AddDate(0, 0, delayedDays)
}
var minorVersion = safariMinorMap[(anchoredTime.Unix() - splitPoint.Unix()) / 1296000]
return strconv.Itoa(releaseYear - 1999) + "." + strconv.Itoa(minorVersion)
minorVersion := safariMinorMap[(anchoredTime.Unix()-splitPoint.Unix())/1296000]
return strconv.Itoa(releaseYear-1999) + "." + strconv.Itoa(minorVersion)
}
// The full Chromium brand GREASE implementation
var clientHintGreaseNA = []string{" ", "(", ":", "-", ".", "/", ")", ";", "=", "?", "_"}
var clientHintVersionNA = []string{"8", "99", "24"}
var clientHintShuffle3 = [][3]int{{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}}
var clientHintShuffle4 = [][4]int{
{0, 1, 2, 3}, {0, 1, 3, 2}, {0, 2, 1, 3}, {0, 2, 3, 1}, {0, 3, 1, 2}, {0, 3, 2, 1},
{1, 0, 2, 3}, {1, 0, 3, 2}, {1, 2, 0, 3}, {1, 2, 3, 0}, {1, 3, 0, 2}, {1, 3, 2, 0},
{2, 0, 1, 3}, {2, 0, 3, 1}, {2, 1, 0, 3}, {2, 1, 3, 0}, {2, 3, 0, 1}, {2, 3, 1, 0},
{3, 0, 1, 2}, {3, 0, 2, 1}, {3, 1, 0, 2}, {3, 1, 2, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}}
var (
clientHintGreaseNA = []string{" ", "(", ":", "-", ".", "/", ")", ";", "=", "?", "_"}
clientHintVersionNA = []string{"8", "99", "24"}
clientHintShuffle3 = [][3]int{{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}}
clientHintShuffle4 = [][4]int{
{0, 1, 2, 3},
{0, 1, 3, 2},
{0, 2, 1, 3},
{0, 2, 3, 1},
{0, 3, 1, 2},
{0, 3, 2, 1},
{1, 0, 2, 3},
{1, 0, 3, 2},
{1, 2, 0, 3},
{1, 2, 3, 0},
{1, 3, 0, 2},
{1, 3, 2, 0},
{2, 0, 1, 3},
{2, 0, 3, 1},
{2, 1, 0, 3},
{2, 1, 3, 0},
{2, 3, 0, 1},
{2, 3, 1, 0},
{3, 0, 1, 2},
{3, 0, 2, 1},
{3, 1, 0, 2},
{3, 1, 2, 0},
{3, 2, 0, 1},
{3, 2, 1, 0},
}
)
func getGreasedChInvalidBrand(seed int) string {
return "\"Not" + clientHintGreaseNA[seed % len(clientHintGreaseNA)] + "A" + clientHintGreaseNA[(seed + 1) % len(clientHintGreaseNA)] + "Brand\";v=\"" + clientHintVersionNA[seed % len(clientHintVersionNA)] + "\"";
return "\"Not" + clientHintGreaseNA[seed%len(clientHintGreaseNA)] + "A" + clientHintGreaseNA[(seed+1)%len(clientHintGreaseNA)] + "Brand\";v=\"" + clientHintVersionNA[seed%len(clientHintVersionNA)] + "\""
}
func getGreasedChOrder(brandLength int, seed int) []int {
switch brandLength {
case 1:
return []int{0}
case 2:
return []int{seed % brandLength, (seed + 1) % brandLength}
case 3:
return clientHintShuffle3[seed % len(clientHintShuffle3)][:]
default:
return clientHintShuffle4[seed % len(clientHintShuffle4)][:]
case 1:
return []int{0}
case 2:
return []int{seed % brandLength, (seed + 1) % brandLength}
case 3:
return clientHintShuffle3[seed%len(clientHintShuffle3)][:]
default:
return clientHintShuffle4[seed%len(clientHintShuffle4)][:]
}
//return []int{}
}
func getUngreasedChUa(majorVersion int, forkName string) []string {
// Set the capacity to 4, the maximum allowed brand size, so Go will never allocate memory twice
baseChUa := make([]string, 0, 4)
baseChUa = append(baseChUa, getGreasedChInvalidBrand(majorVersion),
"\"Chromium\";v=\"" + strconv.Itoa(majorVersion) + "\"")
"\"Chromium\";v=\""+strconv.Itoa(majorVersion)+"\"")
switch forkName {
case "chrome":
baseChUa = append(baseChUa, "\"Google Chrome\";v=\"" + strconv.Itoa(majorVersion) + "\"")
baseChUa = append(baseChUa, "\"Google Chrome\";v=\""+strconv.Itoa(majorVersion)+"\"")
case "edge":
baseChUa = append(baseChUa, "\"Microsoft Edge\";v=\"" + strconv.Itoa(majorVersion) + "\"")
baseChUa = append(baseChUa, "\"Microsoft Edge\";v=\""+strconv.Itoa(majorVersion)+"\"")
}
return baseChUa
}
func getGreasedChUa(majorVersion int, forkName string) string {
ungreasedCh := getUngreasedChUa(majorVersion, forkName)
shuffleMap := getGreasedChOrder(len(ungreasedCh), majorVersion)
@@ -114,16 +146,18 @@ func getGreasedChUa(majorVersion int, forkName string) string {
}
// The code below provides a coherent default browser user agent string based on a CPU-seeded PRNG.
var CurlUA = "curl/" + CurlVersion()
var AnchoredFirefoxVersion = strconv.Itoa(FirefoxVersion())
var FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:" + AnchoredFirefoxVersion + ".0) Gecko/20100101 Firefox/" + AnchoredFirefoxVersion + ".0"
var SafariUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/" + SafariVersion() + " Safari/605.1.15"
// Chromium browsers.
var AnchoredChromeVersion = ChromeVersion()
var ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 Safari/537.36"
var ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome")
var MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0"
var MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge")
var (
CurlUA = "curl/" + CurlVersion()
AnchoredFirefoxVersion = strconv.Itoa(FirefoxVersion())
FirefoxUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:" + AnchoredFirefoxVersion + ".0) Gecko/20100101 Firefox/" + AnchoredFirefoxVersion + ".0"
SafariUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/" + SafariVersion() + " Safari/605.1.15"
// Chromium browsers.
AnchoredChromeVersion = ChromeVersion()
ChromeUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0 Safari/537.36"
ChromeUACH = getGreasedChUa(AnchoredChromeVersion, "chrome")
MSEdgeUA = ChromeUA + "Edg/" + strconv.Itoa(AnchoredChromeVersion) + ".0.0.0"
MSEdgeUACH = getGreasedChUa(AnchoredChromeVersion, "edge")
)
func applyMasqueradedHeaders(header http.Header, browser string, variant string) {
// Browser-specific.
+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)
}
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
}
+59
View File
@@ -0,0 +1,59 @@
package utils
import (
"maps"
"runtime"
"sync"
"weak"
)
// WeakCacheMap is a map that holds weak references to values.
// Use for shared expensive objects and automatic cleanup when no longer used.
// This object can be GC and no goroutine is used for cleanup.
type WeakCacheMap[K comparable, V any] struct {
mu sync.Mutex
m map[K]weak.Pointer[V]
}
func NewWeakCacheMap[K comparable, V any]() *WeakCacheMap[K, V] {
return &WeakCacheMap[K, V]{
m: make(map[K]weak.Pointer[V]),
}
}
func (c *WeakCacheMap[K, V]) Load(key K) (value *V, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
weakPtr := c.m[key].Value()
if weakPtr != nil {
return weakPtr, true
}
return nil, false
}
func (c *WeakCacheMap[K, V]) Store(key K, value *V) {
c.mu.Lock()
defer c.mu.Unlock()
weakPtr := weak.Make(value)
c.m[key] = weakPtr
runtime.AddCleanup(value, func(struct{}) {
c.mu.Lock()
defer c.mu.Unlock()
if c.m[key] == weakPtr {
delete(c.m, key)
}
}, struct{}{})
}
func (c *WeakCacheMap[K, V]) Range(f func(K, *V) bool) {
c.mu.Lock()
snapshot := maps.Clone(c.m)
c.mu.Unlock()
for k, v := range snapshot {
if value := v.Value(); value != nil {
if !f(k, value) {
break
}
}
}
}
+33 -19
View File
@@ -8,7 +8,7 @@ import (
"io"
"strconv"
"strings"
"time"
"sync/atomic"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
@@ -27,25 +27,39 @@ var AddrParser = protocol.NewAddressParser(
)
var (
Show bool
BaseKey []byte
Show atomic.Bool
baseKey atomic.Value
)
func init() {
if strings.ToLower(platform.NewEnvFlag(platform.XUDPLog).GetValue(func() string { return "" })) == "true" {
Show = true
func reloadEnvSettings() error {
Show.Store(strings.ToLower(platform.NewEnvFlag(platform.XUDPLog).GetValue(func() string { return "" })) == "true")
raw := platform.NewEnvFlag(platform.XUDPBaseKey).GetValue(func() string { return "" })
if raw == "" {
ensureBaseKey()
return nil
}
BaseKey = make([]byte, 32)
rand.Read(BaseKey)
go func() {
time.Sleep(100 * time.Millisecond) // this is not nice, but need to give some time for Android to setup ENV
if raw := platform.NewEnvFlag(platform.XUDPBaseKey).GetValue(func() string { return "" }); raw != "" {
if BaseKey, _ = base64.RawURLEncoding.DecodeString(raw); len(BaseKey) == 32 {
return
}
panic(platform.XUDPBaseKey + ": invalid value (BaseKey must be 32 bytes): " + raw + " len " + strconv.Itoa(len(BaseKey)))
}
}()
key, _ := base64.RawURLEncoding.DecodeString(raw)
if len(key) != 32 {
return errors.New(platform.XUDPBaseKey + ": invalid value (BaseKey must be 32 bytes): " + raw + " len " + strconv.Itoa(len(key)))
}
baseKey.Store(append([]byte(nil), key...))
return nil
}
func ensureBaseKey() []byte {
if key := baseKey.Load(); key != nil {
return key.([]byte)
}
key := make([]byte, 32)
if _, err := rand.Read(key); err != nil {
panic(err)
}
baseKey.Store(key)
return key
}
func init() {
platform.RegisterEnvReload(reloadEnvSettings)
}
func GetGlobalID(ctx context.Context) (globalID [8]byte) {
@@ -54,10 +68,10 @@ func GetGlobalID(ctx context.Context) (globalID [8]byte) {
}
if inbound := session.InboundFromContext(ctx); inbound != nil && inbound.Source.Network == net.Network_UDP &&
(inbound.Name == "dokodemo-door" || inbound.Name == "socks" || inbound.Name == "shadowsocks" || inbound.Name == "tun") {
h := blake3.New(8, BaseKey)
h := blake3.New(8, ensureBaseKey())
h.Write([]byte(inbound.Source.String()))
copy(globalID[:], h.Sum(nil))
if Show {
if Show.Load() {
errors.LogInfo(ctx, fmt.Sprintf("XUDP inbound.Source.String(): %v\tglobalID: %v\n", inbound.Source.String(), globalID))
}
}
+2 -2
View File
@@ -19,8 +19,8 @@ import (
var (
Version_x byte = 26
Version_y byte = 5
Version_z byte = 9
Version_y byte = 7
Version_z byte = 28
)
var (
+3
View File
@@ -187,6 +187,9 @@ func NewWithContext(ctx context.Context, config *Config) (*Instance, error) {
}
func initInstanceWithConfig(config *Config, server *Instance) (bool, error) {
if err := platform.ReloadEnvSettings(); err != nil {
return true, errors.New("failed to reload environment settings").Base(err)
}
server.ctx = context.WithValue(server.ctx, "cone",
platform.NewEnvFlag(platform.UseCone).GetValue(func() string { return "" }) != "true")
+18 -8
View File
@@ -3,6 +3,7 @@ package policy
import (
"context"
"runtime"
"sync/atomic"
"time"
"github.com/xtls/xray-core/common/platform"
@@ -82,32 +83,41 @@ func ManagerType() interface{} {
return (*Manager)(nil)
}
var defaultBufferSize int32
var defaultBufferSize atomic.Int32
func init() {
func reloadEnvSettings() error {
defaultBufferSize.Store(readDefaultBufferSize())
return nil
}
func readDefaultBufferSize() int32 {
const defaultValue = -17
size := platform.NewEnvFlag(platform.BufferSize).GetValueAsInt(defaultValue)
switch size {
case 0:
defaultBufferSize = -1 // For pipe to use unlimited size
return -1 // For pipe to use unlimited size
case defaultValue: // Env flag not defined. Use default values per CPU-arch.
switch runtime.GOARCH {
case "arm", "mips", "mipsle":
defaultBufferSize = 0
return 0
case "arm64", "mips64", "mips64le":
defaultBufferSize = 4 * 1024 // 4k cache for low-end devices
return 4 * 1024 // 4k cache for low-end devices
default:
defaultBufferSize = 512 * 1024
return 512 * 1024
}
default:
defaultBufferSize = int32(size) * 1024 * 1024
return int32(size) * 1024 * 1024
}
}
func init() {
platform.RegisterEnvReload(reloadEnvSettings)
}
func defaultBufferPolicy() Buffer {
return Buffer{
PerConnection: defaultBufferSize,
PerConnection: defaultBufferSize.Load(),
}
}
-1
View File
@@ -34,7 +34,6 @@ func (ctx *Context) GetSourceIPs() []net.IP {
}
return nil
}
// GetSourcePort implements routing.Context.
+21 -30
View File
@@ -81,6 +81,8 @@ type Manager interface {
// RegisterCounter registers a new counter to the manager. The identifier string must not be empty, and unique among other counters.
RegisterCounter(string) (Counter, error)
// GetOrRegisterCounter returns the counter by its identifier, atomically creating and registering it if absent.
GetOrRegisterCounter(string) (Counter, error)
// UnregisterCounter unregisters a counter from the manager by its identifier.
UnregisterCounter(string) error
// GetCounter returns a counter by its identifier.
@@ -91,6 +93,8 @@ type Manager interface {
// RegisterOnlineMap registers a new OnlineMap to the manager. The identifier string must not be empty, and unique among other OnlineMaps.
RegisterOnlineMap(string) (OnlineMap, error)
// GetOrRegisterOnlineMap returns the OnlineMap by its identifier, atomically creating and registering it if absent.
GetOrRegisterOnlineMap(string) (OnlineMap, error)
// UnregisterOnlineMap unregisters an OnlineMap from the manager by its identifier.
UnregisterOnlineMap(string) error
// GetOnlineMap returns an OnlineMap by its identifier.
@@ -101,6 +105,8 @@ type Manager interface {
// RegisterChannel registers a new channel to the manager. The identifier string must not be empty, and unique among other channels.
RegisterChannel(string) (Channel, error)
// GetOrRegisterChannel returns the channel by its identifier, atomically creating and registering it if absent.
GetOrRegisterChannel(string) (Channel, error)
// UnregisterChannel unregisters a channel from the manager by its identifier.
UnregisterChannel(string) error
// GetChannel returns a channel by its identifier.
@@ -110,36 +116,6 @@ type Manager interface {
GetAllOnlineUsers() []string
}
// GetOrRegisterCounter tries to get the StatCounter first. If not exist, it then tries to create a new counter.
func GetOrRegisterCounter(m Manager, name string) (Counter, error) {
counter := m.GetCounter(name)
if counter != nil {
return counter, nil
}
return m.RegisterCounter(name)
}
// GetOrRegisterOnlineMap tries to get the OnlineMap first. If not exist, it then tries to create a new OnlineMap.
func GetOrRegisterOnlineMap(m Manager, name string) (OnlineMap, error) {
onlineMap := m.GetOnlineMap(name)
if onlineMap != nil {
return onlineMap, nil
}
return m.RegisterOnlineMap(name)
}
// GetOrRegisterChannel tries to get the StatChannel first. If not exist, it then tries to create a new channel.
func GetOrRegisterChannel(m Manager, name string) (Channel, error) {
channel := m.GetChannel(name)
if channel != nil {
return channel, nil
}
return m.RegisterChannel(name)
}
// ManagerType returns the type of Manager interface. Can be used to implement common.HasType.
//
// xray:api:stable
@@ -160,6 +136,11 @@ func (NoopManager) RegisterCounter(string) (Counter, error) {
return nil, errors.New("not implemented")
}
// GetOrRegisterCounter implements Manager.
func (NoopManager) GetOrRegisterCounter(string) (Counter, error) {
return nil, errors.New("not implemented")
}
// UnregisterCounter implements Manager.
func (NoopManager) UnregisterCounter(string) error {
return nil
@@ -178,6 +159,11 @@ func (NoopManager) RegisterOnlineMap(string) (OnlineMap, error) {
return nil, errors.New("not implemented")
}
// GetOrRegisterOnlineMap implements Manager.
func (NoopManager) GetOrRegisterOnlineMap(string) (OnlineMap, error) {
return nil, errors.New("not implemented")
}
// UnregisterOnlineMap implements Manager.
func (NoopManager) UnregisterOnlineMap(string) error {
return nil
@@ -196,6 +182,11 @@ func (NoopManager) RegisterChannel(string) (Channel, error) {
return nil, errors.New("not implemented")
}
// GetOrRegisterChannel implements Manager.
func (NoopManager) GetOrRegisterChannel(string) (Channel, error) {
return nil, errors.New("not implemented")
}
// UnregisterChannel implements Manager.
func (NoopManager) UnregisterChannel(string) error {
return nil
+19 -13
View File
@@ -4,15 +4,17 @@ go 1.26
require (
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716
github.com/cloudflare/circl v1.6.3
github.com/cloudflare/circl v1.6.4
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
github.com/golang/mock v1.7.0-rc.1
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/klauspost/cpuid/v2 v2.3.0
github.com/klauspost/cpuid/v2 v2.4.0
github.com/miekg/dns v1.1.72
github.com/pelletier/go-toml v1.9.5
github.com/pires/go-proxyproto v0.12.0
github.com/pion/stun/v3 v3.1.6
github.com/pires/go-proxyproto v0.15.0
github.com/refraction-networking/utls v1.8.3-0.20260301010127-aa6edf4b11af
github.com/robfig/cron/v3 v3.0.1
github.com/sagernet/sing v0.5.1
@@ -21,15 +23,15 @@ require (
github.com/vishvananda/netlink v1.3.1
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.50.0
golang.org/x/crypto v0.54.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.53.0
golang.org/x/sync v0.20.0
golang.org/x/sys v0.43.0
golang.org/x/net v0.57.0
golang.org/x/sync v0.22.0
golang.org/x/sys v0.47.0
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
golang.zx2c4.com/wireguard/windows v1.0.1
google.golang.org/grpc v1.81.0
google.golang.org/grpc v1.82.1
google.golang.org/protobuf v1.36.11
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0
h12.io/socks v1.0.3
@@ -43,14 +45,18 @@ require (
github.com/juju/ratelimit v1.0.2 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pion/dtls/v3 v3.1.4 // indirect
github.com/pion/logging v0.2.4 // indirect
github.com/pion/transport/v4 v4.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/vishvananda/netns v0.0.5 // indirect
golang.org/x/mod v0.34.0 // indirect
golang.org/x/text v0.36.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.43.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
github.com/wlynxg/anet v0.0.5 // indirect
golang.org/x/mod v0.37.0 // indirect
golang.org/x/text v0.40.0 // indirect
golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.47.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+36 -26
View File
@@ -4,8 +4,8 @@ github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716 h1:J1O+xpLuJWkd
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/cloudflare/circl v1.6.4 h1:pOXuDTCEYyzydgUpQ0CQz3LsinKjiSk6nNP5Lt5K64U=
github.com/cloudflare/circl v1.6.4/go.mod h1:YxarevkLlbaHuWsxG6vmYNWBEsSp4pnp7j+4VljMavY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -33,8 +33,8 @@ github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -45,8 +45,16 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
github.com/pion/dtls/v3 v3.1.4 h1:QhvtMflMfu9Kf0RcDC5BJBle4caPskByrKQR6uuYqpY=
github.com/pion/dtls/v3 v3.1.4/go.mod h1:cr/qotLISUw/9C1m83ZPNZtj9WnXkYLpfCptPqbkInc=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/stun/v3 v3.1.6 h1:WnhsD0eHCiwCfKNkVx0VJJwr2Y3eV4Ueih3KJ+dfZy8=
github.com/pion/stun/v3 v3.1.6/go.mod h1:zRUghXSQU32Lx5orJsz3uYMkIihweXb3mu5gIns02fs=
github.com/pion/transport/v4 v4.0.2 h1:ifYlPqNwsy6aKQ9y8yzxXlHae5431ZrH2avkD/Rn6Tk=
github.com/pion/transport/v4 v4.0.2/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM=
github.com/pires/go-proxyproto v0.15.0 h1:dTshmNbFm/D+0+sbrxUuddPOZ5Y0B7c5NhtsBkm6LqI=
github.com/pires/go-proxyproto v0.15.0/go.mod h1:OXsCrKwrK2tXS9YrI5tkHx5xaQlO8FH3lFW76orFh24=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
@@ -67,6 +75,8 @@ github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f h1:iy2JRioxmUpoJ3SzbFPyTxHZMbR/rSHP7dOOgYaq1O8=
github.com/xtls/reality v0.0.0-20260322125925-9234c772ba8f/go.mod h1:DsJblcWDGt76+FVqBVwbwRhxyyNJsGV48gJLch0OOWI=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
@@ -88,22 +98,22 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -111,21 +121,21 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -137,10 +147,10 @@ golang.zx2c4.com/wireguard/windows v1.0.1 h1:eOxiDVbywPC+ZQqvdCK7x+ZwWXKbYv50TtH
golang.zx2c4.com/wireguard/windows v1.0.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.81.0 h1:W3G9N3KQf3BU+YuCtGKJk0CmxQNbAISICD/9AORxLIw=
google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+2 -1
View File
@@ -48,4 +48,5 @@ var configLoader = NewJSONConfigLoader(
"http": func() interface{} { return new(HTTPResponse) },
},
"type",
"")
"",
)
+2 -1
View File
@@ -3,6 +3,7 @@ package conf
import (
"encoding/json"
"fmt"
"math"
"strconv"
"strings"
@@ -199,7 +200,7 @@ func (v *PortRange) UnmarshalJSON(data []byte) error {
if err == nil {
v.From = uint32(from)
v.To = uint32(to)
if v.From > v.To {
if v.From > v.To || v.To > math.MaxUint16 {
return errors.New("invalid port range ", v.From, " -> ", v.To)
}
return nil
+22 -20
View File
@@ -12,8 +12,9 @@ import (
type DNSOutboundRuleConfig struct {
Action string `json:"action"`
QType *PortList `json:"qtype"`
QType *PortList `json:"qType"`
Domain *StringList `json:"domain"`
RCode uint32 `json:"rCode"`
}
func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
@@ -24,8 +25,8 @@ func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
rule.Action = dns.RuleAction_Direct
case "drop":
rule.Action = dns.RuleAction_Drop
case "reject":
rule.Action = dns.RuleAction_Reject
case "return":
rule.Action = dns.RuleAction_Return
case "hijack":
rule.Action = dns.RuleAction_Hijack
default:
@@ -34,14 +35,8 @@ func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
if c.QType != nil {
for _, r := range c.QType.Range {
if r.From > r.To {
return nil, errors.New("invalid qtype range: ", r.String())
}
if r.To > 65535 {
return nil, errors.New("dns rule qtype out of range: ", r.String())
}
for qtype := r.From; qtype <= r.To; qtype++ {
rule.Qtype = append(rule.Qtype, int32(qtype))
for qType := r.From; qType <= r.To; qType++ {
rule.QType = append(rule.QType, int32(qType))
}
}
}
@@ -54,6 +49,11 @@ func (c *DNSOutboundRuleConfig) Build() (*dns.DNSRuleConfig, error) {
rule.Domain = rules
}
if c.RCode > 65535 {
return nil, errors.New("rCode out of range: ", c.RCode)
}
rule.RCode = c.RCode
return rule, nil
}
@@ -133,28 +133,30 @@ func (c *DNSOutboundConfig) buildLegacyDNSPolicy() ([]*dns.DNSRuleConfig, error)
if c.BlockTypes != nil && len(*c.BlockTypes) > 0 {
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Drop}
if mode == "reject" {
rule.Action = dns.RuleAction_Reject
rule.Action = dns.RuleAction_Return
rule.RCode = 5
}
for _, qtype := range *c.BlockTypes {
if qtype < 0 || qtype > 65535 {
return nil, errors.New("legacy blockTypes qtype out of range: ", qtype)
for _, qType := range *c.BlockTypes {
if qType < 0 || qType > 65535 {
return nil, errors.New("legacy blockTypes qType out of range: ", qType)
}
rule.Qtype = append(rule.Qtype, qtype)
rule.QType = append(rule.QType, qType)
}
rules = append(rules, rule)
}
{
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Hijack}
rule.Qtype = append(rule.Qtype, 1)
rule.Qtype = append(rule.Qtype, 28)
rule.QType = append(rule.QType, 1)
rule.QType = append(rule.QType, 28)
rules = append(rules, rule)
}
{
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Reject}
rule := &dns.DNSRuleConfig{Action: dns.RuleAction_Return}
if mode == "reject" {
rule.Action = dns.RuleAction_Reject
rule.Action = dns.RuleAction_Return
rule.RCode = 5
} else if mode == "drop" {
rule.Action = dns.RuleAction_Drop
} else if mode == "skip" {

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